Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
449c4d4
fix(file): keep the image layout when writing a partition back
JanZachmann Sep 28, 2026
5186d4b
chore: bump dependencies including major versions
JanZachmann Sep 28, 2026
2c7f060
style(device_update): drop redundant borrows in info! arguments
JanZachmann Sep 28, 2026
e9163a5
fix(file): keep holes when copying the image
JanZachmann Sep 28, 2026
e2443b2
fix(file): map the ext4 journals in a generated bmap
JanZachmann Sep 28, 2026
52fe21d
fix: copy image data segments larger than 2 GiB completely
JanZachmann Sep 28, 2026
19bcd30
fix(file): harden ext4 journal lookup for bmap generation
JanZachmann Sep 28, 2026
d7e7242
build(docker): install e2fsprogs in the builder
JanZachmann Sep 28, 2026
2912452
fix(file): keep zero blocks as holes when decompressing
JanZachmann Sep 28, 2026
84ba27e
refactor(file): move bmap generation into its own module
JanZachmann Sep 28, 2026
9842fef
test(file): cover ext4 journal edge cases and a flash onto old content
JanZachmann Sep 28, 2026
efea1b0
Merge remote-tracking branch 'upstream/main' into jz-2026-09-28-keep-…
JanZachmann Oct 23, 2026
68f21f4
fix(deps): resolve RUSTSEC-2026-0385 (ctutils)
JanZachmann Oct 23, 2026
7e75052
fix(file): detect truncated input and harden sparse decompress
JanZachmann Oct 28, 2026
a0da23c
fix(file): keep the ranges of the input bmap mapped
JanZachmann Oct 28, 2026
fb4441d
fix(file): address review findings on input bmap handling
JanZachmann Oct 28, 2026
cb3c476
feat(cli)!: rename bmap options to --input-bmap and --output-bmap
JanZachmann Oct 28, 2026
46d0362
fix(file): map zero blocks that a copy writes into a hole
JanZachmann Oct 28, 2026
02a712b
fix(file): reject an input bmap whose checksums do not match the image
JanZachmann Oct 28, 2026
5e827ac
docs(file): explain a checksum mismatch after a command without -b
JanZachmann Oct 28, 2026
279f8a8
feat(file): write the output bmap in Rust and keep the image sparse
JanZachmann Oct 28, 2026
57a1580
perf(file): compare only blocks that hold data in a partition write back
JanZachmann Oct 28, 2026
b59788c
test(file): check the bmaptool format only in the integration tests
JanZachmann Oct 28, 2026
53ebcd8
chore(docker): drop dd and sync from the image
JanZachmann Oct 28, 2026
d386816
fix(file): decompress images named .gz and .bz2
JanZachmann Oct 28, 2026
cdc299c
refactor(cli): share the bmap and compression options of image commands
JanZachmann Oct 28, 2026
92ce896
test(file): make the write back failure test work as root
JanZachmann Oct 28, 2026
04bb5f1
fix(file): name the output bmap after the output image
JanZachmann Oct 28, 2026
beafb45
perf(file): do not sync the temp image after a partition write
JanZachmann Oct 28, 2026
d7ba2da
test(file): cover a failed compress trailer and a partition past the …
JanZachmann Oct 28, 2026
1ce9e1a
refactor(file): share the sparse copy into a new file
JanZachmann Oct 29, 2026
c18fc17
fix(file): do not write the image back after copy-from-image
JanZachmann Oct 29, 2026
c51066f
feat(file)!: fail on output name conflicts, add --output-image and --…
JanZachmann Oct 29, 2026
f0ead21
perf(file): check the input bmap of an uncompressed image before the …
JanZachmann Oct 29, 2026
be84b33
fix(file): read concatenated compressed streams
JanZachmann Oct 29, 2026
453b124
fix(file): check every bmap that bmaptool would use
JanZachmann Oct 29, 2026
d0800f8
fix(file)!: name a bzip2 image .bz2
JanZachmann Oct 29, 2026
c477f9a
fix(file): check the extension of the --output-image name
JanZachmann Oct 29, 2026
7ab73f8
fix(file): write the output image through a rename
JanZachmann Oct 29, 2026
20c114b
refactor(file): use tempfile::TempDir for the temp image
JanZachmann Oct 29, 2026
342bea1
fix(file): keep the partition files in their own temp dir
JanZachmann Oct 29, 2026
53672ec
perf(file): run copy-from-image on an uncompressed image directly
JanZachmann Oct 29, 2026
b328b45
fix(file): read partitions of a GPT with 4096-byte sectors
JanZachmann Oct 29, 2026
ee181b4
docs(file): describe the --output-image extension rule and the rename
JanZachmann Oct 29, 2026
961f95b
fix(file): keep the name of an image with any extension of the -p com…
JanZachmann Oct 29, 2026
3504b5a
fix(file): find the input bmap the way bmaptool does
JanZachmann Oct 29, 2026
3da5a4d
fix(file): sync the output image and remove the old bmap before the r…
JanZachmann Oct 29, 2026
039b861
fix(file): copy into an output image that is a mount point
JanZachmann Nov 3, 2026
6c599be
fix(file): keep a read-only output image unchanged
JanZachmann Nov 3, 2026
8d67433
refactor(file): write the output bmap into its staged file directly
JanZachmann Nov 3, 2026
9126d55
refactor(file): use Path::with_added_extension for bmap and compresse…
JanZachmann Nov 3, 2026
8f08735
fix(file): refuse a bmap with another root element or a newer major v…
JanZachmann Nov 3, 2026
19bb6be
docs(file): describe the space a command needs in TMPDIR
JanZachmann Nov 3, 2026
a75f17f
fix(file): replace an output bmap that is a mount point
JanZachmann Nov 3, 2026
6e9c9e1
fix(file): copy into a detected mount point without a rename
JanZachmann Nov 3, 2026
32c4a0b
refactor(file): share the sparse file copy in sparse::copy_file
JanZachmann Nov 4, 2026
44e456f
fix(file): keep the staged file when the copy into the destination fails
JanZachmann Nov 4, 2026
5e772f9
fix(file): skip the sync of a staged file that is only copied
JanZachmann Nov 4, 2026
bac7b43
test(file): check / as mount root only where the kernel reports it
JanZachmann Nov 4, 2026
483adce
test(file): make the bmap of another image valid except for its size
JanZachmann Nov 4, 2026
f918204
test(file): build bmap paths with bmap::bmap_path
JanZachmann Nov 4, 2026
ddb60e9
fix(identity): write the new device certificate and key into a temp dir
JanZachmann Nov 4, 2026
7b61618
fix(file): remove the staging dir when a stop signal ends the command
JanZachmann Nov 4, 2026
334d81d
docs: list the output and signal modules in the project context
JanZachmann Nov 4, 2026
224b7bf
fix(file): keep the staged file when a signal stops the copy into a m…
JanZachmann Nov 4, 2026
47c05a5
fix(identity): remove the new device key when a stop signal ends the …
JanZachmann Nov 4, 2026
acf2cb3
fix(file): do not handle a stop signal the process ignores
JanZachmann Nov 4, 2026
13dfac7
fix(file): replace the image before the bmap
JanZachmann Nov 4, 2026
95c0918
fix(file): refuse a partition that does not fit into the image
JanZachmann Nov 4, 2026
626c64f
fix(file): follow a symlink whose target does not exist yet
JanZachmann Nov 4, 2026
7447910
fix(file): check the staging directory before the command runs
JanZachmann Nov 4, 2026
dd1f171
fix(file): write a compressed image only with the extension bmaptool …
JanZachmann Nov 4, 2026
6d80f92
fix(file): remove every temp dir of a command on a stop signal
JanZachmann Nov 4, 2026
59a6cf9
docs: describe where a failed copy keeps the image and the stop signa…
JanZachmann Nov 4, 2026
aaa1c8a
test(file): build the last inline bmap path with bmap::bmap_path
JanZachmann Nov 4, 2026
32d03c9
fix(file): suggest the right -o name for the -p compression
JanZachmann Nov 4, 2026
e20ca4d
fix(file): refuse an output image that is not a regular file
JanZachmann Nov 5, 2026
203750f
fix(file): detect the compression of a symlinked image
JanZachmann Nov 5, 2026
6f2bd85
refactor(file): strip a compression extension in one place
JanZachmann Nov 5, 2026
52e9390
fix(file): compare a block with a zero block in is_zero
JanZachmann Nov 5, 2026
cafe7db
refactor(file): keep the internal steps of the sparse copy private
JanZachmann Nov 5, 2026
979feb0
docs: say that the output image must be a regular file
JanZachmann Nov 5, 2026
879c78a
fix(file): create temp dirs that only the user can read
JanZachmann Nov 5, 2026
c70c26e
fix(file): keep the new bmap and clean up after a copy into a mount p…
JanZachmann Nov 5, 2026
017daab
fix(file): replace the bmap of an image changed in place
JanZachmann Nov 5, 2026
a960bb7
fix(file): accept an output file without a directory in copy-from-image
JanZachmann Nov 5, 2026
a179ff7
refactor(file): find the output bmap path once
JanZachmann Nov 5, 2026
05bcf4f
docs: say that only a failed copy keeps the new bmap
JanZachmann Nov 5, 2026
2332999
docs(file): move the is_zero doc back to is_zero
JanZachmann Nov 6, 2026
e5dd850
fix(file): remove a temp dir before it is unregistered
JanZachmann Nov 6, 2026
222296f
fix(file): protect bmaps of other images and keep in-place output in …
JanZachmann Nov 6, 2026
dd691ee
test(file): restore the name of the is_mount_root test
JanZachmann Nov 6, 2026
5377d82
refactor(file): read the kept state from the temp dir
JanZachmann Nov 6, 2026
e9c9512
fix(file): find the images that share a bmap by name and lookup order
JanZachmann Nov 6, 2026
62830aa
fix(file): copy into the output if a sticky dir refuses the rename
JanZachmann Nov 6, 2026
bedee31
fix(file): do not follow a symlink when copying into the output
JanZachmann Nov 6, 2026
b65abea
fix(file): write .gzip again for -p gzip
JanZachmann Nov 6, 2026
346cbd8
fix(file): copy into an existing output without O_CREAT
JanZachmann Nov 6, 2026
9b7080b
fix(file): check the sector count of a GPT partition for overflow
JanZachmann Nov 6, 2026
f89c64b
fix(file): refuse an output image that would be its own bmap
JanZachmann Nov 6, 2026
1919a21
fix(file): create a missing output in the copy fallback
JanZachmann Nov 6, 2026
d6070b6
fix(file): name an old bmap that stays in every commit error
JanZachmann Nov 6, 2026
cc19446
fix(file): find the images named after the target of a symlinked bmap
JanZachmann Nov 6, 2026
3f2462e
chore(deps): update actix-web and actix-http
JanZachmann Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 35 additions & 28 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
name = "omnect-cli"
readme = "README.md"
repository = "https://github.com/omnect/omnect-cli"
version = "0.28.5"
version = "0.29.0"

