Compare commits

...

19 Commits

Author SHA1 Message Date
Andrew Gunnerson 07b6901d8e Version 3.29.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-23 11:36:35 -04:00
Andrew Gunnerson b2b3d98005 CHANGELOG.md: Add entry for PR #594
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-23 11:26:51 -04:00
Andrew Gunnerson 21c1dc86ce format/payload: Allow manifests up to 16 MiB
The memory limit prevented some larger incremental OTAs from being
loaded.

Fixes: #593

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-23 11:24:57 -04:00
Andrew Gunnerson 462084ac18 Version 3.29.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 01:15:32 -04:00
Andrew Gunnerson 4c3e1efd99 CHANGELOG.md: Add entry for PR #587
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 01:14:41 -04:00
Andrew Gunnerson 9b7f27e384 cli/payload: Add support for unpacking skeleton images
This is useful for getting properly sized (but empty) image files when
unpacking delta payloads.

Discussion: #582

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 01:03:37 -04:00
Andrew Gunnerson 2120827755 CHANGELOG.md: Add entry for PR #586
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 00:56:58 -04:00
Andrew Gunnerson 8b33645026 cli/zip: Add support for packing/unpacking the payload at the same time
This is more efficient since there's no need to waste space on an extra
copy of payload.bin when we can directly read it from the zip file or
directly write to the zip file.

Discussion: #582

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 00:22:02 -04:00
Andrew Gunnerson eafc6ad3b4 Version 3.28.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-25 18:48:32 -04:00
Andrew Gunnerson 188d256d03 CHANGELOG.md: Add entry for PR #584
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-25 18:41:08 -04:00
Andrew Gunnerson c1836d01a4 cli/payload: Allow unpacking only metadata from delta payloads
The images are the only parts that can't be extracted. Having the
unpacked metadata is useful because it allows for manually building a
full payload using the metadata from the delta payload.

