From 449c4d4fb1be8b8b46dca266860ad9713d3e7f3d Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:39:15 +0200 Subject: [PATCH 01/11] fix(file): keep the image layout when writing a partition back write_partition ran `fallocate -d` on the whole image, which turned every zero-filled block into a hole. A bmap created afterwards with `-b` then skipped those blocks, e.g. ext4 journals that the image deliberately writes as zeros, so a bmap-based flash left the old journal data on the device. Write only the blocks that differ from the image instead. Unchanged blocks keep their allocation, and a block that changed to all zeros is now written too (`dd conv=sparse` skipped it and kept the old content). Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- Cargo.lock | 2 +- Cargo.toml | 2 +- Dockerfile | 1 - project-context.md | 2 +- src/file/functions.rs | 68 ++++++++++++++++++++++++++++---------- tests/integration_tests.rs | 54 +++++++++++++++++++++++++++++- 6 files changed, 106 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d4efd9a..8b7d028d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1789,7 +1789,7 @@ dependencies = [ [[package]] name = "omnect-cli" -version = "0.28.2" +version = "0.28.3" dependencies = [ "actix-web", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index c2c160d4..f2f9ebf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.2" +version = "0.28.3" [dependencies] actix-web = "4.11" diff --git a/Dockerfile b/Dockerfile index 989ae539..8eb12929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,6 @@ RUN < Result<()> { - let mut dd = Command::new("dd"); - dd.arg(format!("if={partition_file}")) - .arg(format!("of={image_file}")) - .arg("bs=512") - .arg(format!("seek={}", partition_info.start)) - .arg(format!("count={}", partition_info.count)) - .arg("conv=notrunc,sparse") - .arg("status=none"); - exec_cmd!(dd); - - let mut fallocate = Command::new("fallocate"); - fallocate.arg("-d").arg(image_file); - exec_cmd!(fallocate); - - let mut sync = Command::new("sync"); - exec_cmd!(sync); + let partition = fs::File::open(partition_file) + .context(format!("write_partition: cannot open {partition_file}"))?; + let image = fs::OpenOptions::new() + .read(true) + .write(true) + .open(image_file) + .context(format!("write_partition: cannot open {image_file}"))?; + + let image_offset = partition_info.start * SECTOR_SIZE; + let len = partition + .metadata() + .context("write_partition: cannot get partition file size")? + .len() + .min(partition_info.count * SECTOR_SIZE); + + // Only write blocks that differ: unchanged blocks keep their allocation, so a bmap + // created from the result still maps every block the source image mapped. + let mut src = vec![0u8; COPY_CHUNK_SIZE]; + let mut dst = vec![0u8; COPY_CHUNK_SIZE]; + let mut pos = 0; + while pos < len { + let n = usize::try_from((len - pos).min(COPY_CHUNK_SIZE as u64))?; + partition + .read_exact_at(&mut src[..n], pos) + .context("write_partition: cannot read partition file")?; + image + .read_exact_at(&mut dst[..n], image_offset + pos) + .context("write_partition: cannot read image")?; + + let blocks = src[..n] + .chunks(COMPARE_BLOCK_SIZE) + .zip(dst[..n].chunks(COMPARE_BLOCK_SIZE)); + for (i, (s, d)) in blocks.enumerate() { + if s != d { + image + .write_all_at(s, image_offset + pos + (i * COMPARE_BLOCK_SIZE) as u64) + .context("write_partition: cannot write image")?; + } + } + pos += n as u64; + } - Ok(()) + image + .sync_all() + .context("write_partition: cannot sync image") } pub fn generate_bmap_file(image_file: &str) -> Result<()> { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index ac3e7167..e51b00c0 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,7 +5,7 @@ use common::Testrunner; use httpmock::prelude::*; use omnect_cli::ssh; use predicates::prelude::*; -use std::{fs::create_dir_all, path::PathBuf}; +use std::{fs::create_dir_all, os::unix::fs::MetadataExt, path::PathBuf}; use stdext::function_name; #[test] @@ -895,6 +895,58 @@ fn check_file_copy(tr: Testrunner, partition: &str) { assert!(file_diff::diff(in_file4, out_file4)); } +// unit of `st_blocks` +const STAT_BLOCK_SIZE: u64 = 512; + +fn bmap_value(bmap: &str, tag: &str) -> u64 { + // the header comment of a bmap file mentions the tags too; only the closing tag is unique + let open_tag = format!("<{tag}>"); + let end = bmap + .find(&format!("")) + .expect("closing tag not found in bmap"); + let start = bmap[..end] + .rfind(&open_tag) + .expect("opening tag not found in bmap") + + open_tag.len(); + bmap[start..end] + .trim() + .parse() + .expect("tag value is no number") +} + +#[test] +fn check_file_copy_keeps_image_layout() { + let tr = Testrunner::new(function_name!().split("::").last().expect("test name")); + let image_path = tr.to_pathbuf("testfiles/image.wic"); + let bmap_path = PathBuf::from(format!("{}.bmap", image_path.to_string_lossy())); + let in_file = tr.to_pathbuf("testfiles/boot.scr"); + + let meta = image_path.metadata().expect("image metadata"); + assert!( + meta.blocks() * STAT_BLOCK_SIZE >= meta.len(), + "test image must be fully allocated" + ); + + Command::cargo_bin("omnect-cli") + .expect("omnect-cli binary") + .arg("file") + .arg("copy-to-image") + .arg("-f") + .arg(format!("{},factory:/my-file", in_file.to_string_lossy())) + .arg("-i") + .arg(&image_path) + .arg("-b") + .assert() + .success(); + + // zero blocks of the input must stay mapped, e.g. zeroed ext4 journals + let bmap = std::fs::read_to_string(&bmap_path).expect("read bmap"); + assert_eq!( + bmap_value(&bmap, "MappedBlocksCount"), + bmap_value(&bmap, "BlocksCount") + ); +} + #[test] fn check_bmap_generation_wic() { let tr = Testrunner::new(function_name!().split("::").last().unwrap()); From 5186d4b34f36de75cfe5544fb8c8e95190746087 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:10:51 +0200 Subject: [PATCH 02/11] chore: bump dependencies including major versions - all direct dependencies to their latest version, omnect-crypto to 0.5.0 (create_cert_and_key no longer takes the unused extensions argument; the extensions of a certificate without CSR are the same) - actix-web without default features: the OAuth callback server only serves plain HTTP on localhost, and the http2 feature pulled in h2 0.3 (RUSTSEC-2026-0258, no fixed 0.3 release) - flate2 selects its rust_backend itself instead of relying on actix-web's compression features to enable one Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- Cargo.lock | 1307 ++++++++++++++++++++++------------------------------ Cargo.toml | 26 +- src/lib.rs | 2 +- 3 files changed, 571 insertions(+), 764 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b7d028d..a6f3a345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "actix-codec" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +checksum = "4c13df95297bcf9014dc89162b0cc69431e192e34e3b419612fc124cfcd45dbf" dependencies = [ "bitflags", "bytes", @@ -21,51 +21,43 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.1" +version = "3.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +checksum = "e2ab7d72c0bb26f10df574ea560177b4de2f27ee8f421b2c1e9b75628bd9db23" dependencies = [ "actix-codec", - "actix-rt", "actix-service", "actix-utils", - "base64", "bitflags", - "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", - "flate2", "foldhash", "futures-core", - "h2 0.3.27", "http 0.2.12", "httparse", "httpdate", "itoa", "language-tags", - "local-channel", "mime", "percent-encoding", "pin-project-lite", - "rand 0.10.1", - "sha1 0.11.0", "smallvec", "tokio", "tokio-util", "tracing", - "zstd", ] [[package]] name = "actix-macros" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b" dependencies = [ + "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -77,7 +69,6 @@ dependencies = [ "bytestring", "cfg-if", "http 0.2.12", - "regex", "regex-lite", "serde", "tracing", @@ -85,9 +76,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.11.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +checksum = "e5f794807f82bbd36430c12cd600c73bbab0f52fdde4f0ed49978df113f4807f" dependencies = [ "futures-core", "tokio", @@ -95,17 +86,16 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.6.0" +version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +checksum = "8db2cf6f034e227c0c79825fbf11f3cad48569109515f62542b84d8abedb9376" dependencies = [ "actix-rt", "actix-service", - "actix-utils", "futures-core", "futures-util", "mio", - "socket2 0.5.10", + "socket2", "tokio", "tracing", ] @@ -122,9 +112,9 @@ dependencies = [ [[package]] name = "actix-utils" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d" dependencies = [ "local-waker", "pin-project-lite", @@ -132,9 +122,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.13.0" +version = "4.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +checksum = "bbacab3593b6b4f7be815076fc52d60a83c873426824675417e2abdd229e2e36" dependencies = [ "actix-codec", "actix-http", @@ -148,7 +138,6 @@ dependencies = [ "bytes", "bytestring", "cfg-if", - "cookie", "derive_more", "encoding_rs", "foldhash", @@ -161,13 +150,12 @@ dependencies = [ "mime", "once_cell", "pin-project-lite", - "regex", "regex-lite", "serde", "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.4", + "socket2", "time", "tracing", "url", @@ -175,14 +163,14 @@ dependencies = [ [[package]] name = "actix-web-codegen" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +checksum = "b96b09c4878563f8ab4a5fd0c59f9f0d6e0e9f60eb9b748526a0b9604fd89c50" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -193,33 +181,18 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -232,9 +205,48 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "assert-json-diff" @@ -284,13 +296,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -311,6 +323,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bincode" version = "2.0.1" @@ -333,15 +351,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -367,36 +385,15 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "brotli" -version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bstr" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -405,11 +402,17 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ "serde", ] @@ -434,32 +437,19 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.64" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chacha20" -version = "0.10.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", -] +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "chrono" @@ -477,9 +467,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -487,9 +477,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstyle", "clap_lex", @@ -497,21 +487,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "cmov" @@ -519,21 +509,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "convert_case" version = "0.10.0" @@ -543,17 +518,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -570,6 +534,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -581,9 +551,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -605,18 +575,18 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-common" @@ -648,79 +618,98 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] name = "darling_core" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core 0.20.11", + "darling_core 0.21.3", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.21.3", + "darling_core 0.23.0", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "data-encoding" -version = "2.11.0" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] [[package]] name = "deranged" @@ -747,7 +736,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -774,7 +763,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid", "crypto-common 0.2.2", "ctutils", ] @@ -802,38 +790,44 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", + "regex", ] [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "env_filter", "log", @@ -857,11 +851,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -878,9 +871,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "file_diff" @@ -911,15 +904,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", @@ -942,9 +935,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-types" @@ -978,41 +971,41 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-timer" @@ -1022,9 +1015,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-macro", @@ -1058,28 +1051,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasip2", - "wasip3", + "r-efi", ] [[package]] @@ -1089,41 +1067,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd684d0cdb92e5993c63ef59bb8d1e959ff32ae131e5614fc143612f8544cc" dependencies = [ "crc", - "thiserror 2.0.18", + "thiserror 2.0.21", "wincode", ] [[package]] name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.15" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.2", + "http 1.5.0", "indexmap", "slab", "tokio", @@ -1131,15 +1090,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - [[package]] name = "hashbrown" version = "0.17.1" @@ -1148,17 +1098,17 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "headers" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +checksum = "fc2700e3fe555c3310aa7286cac6167449f2c87e0eb58769c9208a1c58a1d106" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "headers-core", - "http 1.4.2", + "http 1.5.0", "httpdate", "mime", - "sha1 0.10.6", + "sha1", ] [[package]] @@ -1167,7 +1117,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.4.2", + "http 1.5.0", ] [[package]] @@ -1178,9 +1128,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hmac" @@ -1204,9 +1154,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1214,23 +1164,23 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.4.2", + "http 1.5.0", ] [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", - "http 1.4.2", + "http 1.5.0", "http-body", "pin-project-lite", ] @@ -1256,14 +1206,14 @@ dependencies = [ "assert-json-diff", "async-object-pool", "async-trait", - "base64", + "base64 0.22.1", "bytes", "crossbeam-utils", "form_urlencoded", "futures-timer", "futures-util", "headers", - "http 1.4.2", + "http 1.5.0", "http-body-util", "hyper", "hyper-util", @@ -1275,7 +1225,7 @@ dependencies = [ "similar", "stringmetrics", "tabwriter", - "thiserror 2.0.18", + "thiserror 2.0.21", "tokio", "tracing", "url", @@ -1283,25 +1233,25 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.15", - "http 1.4.2", + "h2", + "http 1.5.0", "http-body", "httparse", "httpdate", @@ -1330,22 +1280,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-channel", "futures-util", - "http 1.4.2", + "http 1.5.0", "http-body", + "httparse", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2", "tokio", "tower-service", "tracing", @@ -1377,9 +1328,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1391,9 +1342,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1404,9 +1355,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1418,16 +1369,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1438,15 +1390,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -1457,12 +1409,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1492,27 +1438,25 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.1.9" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +checksum = "30c0cddce6b7505483307994f60d97c4b156020e1504d0cf1f05a21b1b325e64" [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", + "hashbrown", ] [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is-docker" @@ -1539,21 +1483,11 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" -version = "0.3.102" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -1576,10 +1510,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libbz2-rs-sys" @@ -1589,9 +1523,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libfs" @@ -1604,15 +1538,15 @@ dependencies = [ "linux-raw-sys", "log", "rustix", - "thiserror 2.0.18", + "thiserror 2.0.21", "xattr", ] [[package]] name = "libredox" -version = "0.1.17" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ "libc", ] @@ -1625,20 +1559,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "local-channel" -version = "0.1.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "local-waker" @@ -1657,9 +1580,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lzma-sys" @@ -1682,14 +1605,14 @@ dependencies = [ "bitvec", "serde", "serde-big-array", - "thiserror 2.0.18", + "thiserror 2.0.21", ] [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mime" @@ -1697,11 +1620,17 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", "simd-adler32", @@ -1709,9 +1638,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "log", @@ -1719,6 +1648,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "native-tls" version = "0.2.18" @@ -1736,18 +1671,47 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1773,11 +1737,11 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "getrandom 0.2.17", - "http 1.4.2", - "rand 0.8.6", + "http 1.5.0", + "rand", "reqwest 0.12.28", "serde", "serde_json", @@ -1787,6 +1751,15 @@ dependencies = [ "url", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + [[package]] name = "omnect-cli" version = "0.28.3" @@ -1795,7 +1768,7 @@ dependencies = [ "anyhow", "assert-json-diff", "assert_cmd", - "base64", + "base64 0.23.1", "bzip2", "clap", "data-encoding", @@ -1817,7 +1790,7 @@ dependencies = [ "open", "predicates", "regex", - "reqwest 0.13.4", + "reqwest 0.13.5", "ring", "serde", "serde_json", @@ -1839,13 +1812,17 @@ dependencies = [ [[package]] name = "omnect-crypto" -version = "0.4.0" -source = "git+https://github.com/omnect/omnect-crypto.git?tag=0.4.0#2cf5fb05c514c9a8d3d7c4f2cdd4d4eeaaeef485" +version = "0.5.0" +source = "git+https://github.com/omnect/omnect-crypto.git?tag=0.5.0#128777f9946d9a6d31546da36fd500a700ecdee9" dependencies = [ "anyhow", + "byteorder", + "der", + "der-parser", "log", "openssl", "openssl-sys", + "x509-parser", ] [[package]] @@ -1856,13 +1833,12 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "open" -version = "5.3.5" +version = "5.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" dependencies = [ "is-wsl", "libc", - "pathdiff", ] [[package]] @@ -1887,7 +1863,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1952,12 +1928,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1972,15 +1942,15 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -2031,61 +2001,45 @@ dependencies = [ ] [[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "proc-macro-error-attr3" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +checksum = "9e564d14133360e1ae169ffde5da25881b5fa47261665b8e5713c212c27799da" dependencies = [ "proc-macro2", "quote", ] [[package]] -name = "proc-macro-error2" -version = "2.0.1" +name = "proc-macro-error3" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +checksum = "8f0d4471b3436c22106b21913b1dda531558918ae9b7ec55d58aa84b43552233" dependencies = [ - "proc-macro-error-attr2", + "proc-macro-error-attr3", "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -2100,24 +2054,13 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" -dependencies = [ - "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.1", + "rand_core", ] [[package]] @@ -2127,7 +2070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2139,12 +2082,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - [[package]] name = "redox_syscall" version = "0.5.18" @@ -2156,20 +2093,19 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror 2.0.21", ] [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2179,9 +2115,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2206,10 +2142,10 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", - "http 1.4.2", + "http 1.5.0", "http-body", "http-body-util", "hyper", @@ -2238,14 +2174,14 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-core", - "http 1.4.2", + "http 1.5.0", "http-body", "http-body-util", "hyper", @@ -2294,11 +2230,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", @@ -2309,18 +2254,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -2374,9 +2319,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2393,29 +2338,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2468,26 +2413,15 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2506,7 +2440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -2528,9 +2462,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -2546,25 +2486,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2609,14 +2539,36 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -2634,13 +2586,25 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -2676,7 +2640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2699,11 +2663,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.21", ] [[package]] @@ -2714,25 +2678,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "time" -version = "0.3.49" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "num-conv", @@ -2750,9 +2714,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.29" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -2760,9 +2724,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -2770,9 +2734,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -2780,20 +2744,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.4", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -2808,22 +2772,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ "serde_core", "serde_spanned", @@ -2843,9 +2808,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] @@ -2874,7 +2839,7 @@ dependencies = [ "bitflags", "bytes", "futures-util", - "http 1.4.2", + "http 1.5.0", "http-body", "pin-project-lite", "tower", @@ -2915,7 +2880,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2941,9 +2906,9 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-segmentation" @@ -2996,21 +2961,20 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.3" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", ] [[package]] name = "validator" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +checksum = "c3d68c6633c483df6780cc5277a417c7c2d1bceee2649d06c8ab6b0fd2dd3c81" dependencies = [ "idna", - "once_cell", "regex", "serde", "serde_derive", @@ -3021,16 +2985,15 @@ dependencies = [ [[package]] name = "validator_derive" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +checksum = "240e4b81c20a1d6d50d1d7265c658dfbd204e8b9ac4d80f3c931f39462196335" dependencies = [ - "darling 0.20.11", - "once_cell", - "proc-macro-error2", + "darling 0.23.0", + "proc-macro-error3", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3075,29 +3038,11 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -3108,19 +3053,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.75" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3128,65 +3074,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.102" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -3200,7 +3112,7 @@ checksum = "d5cec722a3274e47d1524cbe2cea762f2c19d615bd9d73ada21db9066349d57e" dependencies = [ "proc-macro2", "quote", - "thiserror 2.0.18", + "thiserror 2.0.21", "wincode-derive", ] @@ -3213,7 +3125,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3237,7 +3149,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3248,7 +3160,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3359,109 +3271,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "wyz" @@ -3472,6 +3290,23 @@ dependencies = [ "tap", ] +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xattr" version = "1.6.1" @@ -3504,34 +3339,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 3.0.6", + "synstructure 0.14.0", ] [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3545,14 +3380,14 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 3.0.6", + "synstructure 0.14.0", ] [[package]] @@ -3563,9 +3398,9 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -3574,9 +3409,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -3585,45 +3420,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index f2f9ebf2..e1cccc79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,11 @@ repository = "https://github.com/omnect/omnect-cli" version = "0.28.3" [dependencies] -actix-web = "4.11" +actix-web = { version = "4.15", default-features = false, features = ["macros"] } anyhow = "1" -base64 = { version = "0.22", default-features = false } +base64 = { version = "0.23", default-features = false } bzip2 = { version = "0.6", default-features = true } -clap = { version = "4.5", default-features = false, features = [ +clap = { version = "4.6", default-features = false, features = [ "derive", "help", "std", @@ -25,11 +25,11 @@ env_logger = { version = "0.11", default-features = false } filemagic = { version = "0.13", default-features = false, features = [ "pkg-config", ] } -flate2 = { version = "1.1", default-features = false } +flate2 = { version = "1.1", default-features = false, features = ["rust_backend"] } gptman = { version = "3.1", default-features = false } 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.4.0" } +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 } log = { version = "0.4", default-features = false } @@ -38,8 +38,8 @@ oauth2 = { version = "5.0", default-features = false, features = [ "reqwest", "native-tls", ] } -open = { version = "5.3", default-features = false } -regex = { version = "1.11", default-features = false } +open = { version = "5.4", default-features = false } +regex = { version = "1.13", default-features = false } reqwest = { version = "0.13", default-features = false, features = [ "json", "native-tls", @@ -51,28 +51,28 @@ sha2 = { version = "0.11", default-features = false } stdext = { version = "0.3", default-features = false } strum = { version = "0.28", default-features = false } strum_macros = { version = "0.28", default-features = false } -tempfile = { version = "3.20", default-features = false } +tempfile = { version = "3.27", default-features = false } time = { version = "0.3", default-features = false } tokio = { version = "1", default-features = false, features = [ "macros", "fs", "rt-multi-thread", ] } -toml = { version = "1.0", default-features = false, features = [ +toml = { version = "1.1", default-features = false, features = [ "parse", "serde", ] } -uuid = { version = "1.17", default-features = false, features = ["v4"] } +uuid = { version = "1.26", default-features = false, features = ["v4"] } url = { version = "2.5", default-features = false } -validator = { version = "0.20", default-features = false, features = [ +validator = { version = "0.21", default-features = false, features = [ "derive", ] } xz2 = { version = "0.1", default-features = false } [dev-dependencies] -assert_cmd = "2.0" +assert_cmd = "2.2" assert-json-diff = "2.0" -data-encoding = "2.9" +data-encoding = "2.11" file_diff = "1.0" httpmock = "0.8" predicates = "3.1" diff --git a/src/lib.rs b/src/lib.rs index 11eeaa62..343f912e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,7 +175,7 @@ fn create_image_cert(image: &Path, cert_opts: CertificateOptions) -> Result Date: Mon, 28 Sep 2026 13:12:50 +0200 Subject: [PATCH 03/11] style(device_update): drop redundant borrows in info! arguments Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- src/device_update/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/device_update/mod.rs b/src/device_update/mod.rs index 8e2f6da3..761aa5c0 100644 --- a/src/device_update/mod.rs +++ b/src/device_update/mod.rs @@ -292,7 +292,7 @@ pub async fn import_update( debug!("import update: {import_update}"); let import_update_response = adu_client.import_update(instance_id, import_update).await?; - info!("Result of import update: {:?}", &import_update_response); + info!("Result of import update: {:?}", import_update_response); Ok(()) } @@ -321,7 +321,7 @@ pub async fn remove_update( let remove_update_response = adu_client .delete_update(instance_id, provider, name, version) .await?; - info!("Result of remove update: {:?}", &remove_update_response); + info!("Result of remove update: {:?}", remove_update_response); Ok(()) } From e9163a560b190610973acbcd3902f41a046001d1 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:01:01 +0200 Subject: [PATCH 04/11] fix(file): keep holes when copying the image libfs was built with `default-features = false`, which also drops its `use_linux` feature. Without it libfs copies byte by byte, so every image omnect-cli works on came back fully allocated, and a bmap created with `-b` mapped the whole image. The `fallocate -d` that write_partition used to run hid this. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- Cargo.toml | 2 +- tests/integration_tests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e1cccc79..6451b69d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } +libfs = { version = "0.9", default-features = false, features = ["use_linux"] } log = { version = "0.4", default-features = false } num_cpus = { version = "1.17", default-features = false } oauth2 = { version = "5.0", default-features = false, features = [ diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e51b00c0..4052768b 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -947,6 +947,40 @@ fn check_file_copy_keeps_image_layout() { ); } +#[test] +fn check_file_copy_keeps_holes() { + let tr = Testrunner::new(function_name!().split("::").last().expect("test name")); + let image_path = tr.to_pathbuf("testfiles/image.wic"); + let sparse_path = image_path.with_file_name("sparse.wic"); + let bmap_path = PathBuf::from(format!("{}.bmap", sparse_path.to_string_lossy())); + let in_file = tr.to_pathbuf("testfiles/boot.scr"); + + let status = std::process::Command::new("cp") + .arg("--sparse=always") + .arg(&image_path) + .arg(&sparse_path) + .status() + .expect("run cp"); + assert!(status.success(), "sparse copy of the test image failed"); + + Command::cargo_bin("omnect-cli") + .expect("omnect-cli binary") + .arg("file") + .arg("copy-to-image") + .arg("-f") + .arg(format!("{},factory:/my-file", in_file.to_string_lossy())) + .arg("-i") + .arg(&sparse_path) + .arg("-b") + .assert() + .success(); + + let meta = sparse_path.metadata().expect("image metadata"); + assert!(meta.blocks() * STAT_BLOCK_SIZE < meta.len()); + let bmap = std::fs::read_to_string(&bmap_path).expect("read bmap"); + assert!(bmap_value(&bmap, "MappedBlocksCount") < bmap_value(&bmap, "BlocksCount")); +} + #[test] fn check_bmap_generation_wic() { let tr = Testrunner::new(function_name!().split("::").last().unwrap()); From e2443b265d70789ae496d5b05d6c048f16f7d6ea Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:01:02 +0200 Subject: [PATCH 05/11] fix(file): map the ext4 journals in a generated bmap A fresh ext4 journal is all zeros, and a sparse image, e.g. one that xz decompressed into a file, holds it as holes. A bmap then skips the journal, so a bmap-based flash leaves the journal of the previous installation on the device, and after a power cut its replay can apply an old transaction to the new filesystem. Before creating the bmap, rewrite the journal blocks of every ext4 partition with their own content. Holes become allocated zero blocks and all other blocks stay unchanged. debugfs lists the journal blocks, so the package now depends on e2fsprogs. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- Cargo.toml | 2 +- src/file/functions.rs | 226 ++++++++++++++++++++++++++++++++++++++++++ src/file/partition.rs | 36 +++++++ 3 files changed, 263 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6451b69d..b9efc7d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,5 +81,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.34), libmagic1, libssl3 (>= 3.0.0), mtools" +depends = "bmap-tools, e2fsprogs, e2tools, keychain, libc6 (>= 2.34), libmagic1, libssl3 (>= 3.0.0), mtools" revision = "" diff --git a/src/file/functions.rs b/src/file/functions.rs index 49a486c7..800ff5a4 100644 --- a/src/file/functions.rs +++ b/src/file/functions.rs @@ -14,6 +14,16 @@ const SECTOR_SIZE: u64 = 512; const COMPARE_BLOCK_SIZE: usize = 4096; const COPY_CHUNK_SIZE: usize = 1024 * 1024; +const EXT4_SUPERBLOCK_OFFSET: u64 = 1024; +const EXT4_SUPERBLOCK_SIZE: usize = 1024; +const EXT4_S_LOG_BLOCK_SIZE: usize = 0x18; +const EXT4_S_MAGIC: usize = 0x38; +const EXT4_S_FEATURE_COMPAT: usize = 0x5c; +const EXT4_S_JOURNAL_INUM: usize = 0xe0; +const EXT4_MAGIC: u16 = 0xef53; +const EXT4_FEATURE_COMPAT_HAS_JOURNAL: u32 = 0x4; +const EXT4_MIN_BLOCK_SIZE: u64 = 1024; + #[derive(clap::ValueEnum, Debug, Clone, Eq, Hash, PartialEq)] #[clap(rename_all = "verbatim")] #[allow(non_camel_case_types)] @@ -485,7 +495,113 @@ fn write_partition( .context("write_partition: cannot sync image") } +struct Ext4Journal { + inode: u32, + block_size: u64, +} + +fn read_ext4_journal(image: &fs::File, offset: u64) -> Result> { + let mut sb = [0u8; EXT4_SUPERBLOCK_SIZE]; + image + .read_exact_at(&mut sb, offset + EXT4_SUPERBLOCK_OFFSET) + .context("read_ext4_journal: cannot read superblock")?; + let le16 = |o: usize| u16::from_le_bytes([sb[o], sb[o + 1]]); + let le32 = |o: usize| u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]); + + if le16(EXT4_S_MAGIC) != EXT4_MAGIC + || le32(EXT4_S_FEATURE_COMPAT) & EXT4_FEATURE_COMPAT_HAS_JOURNAL == 0 + { + return Ok(None); + } + + // an external journal has no inode in this filesystem + let inode = le32(EXT4_S_JOURNAL_INUM); + if inode == 0 { + return Ok(None); + } + + let block_size = EXT4_MIN_BLOCK_SIZE + .checked_shl(le32(EXT4_S_LOG_BLOCK_SIZE)) + .context("read_ext4_journal: invalid block size")?; + + Ok(Some(Ext4Journal { inode, block_size })) +} + +fn ext4_journal_blocks(image_file: &str, offset: u64, inode: u32) -> Result> { + let mut debugfs = Command::new("debugfs"); + debugfs + .arg("-R") + .arg(format!("blocks <{inode}>")) + .arg(format!("{image_file}?offset={offset}")); + let output = debugfs + .output() + .context(format!("ext4_journal_blocks: cannot run {debugfs:?}"))?; + anyhow::ensure!( + output.status.success(), + "ext4_journal_blocks: {debugfs:?} failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let blocks = String::from_utf8(output.stdout) + .context("ext4_journal_blocks: invalid debugfs output")? + .split_whitespace() + .map(|b| { + b.parse::() + .context(format!("ext4_journal_blocks: invalid block number {b}")) + }) + .collect::>>()?; + anyhow::ensure!( + !blocks.is_empty(), + "ext4_journal_blocks: no journal blocks at offset {offset}" + ); + + Ok(blocks) +} + +/// Rewrites the journal blocks of every ext4 partition with their own content. Blocks that +/// are holes become allocated, so a bmap maps the whole journal and a bmap-based flash +/// overwrites the journal an earlier installation left on the device. +fn allocate_ext4_journals(image_file: &str) -> Result<()> { + use crate::file::partition::get_partitions; + + let image = fs::OpenOptions::new() + .read(true) + .write(true) + .open(image_file) + .context(format!("allocate_ext4_journals: cannot open {image_file}"))?; + + for partition in get_partitions(image_file)? { + let offset = partition.start * SECTOR_SIZE; + let Some(journal) = read_ext4_journal(&image, offset)? else { + continue; + }; + let blocks = ext4_journal_blocks(image_file, offset, journal.inode)?; + debug!( + "allocate_ext4_journals: partition {}: {} journal blocks", + partition.num, + blocks.len() + ); + + let mut buf = vec![0u8; usize::try_from(journal.block_size)?]; + for block in blocks { + let pos = offset + block * journal.block_size; + image + .read_exact_at(&mut buf, pos) + .context("allocate_ext4_journals: cannot read journal block")?; + image + .write_all_at(&buf, pos) + .context("allocate_ext4_journals: cannot write journal block")?; + } + } + + image + .sync_all() + .context("allocate_ext4_journals: cannot sync image") +} + pub fn generate_bmap_file(image_file: &str) -> Result<()> { + allocate_ext4_journals(image_file)?; + let mut bmaptool = Command::new("bmaptool"); bmaptool .arg("create") @@ -496,3 +612,113 @@ pub fn generate_bmap_file(image_file: &str) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + const PART_START_LBA: u32 = 2048; + const PART_SECTORS: u32 = 16384; + const FS_BLOCK_SIZE: u64 = 1024; + const MBR_DISK_SIGNATURE: [u8; 4] = [1, 2, 3, 4]; + const MBR_TYPE_LINUX: u8 = 0x83; + const BMAP_BLOCK_SIZE: u64 = 4096; + + fn run(cmd: &mut Command) { + let status = cmd.status().expect("run command"); + assert!(status.success(), "{cmd:?} failed"); + } + + fn create_image_with_ext4_journal(image: &Path) { + let raw = image.with_extension("raw"); + let mut f = fs::File::create(&raw).expect("create image"); + f.set_len(u64::from(PART_START_LBA + PART_SECTORS) * SECTOR_SIZE) + .expect("set image size"); + let mut mbr = mbrman::MBR::new_from(&mut f, SECTOR_SIZE as u32, MBR_DISK_SIGNATURE) + .expect("create mbr"); + mbr[1] = mbrman::MBRPartitionEntry { + boot: mbrman::BOOT_INACTIVE, + first_chs: mbrman::CHS::empty(), + sys: MBR_TYPE_LINUX, + last_chs: mbrman::CHS::empty(), + starting_lba: PART_START_LBA, + sectors: PART_SECTORS, + }; + mbr.write_into(&mut f).expect("write mbr"); + + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + let fs_blocks = u64::from(PART_SECTORS) * SECTOR_SIZE / FS_BLOCK_SIZE; + run(Command::new("mkfs.ext4") + .arg("-q") + .arg("-F") + .arg("-b") + .arg(FS_BLOCK_SIZE.to_string()) + .arg("-J") + .arg("size=1") + .arg("-E") + .arg(format!("offset={offset}")) + .arg(&raw) + .arg(fs_blocks.to_string())); + // mke2fs zeroes the journal as unwritten extents, which a bmap maps; a sparse copy + // turns the zeros into holes, as a sparse decompress does + run(Command::new("cp") + .arg("--sparse=always") + .arg(&raw) + .arg(image)); + } + + fn bmap_ranges(bmap: &str) -> Vec<(u64, u64)> { + bmap.lines() + .filter_map(|l| { + let range = l.split_once("')?.1; + let range = range.split_once(" usize { + let ranges = bmap_ranges(bmap); + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + let file = fs::File::open(image).expect("open image"); + let journal = read_ext4_journal(&file, offset) + .expect("read superblock") + .expect("ext4 with journal"); + ext4_journal_blocks(image, offset, journal.inode) + .expect("journal blocks") + .iter() + .map(|b| (offset + b * journal.block_size) / BMAP_BLOCK_SIZE) + .filter(|b| !ranges.iter().any(|(s, e)| s <= b && b <= e)) + .count() + } + + #[test] + fn generate_bmap_file_maps_ext4_journal() { + let dir = tempfile::tempdir().expect("create temp dir"); + let image_path = dir.path().join("image.wic"); + create_image_with_ext4_journal(&image_path); + let image = image_path.to_str().expect("image path"); + let bmap_path = format!("{image}.bmap"); + + run(Command::new("bmaptool") + .arg("create") + .arg("-o") + .arg(&bmap_path) + .arg(image)); + let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); + assert!(unmapped_journal_blocks(image, &bmap) > 0); + + generate_bmap_file(image).expect("generate bmap"); + let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); + assert_eq!(unmapped_journal_blocks(image, &bmap), 0); + + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + run(Command::new("e2fsck") + .arg("-fn") + .arg(format!("{image}?offset={offset}"))); + } +} diff --git a/src/file/partition.rs b/src/file/partition.rs index 04d2fc6d..cc7b28a3 100644 --- a/src/file/partition.rs +++ b/src/file/partition.rs @@ -55,6 +55,42 @@ pub fn get_partition_data>(path: P, partition_num: u32) -> Result anyhow::bail!("partition {partition_num} not found in image") } +pub fn get_partitions>(path: P) -> Result> { + let path = path.as_ref(); + let mut file = + File::open(path).with_context(|| format!("failed to open image: {}", path.display()))?; + + let gpt_err = match gptman::GPT::find_from(&mut file) { + Ok(gpt) => { + return Ok(gpt + .iter() + .filter(|(_, e)| e.is_used() && e.ending_lba >= e.starting_lba) + .map(|(num, e)| PartitionData { + num, + start: e.starting_lba, + count: e.ending_lba - e.starting_lba + 1, + }) + .collect()); + } + Err(e) => e, + }; + + file.seek(SeekFrom::Start(0)) + .context("failed to seek to start of image")?; + let mbr = mbrman::MBR::read_from(&mut file, 512) + .with_context(|| format!("image is neither valid GPT nor MBR (GPT error: {gpt_err})"))?; + mbr.iter() + .filter(|(_, p)| p.is_used()) + .map(|(num, p)| { + Ok(PartitionData { + num: u32::try_from(num).context("MBR partition number out of range")?, + start: p.starting_lba as u64, + count: p.sectors as u64, + }) + }) + .collect() +} + pub fn is_gpt>(path: P) -> Result { let mut file = File::open(path.as_ref()).context("is_gpt: failed to open image")?; Ok(gptman::GPT::find_from(&mut file).is_ok()) From 52fe21d5cbfb87bed1ceaae052b09f0fd38a57da Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:05:32 +0200 Subject: [PATCH 06/11] fix: copy image data segments larger than 2 GiB completely libfs::copy_file copies each data segment with one copy_file_range call and ignores a short copy, so a segment over 2 GiB was cut off without error. copy_image keeps the holes and loops until each segment is copied. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- src/lib.rs | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 343f912e..a8ce6bf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use cli::{ }; use file::{compression::Compression, functions::FileCopyToParams}; use log::error; -use std::{fs, path::Path, path::PathBuf}; +use std::{fs, io::Read, path::Path, path::PathBuf}; use tokio::fs::remove_dir_all; use uuid::Uuid; @@ -93,9 +93,8 @@ where tmp_image_file = compression::decompress(&tmp_image_file, &source_compression)?; dest_image_file.set_extension(""); } else { - // copy sparse file (std::fs::copy isn't able) - libfs::copy_file(&image_file, &tmp_image_file).context(format!( - "error: libfs::copy_file({:?}, {:?})", + copy_image(&image_file, &tmp_image_file).context(format!( + "error: copy_image({:?}, {:?})", image_file, tmp_image_file ))?; } @@ -140,9 +139,8 @@ where tmp_image_file, dest_image_file ))?; } else { - // copy sparse file (std::fs::copy isn't able) - libfs::copy_file(&tmp_image_file, &dest_image_file).context(format!( - "error: libfs::copy_file({:?}, {:?})", + copy_image(&tmp_image_file, &dest_image_file).context(format!( + "error: copy_image({:?}, {:?})", tmp_image_file, dest_image_file ))?; } @@ -150,6 +148,35 @@ where Ok(()) } +/// Copies `from` to `to` and keeps the holes of a sparse `from`. +fn copy_image(from: &Path, to: &Path) -> Result<()> { + let src = fs::File::open(from).context("copy_image: cannot open source")?; + let len = src + .metadata() + .context("copy_image: cannot get source size")? + .len(); + let dst = fs::File::create(to).context("copy_image: cannot create destination")?; + dst.set_len(len) + .context("copy_image: cannot set destination size")?; + + // one copy_file_range call copies at most 2 GiB; io::copy repeats it until a segment is done + let sparse = libfs::probably_sparse(&src)?; + let mut pos = 0; + while pos < len { + let (data, hole) = if sparse { + libfs::next_sparse_segments(&src, &dst, pos)? + } else { + (pos, len) + }; + let copied = std::io::copy(&mut (&src).take(hole - data), &mut &dst) + .context("copy_image: cannot copy data")?; + anyhow::ensure!(copied == hole - data, "copy_image: source ended early"); + pos = hole; + } + + Ok(()) +} + struct CertInfo { cert_path: PathBuf, key_path: PathBuf, From 19bcd306f3966d384bb029b9b99de70515a7078c Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:05:33 +0200 Subject: [PATCH 07/11] fix(file): harden ext4 journal lookup for bmap generation - fall back to /usr/sbin/debugfs when debugfs is not in PATH - reject an invalid s_log_block_size before it is used as a shift - skip partitions whose superblock is past the end of the image - skip the MBR extended container entry - share SECTOR_SIZE between partition and functions Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- src/file/functions.rs | 49 +++++++++++++++++++++++++++++++------------ src/file/partition.rs | 8 ++++--- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/file/functions.rs b/src/file/functions.rs index 800ff5a4..241b0dd2 100644 --- a/src/file/functions.rs +++ b/src/file/functions.rs @@ -1,3 +1,4 @@ +use crate::file::partition::SECTOR_SIZE; use anyhow::{Context, Result}; use log::{debug, warn}; use std::collections::HashMap; @@ -10,7 +11,6 @@ use std::str::FromStr; use stdext::function_name; use uuid::Uuid; -const SECTOR_SIZE: u64 = 512; const COMPARE_BLOCK_SIZE: usize = 4096; const COPY_CHUNK_SIZE: usize = 1024 * 1024; @@ -23,6 +23,11 @@ const EXT4_S_JOURNAL_INUM: usize = 0xe0; const EXT4_MAGIC: u16 = 0xef53; const EXT4_FEATURE_COMPAT_HAS_JOURNAL: u32 = 0x4; const EXT4_MIN_BLOCK_SIZE: u64 = 1024; +const EXT4_MAX_LOG_BLOCK_SIZE: u32 = 6; + +const DEBUGFS: &str = "debugfs"; +// debugfs is installed to sbin, which is not always in the PATH of a normal user +const DEBUGFS_SBIN: &str = "/usr/sbin/debugfs"; #[derive(clap::ValueEnum, Debug, Clone, Eq, Hash, PartialEq)] #[clap(rename_all = "verbatim")] @@ -520,22 +525,33 @@ fn read_ext4_journal(image: &fs::File, offset: u64) -> Result Result> { - let mut debugfs = Command::new("debugfs"); - debugfs - .arg("-R") - .arg(format!("blocks <{inode}>")) - .arg(format!("{image_file}?offset={offset}")); - let output = debugfs - .output() - .context(format!("ext4_journal_blocks: cannot run {debugfs:?}"))?; + let debugfs_cmd = |program| { + let mut cmd = Command::new(program); + cmd.arg("-R") + .arg(format!("blocks <{inode}>")) + .arg(format!("{image_file}?offset={offset}")); + cmd + }; + let mut debugfs = debugfs_cmd(DEBUGFS); + let output = match debugfs.output() { + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + debugfs = debugfs_cmd(DEBUGFS_SBIN); + debugfs.output() + } + output => output, + } + .context(format!("ext4_journal_blocks: cannot run {debugfs:?}"))?; anyhow::ensure!( output.status.success(), "ext4_journal_blocks: {debugfs:?} failed: {}", @@ -569,9 +585,16 @@ fn allocate_ext4_journals(image_file: &str) -> Result<()> { .write(true) .open(image_file) .context(format!("allocate_ext4_journals: cannot open {image_file}"))?; + let image_len = image + .metadata() + .context("allocate_ext4_journals: cannot get image size")? + .len(); for partition in get_partitions(image_file)? { let offset = partition.start * SECTOR_SIZE; + if offset + EXT4_SUPERBLOCK_OFFSET + EXT4_SUPERBLOCK_SIZE as u64 > image_len { + continue; + } let Some(journal) = read_ext4_journal(&image, offset)? else { continue; }; @@ -615,7 +638,7 @@ pub fn generate_bmap_file(image_file: &str) -> Result<()> { #[cfg(test)] mod tests { - use super::*; + use crate::file::functions::*; const PART_START_LBA: u32 = 2048; const PART_SECTORS: u32 = 16384; diff --git a/src/file/partition.rs b/src/file/partition.rs index cc7b28a3..9d9d0b8c 100644 --- a/src/file/partition.rs +++ b/src/file/partition.rs @@ -4,6 +4,8 @@ use std::io::Seek; use std::io::SeekFrom; use std::path::Path; +pub const SECTOR_SIZE: u64 = 512; + pub struct PartitionData { pub num: u32, pub start: u64, @@ -41,7 +43,7 @@ pub fn get_partition_data>(path: P, partition_num: u32) -> Result // Try MBR — iter() includes logical partitions (5, 6, ...) file.seek(SeekFrom::Start(0)) .context("failed to seek to start of image")?; - let mbr = mbrman::MBR::read_from(&mut file, 512) + let mbr = mbrman::MBR::read_from(&mut file, SECTOR_SIZE as u32) .with_context(|| format!("image is neither valid GPT nor MBR (GPT error: {gpt_err})"))?; for (i, p) in mbr.iter() { if u32::try_from(i).ok() == Some(partition_num) && p.is_used() { @@ -77,10 +79,10 @@ pub fn get_partitions>(path: P) -> Result> { file.seek(SeekFrom::Start(0)) .context("failed to seek to start of image")?; - let mbr = mbrman::MBR::read_from(&mut file, 512) + let mbr = mbrman::MBR::read_from(&mut file, SECTOR_SIZE as u32) .with_context(|| format!("image is neither valid GPT nor MBR (GPT error: {gpt_err})"))?; mbr.iter() - .filter(|(_, p)| p.is_used()) + .filter(|(_, p)| p.is_used() && !p.is_extended()) .map(|(num, p)| { Ok(PartitionData { num: u32::try_from(num).context("MBR partition number out of range")?, From d7e724244c2ef4aa10a162ba7ab4ceff2b205186 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:05:34 +0200 Subject: [PATCH 08/11] build(docker): install e2fsprogs in the builder The deb package depends on e2fsprogs for debugfs. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 8eb12929..4fc20c1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ bmap-tools \ ca-certificates \ + e2fsprogs \ e2tools \ fdisk \ keychain \ From 2912452cebd3eb6d2eeeae94d11149d998f708a2 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:13:38 +0200 Subject: [PATCH 09/11] fix(file): keep zero blocks as holes when decompressing Decompress wrote every zero block as data, so a bmap of a compressed input mapped the whole image. The decompressed file now seeks over zero blocks and keeps them as holes. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- src/file/compression.rs | 97 ++++++++++++++++++++++++++++++++++++-- tests/integration_tests.rs | 3 ++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/file/compression.rs b/src/file/compression.rs index 16f18626..fca98d49 100644 --- a/src/file/compression.rs +++ b/src/file/compression.rs @@ -3,11 +3,14 @@ use filemagic::Magic; use log::debug; use std::env; use std::fs::File; +use std::io::{Seek, SeekFrom, Write}; use std::path::PathBuf; use std::str::FromStr; use strum::IntoEnumIterator; use strum_macros::EnumIter; +const SPARSE_BLOCK_SIZE: u64 = 4096; + #[derive(Clone, Debug, EnumIter)] #[allow(non_camel_case_types)] pub enum Compression { @@ -76,15 +79,21 @@ impl Compression { source: &mut std::fs::File, destination: &mut std::fs::File, ) -> std::io::Result { - let mut dec: Box = match &self { - Compression::bzip2 => Box::new(bzip2::write::BzDecoder::new(destination)), - Compression::gzip => Box::new(flate2::write::GzDecoder::new(destination)), - Compression::xz { .. } => Box::new(xz2::write::XzDecoder::new(destination)), + let sparse = SparseWriter(destination); + let mut dec: Box = match &self { + Compression::bzip2 => Box::new(bzip2::write::BzDecoder::new(sparse)), + Compression::gzip => Box::new(flate2::write::GzDecoder::new(sparse)), + Compression::xz { .. } => Box::new(xz2::write::XzDecoder::new(sparse)), }; let bytes_written = std::io::copy(source, &mut dec)?; dec.write_all(&[])?; dec.flush()?; + drop(dec); + + // zero blocks at the end were only seeked over + let len = destination.stream_position()?; + destination.set_len(len)?; Ok(bytes_written) } @@ -126,6 +135,27 @@ impl Compression { } } +/// Seeks over zero blocks instead of writing them, so they stay holes in a new file. +struct SparseWriter<'a>(&'a mut File); + +impl Write for SparseWriter<'_> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let pos = self.0.stream_position()?; + let to_block_end = SPARSE_BLOCK_SIZE - pos % SPARSE_BLOCK_SIZE; + let chunk = &buf[..buf.len().min(to_block_end as usize)]; + if chunk.iter().all(|b| *b == 0) { + self.0.seek(SeekFrom::Current(chunk.len() as i64))?; + Ok(chunk.len()) + } else { + self.0.write(chunk) + } + } + + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} + pub fn decompress(image_file_name: &PathBuf, compression: &Compression) -> Result { let mut new_image_file = PathBuf::from(image_file_name); @@ -157,3 +187,62 @@ pub fn compress(image_file_name: &PathBuf, compression: &Compression) -> Result< debug!("image::compress: copied {} bytes.", bytes_written); Ok(new_image_file) } + +#[cfg(test)] +mod tests { + use crate::file::compression::*; + use std::io::Read; + use std::os::unix::fs::MetadataExt; + + // unit of `st_blocks` + const STAT_BLOCK_SIZE: u64 = 512; + const DATA_OFFSET: usize = 1024 * 1024 + 100; + const IMAGE_LEN: usize = 3 * 1024 * 1024 + 123; + + fn roundtrip(compression: Compression, image: &[u8]) -> File { + let mut source = tempfile::tempfile().expect("create source"); + source.write_all(image).expect("write source"); + source.rewind().expect("rewind source"); + + let mut compressed = tempfile::tempfile().expect("create compressed"); + compression + .compress(&mut source, &mut compressed) + .expect("compress"); + compressed.rewind().expect("rewind compressed"); + + let mut decompressed = tempfile::tempfile().expect("create decompressed"); + compression + .decompress(&mut compressed, &mut decompressed) + .expect("decompress"); + decompressed + } + + #[test] + fn decompress_keeps_zero_blocks_as_holes() { + let mut image = vec![0u8; IMAGE_LEN]; + image[DATA_OFFSET..DATA_OFFSET + 3].copy_from_slice(b"abc"); + + for compression in [ + Compression::bzip2, + Compression::gzip, + Compression::xz { + compression_level: 1, + }, + ] { + let mut decompressed = roundtrip(compression.clone(), &image); + + let mut content = Vec::new(); + decompressed.rewind().expect("rewind decompressed"); + decompressed + .read_to_end(&mut content) + .expect("read decompressed"); + assert!(content == image, "{compression:?}: content differs"); + + let meta = decompressed.metadata().expect("metadata"); + assert!( + meta.blocks() * STAT_BLOCK_SIZE < meta.len(), + "{compression:?}: no holes" + ); + } + } +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 4052768b..c807429d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1051,6 +1051,9 @@ fn check_bmap_generation_wic_xz() { .assert(); assert.success(); + let bmap = std::fs::read_to_string(&image_path_bmap).expect("read bmap"); + assert!(bmap_value(&bmap, "MappedBlocksCount") < bmap_value(&bmap, "BlocksCount")); + // use bmaptool to verify that the checksum of the bmap file and the image // still match after the copy operations let assert = Command::new("bmaptool") From 84ba27e6d68445033af07aa6222b7dec49d873e9 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:18:24 +0200 Subject: [PATCH 10/11] refactor(file): move bmap generation into its own module The ext4 journal lookup is only used for bmap generation, so it moves with generate_bmap_file from functions.rs to bmap.rs. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- src/file/bmap.rs | 269 ++++++++++++++++++++++++++++++++++++++++++ src/file/functions.rs | 261 ---------------------------------------- src/file/mod.rs | 1 + src/lib.rs | 2 +- 4 files changed, 271 insertions(+), 262 deletions(-) create mode 100644 src/file/bmap.rs diff --git a/src/file/bmap.rs b/src/file/bmap.rs new file mode 100644 index 00000000..e6b6352b --- /dev/null +++ b/src/file/bmap.rs @@ -0,0 +1,269 @@ +use crate::file::partition::{SECTOR_SIZE, get_partitions}; +use anyhow::{Context, Result}; +use log::debug; +use std::fs; +use std::os::unix::fs::FileExt; +use std::process::Command; + +const EXT4_SUPERBLOCK_OFFSET: u64 = 1024; +const EXT4_SUPERBLOCK_SIZE: usize = 1024; +const EXT4_S_LOG_BLOCK_SIZE: usize = 0x18; +const EXT4_S_MAGIC: usize = 0x38; +const EXT4_S_FEATURE_COMPAT: usize = 0x5c; +const EXT4_S_JOURNAL_INUM: usize = 0xe0; +const EXT4_MAGIC: u16 = 0xef53; +const EXT4_FEATURE_COMPAT_HAS_JOURNAL: u32 = 0x4; +const EXT4_MIN_BLOCK_SIZE: u64 = 1024; +const EXT4_MAX_LOG_BLOCK_SIZE: u32 = 6; + +const DEBUGFS: &str = "debugfs"; +// debugfs is installed to sbin, which is not always in the PATH of a normal user +const DEBUGFS_SBIN: &str = "/usr/sbin/debugfs"; + +struct Ext4Journal { + inode: u32, + block_size: u64, +} + +fn read_ext4_journal(image: &fs::File, offset: u64) -> Result> { + let mut sb = [0u8; EXT4_SUPERBLOCK_SIZE]; + image + .read_exact_at(&mut sb, offset + EXT4_SUPERBLOCK_OFFSET) + .context("read_ext4_journal: cannot read superblock")?; + let le16 = |o: usize| u16::from_le_bytes([sb[o], sb[o + 1]]); + let le32 = |o: usize| u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]); + + if le16(EXT4_S_MAGIC) != EXT4_MAGIC + || le32(EXT4_S_FEATURE_COMPAT) & EXT4_FEATURE_COMPAT_HAS_JOURNAL == 0 + { + return Ok(None); + } + + // an external journal has no inode in this filesystem + let inode = le32(EXT4_S_JOURNAL_INUM); + if inode == 0 { + return Ok(None); + } + + let log_block_size = le32(EXT4_S_LOG_BLOCK_SIZE); + anyhow::ensure!( + log_block_size <= EXT4_MAX_LOG_BLOCK_SIZE, + "read_ext4_journal: invalid block size" + ); + let block_size = EXT4_MIN_BLOCK_SIZE << log_block_size; + + Ok(Some(Ext4Journal { inode, block_size })) +} + +fn ext4_journal_blocks(image_file: &str, offset: u64, inode: u32) -> Result> { + let debugfs_cmd = |program| { + let mut cmd = Command::new(program); + cmd.arg("-R") + .arg(format!("blocks <{inode}>")) + .arg(format!("{image_file}?offset={offset}")); + cmd + }; + let mut debugfs = debugfs_cmd(DEBUGFS); + let output = match debugfs.output() { + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + debugfs = debugfs_cmd(DEBUGFS_SBIN); + debugfs.output() + } + output => output, + } + .context(format!("ext4_journal_blocks: cannot run {debugfs:?}"))?; + anyhow::ensure!( + output.status.success(), + "ext4_journal_blocks: {debugfs:?} failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let blocks = String::from_utf8(output.stdout) + .context("ext4_journal_blocks: invalid debugfs output")? + .split_whitespace() + .map(|b| { + b.parse::() + .context(format!("ext4_journal_blocks: invalid block number {b}")) + }) + .collect::>>()?; + anyhow::ensure!( + !blocks.is_empty(), + "ext4_journal_blocks: no journal blocks at offset {offset}" + ); + + Ok(blocks) +} + +/// Rewrites the journal blocks of every ext4 partition with their own content. Blocks that +/// are holes become allocated, so a bmap maps the whole journal and a bmap-based flash +/// overwrites the journal an earlier installation left on the device. +fn allocate_ext4_journals(image_file: &str) -> Result<()> { + let image = fs::OpenOptions::new() + .read(true) + .write(true) + .open(image_file) + .context(format!("allocate_ext4_journals: cannot open {image_file}"))?; + let image_len = image + .metadata() + .context("allocate_ext4_journals: cannot get image size")? + .len(); + + for partition in get_partitions(image_file)? { + let offset = partition.start * SECTOR_SIZE; + if offset + EXT4_SUPERBLOCK_OFFSET + EXT4_SUPERBLOCK_SIZE as u64 > image_len { + continue; + } + let Some(journal) = read_ext4_journal(&image, offset)? else { + continue; + }; + let blocks = ext4_journal_blocks(image_file, offset, journal.inode)?; + debug!( + "allocate_ext4_journals: partition {}: {} journal blocks", + partition.num, + blocks.len() + ); + + let mut buf = vec![0u8; usize::try_from(journal.block_size)?]; + for block in blocks { + let pos = offset + block * journal.block_size; + image + .read_exact_at(&mut buf, pos) + .context("allocate_ext4_journals: cannot read journal block")?; + image + .write_all_at(&buf, pos) + .context("allocate_ext4_journals: cannot write journal block")?; + } + } + + image + .sync_all() + .context("allocate_ext4_journals: cannot sync image") +} + +pub fn generate_bmap_file(image_file: &str) -> Result<()> { + allocate_ext4_journals(image_file)?; + + let mut bmaptool = Command::new("bmaptool"); + bmaptool + .arg("create") + .arg("-o") + .arg(format!("{image_file}.bmap")) + .arg(image_file); + let status = bmaptool + .status() + .context(format!("generate_bmap_file: cannot run {bmaptool:?}"))?; + anyhow::ensure!(status.success(), "generate_bmap_file: {bmaptool:?} failed"); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::file::bmap::*; + use std::path::Path; + + const PART_START_LBA: u32 = 2048; + const PART_SECTORS: u32 = 16384; + const FS_BLOCK_SIZE: u64 = 1024; + const MBR_DISK_SIGNATURE: [u8; 4] = [1, 2, 3, 4]; + const MBR_TYPE_LINUX: u8 = 0x83; + const BMAP_BLOCK_SIZE: u64 = 4096; + + fn run(cmd: &mut Command) { + let status = cmd.status().expect("run command"); + assert!(status.success(), "{cmd:?} failed"); + } + + fn create_image_with_ext4_journal(image: &Path) { + let raw = image.with_extension("raw"); + let mut f = fs::File::create(&raw).expect("create image"); + f.set_len(u64::from(PART_START_LBA + PART_SECTORS) * SECTOR_SIZE) + .expect("set image size"); + let mut mbr = mbrman::MBR::new_from(&mut f, SECTOR_SIZE as u32, MBR_DISK_SIGNATURE) + .expect("create mbr"); + mbr[1] = mbrman::MBRPartitionEntry { + boot: mbrman::BOOT_INACTIVE, + first_chs: mbrman::CHS::empty(), + sys: MBR_TYPE_LINUX, + last_chs: mbrman::CHS::empty(), + starting_lba: PART_START_LBA, + sectors: PART_SECTORS, + }; + mbr.write_into(&mut f).expect("write mbr"); + + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + let fs_blocks = u64::from(PART_SECTORS) * SECTOR_SIZE / FS_BLOCK_SIZE; + run(Command::new("mkfs.ext4") + .arg("-q") + .arg("-F") + .arg("-b") + .arg(FS_BLOCK_SIZE.to_string()) + .arg("-J") + .arg("size=1") + .arg("-E") + .arg(format!("offset={offset}")) + .arg(&raw) + .arg(fs_blocks.to_string())); + // mke2fs zeroes the journal as unwritten extents, which a bmap maps; a sparse copy + // turns the zeros into holes, as a sparse decompress does + run(Command::new("cp") + .arg("--sparse=always") + .arg(&raw) + .arg(image)); + } + + fn bmap_ranges(bmap: &str) -> Vec<(u64, u64)> { + bmap.lines() + .filter_map(|l| { + let range = l.split_once("')?.1; + let range = range.split_once(" usize { + let ranges = bmap_ranges(bmap); + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + let file = fs::File::open(image).expect("open image"); + let journal = read_ext4_journal(&file, offset) + .expect("read superblock") + .expect("ext4 with journal"); + ext4_journal_blocks(image, offset, journal.inode) + .expect("journal blocks") + .iter() + .map(|b| (offset + b * journal.block_size) / BMAP_BLOCK_SIZE) + .filter(|b| !ranges.iter().any(|(s, e)| s <= b && b <= e)) + .count() + } + + #[test] + fn generate_bmap_file_maps_ext4_journal() { + let dir = tempfile::tempdir().expect("create temp dir"); + let image_path = dir.path().join("image.wic"); + create_image_with_ext4_journal(&image_path); + let image = image_path.to_str().expect("image path"); + let bmap_path = format!("{image}.bmap"); + + run(Command::new("bmaptool") + .arg("create") + .arg("-o") + .arg(&bmap_path) + .arg(image)); + let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); + assert!(unmapped_journal_blocks(image, &bmap) > 0); + + generate_bmap_file(image).expect("generate bmap"); + let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); + assert_eq!(unmapped_journal_blocks(image, &bmap), 0); + + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + run(Command::new("e2fsck") + .arg("-fn") + .arg(format!("{image}?offset={offset}"))); + } +} diff --git a/src/file/functions.rs b/src/file/functions.rs index 241b0dd2..d6591e9f 100644 --- a/src/file/functions.rs +++ b/src/file/functions.rs @@ -14,21 +14,6 @@ use uuid::Uuid; const COMPARE_BLOCK_SIZE: usize = 4096; const COPY_CHUNK_SIZE: usize = 1024 * 1024; -const EXT4_SUPERBLOCK_OFFSET: u64 = 1024; -const EXT4_SUPERBLOCK_SIZE: usize = 1024; -const EXT4_S_LOG_BLOCK_SIZE: usize = 0x18; -const EXT4_S_MAGIC: usize = 0x38; -const EXT4_S_FEATURE_COMPAT: usize = 0x5c; -const EXT4_S_JOURNAL_INUM: usize = 0xe0; -const EXT4_MAGIC: u16 = 0xef53; -const EXT4_FEATURE_COMPAT_HAS_JOURNAL: u32 = 0x4; -const EXT4_MIN_BLOCK_SIZE: u64 = 1024; -const EXT4_MAX_LOG_BLOCK_SIZE: u32 = 6; - -const DEBUGFS: &str = "debugfs"; -// debugfs is installed to sbin, which is not always in the PATH of a normal user -const DEBUGFS_SBIN: &str = "/usr/sbin/debugfs"; - #[derive(clap::ValueEnum, Debug, Clone, Eq, Hash, PartialEq)] #[clap(rename_all = "verbatim")] #[allow(non_camel_case_types)] @@ -499,249 +484,3 @@ fn write_partition( .sync_all() .context("write_partition: cannot sync image") } - -struct Ext4Journal { - inode: u32, - block_size: u64, -} - -fn read_ext4_journal(image: &fs::File, offset: u64) -> Result> { - let mut sb = [0u8; EXT4_SUPERBLOCK_SIZE]; - image - .read_exact_at(&mut sb, offset + EXT4_SUPERBLOCK_OFFSET) - .context("read_ext4_journal: cannot read superblock")?; - let le16 = |o: usize| u16::from_le_bytes([sb[o], sb[o + 1]]); - let le32 = |o: usize| u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]); - - if le16(EXT4_S_MAGIC) != EXT4_MAGIC - || le32(EXT4_S_FEATURE_COMPAT) & EXT4_FEATURE_COMPAT_HAS_JOURNAL == 0 - { - return Ok(None); - } - - // an external journal has no inode in this filesystem - let inode = le32(EXT4_S_JOURNAL_INUM); - if inode == 0 { - return Ok(None); - } - - let log_block_size = le32(EXT4_S_LOG_BLOCK_SIZE); - anyhow::ensure!( - log_block_size <= EXT4_MAX_LOG_BLOCK_SIZE, - "read_ext4_journal: invalid block size" - ); - let block_size = EXT4_MIN_BLOCK_SIZE << log_block_size; - - Ok(Some(Ext4Journal { inode, block_size })) -} - -fn ext4_journal_blocks(image_file: &str, offset: u64, inode: u32) -> Result> { - let debugfs_cmd = |program| { - let mut cmd = Command::new(program); - cmd.arg("-R") - .arg(format!("blocks <{inode}>")) - .arg(format!("{image_file}?offset={offset}")); - cmd - }; - let mut debugfs = debugfs_cmd(DEBUGFS); - let output = match debugfs.output() { - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - debugfs = debugfs_cmd(DEBUGFS_SBIN); - debugfs.output() - } - output => output, - } - .context(format!("ext4_journal_blocks: cannot run {debugfs:?}"))?; - anyhow::ensure!( - output.status.success(), - "ext4_journal_blocks: {debugfs:?} failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let blocks = String::from_utf8(output.stdout) - .context("ext4_journal_blocks: invalid debugfs output")? - .split_whitespace() - .map(|b| { - b.parse::() - .context(format!("ext4_journal_blocks: invalid block number {b}")) - }) - .collect::>>()?; - anyhow::ensure!( - !blocks.is_empty(), - "ext4_journal_blocks: no journal blocks at offset {offset}" - ); - - Ok(blocks) -} - -/// Rewrites the journal blocks of every ext4 partition with their own content. Blocks that -/// are holes become allocated, so a bmap maps the whole journal and a bmap-based flash -/// overwrites the journal an earlier installation left on the device. -fn allocate_ext4_journals(image_file: &str) -> Result<()> { - use crate::file::partition::get_partitions; - - let image = fs::OpenOptions::new() - .read(true) - .write(true) - .open(image_file) - .context(format!("allocate_ext4_journals: cannot open {image_file}"))?; - let image_len = image - .metadata() - .context("allocate_ext4_journals: cannot get image size")? - .len(); - - for partition in get_partitions(image_file)? { - let offset = partition.start * SECTOR_SIZE; - if offset + EXT4_SUPERBLOCK_OFFSET + EXT4_SUPERBLOCK_SIZE as u64 > image_len { - continue; - } - let Some(journal) = read_ext4_journal(&image, offset)? else { - continue; - }; - let blocks = ext4_journal_blocks(image_file, offset, journal.inode)?; - debug!( - "allocate_ext4_journals: partition {}: {} journal blocks", - partition.num, - blocks.len() - ); - - let mut buf = vec![0u8; usize::try_from(journal.block_size)?]; - for block in blocks { - let pos = offset + block * journal.block_size; - image - .read_exact_at(&mut buf, pos) - .context("allocate_ext4_journals: cannot read journal block")?; - image - .write_all_at(&buf, pos) - .context("allocate_ext4_journals: cannot write journal block")?; - } - } - - image - .sync_all() - .context("allocate_ext4_journals: cannot sync image") -} - -pub fn generate_bmap_file(image_file: &str) -> Result<()> { - allocate_ext4_journals(image_file)?; - - let mut bmaptool = Command::new("bmaptool"); - bmaptool - .arg("create") - .arg("-o") - .arg(format!("{image_file}.bmap")) - .arg(image_file); - exec_cmd!(bmaptool); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::file::functions::*; - - const PART_START_LBA: u32 = 2048; - const PART_SECTORS: u32 = 16384; - const FS_BLOCK_SIZE: u64 = 1024; - const MBR_DISK_SIGNATURE: [u8; 4] = [1, 2, 3, 4]; - const MBR_TYPE_LINUX: u8 = 0x83; - const BMAP_BLOCK_SIZE: u64 = 4096; - - fn run(cmd: &mut Command) { - let status = cmd.status().expect("run command"); - assert!(status.success(), "{cmd:?} failed"); - } - - fn create_image_with_ext4_journal(image: &Path) { - let raw = image.with_extension("raw"); - let mut f = fs::File::create(&raw).expect("create image"); - f.set_len(u64::from(PART_START_LBA + PART_SECTORS) * SECTOR_SIZE) - .expect("set image size"); - let mut mbr = mbrman::MBR::new_from(&mut f, SECTOR_SIZE as u32, MBR_DISK_SIGNATURE) - .expect("create mbr"); - mbr[1] = mbrman::MBRPartitionEntry { - boot: mbrman::BOOT_INACTIVE, - first_chs: mbrman::CHS::empty(), - sys: MBR_TYPE_LINUX, - last_chs: mbrman::CHS::empty(), - starting_lba: PART_START_LBA, - sectors: PART_SECTORS, - }; - mbr.write_into(&mut f).expect("write mbr"); - - let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; - let fs_blocks = u64::from(PART_SECTORS) * SECTOR_SIZE / FS_BLOCK_SIZE; - run(Command::new("mkfs.ext4") - .arg("-q") - .arg("-F") - .arg("-b") - .arg(FS_BLOCK_SIZE.to_string()) - .arg("-J") - .arg("size=1") - .arg("-E") - .arg(format!("offset={offset}")) - .arg(&raw) - .arg(fs_blocks.to_string())); - // mke2fs zeroes the journal as unwritten extents, which a bmap maps; a sparse copy - // turns the zeros into holes, as a sparse decompress does - run(Command::new("cp") - .arg("--sparse=always") - .arg(&raw) - .arg(image)); - } - - fn bmap_ranges(bmap: &str) -> Vec<(u64, u64)> { - bmap.lines() - .filter_map(|l| { - let range = l.split_once("')?.1; - let range = range.split_once(" usize { - let ranges = bmap_ranges(bmap); - let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; - let file = fs::File::open(image).expect("open image"); - let journal = read_ext4_journal(&file, offset) - .expect("read superblock") - .expect("ext4 with journal"); - ext4_journal_blocks(image, offset, journal.inode) - .expect("journal blocks") - .iter() - .map(|b| (offset + b * journal.block_size) / BMAP_BLOCK_SIZE) - .filter(|b| !ranges.iter().any(|(s, e)| s <= b && b <= e)) - .count() - } - - #[test] - fn generate_bmap_file_maps_ext4_journal() { - let dir = tempfile::tempdir().expect("create temp dir"); - let image_path = dir.path().join("image.wic"); - create_image_with_ext4_journal(&image_path); - let image = image_path.to_str().expect("image path"); - let bmap_path = format!("{image}.bmap"); - - run(Command::new("bmaptool") - .arg("create") - .arg("-o") - .arg(&bmap_path) - .arg(image)); - let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); - assert!(unmapped_journal_blocks(image, &bmap) > 0); - - generate_bmap_file(image).expect("generate bmap"); - let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); - assert_eq!(unmapped_journal_blocks(image, &bmap), 0); - - let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; - run(Command::new("e2fsck") - .arg("-fn") - .arg(format!("{image}?offset={offset}"))); - } -} diff --git a/src/file/mod.rs b/src/file/mod.rs index 6f9db481..e9ccd43e 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -1,3 +1,4 @@ +pub mod bmap; pub mod compression; pub mod functions; mod partition; diff --git a/src/lib.rs b/src/lib.rs index a8ce6bf4..c487ceda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,7 @@ where .to_str() .context("cannot get image file path")? )); - file::functions::generate_bmap_file( + file::bmap::generate_bmap_file( tmp_image_file .to_str() .context("cannot get image file path")?, From 9842fef98392f3df43fb94fb84e25f6e66e24103 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:35:36 +0200 Subject: [PATCH 11/11] test(file): cover ext4 journal edge cases and a flash onto old content - a bmap flash onto a device with old content now overwrites the journal - an invalid s_log_block_size is rejected - a partition that starts past the end of the image is skipped - the MBR extended container entry is not returned as a partition Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- src/file/bmap.rs | 99 ++++++++++++++++++++++++++++++++++++++++--- src/file/partition.rs | 19 +++++++++ 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/file/bmap.rs b/src/file/bmap.rs index e6b6352b..f31259b7 100644 --- a/src/file/bmap.rs +++ b/src/file/bmap.rs @@ -168,17 +168,19 @@ mod tests { const MBR_DISK_SIGNATURE: [u8; 4] = [1, 2, 3, 4]; const MBR_TYPE_LINUX: u8 = 0x83; const BMAP_BLOCK_SIZE: u64 = 4096; + // content an earlier installation left on the device + const OLD_CONTENT_BYTE: u8 = 0xa5; + const JOURNAL_INODE: u32 = 8; + const INVALID_LOG_BLOCK_SIZE: u32 = 40; fn run(cmd: &mut Command) { let status = cmd.status().expect("run command"); assert!(status.success(), "{cmd:?} failed"); } - fn create_image_with_ext4_journal(image: &Path) { - let raw = image.with_extension("raw"); - let mut f = fs::File::create(&raw).expect("create image"); - f.set_len(u64::from(PART_START_LBA + PART_SECTORS) * SECTOR_SIZE) - .expect("set image size"); + fn create_image_with_mbr(image: &Path, len: u64) { + let mut f = fs::File::create(image).expect("create image"); + f.set_len(len).expect("set image size"); let mut mbr = mbrman::MBR::new_from(&mut f, SECTOR_SIZE as u32, MBR_DISK_SIGNATURE) .expect("create mbr"); mbr[1] = mbrman::MBRPartitionEntry { @@ -190,6 +192,11 @@ mod tests { sectors: PART_SECTORS, }; mbr.write_into(&mut f).expect("write mbr"); + } + + fn create_image_with_ext4_journal(image: &Path) { + let raw = image.with_extension("raw"); + create_image_with_mbr(&raw, u64::from(PART_START_LBA + PART_SECTORS) * SECTOR_SIZE); let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; let fs_blocks = u64::from(PART_SECTORS) * SECTOR_SIZE / FS_BLOCK_SIZE; @@ -241,6 +248,55 @@ mod tests { .count() } + /// Writes the blocks `bmap` maps onto a device that still holds old content, as a bmap + /// flash does. + fn bmap_flash(image: &str, bmap: &str, device: &Path) { + let image = fs::File::open(image).expect("open image"); + let len = image.metadata().expect("image metadata").len(); + fs::write( + device, + vec![OLD_CONTENT_BYTE; usize::try_from(len).expect("image size")], + ) + .expect("write old content"); + let device = fs::OpenOptions::new() + .write(true) + .open(device) + .expect("open device"); + + let mut buf = vec![0u8; usize::try_from(BMAP_BLOCK_SIZE).expect("block size")]; + for (first, last) in bmap_ranges(bmap) { + for block in first..=last { + image + .read_exact_at(&mut buf, block * BMAP_BLOCK_SIZE) + .expect("read image block"); + device + .write_all_at(&buf, block * BMAP_BLOCK_SIZE) + .expect("write device block"); + } + } + } + + fn journal_on_device_matches_image(image: &str, device: &Path) -> bool { + let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; + let image_file = fs::File::open(image).expect("open image"); + let device = fs::File::open(device).expect("open device"); + let journal = read_ext4_journal(&image_file, offset) + .expect("read superblock") + .expect("ext4 with journal"); + + let block_size = usize::try_from(journal.block_size).expect("block size"); + let (mut a, mut b) = (vec![0u8; block_size], vec![0u8; block_size]); + ext4_journal_blocks(image, offset, journal.inode) + .expect("journal blocks") + .iter() + .all(|block| { + let pos = offset + block * journal.block_size; + image_file.read_exact_at(&mut a, pos).expect("read image"); + device.read_exact_at(&mut b, pos).expect("read device"); + a == b + }) + } + #[test] fn generate_bmap_file_maps_ext4_journal() { let dir = tempfile::tempdir().expect("create temp dir"); @@ -256,14 +312,47 @@ mod tests { .arg(image)); let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); assert!(unmapped_journal_blocks(image, &bmap) > 0); + let device = dir.path().join("device"); + bmap_flash(image, &bmap, &device); + assert!(!journal_on_device_matches_image(image, &device)); generate_bmap_file(image).expect("generate bmap"); let bmap = fs::read_to_string(&bmap_path).expect("read bmap"); assert_eq!(unmapped_journal_blocks(image, &bmap), 0); + bmap_flash(image, &bmap, &device); + assert!(journal_on_device_matches_image(image, &device)); let offset = u64::from(PART_START_LBA) * SECTOR_SIZE; run(Command::new("e2fsck") .arg("-fn") .arg(format!("{image}?offset={offset}"))); } + + #[test] + fn read_ext4_journal_rejects_invalid_block_size() { + let mut sb = [0u8; EXT4_SUPERBLOCK_SIZE]; + let mut set = |o: usize, v: &[u8]| sb[o..o + v.len()].copy_from_slice(v); + set(EXT4_S_MAGIC, &EXT4_MAGIC.to_le_bytes()); + set( + EXT4_S_FEATURE_COMPAT, + &EXT4_FEATURE_COMPAT_HAS_JOURNAL.to_le_bytes(), + ); + set(EXT4_S_JOURNAL_INUM, &JOURNAL_INODE.to_le_bytes()); + set(EXT4_S_LOG_BLOCK_SIZE, &INVALID_LOG_BLOCK_SIZE.to_le_bytes()); + + let image = tempfile::tempfile().expect("create image"); + image + .write_all_at(&sb, EXT4_SUPERBLOCK_OFFSET) + .expect("write superblock"); + assert!(read_ext4_journal(&image, 0).is_err()); + } + + #[test] + fn allocate_ext4_journals_skips_partition_past_image_end() { + let dir = tempfile::tempdir().expect("create temp dir"); + let image = dir.path().join("image.wic"); + create_image_with_mbr(&image, u64::from(PART_START_LBA) * SECTOR_SIZE); + + allocate_ext4_journals(image.to_str().expect("image path")).expect("allocate journals"); + } } diff --git a/src/file/partition.rs b/src/file/partition.rs index 9d9d0b8c..12cbc5e0 100644 --- a/src/file/partition.rs +++ b/src/file/partition.rs @@ -97,3 +97,22 @@ pub fn is_gpt>(path: P) -> Result { let mut file = File::open(path.as_ref()).context("is_gpt: failed to open image")?; Ok(gptman::GPT::find_from(&mut file).is_ok()) } + +#[cfg(test)] +mod tests { + use crate::file::partition::*; + + const TEST_IMAGE: &str = "testfiles/image.wic"; + const MBR_EXTENDED_PARTITION: u32 = 4; + + #[test] + fn get_partitions_skips_mbr_extended_entry() { + let nums: Vec = get_partitions(TEST_IMAGE) + .expect("read partitions") + .iter() + .map(|p| p.num) + .collect(); + assert!(!nums.contains(&MBR_EXTENDED_PARTITION)); + assert!(nums.contains(&(MBR_EXTENDED_PARTITION + 1))); + } +}