[dependencies]
actix-web = { version = "4.17", default-features = false, features = ["macros"] }
Expand All @@ -31,7 +31,7 @@ hmac = { version = "0.13", default-features = false }
mbrman = { version = "0.6", default-features = false }
omnect-crypto = { git = "https://github.com/omnect/omnect-crypto.git", tag = "0.5.0" }
keyring-core = { version = "1.0", default-features = false }
libfs = { version = "0.9", default-features = false }
libc = { version = "0.2", default-features = false }
log = { version = "0.4", default-features = false }
num_cpus = { version = "1.17", default-features = false }
oauth2 = { version = "5.0", default-features = false, features = [
Expand All @@ -44,10 +44,13 @@ reqwest = { version = "0.13", default-features = false, features = [
"json",
"native-tls",
] }
roxmltree = { version = "0.21", default-features = false, features = ["std"] }
rustix = { version = "1.1", default-features = false, features = ["fs", "std"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
serde_path_to_error = { version = "0.1", default-features = false }
sha2 = { version = "0.11", default-features = false }
signal-hook = { version = "0.4", default-features = false, features = ["iterator"] }
stdext = { version = "0.3", default-features = false }
strum = { version = "0.28", default-features = false }
strum_macros = { version = "0.28", default-features = false }
Expand Down Expand Up @@ -81,5 +84,5 @@ tar = "0.4"

# metadata for building with cargo-deb (https://crates.io/crates/cargo-deb)
[package.metadata.deb]
depends = "bmap-tools, e2tools, keychain, libc6 (>= 2.39), libmagic1, libssl3 (>= 3.0.0), mtools"
depends = "e2tools, keychain, libc6 (>= 2.39), libmagic1, libssl3 (>= 3.0.0), mtools"
revision = ""
4 changes: 0 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ COPY ${debian_dir}/omnect-cli_${omnect_cli_version}_amd64.deb omnect-cli_${omnec

RUN apt-get update && \
apt-get install -y --no-install-recommends \
bmap-tools \
ca-certificates \
e2tools \
fdisk \
Expand All @@ -34,14 +33,11 @@ RUN <<EOT
mkdir -p /copy/status.d /copy/usr/lib

executables=(
/usr/bin/dd \
/usr/bin/e2cp \
/usr/bin/e2mkdir \
/usr/bin/fallocate \
/usr/bin/mcopy \
/usr/bin/omnect-cli \
/usr/bin/ssh-keygen \
/usr/bin/sync \
/usr/sbin/fdisk \
)

Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ docker run --rm -it \
omnect/omnect-cli:latest file copy-to-image --files /source/my-source-file,boot:/my-dest-file -i /source/my-image.wic
```

**Note1**: `-b` option to create bmap file is not supported by omnect-cli docker image.<br>
**Note2**: The ssh tunnel option requires some additional settings. See [here](Usage-with-docker) for more details.<br>
**Note3**: The docker inject command is not supported by omnect-cli docker image.<br>.
**Note1**: The ssh tunnel option requires some additional settings. See [here](Usage-with-docker) for more details.<br>
**Note2**: The docker inject command is not supported by omnect-cli docker image.<br>.

# Build from sources

Expand Down Expand Up @@ -184,6 +183,22 @@ omnect-cli file copy-to-image --help
- File permissions: inject `systemd-tmpfiles.d`
- Wifi: inject `wpa_supplicant-wlan0.conf`

## Output image

A command that changes an image writes the result to the input image. For a compressed image it writes the uncompressed image next to it, e.g. `my-image.wic` for `my-image.wic.xz`. With `-p` (`--pack-image`) it adds the extension of the compression (`.xz`, `.bz2` or `.gzip`), e.g. `my-image.wic.xz` for `my-image.wic`. An image that already has an extension of the compression keeps its name, except that `.bzip2` becomes `.bz2`, because `bmaptool` does not read `.bzip2`. `-o` (`--output-image`) writes the result to the given path instead, and the input image stays unchanged. With `-p` its name must end in an extension of the compression that `bmaptool` reads (`.xz`, `.bz2`, `.gzip` or `.gz`), and without `-p` it must not have a compression extension.

The command fails instead of overwriting an existing file that is not the input image. To overwrite it, name it with `-o`. An existing output image must be a regular file, so `-o` cannot name a device or a directory; to write a device, flash the image with `bmaptool copy`. It also fails before it starts if the output image or its bmap exists and is not writable for the user, or if the directory of the new file is not writable. The output image is written to a new file in its directory and then renamed, so the directory needs write access and space for one more image. An existing output image in a directory you cannot write is staged in `TMPDIR` instead and copied into it. Until the rename, the new file is in a hidden `.omnect-cli-*` directory. The command removes it when it ends or is stopped with Ctrl-C, `SIGTERM` or `SIGHUP`. It stays after a `SIGKILL` or a power loss, and you can delete it. A rename cannot replace a mount point, e.g. an image mounted with `docker run -v "$(pwd)/my-image.wic":/source/my-image.wic`, so the new image is staged in `TMPDIR` and copied into it. If this copy fails or is stopped, the new image stays, and the log names it. If the copy fails, the new bmap of `-b` stays next to it. With `-b` the command fails for such a mounted image, because the bmap would be written next to the mount point and not next to the image. Mount the directory instead. The new file belongs to the user who runs `omnect-cli`. `copy-from-image` only reads the image and writes nothing.

A command copies each partition it reads or changes to `TMPDIR` (default `/tmp`). A command that changes an image, or reads a compressed one, also copies the uncompressed image there, and for an output image that is a mount point or in a directory you cannot write also the new image. So `TMPDIR` needs space for all of them. Ctrl-C, `SIGTERM` or `SIGHUP` removes them too. If `/tmp` is a small tmpfs, set `TMPDIR` to a directory with more space.

## bmap files

Commands that change an image create a bmap file with `-b` (`--output-bmap`). It is written next to the output image as `<output image>.bmap`, e.g. `my-image.wic.bmap` for `my-image.wic` and `my-image.wic.xz.bmap` for `my-image.wic.xz`, and replaces a file with this name. An image changed in place keeps the name of its bmap: the command replaces the bmap that `bmaptool` finds for it, e.g. `my-image.wic.bmap` for `-i my-image.wic.xz -p xz`. `bmaptool` finds the bmap there without `--bmap`. The command fails if `bmaptool` would use this file also for an image that stays (`<name>` or `<name>.<compression>` next to it, for which no other bmap comes first), because it would not match that image. E.g. `my-image.wic.bmap` when `-i my-image.wic.xz` writes `my-image.wic`, also if the bmap does not exist yet, or when `-i my-image.wic` changes the image in place and `my-image.wic.xz` without its own bmap is next to it. Choose another output with `-o` then.

The bmap of the input image is the base for it: `omnect-cli` takes the file given with `--input-bmap`, else the bmap next to the image that `bmaptool` would use: `<image>.bmap`, else the same with one extension of the image removed at a time, e.g. `my-image.wic.bmap` for `my-image.wic.xz` as the Yocto build creates it. The input bmap must have the size of the image and sha256 range checksums that match it, otherwise the command fails. A command without `-b` fails if such a bmap exists for the output image (e.g. `my-image.wic.bmap` for `my-image.wic.xz`), because `bmaptool` would use it and it would not match the image. So when you run several commands on one image, use `-b` on each of them. `--no-input-bmap` skips a bmap next to the image that does not match, and the created bmap then maps the whole image. The created bmap maps every range of the input bmap, also when it holds only zeros, so a bmap-based flash overwrites what an earlier installation left there (partition table entries, FAT, ext4 journals). It also maps every block that the image file stores as data, which includes the blocks `omnect-cli` changed. The image file itself stays sparse.

Without an input bmap, the created bmap maps the whole image, and a flash writes every block.

## ssh tunnel

### Inject ssh tunnel credentials
Expand Down
8 changes: 6 additions & 2 deletions project-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,19 @@
- `src/docker.rs` — `docker pull --platform` + `docker save` for multi-arch images
- `src/image.rs` — firmware image architecture detection (ARM32/ARM64/x86_64)
- `src/file/mod.rs` — high-level image operations: identity config, certs, hostname patching
- `src/file/functions.rs` — partition read/modify/write via dd + e2cp/mcopy
- `src/file/functions.rs` — sparse partition read, modify via e2cp/mcopy, write back of changed blocks only
Comment thread
mlilien marked this conversation as resolved.
Resolved
- `src/file/bmap.rs` — input bmap lookup and checksum check; writes the output bmap: input ranges (or the whole image) plus data blocks
- `src/file/output.rs` — output image name, conflict checks, staged write and commit of the output image and bmap
- `src/file/sparse.rs` — data/hole ranges via `SEEK_DATA`/`SEEK_HOLE`, sparse range copy
- `src/file/signal.rs` — `RegisteredTempDir`: a temp dir that SIGINT/SIGTERM/SIGHUP (unless ignored) also removes
- `src/file/partition.rs` — GPT/MBR partition table parsing
- `src/file/compression.rs` — xz/bzip2/gzip compress/decompress with auto-detection
- `src/validators/` — validation for identity config (TOML), device-update config (JSON), SSH keys
- `conf/*.template` — 11 config templates (identity, device-update, WiFi)

## 4. Repository-Specific Constraints

- External tools `e2cp`, `e2mkdir`, `mcopy`, `mmd`, `dd`, `fallocate`, `ssh-keygen`, `fdisk` must be available at runtime (Dockerfile copies them explicitly).
- External tools `e2cp`, `e2mkdir`, `mcopy`, `mmd`, `ssh-keygen`, `fdisk` must be available at runtime (Dockerfile copies them explicitly).
- Partition enum maps partition names to numbers differently for GPT vs MBR — see `file/functions.rs`.
- OAuth2 callback binds to `127.0.0.1:4000` and `[::1]:4000`; container mode overrides to `0.0.0.0`.
- `conf/` directory uses `.gitignore` to track only `*.template` files — actual configs are generated, never committed.
Expand Down
Loading
Loading