Discussion: #582

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-25 18:40:07 -04:00
Andrew Gunnerson e8532870b1 Version 3.27.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-20 00:01:47 -04:00
Andrew Gunnerson b854654cab CHANGELOG.md: Add entry for PR #580
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-20 00:00:40 -04:00
Andrew Gunnerson f68a492131 format/compression: Add support for LZMA
Fixes: #578

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-19 21:04:09 -04:00
Andrew Gunnerson c22a5d6eca CHANGELOG.md: Add entry for PR #579
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-19 20:06:42 -04:00
Andrew Gunnerson 2631228e8d Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-19 19:33:15 -04:00
Andrew Gunnerson f2b5d9646e Version 3.26.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-23 22:27:43 -05:00
Andrew Gunnerson 4991c5192e CHANGELOG.md: Add entry for PR #573
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-23 22:14:38 -05:00
Andrew Gunnerson 8ef9f89cdd Bump Magisk version upper bound to 30800
There are no breaking changes.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-23 22:12:09 -05:00
16 changed files with 597 additions and 254 deletions
+2 -2
View File
@@ -89,7 +89,7 @@ jobs:
done
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ matrix.artifact.name }}
@@ -160,7 +160,7 @@ jobs:
run: cp LICENSE README.md target/output/
- name: Archive executable
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: avbroot-${{ steps.get_version.outputs.version }}-${{ matrix.artifact.name }}
path: |
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Create release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
tag_name: v${{ steps.get_version.outputs.version }}
name: Version ${{ steps.get_version.outputs.version }}
+32
View File
@@ -7,6 +7,28 @@
to update the actual links at the bottom of the file.
-->
### Version 3.29.1
* Increase memory limit for payload manifest size to 16 MiB ([Issue #593], [PR #594])
### Version 3.29.0
* Add support for unpacking/packing payloads at the same time as the OTA in `avbroot zip {unpack,pack}` ([Discussion #582], [PR #586])
* Add new `--skeleton` option to `avbroot payload unpack` ([Discussion #582], [PR #587])
### Version 3.28.0
* Add support for unpacking metadata from delta payloads in `avbroot payload unpack` ([Discussion #582], [PR #584])
### Version 3.27.0
* Add support for LZMA compression for ramdisks ([Issue #578], [PR #580])
* Update dependencies ([PR #579])
### Version 3.26.1
* Add support for Magisk 30700 ([PR #573])
### Version 3.26.0
* Add new `--re-sign` option to `avbroot ota patch` for re-signing partitions that would otherwise be unmodified ([Issue #569], [PR #570])
@@ -432,6 +454,7 @@ Behind-the-scenes changes:
[Discussion #294]: https://github.com/chenxiaolong/avbroot/discussions/294
[Discussion #417]: https://github.com/chenxiaolong/avbroot/discussions/417
[Discussion #426]: https://github.com/chenxiaolong/avbroot/discussions/426
[Discussion #582]: https://github.com/chenxiaolong/avbroot/discussions/582
[Issue #138]: https://github.com/chenxiaolong/avbroot/issues/138
[Issue #144]: https://github.com/chenxiaolong/avbroot/issues/144
[Issue #145]: https://github.com/chenxiaolong/avbroot/issues/145
@@ -470,6 +493,8 @@ Behind-the-scenes changes:
[Issue #537]: https://github.com/chenxiaolong/avbroot/issues/537
[Issue #554]: https://github.com/chenxiaolong/avbroot/issues/554
[Issue #569]: https://github.com/chenxiaolong/avbroot/issues/569
[Issue #578]: https://github.com/chenxiaolong/avbroot/issues/578
[Issue #593]: https://github.com/chenxiaolong/avbroot/issues/593
[PR #130]: https://github.com/chenxiaolong/avbroot/pull/130
[PR #132]: https://github.com/chenxiaolong/avbroot/pull/132
[PR #133]: https://github.com/chenxiaolong/avbroot/pull/133
@@ -694,3 +719,10 @@ Behind-the-scenes changes:
[PR #565]: https://github.com/chenxiaolong/avbroot/pull/565
[PR #570]: https://github.com/chenxiaolong/avbroot/pull/570
[PR #571]: https://github.com/chenxiaolong/avbroot/pull/571
[PR #573]: https://github.com/chenxiaolong/avbroot/pull/573
[PR #579]: https://github.com/chenxiaolong/avbroot/pull/579
[PR #580]: https://github.com/chenxiaolong/avbroot/pull/580
[PR #584]: https://github.com/chenxiaolong/avbroot/pull/584
[PR #586]: https://github.com/chenxiaolong/avbroot/pull/586
[PR #587]: https://github.com/chenxiaolong/avbroot/pull/587
[PR #594]: https://github.com/chenxiaolong/avbroot/pull/594
Generated
+102 -118
View File
@@ -30,9 +30,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.21"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -45,15 +45,15 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
@@ -80,9 +80,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.101"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arbitrary"
@@ -104,7 +104,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "avbroot"
version = "3.26.0"
version = "3.29.1"
dependencies = [
"anyhow",
"assert_matches",
@@ -192,7 +192,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -268,9 +268,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.56"
version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -320,9 +320,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.58"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
dependencies = [
"clap_builder",
"clap_derive",
@@ -330,9 +330,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.58"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
@@ -342,30 +342,30 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.66"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031"
checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.55"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
name = "clap_lex"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cms"
@@ -381,9 +381,9 @@ dependencies = [
[[package]]
name = "colorchoice"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "const-oid"
@@ -406,21 +406,6 @@ dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.5.0"
@@ -467,9 +452,9 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.5.1"
version = "3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790"
checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
dependencies = [
"dispatch2",
"nix",
@@ -532,7 +517,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -549,9 +534,9 @@ dependencies = [
[[package]]
name = "dispatch2"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"block2",
@@ -567,7 +552,7 @@ checksum = "ecb08c4819242b1ec89b3d0c6affa229005bef46ae4f7eed8b80768187c10087"
[[package]]
name = "e2e"
version = "3.26.0"
version = "3.29.1"
dependencies = [
"anyhow",
"avbroot",
@@ -663,7 +648,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "fuzz"
version = "3.26.0"
version = "3.29.1"
dependencies = [
"avbroot",
"honggfuzz",
@@ -692,9 +677,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
@@ -773,9 +758,9 @@ dependencies = [
[[package]]
name = "honggfuzz"
version = "0.5.58"
version = "0.5.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8319f3cc8fe416e7aa1ab95dcc04fd49f35397a47d0b2f0f225f6dba346a07"
checksum = "9724607fd5cc535fceca960d7f684a1735c4e96c26b4e43986fe8ce798c2550e"
dependencies = [
"arbitrary",
"lazy_static",
@@ -871,9 +856,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]]
name = "libc"
version = "0.2.182"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "libloading"
@@ -893,9 +878,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
@@ -925,7 +910,7 @@ dependencies = [
"quote",
"regex-syntax",
"rustc_version",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -939,20 +924,19 @@ dependencies = [
[[package]]
name = "lz4_flex"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e"
checksum = "db9a0d582c2874f68138a16ce1867e0ffde6c0bb0a0df85e1f36d04146db488a"
dependencies = [
"twox-hash",
]
[[package]]
name = "lzma-rust2"
version = "0.16.1"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d673a11333485e7d8b93d62a9a5b07b22daf5e8a8655a44c1bb18aa4bf3d1524"
checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae"
dependencies = [
"crc",
"sha2",
]
@@ -990,7 +974,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1017,9 +1001,9 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
name = "nix"
version = "0.30.1"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
dependencies = [
"bitflags",
"cfg-if",
@@ -1095,9 +1079,9 @@ dependencies = [
[[package]]
name = "objc2"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
@@ -1110,9 +1094,9 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
@@ -1191,7 +1175,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1205,9 +1189,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkcs1"
@@ -1263,7 +1247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1300,7 +1284,7 @@ dependencies = [
"prost",
"prost-types",
"regex",
"syn 2.0.116",
"syn 2.0.117",
"tempfile",
]
@@ -1314,7 +1298,7 @@ dependencies = [
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1367,18 +1351,18 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.44"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
@@ -1412,9 +1396,9 @@ dependencies = [
[[package]]
name = "rawzip"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a111bd8bfbbf5c3740c1de3e09bcf5bb487b8d1dbeef5690c03acbb9e65450aa"
checksum = "9d9575f44c8cf85bc843ad666dcdf20d05a7753772bef56eb2a5140282b32150"
[[package]]
name = "rayon"
@@ -1461,9 +1445,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.9"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "ring"
@@ -1518,9 +1502,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
@@ -1582,7 +1566,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1719,9 +1703,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.116"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -1750,12 +1734,12 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.25.0"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.1",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
@@ -1778,7 +1762,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1808,14 +1792,14 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
name = "toml"
version = "1.0.1+spec-1.1.0"
version = "1.0.7+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe30f93627849fa362d4a602212d41bb237dc2bd0f8ba0b2ce785012e124220"
checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96"
dependencies = [
"indexmap",
"serde_core",
@@ -1828,18 +1812,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "1.0.0+spec-1.1.0"
version = "1.0.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.1+spec-1.1.0"
version = "0.25.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "538ed8bc4ada0b2549f2a7be9654aac2dfa358faeabb97d7845c3eb5a297e5d6"
checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1"
dependencies = [
"indexmap",
"toml_datetime",
@@ -1850,18 +1834,18 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.8+spec-1.1.0"
version = "1.0.10+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.6+spec-1.1.0"
version = "1.0.7+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d"
[[package]]
name = "topological-sort"
@@ -1888,7 +1872,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -1914,9 +1898,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"nu-ansi-term",
"sharded-slab",
@@ -2128,9 +2112,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.14"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
dependencies = [
"memchr",
]
@@ -2165,7 +2149,7 @@ dependencies = [
"heck",
"indexmap",
"prettyplease",
"syn 2.0.116",
"syn 2.0.117",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -2181,7 +2165,7 @@ dependencies = [
"prettyplease",
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -2239,7 +2223,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "3.26.0"
version = "3.29.1"
dependencies = [
"anyhow",
"clap",
@@ -2249,22 +2233,22 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.39"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
@@ -2284,14 +2268,14 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
"syn 2.0.117",
]
[[package]]
name = "zlib-rs"
version = "0.6.0"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c"
checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513"
[[package]]
name = "zmij"
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.26.0"
version = "3.29.1"
license = "GPL-3.0-only"
edition = "2024"
repository = "https://github.com/chenxiaolong/avbroot"
+7 -3
View File
@@ -340,7 +340,7 @@ avbroot payload unpack -i <input payload>
This subcommand unpacks the payload header information to `payload.toml` and the partition images to the `payload_images` directory.
Only full payload binaries can be unpacked. Delta payload binaries from incremental OTAs are not supported.
Data can only be unpacked from full payload binaries. For delta payloads, either use `--no-output-images` to skip image extraction or use `--skeleton` to create properly sized sparse files with no data.
### Packing a payload binary
@@ -429,19 +429,23 @@ This set of commands is for working with raw OTA zip files. They are intentional
### Unpacking an OTA zip
```bash
avbroot zip unpack -i <input OTA>
avbroot zip unpack -i <input OTA> [--payload]
```
This subcommand unpacks the OTA metadata to `ota.toml` and the OTA files to the `ota_files` directory.
When `--payload` is specified, the payload and OTA zip will be unpacked at the same time, which is significantly more efficient that unpack the payload and OTA zip separately. The payload unpacking process writes to the same `payload.toml` and `payload_images` outputs as `avbroot payload unpack`.
### Packing an OTA zip
```bash
avbroot zip pack -o <output OTA> -k <OTA private key>
avbroot zip pack -o <output OTA> -k <OTA private key> [--payload]
```
This subcommand packs a new OTA zip from the `ota.toml` file and `ota_files` directory. Any files in the `ota_files` directory that don't have a corresponding entry in `ota.toml` are silently ignored.
When `--payload` is specified, the payload and OTA zip will be packed at the same time, which is significantly more efficient than packing the payload and OTA zip separately. The payload packing process reads the same `payload.toml` and `payload_images` inputs as `avbroot payload pack`.
When packing an OTA zip, the `metadata.property_files` field in `ota.toml` may potentially be recomputed. To write a TOML file containing the new values, use `--output-info <output TOML>`. It is safe to overwrite the existing `ota.toml` if desired.
### Repacking an OTA zip
+1 -1
View File
@@ -27,7 +27,7 @@ dlv-list = "0.6.0"
flate2 = { version = "1.0.29", features = ["zlib-rs"] }
gf256 = { version = "0.3.0", features = ["rs"] }
hex = { version = "0.4.3", features = ["serde"] }
lz4_flex = "0.12.0"
lz4_flex = "0.13.0"
lzma-rust2 = "0.16.1"
memchr = "2.6.0"
# We can't upgrade to 0.9.0 until rsa updates its dependency.
+2 -2
View File
@@ -1228,11 +1228,11 @@ fn patch_ota_payload(
.with_context(|| format!("Failed to copy from original payload: {name}"))?;
}
let (_, _, properties, metadata_size) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
Ok((properties, metadata_size))
Ok((properties, header.blob_offset))
}
#[allow(clippy::too_many_arguments)]
+140 -59
View File
@@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2024-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
collections::HashMap,
ffi::OsString,
fs::{self, File},
io::{BufReader, BufWriter, Seek, SeekFrom},
io::{BufReader, BufWriter, Seek, SeekFrom, Write},
path::{Path, PathBuf},
sync::{Arc, atomic::AtomicBool},
};
@@ -18,31 +18,34 @@ use crate::{
cli::ota,
crypto::{self, PassphraseSource, RsaSigningKey},
format::payload::{PayloadHeader, PayloadWriter},
stream::{self, FromReader},
stream::{self, FromReader, SectionReader},
util,
};
fn open_reader(path: &Path, allow_delta: bool) -> Result<(BufReader<File>, PayloadHeader)> {
let mut reader = File::open(path)
.map(BufReader::new)
.with_context(|| format!("Failed to open payload for reading: {path:?}"))?;
fn open_raw_reader(path: &Path) -> Result<File> {
File::open(path).with_context(|| format!("Failed to open payload for reading: {path:?}"))
}
fn open_reader(path: &Path) -> Result<(BufReader<File>, PayloadHeader)> {
let mut reader = open_raw_reader(path).map(BufReader::new)?;
let header = PayloadHeader::from_reader(&mut reader)
.with_context(|| format!("Failed to read payload header: {path:?}"))?;
if !allow_delta && !header.is_full_ota() {
bail!("Payload is a delta OTA, not a full OTA");
}
Ok((reader, header))
}
fn open_raw_writer(path: &Path) -> Result<BufWriter<File>> {
File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open payload for writing: {path:?}"))
}
fn open_writer(
path: &Path,
header: PayloadHeader,
key: RsaSigningKey,
) -> Result<PayloadWriter<BufWriter<File>>> {
let writer = File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open payload for writing: {path:?}"))?;
let writer = open_raw_writer(path)?;
let payload_writer = PayloadWriter::new(writer, header, key)
.with_context(|| format!("Failed to write payload header: {path:?}"))?;
@@ -67,8 +70,8 @@ fn write_info(path: &Path, manifest: &PayloadHeader) -> Result<()> {
Ok(())
}
fn display_header(cli: &PayloadCli, header: &PayloadHeader) {
if !cli.quiet {
fn display_header(quiet: bool, header: &PayloadHeader) {
if !quiet {
println!("{header:#?}");
}
}
@@ -99,52 +102,105 @@ fn load_key(group: &KeyGroup) -> Result<RsaSigningKey> {
Ok(signing_key)
}
fn unpack_subcommand(
payload_cli: &PayloadCli,
cli: &UnpackCli,
#[allow(clippy::too_many_arguments)]
pub fn unpack_payload(
quiet: bool,
output_info: &Path,
output_images: &Path,
no_output_images: bool,
skeleton: bool,
reader: &File,
payload_offset: u64,
payload_size: u64,
cancel_signal: &AtomicBool,
) -> Result<()> {
let (mut reader, header) = open_reader(&cli.input, false)?;
let payload_size = reader
.seek(SeekFrom::End(0))
.with_context(|| format!("Failed to get file size: {:?}", cli.input))?;
let section_reader = SectionReader::new(reader, payload_offset, payload_size)
.context("Failed to directly open payload section")?;
let header =
PayloadHeader::from_reader(section_reader).context("Failed to read payload header")?;
display_header(payload_cli, &header);
display_header(quiet, &header);
write_info(&cli.output_info, &header)?;
write_info(output_info, &header)?;
if !cli.no_output_images {
fs::create_dir_all(&cli.output_images)
.with_context(|| format!("Failed to create directory: {:?}", cli.output_images))?;
if !no_output_images {
fs::create_dir_all(output_images)
.with_context(|| format!("Failed to create directory: {output_images:?}"))?;
ota::extract_payload(
&reader.into_inner(),
&cli.output_images,
0,
payload_size,
&header,
&header
.manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.cloned()
.collect(),
cancel_signal,
)?;
if skeleton {
for partition in &header.manifest.partitions {
let name = &partition.partition_name;
let path = util::path_join_single(output_images, format!("{name}.img"))?;
let size = partition
.new_partition_info
.as_ref()
.and_then(|info| info.size)
.ok_or_else(|| anyhow!("Size not found for partition: {name}"))?;
let file = File::create(&path)
.with_context(|| format!("Failed to create file: {path:?}"))?;
file.set_len(size)
.with_context(|| format!("Failed to truncate file: {path:?}"))?;
}
} else {
if !header.is_full_ota() {
bail!("Cannot extract images from a delta payload");
}
ota::extract_payload(
reader,
output_images,
payload_offset,
payload_size,
&header,
&header
.manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.cloned()
.collect(),
cancel_signal,
)?;
}
}
Ok(())
}
fn pack_subcommand(
fn unpack_subcommand(
payload_cli: &PayloadCli,
cli: &PackCli,
cli: &UnpackCli,
cancel_signal: &AtomicBool,
) -> Result<()> {
let signing_key = load_key(&cli.key)?;
let mut reader = open_raw_reader(&cli.input)?;
let payload_size = reader
.seek(SeekFrom::End(0))
.with_context(|| format!("Failed to get file size: {:?}", cli.input))?;
let mut header = read_info(&cli.input_info)?;
unpack_payload(
payload_cli.quiet,
&cli.output_info,
&cli.output_images,
cli.no_output_images,
cli.skeleton,
&reader,
0,
payload_size,
cancel_signal,
)
}
pub fn pack_payload(
quiet: bool,
input_info: &Path,
input_images: &Path,
writer: &mut dyn Write,
signing_key: &RsaSigningKey,
cancel_signal: &AtomicBool,
) -> Result<(String, u64)> {
let mut header = read_info(input_info)?;
// Pre-open all of the image files.
let input_files = header
@@ -152,8 +208,7 @@ fn pack_subcommand(
.partitions
.iter()
.map(|p| {
let path =
util::path_join_single(&cli.input_images, format!("{}.img", p.partition_name))?;
let path = util::path_join_single(input_images, format!("{}.img", p.partition_name))?;
let file = File::open(&path)
.map(Arc::new)
.with_context(|| format!("Failed to open file: {path:?}"))?;
@@ -199,7 +254,8 @@ fn pack_subcommand(
// Now we can write the actual payload. With everything precomputed, this is
// mostly just a simple copy.
let mut payload_writer = open_writer(&cli.output, header.clone(), signing_key)?;
let mut payload_writer = PayloadWriter::new(writer, header.clone(), signing_key.clone())
.context("Failed to write payload header")?;
while payload_writer
.begin_next_operation()
@@ -235,14 +291,35 @@ fn pack_subcommand(
.with_context(|| format!("Failed to copy from replacement image: {name}"))?;
}
let (raw_writer, header, properties, _) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
raw_writer.into_inner().context("Failed to flush payload")?;
// Display the header information now that it has been finalized.
display_header(payload_cli, &header);
display_header(quiet, &header);
Ok((properties, header.blob_offset))
}
fn pack_subcommand(
payload_cli: &PayloadCli,
cli: &PackCli,
cancel_signal: &AtomicBool,
) -> Result<()> {
let signing_key = load_key(&cli.key)?;
let mut writer = open_raw_writer(&cli.output)?;
let (properties, _) = pack_payload(
payload_cli.quiet,
&cli.input_info,
&cli.input_images,
&mut writer,
&signing_key,
cancel_signal,
)?;
writer.into_inner().context("Failed to flush payload")?;
// Optionally, write payload_properties.txt.
if let Some(path) = &cli.output_properties {
@@ -260,7 +337,7 @@ fn repack_subcommand(
) -> Result<()> {
let signing_key = load_key(&cli.key)?;
let (mut reader, header) = open_reader(&cli.input, true)?;
let (mut reader, header) = open_reader(&cli.input)?;
info!("Generating new OTA payload");
@@ -299,12 +376,12 @@ fn repack_subcommand(
.with_context(|| format!("Failed to copy from original payload: {name}"))?;
}
let (_, header, properties, _) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
// Display the header information now that it has been finalized.
display_header(payload_cli, &header);
display_header(payload_cli.quiet, &header);
// Optionally, write payload_properties.txt.
if let Some(path) = &cli.output_properties {
@@ -316,9 +393,9 @@ fn repack_subcommand(
}
fn info_subcommand(payload_cli: &PayloadCli, cli: &InfoCli) -> Result<()> {
let (_, header) = open_reader(&cli.input, true)?;
let (_, header) = open_reader(&cli.input)?;
display_header(payload_cli, &header);
display_header(payload_cli.quiet, &header);
Ok(())
}
@@ -391,8 +468,12 @@ struct UnpackCli {
output_images: PathBuf,
/// Do not output images.
#[arg(long, conflicts_with = "output_images")]
#[arg(long, conflicts_with_all = ["output_images", "skeleton"])]
no_output_images: bool,
/// Only create empty sparse files for output images.
#[arg(long)]
skeleton: bool,
}
/// Pack a payload binary.
+151 -29
View File
@@ -5,7 +5,7 @@ use std::{
collections::BTreeMap,
ffi::OsString,
fs::{self, File, OpenOptions},
io::{BufReader, Seek},
io::{BufReader, Seek, Write},
path::{Path, PathBuf},
sync::atomic::AtomicBool,
};
@@ -21,6 +21,7 @@ use tracing::warn;
use x509_cert::Certificate;
use crate::{
cli::payload,
crypto::{self, PassphraseSource, RsaSigningKey},
format::{
ota::{self, SigningWriter, ZipEntry, ZipMode},
@@ -137,8 +138,8 @@ fn write_info(path: &Path, info: &OtaInfo) -> Result<()> {
Ok(())
}
fn display_info(cli: &ZipCli, info: &OtaInfo) {
if !cli.quiet {
fn display_info(quiet: bool, info: &OtaInfo) {
if !quiet {
println!("{info:#?}");
}
}
@@ -285,11 +286,33 @@ fn finalize_ota(
fn unpack_subcommand(zip_cli: &ZipCli, cli: &UnpackCli, cancel_signal: &AtomicBool) -> Result<()> {
let (zip_reader, metadata, input_entries) = open_reader(&cli.input)?;
if !cli.no_output_files {
for (path, input_entry) in &input_entries {
let entry = zip_reader
.get_entry(input_entry.wayfinder)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
for (path, input_entry) in &input_entries {
let entry = zip_reader
.get_entry(input_entry.wayfinder)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
if cli.payload && path == ota::PATH_PAYLOAD {
let (payload_offset, payload_size) = {
let range = entry.compressed_data_range();
(range.0, range.1 - range.0)
};
payload::unpack_payload(
zip_cli.quiet,
&cli.output_payload_info,
&cli.output_payload_images,
cli.no_output_payload_images,
cli.payload_skeleton,
&zip_reader.get_ref().0,
payload_offset,
payload_size,
cancel_signal,
)?;
} else if cli.payload && path == ota::PATH_PROPERTIES {
// We intentionally ignore this since it's recomputed when
// packing with --payload.
} else if !cli.no_output_files {
let mut entry_reader = zip::verifying_reader(&entry, input_entry.compression_method)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
@@ -312,7 +335,7 @@ fn unpack_subcommand(zip_cli: &ZipCli, cli: &UnpackCli, cancel_signal: &AtomicBo
files: input_entries.into_keys().collect(),
};
display_info(zip_cli, &info);
display_info(zip_cli.quiet, &info);
write_info(&cli.output_info, &info)?;
Ok(())
@@ -331,10 +354,15 @@ fn pack_subcommand(zip_cli: &ZipCli, cli: &PackCli, cancel_signal: &AtomicBool)
continue;
}
let input_path = util::path_join(&cli.input_files, path)?;
let input_size = fs::metadata(&input_path)
.map(|m| m.len())
.with_context(|| format!("Failed to get file size: {input_path:?}"))?;
let input_size =
if cli.payload && (path == ota::PATH_PAYLOAD || path == ota::PATH_PROPERTIES) {
0
} else {
let input_path = util::path_join(&cli.input_files, path)?;
fs::metadata(&input_path)
.map(|m| m.len())
.with_context(|| format!("Failed to get file size: {input_path:?}"))?
};
input_entries.insert(path, input_size >= 0xffffffff);
}
@@ -342,28 +370,56 @@ fn pack_subcommand(zip_cli: &ZipCli, cli: &PackCli, cancel_signal: &AtomicBool)
let mut payload_metadata_size = None;
let mut metadata_entries = vec![];
// Only used when packing the zip and payload at the same time.
let mut payload_properties = None;
for (&path, &is_zip64) in &input_entries {
let (offset, mut data_writer) =
start_entry(&mut zip_writer, path, cli.zip_mode.zip_mode, is_zip64)?;
let input_path = util::path_join(&cli.input_files, path)?;
let mut input_file = File::open(&input_path)
.with_context(|| format!("Failed to open for reading: {input_path:?}"))?;
if path == ota::PATH_PAYLOAD && cli.payload {
// Pack the payload directly into the zip.
let (properties, metadata_size) = payload::pack_payload(
zip_cli.quiet,
&cli.input_payload_info,
&cli.input_payload_images,
&mut data_writer,
&signing_key,
cancel_signal,
)?;
if path == ota::PATH_PAYLOAD {
let header = PayloadHeader::from_reader(&mut input_file)
.with_context(|| format!("Failed to read payload header: {input_path:?}"))?;
payload_metadata_size = Some(metadata_size);
payload_properties = Some(properties);
} else if path == ota::PATH_PROPERTIES && cli.payload {
// We process files in order, so this would only happen if the file
// list did not contain the payload.
let properties = payload_properties
.as_ref()
.ok_or_else(|| anyhow!("Payload has not been processed yet"))?;
payload_metadata_size = Some(header.blob_offset);
data_writer
.write_all(properties.as_bytes())
.with_context(|| format!("Failed to write zip entry: {path}"))?;
} else {
let input_path = util::path_join(&cli.input_files, path)?;
let mut input_file = File::open(&input_path)
.with_context(|| format!("Failed to open for reading: {input_path:?}"))?;
input_file
.rewind()
.with_context(|| format!("Failed to seek file: {input_path:?}"))?;
if path == ota::PATH_PAYLOAD {
let header = PayloadHeader::from_reader(&mut input_file)
.with_context(|| format!("Failed to read payload header: {input_path:?}"))?;
payload_metadata_size = Some(header.blob_offset);
input_file
.rewind()
.with_context(|| format!("Failed to seek file: {input_path:?}"))?;
}
stream::copy(&mut input_file, &mut data_writer, cancel_signal)
.with_context(|| format!("Failed to copy zip entry: {path}"))?;
}
stream::copy(&mut input_file, &mut data_writer, cancel_signal)
.with_context(|| format!("Failed to copy zip entry: {path}"))?;
finalize_entry(path, offset, data_writer, &mut metadata_entries)?;
}
@@ -382,7 +438,7 @@ fn pack_subcommand(zip_cli: &ZipCli, cli: &PackCli, cancel_signal: &AtomicBool)
info.files.retain(|p| !is_excluded_path(p));
info.files.sort();
display_info(zip_cli, &info);
display_info(zip_cli.quiet, &info);
if let Some(path) = &cli.output_info {
write_info(path, &info)?;
@@ -446,7 +502,7 @@ fn repack_subcommand(zip_cli: &ZipCli, cli: &RepackCli, cancel_signal: &AtomicBo
files: input_entries.into_keys().collect(),
};
display_info(zip_cli, &info);
display_info(zip_cli.quiet, &info);
Ok(())
}
@@ -458,7 +514,7 @@ fn info_subcommand(zip_cli: &ZipCli, cli: &InfoCli) -> Result<()> {
files: input_entries.into_keys().collect(),
};
display_info(zip_cli, &info);
display_info(zip_cli.quiet, &info);
Ok(())
}
@@ -541,6 +597,45 @@ struct UnpackCli {
/// Do not output files.
#[arg(long, conflicts_with = "output_files")]
no_output_files: bool,
/// Also unpack the payload in the OTA.
///
/// Unpacking the zip and payload at the same time is more efficient than
/// unpacking them separately.
#[arg(long)]
payload: bool,
/// Path to output payload info TOML.
#[arg(
long,
value_name = "FILE",
value_parser,
default_value = "payload.toml",
requires = "payload"
)]
output_payload_info: PathBuf,
/// Path to output payload images directory.
#[arg(
long,
value_name = "DIR",
value_parser,
default_value = "payload_images",
requires = "payload"
)]
output_payload_images: PathBuf,
/// Do not output payload images.
#[arg(
long,
conflicts_with_all = ["output_payload_images", "payload_skeleton"],
requires = "payload"
)]
no_output_payload_images: bool,
/// Only create empty sparse files for output payload images.
#[arg(long, requires = "payload")]
payload_skeleton: bool,
}
/// Pack an OTA zip.
@@ -573,6 +668,33 @@ struct PackCli {
#[arg(long, value_name = "DIR", value_parser, default_value = "ota_files")]
input_files: PathBuf,
/// Also pack the payload in the OTA.
///
/// Packing the zip and payload at the same time is more efficient than
/// packing them separately.
#[arg(long)]
payload: bool,
/// Path to input payload info TOML.
#[arg(
long,
value_name = "FILE",
value_parser,
default_value = "payload.toml",
requires = "payload"
)]
input_payload_info: PathBuf,
/// Path to input payload images directory.
#[arg(
long,
value_name = "DIR",
value_parser,
default_value = "payload_images",
requires = "payload"
)]
input_payload_images: PathBuf,
#[command(flatten)]
key: KeyGroup,
+132 -21
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::io::{self, Read, Seek, Write};
@@ -9,7 +9,7 @@ use flate2::{
write::{DeflateEncoder, GzEncoder},
};
use lz4_flex::frame::FrameDecoder;
use lzma_rust2::{CheckType, XzOptions, XzReader, XzWriter};
use lzma_rust2::{CheckType, LzmaOptions, LzmaReader, LzmaWriter, XzOptions, XzReader, XzWriter};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -18,6 +18,14 @@ use crate::stream::ReadFixedSizeExt;
static GZIP_MAGIC: &[u8; 2] = b"\x1f\x8b";
static LZ4_LEGACY_MAGIC: &[u8; 4] = b"\x02\x21\x4c\x18";
static XZ_MAGIC: &[u8; 6] = b"\xfd\x37\x7a\x58\x5a\x00";
// This is not actually a magic value, but is good enough. The first byte
// encodes (lc=3, lp=0, pb=2) and the next two bytes are two of the U32LE bytes
// encoding the dictionary size. If this heuristic is good enough for libmagic,
// then it's good enough for us too.
// https://nigeltao.github.io/blog/2024/xz-lzma-part-5-xz.html
static LZMA_MAGIC: &[u8; 3] = b"\x5d\x00\x00";
const LZMA_MAX_MEMORY_KIB: u32 = 16 * 1024;
#[derive(Debug, Error)]
pub enum Error {
@@ -26,9 +34,13 @@ pub enum Error {
#[error("I/O error when autodetecting compression format")]
AutoDetect(#[source] io::Error),
#[error("Failed to initialize legacy LZ4 encoder")]
Lz4Init(#[source] io::Error),
Lz4EncodeInit(#[source] io::Error),
#[error("Failed to initialize XZ encoder")]
XzInit(#[source] io::Error),
XzEncodeInit(#[source] io::Error),
#[error("Failed to initialize LZMA decoder")]
LzmaDecodeInit(#[source] io::Error),
#[error("Failed to initialize LZMA encoder")]
LzmaEncodeInit(#[source] io::Error),
}
type Result<T> = std::result::Result<T, Error>;
@@ -113,6 +125,7 @@ pub enum CompressedFormat {
Gzip,
Lz4Legacy,
Xz,
Lzma,
}
pub enum CompressedReader<R: Read> {
@@ -123,16 +136,21 @@ pub enum CompressedReader<R: Read> {
Lz4(FrameDecoder<R>),
/// Boxed because the [`XzReader`] is nearly 4 KiB.
Xz(Box<XzReader<R>>),
Lzma(Box<LzmaReader<R>>),
}
impl<R: Read> CompressedReader<R> {
pub fn with_format(reader: R, format: CompressedFormat) -> Self {
pub fn with_format(reader: R, format: CompressedFormat) -> Result<Self> {
match format {
CompressedFormat::None => Self::None(reader),
CompressedFormat::Deflate => Self::Deflate(DeflateDecoder::new(reader)),
CompressedFormat::Gzip => Self::Gzip(GzDecoder::new(reader)),
CompressedFormat::Lz4Legacy => Self::Lz4(FrameDecoder::new(reader)),
CompressedFormat::Xz => Self::Xz(Box::new(XzReader::new(reader, false))),
CompressedFormat::None => Ok(Self::None(reader)),
CompressedFormat::Deflate => Ok(Self::Deflate(DeflateDecoder::new(reader))),
CompressedFormat::Gzip => Ok(Self::Gzip(GzDecoder::new(reader))),
CompressedFormat::Lz4Legacy => Ok(Self::Lz4(FrameDecoder::new(reader))),
CompressedFormat::Xz => Ok(Self::Xz(Box::new(XzReader::new(reader, false)))),
CompressedFormat::Lzma => Ok(Self::Lzma(Box::new(
LzmaReader::new_mem_limit(reader, LZMA_MAX_MEMORY_KIB, None)
.map_err(Error::LzmaDecodeInit)?,
))),
}
}
@@ -143,6 +161,7 @@ impl<R: Read> CompressedReader<R> {
Self::Gzip(_) => CompressedFormat::Gzip,
Self::Lz4(_) => CompressedFormat::Lz4Legacy,
Self::Xz(_) => CompressedFormat::Xz,
Self::Lzma(_) => CompressedFormat::Lzma,
}
}
@@ -153,6 +172,7 @@ impl<R: Read> CompressedReader<R> {
Self::Gzip(r) => r.into_inner(),
Self::Lz4(r) => r.into_inner(),
Self::Xz(r) => r.into_inner(),
Self::Lzma(r) => r.into_inner(),
}
}
}
@@ -163,17 +183,21 @@ impl<R: Read + Seek> CompressedReader<R> {
reader.rewind().map_err(Error::AutoDetect)?;
if &magic[0..2] == GZIP_MAGIC {
Ok(Self::Gzip(GzDecoder::new(reader)))
let format = if &magic[0..2] == GZIP_MAGIC {
CompressedFormat::Gzip
} else if &magic[0..4] == LZ4_LEGACY_MAGIC {
Ok(Self::Lz4(FrameDecoder::new(reader)))
CompressedFormat::Lz4Legacy
} else if &magic == XZ_MAGIC {
Ok(Self::Xz(Box::new(XzReader::new(reader, false))))
CompressedFormat::Xz
} else if &magic[0..3] == LZMA_MAGIC {
CompressedFormat::Lzma
} else if raw_if_unknown {
Ok(Self::None(reader))
CompressedFormat::None
} else {
Err(Error::UnknownFormat)
}
return Err(Error::UnknownFormat);
};
Self::with_format(reader, format)
}
}
@@ -185,6 +209,7 @@ impl<R: Read> Read for CompressedReader<R> {
Self::Gzip(r) => r.read(buf),
Self::Lz4(r) => r.read(buf),
Self::Xz(r) => r.read(buf),
Self::Lzma(r) => r.read(buf),
}
}
}
@@ -195,7 +220,8 @@ pub enum CompressedWriter<W: Write> {
Deflate(DeflateEncoder<W>),
Gzip(GzEncoder<W>),
Lz4Legacy(Lz4LegacyEncoder<W>),
Xz(XzWriter<W>),
Xz(Box<XzWriter<W>>),
Lzma(Box<LzmaWriter<W>>),
}
impl<W: Write> CompressedWriter<W> {
@@ -210,7 +236,7 @@ impl<W: Write> CompressedWriter<W> {
Ok(Self::Gzip(GzEncoder::new(writer, Compression::default())))
}
CompressedFormat::Lz4Legacy => {
let encoder = Lz4LegacyEncoder::new(writer).map_err(Error::Lz4Init)?;
let encoder = Lz4LegacyEncoder::new(writer).map_err(Error::Lz4EncodeInit)?;
Ok(Self::Lz4Legacy(encoder))
}
CompressedFormat::Xz => {
@@ -218,8 +244,17 @@ impl<W: Write> CompressedWriter<W> {
let mut options = XzOptions::with_preset(6);
options.set_check_sum_type(CheckType::Crc32);
let xz_writer = XzWriter::new(writer, options).map_err(Error::XzInit)?;
Ok(Self::Xz(xz_writer))
let xz_writer = XzWriter::new(writer, options).map_err(Error::XzEncodeInit)?;
Ok(Self::Xz(Box::new(xz_writer)))
}
CompressedFormat::Lzma => {
// Defaults are already (lc=3, lp=0, pb=2) and has preset 6.
let options = LzmaOptions::default();
// We only support streaming output.
let lzma_writer = LzmaWriter::new_use_header(writer, &options, None)
.map_err(Error::LzmaEncodeInit)?;
Ok(Self::Lzma(Box::new(lzma_writer)))
}
}
}
@@ -231,6 +266,7 @@ impl<W: Write> CompressedWriter<W> {
Self::Gzip(_) => CompressedFormat::Gzip,
Self::Lz4Legacy(_) => CompressedFormat::Lz4Legacy,
Self::Xz(_) => CompressedFormat::Xz,
Self::Lzma(_) => CompressedFormat::Lzma,
}
}
@@ -241,6 +277,7 @@ impl<W: Write> CompressedWriter<W> {
Self::Gzip(w) => w.finish(),
Self::Lz4Legacy(w) => w.finish(),
Self::Xz(w) => w.finish(),
Self::Lzma(w) => w.finish(),
}
}
}
@@ -253,6 +290,7 @@ impl<W: Write> Write for CompressedWriter<W> {
Self::Gzip(w) => w.write(buf),
Self::Lz4Legacy(w) => w.write(buf),
Self::Xz(w) => w.write(buf),
Self::Lzma(w) => w.write(buf),
}
}
@@ -263,6 +301,79 @@ impl<W: Write> Write for CompressedWriter<W> {
Self::Gzip(w) => w.flush(),
Self::Lz4Legacy(w) => w.flush(),
Self::Xz(w) => w.flush(),
Self::Lzma(w) => w.flush(),
}
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use rand::Rng;
use super::*;
fn round_trip(format: CompressedFormat, can_autodetect: bool) {
let mut data = vec![0u8; 1024 * 1024];
rand::thread_rng().fill(data.as_mut_slice());
let mut writer = CompressedWriter::new(Cursor::new(Vec::new()), format).unwrap();
writer.write_all(&data).unwrap();
let mut compressed = writer.finish().unwrap();
compressed.rewind().unwrap();
let mut reader = if can_autodetect {
CompressedReader::new(compressed, true).unwrap()
} else {
CompressedReader::with_format(compressed, format).unwrap()
};
assert_eq!(reader.format(), format);
let mut buf = [0u8; 8192];
let mut total_read = 0;
loop {
let n = reader.read(&mut buf).unwrap();
if n == 0 {
break;
}
assert_eq!(&buf[..n], &data[total_read..][..n]);
total_read += n;
}
assert_eq!(total_read, data.len());
}
#[test]
fn round_trip_none() {
round_trip(CompressedFormat::None, true);
}
#[test]
fn round_trip_deflate() {
round_trip(CompressedFormat::Deflate, false);
}
#[test]
fn round_trip_gzip() {
round_trip(CompressedFormat::Gzip, true);
}
#[test]
fn round_trip_lz4_legacy() {
round_trip(CompressedFormat::Lz4Legacy, true);
}
#[test]
fn round_trip_xz() {
round_trip(CompressedFormat::Xz, true);
}
#[test]
fn round_trip_lzma() {
round_trip(CompressedFormat::Lzma, true);
}
}
+10 -8
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2022-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -45,7 +45,7 @@ use crate::{
const PAYLOAD_MAGIC: &[u8; 4] = b"CrAU";
const PAYLOAD_VERSION: u64 = 2;
const MANIFEST_MAX_SIZE: usize = 4 * 1024 * 1024;
const MANIFEST_MAX_SIZE: usize = 16 * 1024 * 1024;
/// Size of each extent. This matches what AOSP's delta_generator does. We also
/// require this to be a multiple of the block size and a multiple of the
@@ -436,11 +436,11 @@ impl<W: Write> PayloadWriter<W> {
/// Finalize the payload. If this function is not called, the payload will
/// be left in an incomplete state. Returns the original writer, the final
/// header, the contents that should be written for `payload_properties.txt`
/// and the length of the header + manifest + manifest signature sections
/// (for constructing the `payload_metadata.bin` OTA metadata property files
/// entry).
pub fn finish(mut self) -> Result<(W, PayloadHeader, String, u64)> {
/// header and the data that should be written for `payload_properties.txt`.
/// [`PayloadHeader::blob_offset`] indicates the length of the header +
/// manifest + manifest signature sections (for constructing the
/// `payload_metadata.bin` OTA metadata property files entry).
pub fn finish(mut self) -> Result<(W, PayloadHeader, String)> {
// Append payload signature.
let payload_partial_hash = self.h_partial.clone().finish();
let payload_sig = sign_digest(payload_partial_hash.as_ref(), &self.key)?;
@@ -465,7 +465,9 @@ impl<W: Write> PayloadWriter<W> {
self.metadata_size as u64,
);
Ok((self.inner, self.header, properties, metadata_with_sig_size))
self.header.blob_offset = metadata_with_sig_size;
Ok((self.inner, self.header, properties))
}
/// Prepare for writing the next source data blob corresponding to an
+12 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2025-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -249,7 +249,8 @@ pub fn compressed_reader<'archive, R: ReaderAt>(
) -> Result<CompressedReader<ZipReader<&'archive R>>, rawzip::Error> {
let format = compression_method_to_format(compression_method)?;
Ok(CompressedReader::with_format(entry.reader(), format))
CompressedReader::with_format(entry.reader(), format)
.map_err(|e| rawzip::ErrorKind::InvalidInput { msg: e.to_string() }.into())
}
pub fn compressed_slice_reader<'archive>(
@@ -259,7 +260,8 @@ pub fn compressed_slice_reader<'archive>(
let format = compression_method_to_format(compression_method)?;
let raw_reader = Cursor::new(entry.data());
Ok(CompressedReader::with_format(raw_reader, format))
CompressedReader::with_format(raw_reader, format)
.map_err(|e| rawzip::ErrorKind::InvalidInput { msg: e.to_string() }.into())
}
pub fn verifying_reader<'archive, R: ReaderAt>(
@@ -286,7 +288,12 @@ pub fn compressed_writer<W: Write>(
match CompressedWriter::new(writer, format) {
Ok(w) => Ok(w),
Err(Error::Lz4Init(e) | Error::XzInit(e)) => Err(e.into()),
Err(
Error::Lz4EncodeInit(e)
| Error::XzEncodeInit(e)
| Error::LzmaDecodeInit(e)
| Error::LzmaEncodeInit(e),
) => Err(e.into()),
Err(Error::UnknownFormat | Error::AutoDetect(_)) => unreachable!(),
}
}
@@ -306,7 +313,7 @@ pub trait ZipArchiveReadAtExt {
impl ZipArchiveReadAtExt for ZipArchive<()> {}
pub struct ReaderAtWrapper<R: ReadAt>(R);
pub struct ReaderAtWrapper<R: ReadAt>(pub R);
impl<R: ReadAt> ReaderAt for ReaderAtWrapper<R> {
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+1 -1
View File
@@ -211,7 +211,7 @@ impl MagiskRootPatcher {
// replaced by PREINITDEVICE
// - Versions newer than the latest supported version are assumed to support
// the same features as the latest version
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30700];
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30800];
const VER_PREINIT_DEVICE: RangeFrom<u32> = 25211..;
const VER_RANDOM_SEED: Range<u32> = 25211..26103;
const VER_PATCH_VBMETA: Range<u32> = Self::VERS_SUPPORTED[0].start..26202;
+2 -2
View File
@@ -704,11 +704,11 @@ fn create_payload(
.with_context(|| format!("Failed to copy from image: {name}"))?;
}
let (_, _, properties, metadata_size) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
Ok((properties, metadata_size))
Ok((properties, header.blob_offset))
}
#[allow(clippy::too_many_arguments)]
+1 -1
View File
@@ -12,7 +12,7 @@ This will update the main [`Cargo.toml`](../Cargo.toml) file.
## Updating changelog links
To add an entry to [`CHANGELOG.md`](../CHANGELOG.md), the user should manually type in a message and add repository references, like `[Issue #0]` or `[PR #0 @user]`.
To add an entry to [`CHANGELOG.md`](../CHANGELOG.md), the user should manually type in a message and add repository references, like `[Issue #0]` or `[PR #0]`.
Then, run the following command to generate the appropriate link references: