mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 07b6901d8e | |||
| b2b3d98005 | |||
| 21c1dc86ce | |||
| 462084ac18 | |||
| 4c3e1efd99 | |||
| 9b7f27e384 | |||
| 2120827755 | |||
| 8b33645026 | |||
| eafc6ad3b4 | |||
| 188d256d03 | |||
| c1836d01a4 | |||
| e8532870b1 | |||
| b854654cab | |||
| f68a492131 | |||
| c22a5d6eca | |||
| 2631228e8d | |||
| f2b5d9646e | |||
| 4991c5192e | |||
| 8ef9f89cdd | |||
| 43cd7eac78 | |||
| faf5a908ec | |||
| 97cad8f508 | |||
| dd73dcbd56 | |||
| 6f8de8b2c4 | |||
| 1be81d87b9 | |||
| 5df2ffa435 | |||
| 16c1c2a4d3 | |||
| 1155a14b00 | |||
| 806870664a | |||
| 4717407a43 | |||
| ff4a2a6db8 | |||
| 49bd389bf3 | |||
| 5afe76bf2b | |||
| 0c8357924c | |||
| ef9e563010 | |||
| 2e53147f87 |
@@ -47,7 +47,7 @@ jobs:
|
||||
android_api: '31'
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# For git describe
|
||||
fetch-depth: 0
|
||||
@@ -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: |
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Run cargo-deny
|
||||
uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
|
||||
|
||||
@@ -24,10 +24,10 @@ jobs:
|
||||
echo "version=${version}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
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 }}
|
||||
|
||||
@@ -7,6 +7,43 @@
|
||||
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])
|
||||
* Warn when a partition is signed with an AOSP test key in `avbroot ota verify` ([Issue #569], [PR #571])
|
||||
|
||||
### Version 3.25.0
|
||||
|
||||
* Remove outdated mentions of guarantees provided by cap-std in `--help` text, which was removed in version 3.20.0 ([PR #561])
|
||||
* Add new commands for packing and unpacking raw OTA zips ([PR #562])
|
||||
* For folks who are familiar with how OTAs are built, this means it's now possible create an OTA from scratch without using `avbroot ota patch`.
|
||||
* Fix `-o` being used as the short option for both `--output` and `--output-properties` in `avbroot payload pack` and `avbroot payload repack` ([PR #563])
|
||||
* The short option for `--output-properties` is now uppercase `-O`.
|
||||
* Add missing file flush operations and remove them where they are no-ops ([PR #564])
|
||||
* Update dependencies ([PR #565])
|
||||
|
||||
### Version 3.24.1
|
||||
|
||||
* Update `avbroot avb verify-device --help` to mention that `ro.boot.vbmeta.public_key_digest` is used when `-p` is not specified ([Issue #554], [PR #555])
|
||||
@@ -417,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
|
||||
@@ -454,6 +492,9 @@ Behind-the-scenes changes:
|
||||
[Issue #536]: https://github.com/chenxiaolong/avbroot/issues/536
|
||||
[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
|
||||
@@ -671,3 +712,17 @@ Behind-the-scenes changes:
|
||||
[PR #550]: https://github.com/chenxiaolong/avbroot/pull/550
|
||||
[PR #555]: https://github.com/chenxiaolong/avbroot/pull/555
|
||||
[PR #559]: https://github.com/chenxiaolong/avbroot/pull/559
|
||||
[PR #561]: https://github.com/chenxiaolong/avbroot/pull/561
|
||||
[PR #562]: https://github.com/chenxiaolong/avbroot/pull/562
|
||||
[PR #563]: https://github.com/chenxiaolong/avbroot/pull/563
|
||||
[PR #564]: https://github.com/chenxiaolong/avbroot/pull/564
|
||||
[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
+279
-124
@@ -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.100"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -104,7 +104,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "avbroot"
|
||||
version = "3.24.1"
|
||||
version = "3.29.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
@@ -192,14 +192,14 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -268,9 +268,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.55"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -320,9 +320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.57"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -330,9 +330,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.57"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -342,30 +342,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.65"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
|
||||
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.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
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.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
|
||||
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.114",
|
||||
"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.24.1"
|
||||
version = "3.29.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"avbroot",
|
||||
@@ -663,7 +648,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz"
|
||||
version = "3.24.1"
|
||||
version = "3.29.1"
|
||||
dependencies = [
|
||||
"avbroot",
|
||||
"honggfuzz",
|
||||
@@ -692,14 +677,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -772,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",
|
||||
@@ -783,6 +769,12 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -797,6 +789,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -833,6 +827,12 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -842,6 +842,12 @@ dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libbz2-rs-sys"
|
||||
version = "0.2.2"
|
||||
@@ -850,9 +856,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -872,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"
|
||||
@@ -904,7 +910,7 @@ dependencies = [
|
||||
"quote",
|
||||
"regex-syntax",
|
||||
"rustc_version",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -918,34 +924,33 @@ 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.15.7"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69"
|
||||
checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.9"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
|
||||
checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -969,7 +974,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -996,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",
|
||||
@@ -1074,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",
|
||||
]
|
||||
@@ -1089,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"
|
||||
@@ -1170,7 +1175,7 @@ dependencies = [
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1184,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"
|
||||
@@ -1242,7 +1247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1279,7 +1284,7 @@ dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
@@ -1293,7 +1298,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1346,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"
|
||||
@@ -1391,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"
|
||||
@@ -1440,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"
|
||||
@@ -1497,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",
|
||||
@@ -1561,7 +1566,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1685,9 +1703,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1716,12 +1734,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -1744,7 +1762,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1774,14 +1792,14 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.11+spec-1.1.0"
|
||||
version = "1.0.7+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
||||
checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
@@ -1794,18 +1812,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
version = "1.0.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.24.0+spec-1.1.0"
|
||||
version = "0.25.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c740b185920170a6d9191122cafef7010bd6270a3824594bff6784c04d7f09e"
|
||||
checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -1816,18 +1834,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
version = "1.0.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
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"
|
||||
@@ -1854,7 +1872,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1880,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",
|
||||
@@ -1906,9 +1924,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
@@ -1916,6 +1934,12 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -1955,6 +1979,49 @@ dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[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 = "windows-link"
|
||||
version = "0.2.1"
|
||||
@@ -2045,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",
|
||||
]
|
||||
@@ -2057,6 +2124,88 @@ 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-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 2.0.117",
|
||||
"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 2.0.117",
|
||||
"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"
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-cert"
|
||||
@@ -2074,7 +2223,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "3.24.1"
|
||||
version = "3.29.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2084,22 +2233,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.38"
|
||||
version = "0.8.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961"
|
||||
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.38"
|
||||
version = "0.8.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75"
|
||||
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2119,11 +2268,17 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"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"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.24.1"
|
||||
version = "3.29.1"
|
||||
license = "GPL-3.0-only"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/chenxiaolong/avbroot"
|
||||
|
||||
+68
-6
@@ -19,7 +19,7 @@ The vbmeta descriptor digests are validated during unpacking. For dm-verity imag
|
||||
### Packing an AVB image
|
||||
|
||||
```bash
|
||||
avbroot avb pack -o <output AVB image> [--key <AVB private key>]
|
||||
avbroot avb pack -o <output AVB image> [-k <AVB private key>]
|
||||
```
|
||||
|
||||
This subcommand packs a new AVB image from the `avb.toml` file and, for appended vbmeta images, the `raw.img` file.
|
||||
@@ -52,6 +52,8 @@ avbroot avb info -i <image>
|
||||
|
||||
This subcommand shows all of the vbmeta header and footer fields. `vbmeta` partition images will only have a header, while partitions with actual data (eg. boot images) will have both a header and a footer.
|
||||
|
||||
(The `unpack`, `pack`, and `repack` subcommands also show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
### Verifying AVB hashes and signatures
|
||||
|
||||
```bash
|
||||
@@ -117,7 +119,9 @@ This subcommand repacks a boot image without writing the individual components t
|
||||
avbroot boot info -i <input boot image>
|
||||
```
|
||||
|
||||
All of the `boot` subcommands show the boot image information. This specific subcommand just does it without performing any other operation. To show avbroot's internal representation of the information, pass in `-d`.
|
||||
This subcommand shows the information contained in the boot image's header. To show avbroot's internal representation of the information, pass in `-d`.
|
||||
|
||||
(All of the `boot` subcommands show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
## `avbroot cpio`
|
||||
|
||||
@@ -159,7 +163,9 @@ This is almost equivalent to running `avbroot cpio unpack` followed by `avbroot
|
||||
avbroot cpio info -i <input cpio archive>
|
||||
```
|
||||
|
||||
All of the `cpio` subcommands show details about all the entries in the archive. This specific subcommand just does it without performing any other operation.
|
||||
This subcommand shows details about every entry in the archive.
|
||||
|
||||
(All of the `cpio` subcommands show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
## `avbroot fec`
|
||||
|
||||
@@ -320,6 +326,8 @@ avbroot lp info -i <first LP image>
|
||||
|
||||
This subcommand shows the LP image metadata, including all metadata slots. If there are multiple images, only the first one is needed because it is the only one that stores the metadata.
|
||||
|
||||
(All of the `lp` subcommands show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
## `avbroot payload`
|
||||
|
||||
This set of commands is for working with payload binary files (`payload.bin`). The `unpack` and `pack` commands can only work with full payloads because they require the complete data to be available, but the `repack` and `info` commands also work with delta payloads.
|
||||
@@ -332,22 +340,24 @@ 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
|
||||
|
||||
```bash
|
||||
avbroot payload pack -o <output payload> --key <OTA private key>
|
||||
avbroot payload pack -o <output payload> -k <OTA private key> [-O <output properties>]
|
||||
```
|
||||
|
||||
This subcommand packs a new payload binary from the `payload.toml` file and `payload_images` directory. Any `.img` files in the `payload_images` directory that don't have a corresponding entry in `payload.toml` are silently ignored.
|
||||
|
||||
When replacing the payload binary in an OTA, it is not sufficient to only update `payload.bin`. The checksums in `payload_properties.txt` need to be updated as well. Use `--output-properties` to generate a new properties file.
|
||||
|
||||
Packing a payload binary requires compressing all of the partition images, which is very CPU intensive. If re-signing an existing payload binary without making any other modifications is all that's needed, use the `repack` subcommand instead.
|
||||
|
||||
### Repacking a payload binary
|
||||
|
||||
```bash
|
||||
avbroot payload repack -i <input payload> -o <output payload> --key <OTA private key>
|
||||
avbroot payload repack -i <input payload> -o <output payload> -k <OTA private key> [-O <output properties>]
|
||||
```
|
||||
|
||||
This subcommand is logically equivalent to `avbroot payload unpack` followed by `avbroot payload pack`, except significantly more efficient. Instead of decompressing and recompressing all partition images, the raw data is directly copied from the input payload binary.
|
||||
@@ -362,6 +372,8 @@ avbroot payload info -i <payload>
|
||||
|
||||
This subcommand shows all of the payload header fields (which will likely be extremely long).
|
||||
|
||||
(All of the `payload` subcommands show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
## `avbroot sparse`
|
||||
|
||||
This set of commands is for working with Android sparse images. All features of the file format are supported, including hole chunks and CRC32 checksums.
|
||||
@@ -405,3 +417,53 @@ avbroot sparse info -i <input sparse image>
|
||||
```
|
||||
|
||||
This subcommand shows the sparse image metadata, including the header and all chunks.
|
||||
|
||||
(All of the `sparse` subcommands show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
## `avbroot zip`
|
||||
|
||||
This set of commands is for working with raw OTA zip files. They are intentionally placed outside of `avbroot ota` to make them less discoverable by accident. These commands are useful for creating OTA zip files without going through the normal `avbroot ota patch` mechanism.
|
||||
|
||||
**WARNING**: Make sure to run `avbroot ota verify` on OTA files before installing them. These pack commands are low level operations that only check that the file structure of the OTA itself is valid, not the contents contained within.
|
||||
|
||||
### Unpacking an OTA zip
|
||||
|
||||
```bash
|
||||
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> [--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
|
||||
|
||||
```bash
|
||||
avbroot zip repack -i <input OTA> -o <output OTA> -k <OTA private key>
|
||||
```
|
||||
|
||||
This subcommand is logically equivalent to `avbroot zip unpack` followed by `avbroot zip pack`, except more efficient.
|
||||
|
||||
**WARNING**: This is generally not a useful command. Resigning the OTA zip without also resigning the payload binary inside results in an invalid OTA.
|
||||
|
||||
### Showing OTA metadata
|
||||
|
||||
```bash
|
||||
avbroot zip info -i <input OTA>
|
||||
```
|
||||
|
||||
This subcommand shows all of the OTA metadata fields. If both the modern protobuf metadata and the legacy plain text metadata exist, the protobuf metadata takes precedence.
|
||||
|
||||
(All of the `zip` subcommands show this information. This specific subcommand just does so without performing any other operation.)
|
||||
|
||||
@@ -421,6 +421,10 @@ The only behavior this changes is where the partition is read from. When using `
|
||||
|
||||
This has no impact on what patches are applied. For example, when using Magisk, the root patch is applied to the boot partition, no matter if the partition came from the original `payload.bin` or from `--replace`.
|
||||
|
||||
### Re-signing partitions
|
||||
|
||||
avbroot will automatically re-sign any partitions in the OTA that it modifies. However, partitions that are otherwise unmodified can also be re-signed with `--re-sign <partition name>`. This is useful, for example, when the OTA contains partitions signed with the public AOSP test key.
|
||||
|
||||
### Booting signed GSIs
|
||||
|
||||
Android's [Dynamic System Updates (DSU)](https://developer.android.com/topic/dsu) feature uses a different root of trust than the regular system. Instead of using the bootloader's `avb_custom_key`, it obtains the trusted keys from the `first_stage_ramdisk/avb/*.avbpubkey` files inside the `init_boot` or `vendor_boot` ramdisk. These files are encoded in the same binary format as `avb_pkmd.bin`.
|
||||
|
||||
+3
-3
@@ -27,8 +27,8 @@ 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"
|
||||
lzma-rust2 = "0.15.2"
|
||||
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.
|
||||
num-bigint-dig = "0.8.4"
|
||||
@@ -53,7 +53,7 @@ sha1 = "0.10.5"
|
||||
sha2 = "0.10.7"
|
||||
tempfile = "3.8.0"
|
||||
thiserror = "2.0.3"
|
||||
toml = { version = "0.9.11", features = ["serde"] }
|
||||
toml = { version = "1.0.1", features = ["serde"] }
|
||||
topological-sort = "0.2.2"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
|
||||
+28
-13
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{env, ffi::OsStr, fs, io, path::Path};
|
||||
@@ -35,6 +35,10 @@ fn main() {
|
||||
const CUE_PU: &str = ".chromeos_update_engine.PartitionUpdate";
|
||||
const CUE_VABCFS: &str = ".chromeos_update_engine.VABCFeatureSet";
|
||||
|
||||
const BTR_DS: &str = ".build.tools.releasetools.DeviceState";
|
||||
const BTR_OM: &str = ".build.tools.releasetools.OtaMetadata";
|
||||
const BTR_PS: &str = ".build.tools.releasetools.PartitionState";
|
||||
|
||||
const DERIVE_SERDE: &str = "#[derive(serde::Deserialize, serde::Serialize)]";
|
||||
const SERDE_DEFAULT: &str = "#[serde(default)]";
|
||||
const SERDE_SKIP: &str = "#[serde(skip)]";
|
||||
@@ -45,19 +49,25 @@ fn main() {
|
||||
prost_build::Config::new()
|
||||
.btree_map(["."])
|
||||
// Allow deserializing and serializing the types we care about.
|
||||
.type_attribute(CUE_AI, DERIVE_SERDE)
|
||||
.type_attribute(CUE_DAM, DERIVE_SERDE)
|
||||
.type_attribute(CUE_DPG, DERIVE_SERDE)
|
||||
.type_attribute(CUE_DPM, DERIVE_SERDE)
|
||||
.type_attribute(CUE_PU, DERIVE_SERDE)
|
||||
.type_attribute(CUE_VABCFS, DERIVE_SERDE)
|
||||
.message_attribute(CUE_AI, DERIVE_SERDE)
|
||||
.message_attribute(CUE_DAM, DERIVE_SERDE)
|
||||
.message_attribute(CUE_DPG, DERIVE_SERDE)
|
||||
.message_attribute(CUE_DPM, DERIVE_SERDE)
|
||||
.message_attribute(CUE_PU, DERIVE_SERDE)
|
||||
.message_attribute(CUE_VABCFS, DERIVE_SERDE)
|
||||
.message_attribute(BTR_DS, DERIVE_SERDE)
|
||||
.message_attribute(BTR_OM, DERIVE_SERDE)
|
||||
.message_attribute(BTR_PS, DERIVE_SERDE)
|
||||
// Allow default-initializing all fields.
|
||||
.type_attribute(CUE_AI, SERDE_DEFAULT)
|
||||
.type_attribute(CUE_DAM, SERDE_DEFAULT)
|
||||
.type_attribute(CUE_DPG, SERDE_DEFAULT)
|
||||
.type_attribute(CUE_DPM, SERDE_DEFAULT)
|
||||
.type_attribute(CUE_PU, SERDE_DEFAULT)
|
||||
.type_attribute(CUE_VABCFS, SERDE_DEFAULT)
|
||||
.message_attribute(CUE_AI, SERDE_DEFAULT)
|
||||
.message_attribute(CUE_DAM, SERDE_DEFAULT)
|
||||
.message_attribute(CUE_DPG, SERDE_DEFAULT)
|
||||
.message_attribute(CUE_DPM, SERDE_DEFAULT)
|
||||
.message_attribute(CUE_PU, SERDE_DEFAULT)
|
||||
.message_attribute(CUE_VABCFS, SERDE_DEFAULT)
|
||||
.message_attribute(BTR_DS, SERDE_DEFAULT)
|
||||
.message_attribute(BTR_OM, SERDE_DEFAULT)
|
||||
.message_attribute(BTR_PS, SERDE_DEFAULT)
|
||||
// Don't serialize fields that define the structure of the payload
|
||||
// binary and that we recompute during packing.
|
||||
.field_attribute(c!(CUE_DAM, ".signatures_offset"), SERDE_SKIP)
|
||||
@@ -84,6 +94,11 @@ fn main() {
|
||||
.field_attribute(c!(CUE_DAM, ".partitions"), SERDE_SKIP_IF_VEC_EMPTY)
|
||||
.field_attribute(c!(CUE_DPG, ".partition_names"), SERDE_SKIP_IF_VEC_EMPTY)
|
||||
.field_attribute(c!(CUE_DPM, ".groups"), SERDE_SKIP_IF_VEC_EMPTY)
|
||||
// Serialize enums as their enum types, not their underlying reprs.
|
||||
.field_attribute(
|
||||
c!(BTR_OM, ".type"),
|
||||
"#[serde(with = \"crate::protobuf::ota_type\")]",
|
||||
)
|
||||
.compile_fds(file_descriptors)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
@@ -13,7 +13,7 @@ use clap::{Parser, Subcommand, ValueEnum};
|
||||
use tracing::{Level, debug};
|
||||
use tracing_subscriber::fmt::{format::Writer, time::FormatTime};
|
||||
|
||||
use crate::cli::{avb, boot, completion, cpio, fec, hashtree, key, lp, ota, payload, sparse};
|
||||
use crate::cli::{avb, boot, completion, cpio, fec, hashtree, key, lp, ota, payload, sparse, zip};
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Subcommand)]
|
||||
@@ -29,6 +29,7 @@ pub enum Command {
|
||||
Ota(ota::OtaCli),
|
||||
Payload(payload::PayloadCli),
|
||||
Sparse(sparse::SparseCli),
|
||||
Zip(zip::ZipCli),
|
||||
/// (Deprecated: Use `avbroot ota patch` instead.)
|
||||
#[command(hide = true)]
|
||||
Patch(ota::PatchCli),
|
||||
@@ -132,6 +133,7 @@ pub fn main(logging_initialized: &AtomicBool, cancel_signal: &AtomicBool) -> Res
|
||||
Command::Ota(c) => ota::ota_main(&c, cancel_signal),
|
||||
Command::Payload(c) => payload::payload_main(&c, cancel_signal),
|
||||
Command::Sparse(c) => sparse::sparse_main(&c, cancel_signal),
|
||||
Command::Zip(c) => zip::zip_main(&c, cancel_signal),
|
||||
// Deprecated aliases.
|
||||
Command::Patch(c) => ota::patch_subcommand(&c, cancel_signal),
|
||||
Command::Extract(c) => ota::extract_subcommand(&c, cancel_signal),
|
||||
|
||||
+57
-7
@@ -29,6 +29,53 @@ use crate::{
|
||||
util,
|
||||
};
|
||||
|
||||
/// SHA-256 digests of the AOSP test keys encoded in the AVB binary format. This
|
||||
/// excludes 8192-bit keys since RustCrypto doesn't support them anyway.
|
||||
const PUBLIC_PUBKEYS: [[u8; 32]; 7] = [
|
||||
// testkey_cert_pik.pem
|
||||
[
|
||||
0xce, 0x46, 0x4c, 0x0f, 0xef, 0xc0, 0x23, 0x76, 0x0a, 0xdc, 0x3f, 0xe0, 0x20, 0x7c, 0x28,
|
||||
0x2d, 0x33, 0x7e, 0x1b, 0x91, 0xe3, 0xdf, 0x7a, 0xb4, 0xfe, 0xe4, 0x3f, 0xcd, 0x62, 0x0d,
|
||||
0xb4, 0x6d,
|
||||
],
|
||||
// testkey_cert_prk.pem
|
||||
[
|
||||
0x86, 0xb0, 0x4a, 0x9d, 0x62, 0x80, 0x7f, 0xda, 0x80, 0xcb, 0xf4, 0x45, 0x11, 0x8e, 0x8a,
|
||||
0x4a, 0x73, 0xaa, 0x59, 0x37, 0x28, 0x3b, 0x49, 0xe0, 0x18, 0xe4, 0x32, 0xdd, 0x8e, 0x3a,
|
||||
0x76, 0xa1,
|
||||
],
|
||||
// testkey_cert_psk.pem
|
||||
[
|
||||
0x7e, 0xc0, 0x2e, 0xe1, 0xbe, 0x69, 0x63, 0x66, 0xf3, 0xfa, 0x91, 0x24, 0x0a, 0x8e, 0xc6,
|
||||
0x81, 0x25, 0xc4, 0x14, 0x5d, 0x69, 0x8f, 0x59, 0x7a, 0xa2, 0xb3, 0x46, 0x4b, 0x59, 0xca,
|
||||
0x7f, 0xc3,
|
||||
],
|
||||
// testkey_cert_puk.pem
|
||||
[
|
||||
0xc6, 0x08, 0xff, 0xa5, 0xe7, 0xee, 0x92, 0x98, 0x4f, 0x44, 0x86, 0xe0, 0x30, 0x88, 0x44,
|
||||
0x0c, 0x36, 0x0c, 0xf3, 0x15, 0xec, 0x6a, 0x5f, 0x52, 0xae, 0xf7, 0x90, 0xcb, 0x30, 0xef,
|
||||
0xce, 0xc9,
|
||||
],
|
||||
// testkey_rsa2048.pem
|
||||
[
|
||||
0x22, 0xde, 0x39, 0x94, 0x53, 0x21, 0x96, 0xf6, 0x1c, 0x03, 0x9e, 0x90, 0x26, 0x0d, 0x78,
|
||||
0xa9, 0x3a, 0x4c, 0x57, 0x36, 0x2c, 0x7e, 0x78, 0x9b, 0xe9, 0x28, 0x03, 0x6e, 0x80, 0xb7,
|
||||
0x7c, 0x8c,
|
||||
],
|
||||
// testkey_rsa2048_2.pem
|
||||
[
|
||||
0x2b, 0xed, 0x47, 0x45, 0x1b, 0xc6, 0x98, 0xe9, 0xe8, 0x2d, 0x92, 0xa6, 0x66, 0x8b, 0xd0,
|
||||
0x3a, 0xb6, 0xcf, 0x8d, 0xd1, 0xa1, 0x44, 0x34, 0x1c, 0xb7, 0xf4, 0x26, 0xf2, 0x0b, 0x28,
|
||||
0x79, 0xcf,
|
||||
],
|
||||
// testkey_rsa4096.pem
|
||||
[
|
||||
0x77, 0x28, 0xe3, 0x0f, 0x50, 0xbf, 0xa5, 0xce, 0xa1, 0x65, 0xf4, 0x73, 0x17, 0x5a, 0x08,
|
||||
0x80, 0x3f, 0x6a, 0x83, 0x46, 0x64, 0x2b, 0x5a, 0xa1, 0x09, 0x13, 0xe9, 0xd9, 0xe6, 0xde,
|
||||
0xfe, 0xf6,
|
||||
],
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
struct AvbInfo {
|
||||
header: Header,
|
||||
@@ -37,9 +84,9 @@ struct AvbInfo {
|
||||
}
|
||||
|
||||
fn read_avb_image(path: &Path) -> Result<(AvbInfo, BufReader<File>)> {
|
||||
let file = File::open(path)
|
||||
let mut reader = File::open(path)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open AVB image for reading: {path:?}"))?;
|
||||
let mut reader = BufReader::new(file);
|
||||
let (header, footer, image_size) = avb::load_image(&mut reader)
|
||||
.with_context(|| format!("Failed to load AVB image: {path:?}"))?;
|
||||
|
||||
@@ -101,14 +148,14 @@ fn write_raw(
|
||||
size: u64,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<File> {
|
||||
let file = fs::OpenOptions::new()
|
||||
let mut writer = fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to open raw image for writing: {path:?}"))?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
|
||||
reader
|
||||
.rewind()
|
||||
@@ -500,6 +547,8 @@ pub fn verify_headers(
|
||||
|
||||
if let Some(k) = &public_key {
|
||||
let prefix = format!("{name} has a signed vbmeta header");
|
||||
let encoded = avb::encode_public_key(k)?;
|
||||
let digest = Sha256::digest(&encoded);
|
||||
|
||||
match trust_method {
|
||||
TrustMethod::Key(expected) => {
|
||||
@@ -510,9 +559,6 @@ pub fn verify_headers(
|
||||
}
|
||||
}
|
||||
TrustMethod::KeyDigest(expected_sha256) => {
|
||||
let encoded = avb::encode_public_key(k)?;
|
||||
let digest = Sha256::digest(&encoded);
|
||||
|
||||
if digest.as_slice() == expected_sha256 {
|
||||
info!("{prefix}");
|
||||
} else {
|
||||
@@ -523,6 +569,10 @@ pub fn verify_headers(
|
||||
warn!("{prefix}, but parent does not list a trusted key");
|
||||
}
|
||||
}
|
||||
|
||||
if PUBLIC_PUBKEYS.iter().any(|p| p == digest.as_slice()) {
|
||||
warn!("{name} is signed with public AOSP test key");
|
||||
}
|
||||
} else {
|
||||
info!("{name} has an unsigned vbmeta header");
|
||||
}
|
||||
|
||||
+20
-12
@@ -16,8 +16,9 @@ use crate::{
|
||||
};
|
||||
|
||||
fn read_image(path: &Path) -> Result<BootImage> {
|
||||
let file = File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
let reader = File::open(path)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
let image = BootImage::from_reader(reader)
|
||||
.with_context(|| format!("Failed to read boot image: {path:?}"))?;
|
||||
|
||||
@@ -25,9 +26,9 @@ fn read_image(path: &Path) -> Result<BootImage> {
|
||||
}
|
||||
|
||||
fn write_image(path: &Path, image: &BootImage) -> Result<()> {
|
||||
let file =
|
||||
File::create(path).with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
let mut writer = File::create(path)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
image
|
||||
.to_writer(&mut writer)
|
||||
.with_context(|| format!("Failed to write boot image: {path:?}"))?;
|
||||
@@ -76,12 +77,12 @@ fn read_text_if_exists(path: &Path) -> Result<Option<String>> {
|
||||
}
|
||||
|
||||
fn read_avb_header_if_exists(path: &Path) -> Result<Option<Header>> {
|
||||
let file = match File::open(path) {
|
||||
Ok(f) => f,
|
||||
let reader = match File::open(path) {
|
||||
Ok(f) => BufReader::new(f),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(e) => Err(e).with_context(|| format!("Failed to open for reading: {path:?}"))?,
|
||||
};
|
||||
let header = Header::from_reader(BufReader::new(file))
|
||||
let header = Header::from_reader(reader)
|
||||
.with_context(|| format!("Failed to read vbmeta header: {path:?}"))?;
|
||||
|
||||
Ok(Some(header))
|
||||
@@ -105,9 +106,15 @@ fn write_text_if_not_empty(path: &Path, text: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
fn write_avb_header(path: &Path, header: &Header) -> Result<()> {
|
||||
let file =
|
||||
File::create(path).with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
header.to_writer(BufWriter::new(file))?;
|
||||
let mut writer = File::create(path)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
header
|
||||
.to_writer(&mut writer)
|
||||
.with_context(|| format!("Failed to write AVB header: {path:?}"))?;
|
||||
writer
|
||||
.flush()
|
||||
.with_context(|| format!("Failed to flush AVB header: {path:?}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -286,8 +293,9 @@ fn info_subcommand(boot_cli: &BootCli, cli: &InfoCli) -> Result<()> {
|
||||
|
||||
pub fn magisk_info_subcommand(cli: &MagiskInfoCli) -> Result<()> {
|
||||
let raw_reader = File::open(&cli.image)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open for reading: {:?}", cli.image))?;
|
||||
let boot_image = BootImage::from_reader(BufReader::new(raw_reader))
|
||||
let boot_image = BootImage::from_reader(raw_reader)
|
||||
.with_context(|| format!("Failed to load boot image: {:?}", cli.image))?;
|
||||
|
||||
let mut ramdisks = vec![];
|
||||
|
||||
@@ -36,9 +36,10 @@ fn open_reader(
|
||||
CpioReader<CompressedReader<BufReader<File>>>,
|
||||
CompressedFormat,
|
||||
)> {
|
||||
let file =
|
||||
File::open(path).with_context(|| format!("Failed to open cpio for reading: {path:?}"))?;
|
||||
let reader = CompressedReader::new(BufReader::new(file), true)
|
||||
let raw_reader = File::open(path)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open cpio for reading: {path:?}"))?;
|
||||
let reader = CompressedReader::new(raw_reader, true)
|
||||
.with_context(|| format!("Failed to open decompressor: {path:?}"))?;
|
||||
let format = reader.format();
|
||||
let cpio_reader = CpioReader::new(reader, include_trailer);
|
||||
@@ -50,9 +51,10 @@ fn open_writer(
|
||||
path: &Path,
|
||||
format: CompressedFormat,
|
||||
) -> Result<CpioWriter<CompressedWriter<BufWriter<File>>>> {
|
||||
let file =
|
||||
File::create(path).with_context(|| format!("Failed to open cpio for writing: {path:?}"))?;
|
||||
let writer = CompressedWriter::new(BufWriter::new(file), format)
|
||||
let raw_writer = File::create(path)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to open cpio for writing: {path:?}"))?;
|
||||
let writer = CompressedWriter::new(raw_writer, format)
|
||||
.with_context(|| format!("Failed to open compressor: {path:?}"))?;
|
||||
let cpio_writer = CpioWriter::new(writer, false);
|
||||
|
||||
@@ -287,8 +289,7 @@ pub fn cpio_main(cli: &CpioCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
/// TOML file, like the UID/GID, permissions, and symlink targets.
|
||||
///
|
||||
/// If any paths inside the cpio archive are unsafe, the extraction process will
|
||||
/// fail and exit. Extracted files are never written outside of the tree
|
||||
/// directory, even if an external process tries to interfere.
|
||||
/// fail and exit.
|
||||
#[derive(Debug, Parser)]
|
||||
struct UnpackCli {
|
||||
/// Path to input cpio file.
|
||||
|
||||
@@ -515,8 +515,7 @@ pub fn lp_main(cli: &LpCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
/// directory. For empty images, the output images directory is unused.
|
||||
///
|
||||
/// If any partition names are unsafe to use in a path, the extraction process
|
||||
/// will fail and exit. Extracted files are never written outside of the tree
|
||||
/// directory, even if an external process tries to interfere.
|
||||
/// will fail and exit.
|
||||
#[derive(Debug, Parser)]
|
||||
struct UnpackCli {
|
||||
/// Path to input LP images.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod args;
|
||||
@@ -13,3 +13,4 @@ pub mod lp;
|
||||
pub mod ota;
|
||||
pub mod payload;
|
||||
pub mod sparse;
|
||||
pub mod zip;
|
||||
|
||||
+161
-32
@@ -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::{
|
||||
@@ -23,7 +23,7 @@ use rawzip::{
|
||||
use rayon::{iter::IntoParallelRefIterator, prelude::ParallelIterator};
|
||||
use tempfile::{NamedTempFile, TempDir};
|
||||
use topological_sort::TopologicalSort;
|
||||
use tracing::{debug_span, error, info, warn};
|
||||
use tracing::{Span, debug_span, error, info, warn};
|
||||
use x509_cert::Certificate;
|
||||
|
||||
use crate::{
|
||||
@@ -35,7 +35,6 @@ use crate::{
|
||||
format::{
|
||||
avb::{self, Descriptor, Header},
|
||||
ota::{self, SigningWriter, ZipEntry, ZipMode},
|
||||
padding,
|
||||
payload::{self, CowVersion, PayloadHeader, PayloadWriter, VabcAlgo, VabcParams},
|
||||
zip::{
|
||||
self, ReaderAtWrapper, ZipArchiveReadAtExt, ZipEntriesSafeExt, ZipFileHeaderRecordExt,
|
||||
@@ -53,7 +52,7 @@ use crate::{
|
||||
},
|
||||
stream::{
|
||||
self, FromReader, HashingWriter, MutexFile, ReadAt, ReadSeek, SectionReader,
|
||||
SectionReaderAt, ToWriter, UserPosFile, WriteAt, WriteSeek,
|
||||
SectionReaderAt, UserPosFile, WriteAt, WriteSeek,
|
||||
},
|
||||
util,
|
||||
};
|
||||
@@ -155,19 +154,23 @@ enum InputFileState {
|
||||
Modified,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InputFile {
|
||||
file: Arc<File>,
|
||||
state: InputFileState,
|
||||
}
|
||||
|
||||
/// Open all input files listed in `required_images`. If an image has a path
|
||||
/// in `external_images`, that file is opened. Otherwise, the image is extracted
|
||||
/// from the payload into a temporary file (that is unnamed if supported by the
|
||||
/// operating system).
|
||||
/// Open all input files listed in `required_images` and `re_sign_images`. If an
|
||||
/// image has a path in `external_images`, that file is opened. Otherwise, the
|
||||
/// image is extracted from the payload into a temporary file (that is unnamed
|
||||
/// if supported by the operating system). Images that are destined to be
|
||||
/// re-signed do not get copied to a temporary file until later when it can be
|
||||
/// determined that it wasn't already modified and re-signed due to patching.
|
||||
fn open_input_files(
|
||||
payload: &(dyn ReadAt + Sync),
|
||||
required_images: &HashMap<String, PartitionFlags>,
|
||||
external_images: &HashMap<String, PathBuf>,
|
||||
re_sign_images: &HashSet<String>,
|
||||
header: &PayloadHeader,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<HashMap<String, InputFile>> {
|
||||
@@ -178,6 +181,7 @@ fn open_input_files(
|
||||
let all_images = required_images
|
||||
.keys()
|
||||
.chain(external_images.keys())
|
||||
.chain(re_sign_images.iter())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for name in all_images {
|
||||
@@ -325,6 +329,102 @@ fn patch_system_image<'a>(
|
||||
Ok((target, ranges))
|
||||
}
|
||||
|
||||
/// Re-sign the images listed in `re_sign_images` if they aren't already
|
||||
/// modified by the patching process. If the original image is signed, then it
|
||||
/// will be re-signed with `key_avb`. Otherwise, it is left untouched. If the
|
||||
/// original image is external, then it will be copied to a temporary file.
|
||||
fn re_sign_unmodified_images(
|
||||
re_sign_images: &HashSet<String>,
|
||||
input_files: &mut HashMap<String, InputFile>,
|
||||
key_avb: &RsaSigningKey,
|
||||
block_size: u64,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<()> {
|
||||
let parent_span = Span::current();
|
||||
let input_files = Mutex::new(input_files);
|
||||
|
||||
re_sign_images
|
||||
.par_iter()
|
||||
.try_for_each(|name| -> Result<()> {
|
||||
let _span = debug_span!(parent: &parent_span, "image", name).entered();
|
||||
|
||||
let mut input_file = input_files.lock().unwrap()[name].clone();
|
||||
|
||||
if input_file.state == InputFileState::Modified {
|
||||
// Already re-signed by some other patching operation.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (mut header, footer, image_size) = avb::load_image(&mut input_file.file)
|
||||
.with_context(|| format!("Failed to load AVB metadata: {name}"))?;
|
||||
let orig_header = header.clone();
|
||||
|
||||
if !header.public_key.is_empty() {
|
||||
header
|
||||
.set_algo_for_key(key_avb)
|
||||
.with_context(|| format!("Failed to set signature algorithm: {name}"))?;
|
||||
header
|
||||
.sign(key_avb)
|
||||
.with_context(|| format!("Failed to sign AVB metadata: {name}"))?;
|
||||
}
|
||||
|
||||
if header == orig_header {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if input_file.state == InputFileState::External {
|
||||
info!("Copying external image for re-signing: {name}");
|
||||
|
||||
// Need to make a copy so that the user's original file doesn't
|
||||
// get modified.
|
||||
let mut temp_file = tempfile::tempfile()
|
||||
.with_context(|| format!("Failed to create temp file for: {name}"))?;
|
||||
|
||||
input_file
|
||||
.file
|
||||
.rewind()
|
||||
.with_context(|| format!("Failed to rewind file: {name}"))?;
|
||||
|
||||
stream::copy(&mut input_file.file.clone(), &mut temp_file, cancel_signal)
|
||||
.with_context(|| format!("Failed to copy file: {name}"))?;
|
||||
|
||||
input_file.file = Arc::new(temp_file);
|
||||
}
|
||||
|
||||
info!("Re-signing image: {name}");
|
||||
|
||||
let original_image_size = footer
|
||||
.as_ref()
|
||||
.map(|f| f.original_image_size)
|
||||
.unwrap_or_default();
|
||||
input_file
|
||||
.file
|
||||
.rewind()
|
||||
.with_context(|| format!("Failed to rewind file: {name}"))?;
|
||||
input_file
|
||||
.file
|
||||
.set_len(original_image_size)
|
||||
.with_context(|| format!("Failed to truncate file: {name}"))?;
|
||||
|
||||
if let Some(mut footer) = footer {
|
||||
avb::write_appended_image(
|
||||
&mut input_file.file,
|
||||
&header,
|
||||
&mut footer,
|
||||
Some(image_size),
|
||||
)
|
||||
} else {
|
||||
avb::write_root_image(&mut input_file.file, &header, block_size)
|
||||
}
|
||||
.with_context(|| format!("Failed to write AVB metadata: {name}"))?;
|
||||
|
||||
input_file.state = InputFileState::Modified;
|
||||
*input_files.lock().unwrap().get_mut(name).unwrap() = input_file;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Load the specified vbmeta image headers. If an image has a vbmeta footer,
|
||||
/// then an error is returned because the vbmeta patching logic only ever writes
|
||||
/// root vbmeta images.
|
||||
@@ -723,7 +823,7 @@ fn update_vbmeta_headers(
|
||||
//
|
||||
// The root vbmeta image is always signed because it is possible to
|
||||
// invoke avbroot is a way that no modifications are made (rootless +
|
||||
// skipping recovery otacerts.zip patch). We still want the result to be
|
||||
// skipping both otacerts.zip patches). We still want the result to be
|
||||
// bootable.
|
||||
if parent_header != &orig_parent_header || name == "vbmeta" {
|
||||
parent_header
|
||||
@@ -735,13 +835,9 @@ fn update_vbmeta_headers(
|
||||
|
||||
let mut writer = tempfile::tempfile()
|
||||
.with_context(|| format!("Failed to create temp file for: {name}"))?;
|
||||
parent_header
|
||||
.to_writer(&mut writer)
|
||||
avb::write_root_image(&mut writer, parent_header, block_size)
|
||||
.with_context(|| format!("Failed to write vbmeta image: {name}"))?;
|
||||
|
||||
padding::write_zeros(&mut writer, block_size)
|
||||
.with_context(|| format!("Failed to write vbmeta padding: {name}"))?;
|
||||
|
||||
let input_file = images.get_mut(name).unwrap();
|
||||
input_file.file = Arc::new(writer);
|
||||
input_file.state = InputFileState::Modified;
|
||||
@@ -909,6 +1005,7 @@ fn patch_ota_payload(
|
||||
payload: &(dyn ReadAt + Sync),
|
||||
writer: impl Write,
|
||||
external_images: &HashMap<String, PathBuf>,
|
||||
re_sign_images: &HashSet<String>,
|
||||
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
|
||||
skip_system_ota_cert: bool,
|
||||
clear_vbmeta_flags: bool,
|
||||
@@ -949,6 +1046,12 @@ fn patch_ota_payload(
|
||||
}
|
||||
}
|
||||
|
||||
for name in re_sign_images {
|
||||
if !all_partitions.contains(name.as_str()) {
|
||||
bail!("Cannot re-sign non-existent {name} partition");
|
||||
}
|
||||
}
|
||||
|
||||
let required_images = get_required_images(&header.manifest, required_flags);
|
||||
let vbmeta_images = required_images
|
||||
.iter()
|
||||
@@ -970,6 +1073,7 @@ fn patch_ota_payload(
|
||||
payload,
|
||||
&required_images,
|
||||
external_images,
|
||||
re_sign_images,
|
||||
&header,
|
||||
cancel_signal,
|
||||
)?;
|
||||
@@ -994,6 +1098,14 @@ fn patch_ota_payload(
|
||||
)?)
|
||||
};
|
||||
|
||||
re_sign_unmodified_images(
|
||||
re_sign_images,
|
||||
&mut input_files,
|
||||
key_avb,
|
||||
header.manifest.block_size().into(),
|
||||
cancel_signal,
|
||||
)?;
|
||||
|
||||
let mut vbmeta_headers = load_vbmeta_images(&mut input_files, &vbmeta_images)?;
|
||||
|
||||
ensure_partitions_protected(&required_images, &vbmeta_headers)?;
|
||||
@@ -1116,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)]
|
||||
@@ -1129,6 +1241,7 @@ fn patch_ota_zip(
|
||||
zip_reader: &ZipArchive<ReaderAtWrapper<&File>>,
|
||||
zip_writer: &mut ZipArchiveWriter<impl Write>,
|
||||
external_images: &HashMap<String, PathBuf>,
|
||||
re_sign_images: &HashSet<String>,
|
||||
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
|
||||
skip_system_ota_cert: bool,
|
||||
clear_vbmeta_flags: bool,
|
||||
@@ -1295,6 +1408,7 @@ fn patch_ota_zip(
|
||||
&payload_reader,
|
||||
&mut data_writer,
|
||||
external_images,
|
||||
re_sign_images,
|
||||
boot_patchers,
|
||||
skip_system_ota_cert,
|
||||
clear_vbmeta_flags,
|
||||
@@ -1519,6 +1633,8 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
|
||||
external_images.insert(name.to_owned(), path.to_owned());
|
||||
}
|
||||
|
||||
let re_sign_images = cli.re_sign.iter().cloned().collect::<HashSet<_>>();
|
||||
|
||||
let mut boot_patchers = Vec::<Box<dyn BootImagePatch + Sync>>::new();
|
||||
|
||||
if let Some(magisk) = &cli.root.magisk {
|
||||
@@ -1582,6 +1698,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
|
||||
&zip_reader,
|
||||
&mut zip_writer,
|
||||
&external_images,
|
||||
&re_sign_images,
|
||||
&boot_patchers,
|
||||
cli.skip_system_ota_cert,
|
||||
cli.clear_vbmeta_flags,
|
||||
@@ -1600,7 +1717,6 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
|
||||
let mut temp_writer = signing_writer
|
||||
.finish(&key_ota, &cert_ota, cancel_signal)
|
||||
.context("Failed to sign output zip")?;
|
||||
temp_writer.flush().context("Failed to flush output zip")?;
|
||||
|
||||
// We do a lot of low-level hackery. Reopen and verify offsets.
|
||||
info!("Verifying metadata offsets");
|
||||
@@ -1940,9 +2056,9 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
};
|
||||
}
|
||||
|
||||
let raw_reader = File::open(&cli.input)
|
||||
let mut reader = File::open(&cli.input)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
|
||||
let mut reader = BufReader::new(raw_reader);
|
||||
|
||||
info!("Verifying whole-file signature");
|
||||
|
||||
@@ -2193,7 +2309,7 @@ pub struct PatchCli {
|
||||
alias = "privkey-avb",
|
||||
value_name = "FILE",
|
||||
value_parser,
|
||||
help_heading = HEADING_KEY
|
||||
help_heading = HEADING_KEY,
|
||||
)]
|
||||
pub key_avb: PathBuf,
|
||||
|
||||
@@ -2206,7 +2322,7 @@ pub struct PatchCli {
|
||||
alias = "privkey-ota",
|
||||
value_name = "FILE",
|
||||
value_parser,
|
||||
help_heading = HEADING_KEY
|
||||
help_heading = HEADING_KEY,
|
||||
)]
|
||||
pub key_ota: PathBuf,
|
||||
|
||||
@@ -2221,7 +2337,7 @@ pub struct PatchCli {
|
||||
value_name = "ENV_VAR",
|
||||
value_parser,
|
||||
group = "pass_avb",
|
||||
help_heading = HEADING_KEY
|
||||
help_heading = HEADING_KEY,
|
||||
)]
|
||||
pub pass_avb_env_var: Option<OsString>,
|
||||
|
||||
@@ -2232,7 +2348,7 @@ pub struct PatchCli {
|
||||
value_name = "FILE",
|
||||
value_parser,
|
||||
group = "pass_avb",
|
||||
help_heading = HEADING_KEY
|
||||
help_heading = HEADING_KEY,
|
||||
)]
|
||||
pub pass_avb_file: Option<PathBuf>,
|
||||
|
||||
@@ -2243,7 +2359,7 @@ pub struct PatchCli {
|
||||
value_name = "ENV_VAR",
|
||||
value_parser,
|
||||
group = "pass_ota",
|
||||
help_heading = HEADING_KEY
|
||||
help_heading = HEADING_KEY,
|
||||
)]
|
||||
pub pass_ota_env_var: Option<OsString>,
|
||||
|
||||
@@ -2254,7 +2370,7 @@ pub struct PatchCli {
|
||||
value_name = "FILE",
|
||||
value_parser,
|
||||
group = "pass_ota",
|
||||
help_heading = HEADING_KEY
|
||||
help_heading = HEADING_KEY,
|
||||
)]
|
||||
pub pass_ota_file: Option<PathBuf>,
|
||||
|
||||
@@ -2273,10 +2389,23 @@ pub struct PatchCli {
|
||||
value_names = ["PARTITION", "FILE"],
|
||||
value_parser = value_parser!(OsString),
|
||||
num_args = 2,
|
||||
help_heading = HEADING_PATH
|
||||
help_heading = HEADING_PATH,
|
||||
)]
|
||||
pub replace: Vec<OsString>,
|
||||
|
||||
/// Re-sign unmodified partition image.
|
||||
///
|
||||
/// Modified partition images are always re-signed. This option forces the
|
||||
/// specified image to be re-signed, even if it was unmodified. When used
|
||||
/// with --replace, a copy of the replacement image will be re-signed, not
|
||||
/// the original file.
|
||||
///
|
||||
/// This option only has an effect if the specified partition image is
|
||||
/// originally signed. If it is unsigned because the AVB descriptors are
|
||||
/// embedded in another signed vbmeta image, then this option is a no-op.
|
||||
#[arg(long, value_names = ["PARTITION"], help_heading = HEADING_PATH)]
|
||||
pub re_sign: Vec<String>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub root: RootGroup,
|
||||
|
||||
@@ -2285,7 +2414,7 @@ pub struct PatchCli {
|
||||
long,
|
||||
value_name = "PARTITION",
|
||||
conflicts_with_all = ["prepatched", "rootless"],
|
||||
help_heading = HEADING_MAGISK
|
||||
help_heading = HEADING_MAGISK,
|
||||
)]
|
||||
pub magisk_preinit_device: Option<String>,
|
||||
|
||||
@@ -2294,7 +2423,7 @@ pub struct PatchCli {
|
||||
long,
|
||||
value_name = "NUMBER",
|
||||
conflicts_with_all = ["prepatched", "rootless"],
|
||||
help_heading = HEADING_MAGISK
|
||||
help_heading = HEADING_MAGISK,
|
||||
)]
|
||||
pub magisk_random_seed: Option<u64>,
|
||||
|
||||
@@ -2302,7 +2431,7 @@ pub struct PatchCli {
|
||||
#[arg(
|
||||
long,
|
||||
conflicts_with_all = ["prepatched", "rootless"],
|
||||
help_heading = HEADING_MAGISK
|
||||
help_heading = HEADING_MAGISK,
|
||||
)]
|
||||
pub ignore_magisk_warnings: bool,
|
||||
|
||||
@@ -2311,7 +2440,7 @@ pub struct PatchCli {
|
||||
long,
|
||||
action = ArgAction::Count,
|
||||
conflicts_with_all = ["magisk", "rootless"],
|
||||
help_heading = HEADING_PREPATCHED
|
||||
help_heading = HEADING_PREPATCHED,
|
||||
)]
|
||||
pub ignore_prepatched_compat: u8,
|
||||
|
||||
@@ -2368,7 +2497,7 @@ pub struct PatchCli {
|
||||
long,
|
||||
value_name = "MODE",
|
||||
default_value_t = ZipMode::Streaming,
|
||||
help_heading = HEADING_OTHER
|
||||
help_heading = HEADING_OTHER,
|
||||
)]
|
||||
pub zip_mode: ZipMode,
|
||||
|
||||
@@ -2377,7 +2506,7 @@ pub struct PatchCli {
|
||||
long,
|
||||
value_name = "PARTITION",
|
||||
hide = true,
|
||||
help_heading = HEADING_OTHER
|
||||
help_heading = HEADING_OTHER,
|
||||
)]
|
||||
pub boot_partition: Option<String>,
|
||||
}
|
||||
|
||||
+144
-62
@@ -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,12 +291,35 @@ fn pack_subcommand(
|
||||
.with_context(|| format!("Failed to copy from replacement image: {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(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 {
|
||||
@@ -258,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");
|
||||
|
||||
@@ -297,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 {
|
||||
@@ -314,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(())
|
||||
}
|
||||
@@ -363,8 +442,7 @@ struct KeyGroup {
|
||||
/// directory. The payload header metadata is written to the info TOML file.
|
||||
///
|
||||
/// If any partition names are unsafe to use in a path, the extraction process
|
||||
/// will fail and exit. Extracted files are never written outside of the tree
|
||||
/// directory, even if an external process tries to interfere.
|
||||
/// will fail and exit.
|
||||
#[derive(Debug, Parser)]
|
||||
struct UnpackCli {
|
||||
/// Path to input payload binary.
|
||||
@@ -390,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.
|
||||
@@ -407,7 +489,7 @@ struct PackCli {
|
||||
output: PathBuf,
|
||||
|
||||
/// Path to output payload properties file.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
#[arg(short = 'O', long, value_name = "FILE", value_parser)]
|
||||
output_properties: Option<PathBuf>,
|
||||
|
||||
/// Path to input info TOML.
|
||||
@@ -447,7 +529,7 @@ struct RepackCli {
|
||||
output: PathBuf,
|
||||
|
||||
/// Path to output payload properties file.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
#[arg(short = 'O', long, value_name = "FILE", value_parser)]
|
||||
output_properties: Option<PathBuf>,
|
||||
|
||||
#[command(flatten)]
|
||||
@@ -457,7 +539,7 @@ struct RepackCli {
|
||||
/// Display payload information.
|
||||
#[derive(Debug, Parser)]
|
||||
struct InfoCli {
|
||||
/// Path to input payload file.
|
||||
/// Path to input payload binary.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
input: PathBuf,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,751 @@
|
||||
// SPDX-FileCopyrightText: 2026 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
ffi::OsString,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufReader, Seek, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::atomic::AtomicBool,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use rawzip::{
|
||||
CompressionMethod, RECOMMENDED_BUFFER_SIZE, ZipArchive, ZipArchiveEntryWayfinder,
|
||||
ZipArchiveWriter, ZipDataWriter, ZipEntryWriter, extra_fields::ExtraFieldId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
use x509_cert::Certificate;
|
||||
|
||||
use crate::{
|
||||
cli::payload,
|
||||
crypto::{self, PassphraseSource, RsaSigningKey},
|
||||
format::{
|
||||
ota::{self, SigningWriter, ZipEntry, ZipMode},
|
||||
payload::PayloadHeader,
|
||||
zip::{
|
||||
self, ReaderAtWrapper, ZipArchiveReadAtExt, ZipEntriesSafeExt, ZipFileHeaderRecordExt,
|
||||
},
|
||||
},
|
||||
protobuf::build::tools::releasetools::OtaMetadata,
|
||||
stream::{self, FromReader},
|
||||
util,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct OtaInfo {
|
||||
metadata: OtaMetadata,
|
||||
files: Vec<String>,
|
||||
}
|
||||
|
||||
struct InputEntry {
|
||||
compression_method: CompressionMethod,
|
||||
is_zip64: bool,
|
||||
wayfinder: ZipArchiveEntryWayfinder,
|
||||
}
|
||||
|
||||
fn is_excluded_path(path: &str) -> bool {
|
||||
path == ota::PATH_METADATA || path == ota::PATH_METADATA_PB || path == ota::PATH_OTACERT
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn open_reader(
|
||||
path: &Path,
|
||||
) -> Result<(
|
||||
ZipArchive<ReaderAtWrapper<File>>,
|
||||
OtaMetadata,
|
||||
BTreeMap<String, InputEntry>,
|
||||
)> {
|
||||
let mut reader =
|
||||
File::open(path).with_context(|| format!("Failed to open OTA for reading: {path:?}"))?;
|
||||
|
||||
let (metadata, _, _, _) = ota::parse_zip_ota_info(&mut reader)
|
||||
.with_context(|| format!("Failed to parse OTA metadata: {path:?}"))?;
|
||||
|
||||
let mut buffer = vec![0u8; RECOMMENDED_BUFFER_SIZE];
|
||||
let zip_reader =
|
||||
ZipArchive::from_read_at(reader, &mut buffer).context("Failed to read OTA zip")?;
|
||||
|
||||
let mut entries = zip_reader.entries_safe(&mut buffer);
|
||||
let mut input_entries = BTreeMap::new();
|
||||
|
||||
while let Some((cd_entry, _)) = entries.next_entry().context("Failed to list zip entries")? {
|
||||
if cd_entry.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = cd_entry
|
||||
.file_path_utf8()
|
||||
.context("Zip contains non-UTF-8 paths")?;
|
||||
|
||||
if is_excluded_path(path) {
|
||||
// OTA metadata is never copied from the input file.
|
||||
continue;
|
||||
}
|
||||
|
||||
input_entries.insert(
|
||||
path.to_owned(),
|
||||
InputEntry {
|
||||
compression_method: cd_entry.compression_method(),
|
||||
// We only check for the sizes here instead of the presence of
|
||||
// the ZIP64 extra field. The central header's extra fields may
|
||||
// have ZIP64 only for the local header offset.
|
||||
is_zip64: cd_entry.compressed_size_hint() >= 0xffffffff
|
||||
|| cd_entry.uncompressed_size_hint() >= 0xffffffff,
|
||||
wayfinder: cd_entry.wayfinder(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok((zip_reader, metadata, input_entries))
|
||||
}
|
||||
|
||||
fn open_writer(path: &Path, zip_mode: ZipMode) -> Result<ZipArchiveWriter<SigningWriter<File>>> {
|
||||
let writer = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)
|
||||
.with_context(|| format!("Failed to open OTA for writing: {path:?}"))?;
|
||||
|
||||
let signing_writer = match zip_mode {
|
||||
ZipMode::Streaming => SigningWriter::new_streaming(writer),
|
||||
ZipMode::Seekable => SigningWriter::new_seekable(writer),
|
||||
};
|
||||
let zip_writer = ZipArchiveWriter::new(signing_writer);
|
||||
|
||||
Ok(zip_writer)
|
||||
}
|
||||
|
||||
fn read_info(path: &Path) -> Result<OtaInfo> {
|
||||
let data = fs::read_to_string(path)
|
||||
.with_context(|| format!("Failed to read OTA info TOML: {path:?}"))?;
|
||||
let info = toml::de::from_str(&data)
|
||||
.with_context(|| format!("Failed to parse OTA info TOML: {path:?}"))?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn write_info(path: &Path, info: &OtaInfo) -> Result<()> {
|
||||
let data = toml::ser::to_string_pretty(info)
|
||||
.with_context(|| format!("Failed to serialize OTA info TOML: {path:?}"))?;
|
||||
fs::write(path, data).with_context(|| format!("Failed to write OTA info TOML: {path:?}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_info(quiet: bool, info: &OtaInfo) {
|
||||
if !quiet {
|
||||
println!("{info:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn load_key(group: &KeyGroup) -> Result<(RsaSigningKey, Certificate)> {
|
||||
let source = PassphraseSource::new(
|
||||
&group.key,
|
||||
group.pass_file.as_deref(),
|
||||
group.pass_env_var.as_deref(),
|
||||
);
|
||||
let signing_key = if let Some(helper) = &group.signing_helper {
|
||||
let public_key = crypto::read_pem_public_key_file(&group.key)
|
||||
.with_context(|| format!("Failed to load key: {:?}", group.key))?;
|
||||
|
||||
RsaSigningKey::External {
|
||||
program: helper.clone(),
|
||||
public_key_file: group.key.clone(),
|
||||
public_key,
|
||||
passphrase_source: source,
|
||||
}
|
||||
} else {
|
||||
let private_key = crypto::read_pem_key_file(&group.key, &source)
|
||||
.with_context(|| format!("Failed to load key: {:?}", group.key))?;
|
||||
|
||||
RsaSigningKey::Internal(private_key)
|
||||
};
|
||||
|
||||
let cert = crypto::read_pem_cert_file(&group.cert)
|
||||
.with_context(|| format!("Failed to load certificate: {:?}", group.cert))?;
|
||||
|
||||
Ok((signing_key, cert))
|
||||
}
|
||||
|
||||
fn start_entry<'a>(
|
||||
zip_writer: &'a mut ZipArchiveWriter<SigningWriter<File>>,
|
||||
path: &str,
|
||||
zip_mode: ZipMode,
|
||||
is_zip64: bool,
|
||||
) -> Result<(u64, ZipDataWriter<ZipEntryWriter<'a, SigningWriter<File>>>)> {
|
||||
let mut builder = zip_writer
|
||||
.new_file(path)
|
||||
.compression_method(CompressionMethod::Store);
|
||||
|
||||
if zip_mode == ZipMode::Seekable && is_zip64 {
|
||||
// We need to reserve space for the ZIP64 extra field when doing the
|
||||
// post-processing to convert a streaming zip to a seekable one.
|
||||
builder = builder.extra_field(
|
||||
ExtraFieldId::ANDROID_ZIP_ALIGNMENT,
|
||||
&[0u8; 16],
|
||||
rawzip::Header::LOCAL,
|
||||
)?;
|
||||
}
|
||||
|
||||
let (entry_writer, data_config) = builder
|
||||
.start()
|
||||
.with_context(|| format!("Failed to begin new zip entry: {path}"))?;
|
||||
let offset = entry_writer.stream_offset();
|
||||
let data_writer = data_config.wrap(entry_writer);
|
||||
|
||||
Ok((offset, data_writer))
|
||||
}
|
||||
|
||||
fn finalize_entry(
|
||||
path: &str,
|
||||
offset: u64,
|
||||
data_writer: ZipDataWriter<ZipEntryWriter<SigningWriter<File>>>,
|
||||
metadata_entries: &mut Vec<ZipEntry>,
|
||||
) -> Result<()> {
|
||||
let size = data_writer
|
||||
.finish()
|
||||
.and_then(|(w, d)| w.finish(d))
|
||||
.with_context(|| format!("Failed to finalize zip entry: {path}"))?;
|
||||
|
||||
metadata_entries.push(ZipEntry {
|
||||
path: path.to_owned(),
|
||||
offset,
|
||||
size,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_otacert_entry(
|
||||
zip_writer: &mut ZipArchiveWriter<SigningWriter<File>>,
|
||||
zip_mode: ZipMode,
|
||||
cert: &Certificate,
|
||||
metadata_entries: &mut Vec<ZipEntry>,
|
||||
) -> Result<()> {
|
||||
let (offset, mut data_writer) = start_entry(zip_writer, ota::PATH_OTACERT, zip_mode, false)?;
|
||||
|
||||
crypto::write_pem_cert(Path::new(ota::PATH_OTACERT), &mut data_writer, cert)
|
||||
.with_context(|| format!("Failed to write entry: {}", ota::PATH_OTACERT))?;
|
||||
|
||||
finalize_entry(ota::PATH_OTACERT, offset, data_writer, metadata_entries)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn finalize_ota(
|
||||
key: &RsaSigningKey,
|
||||
cert: &Certificate,
|
||||
mut zip_writer: ZipArchiveWriter<SigningWriter<File>>,
|
||||
zip_mode: ZipMode,
|
||||
metadata_entries: &mut Vec<ZipEntry>,
|
||||
payload_metadata_size: Option<u64>,
|
||||
metadata: &mut OtaMetadata,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<File> {
|
||||
add_otacert_entry(&mut zip_writer, zip_mode, cert, metadata_entries)?;
|
||||
|
||||
let payload_metadata_size =
|
||||
payload_metadata_size.ok_or_else(|| anyhow!("Missing payload metadata size"))?;
|
||||
let metadata_offset = zip_writer.stream_offset();
|
||||
|
||||
*metadata = ota::add_metadata(
|
||||
metadata_entries,
|
||||
&mut zip_writer,
|
||||
// Offset where next entry would begin.
|
||||
metadata_offset,
|
||||
metadata,
|
||||
payload_metadata_size,
|
||||
)
|
||||
.context("Failed to write new OTA metadata")?;
|
||||
|
||||
let signing_writer = zip_writer
|
||||
.finish()
|
||||
.context("Failed to finalize output zip")?;
|
||||
let mut raw_writer = signing_writer
|
||||
.finish(key, cert, cancel_signal)
|
||||
.context("Failed to sign output zip")?;
|
||||
|
||||
raw_writer.rewind().context("Failed to seek output zip")?;
|
||||
ota::verify_metadata(
|
||||
BufReader::new(&mut raw_writer),
|
||||
metadata,
|
||||
payload_metadata_size,
|
||||
)
|
||||
.context("Failed to verify OTA metadata offsets")?;
|
||||
|
||||
Ok(raw_writer)
|
||||
}
|
||||
|
||||
fn unpack_subcommand(zip_cli: &ZipCli, cli: &UnpackCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let (zip_reader, metadata, input_entries) = open_reader(&cli.input)?;
|
||||
|
||||
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}"))?;
|
||||
|
||||
let output_path = util::path_join(&cli.output_files, path)?;
|
||||
let output_parent = output_path.parent().expect("No parent path");
|
||||
|
||||
fs::create_dir_all(output_parent)
|
||||
.with_context(|| format!("Failed to create directory: {output_parent:?}"))?;
|
||||
|
||||
let mut output_file = File::create(&output_path)
|
||||
.with_context(|| format!("Failed to open for writing: {output_path:?}"))?;
|
||||
|
||||
stream::copy(&mut entry_reader, &mut output_file, cancel_signal)
|
||||
.with_context(|| format!("Failed to extract zip entry: {path}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
let info = OtaInfo {
|
||||
metadata,
|
||||
files: input_entries.into_keys().collect(),
|
||||
};
|
||||
|
||||
display_info(zip_cli.quiet, &info);
|
||||
write_info(&cli.output_info, &info)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pack_subcommand(zip_cli: &ZipCli, cli: &PackCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let (signing_key, cert) = load_key(&cli.key)?;
|
||||
let mut info = read_info(&cli.input_info)?;
|
||||
let mut zip_writer = open_writer(&cli.output, cli.zip_mode.zip_mode)?;
|
||||
|
||||
let mut input_entries = BTreeMap::new();
|
||||
|
||||
for path in &info.files {
|
||||
if is_excluded_path(path) {
|
||||
warn!("Ignoring input file: {path}");
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
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,
|
||||
)?;
|
||||
|
||||
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"))?;
|
||||
|
||||
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:?}"))?;
|
||||
|
||||
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}"))?;
|
||||
}
|
||||
|
||||
finalize_entry(path, offset, data_writer, &mut metadata_entries)?;
|
||||
}
|
||||
|
||||
finalize_ota(
|
||||
&signing_key,
|
||||
&cert,
|
||||
zip_writer,
|
||||
cli.zip_mode.zip_mode,
|
||||
&mut metadata_entries,
|
||||
payload_metadata_size,
|
||||
&mut info.metadata,
|
||||
cancel_signal,
|
||||
)?;
|
||||
|
||||
drop(input_entries);
|
||||
info.files.retain(|p| !is_excluded_path(p));
|
||||
info.files.sort();
|
||||
|
||||
display_info(zip_cli.quiet, &info);
|
||||
|
||||
if let Some(path) = &cli.output_info {
|
||||
write_info(path, &info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn repack_subcommand(zip_cli: &ZipCli, cli: &RepackCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let (signing_key, cert) = load_key(&cli.key)?;
|
||||
let (zip_reader, mut metadata, input_entries) = open_reader(&cli.input)?;
|
||||
let mut zip_writer = open_writer(&cli.output, cli.zip_mode.zip_mode)?;
|
||||
|
||||
let mut payload_metadata_size = None;
|
||||
let mut metadata_entries = vec![];
|
||||
|
||||
for (path, input_entry) in &input_entries {
|
||||
let (offset, mut data_writer) = start_entry(
|
||||
&mut zip_writer,
|
||||
path,
|
||||
cli.zip_mode.zip_mode,
|
||||
input_entry.is_zip64,
|
||||
)?;
|
||||
|
||||
let entry = zip_reader
|
||||
.get_entry(input_entry.wayfinder)
|
||||
.with_context(|| format!("Failed to open zip entry: {path}"))?;
|
||||
|
||||
if path == ota::PATH_PAYLOAD {
|
||||
let entry_reader = zip::verifying_reader(&entry, input_entry.compression_method)
|
||||
.with_context(|| format!("Failed to open zip entry: {path}"))?;
|
||||
|
||||
let header = PayloadHeader::from_reader(entry_reader)
|
||||
.with_context(|| format!("Failed to read payload header: {path:?}"))?;
|
||||
|
||||
payload_metadata_size = Some(header.blob_offset);
|
||||
}
|
||||
|
||||
let mut entry_reader = zip::verifying_reader(&entry, input_entry.compression_method)
|
||||
.with_context(|| format!("Failed to open zip entry: {path}"))?;
|
||||
|
||||
stream::copy(&mut entry_reader, &mut data_writer, cancel_signal)
|
||||
.with_context(|| format!("Failed to copy zip entry: {path}"))?;
|
||||
|
||||
finalize_entry(path, offset, data_writer, &mut metadata_entries)?;
|
||||
}
|
||||
|
||||
finalize_ota(
|
||||
&signing_key,
|
||||
&cert,
|
||||
zip_writer,
|
||||
cli.zip_mode.zip_mode,
|
||||
&mut metadata_entries,
|
||||
payload_metadata_size,
|
||||
&mut metadata,
|
||||
cancel_signal,
|
||||
)?;
|
||||
|
||||
let info = OtaInfo {
|
||||
metadata,
|
||||
files: input_entries.into_keys().collect(),
|
||||
};
|
||||
|
||||
display_info(zip_cli.quiet, &info);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn info_subcommand(zip_cli: &ZipCli, cli: &InfoCli) -> Result<()> {
|
||||
let (_, metadata, input_entries) = open_reader(&cli.input)?;
|
||||
let info = OtaInfo {
|
||||
metadata,
|
||||
files: input_entries.into_keys().collect(),
|
||||
};
|
||||
|
||||
display_info(zip_cli.quiet, &info);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn zip_main(cli: &ZipCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
match &cli.command {
|
||||
ZipCommand::Unpack(c) => unpack_subcommand(cli, c, cancel_signal),
|
||||
ZipCommand::Pack(c) => pack_subcommand(cli, c, cancel_signal),
|
||||
ZipCommand::Repack(c) => repack_subcommand(cli, c, cancel_signal),
|
||||
ZipCommand::Info(c) => info_subcommand(cli, c),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct KeyGroup {
|
||||
/// Path to signing key.
|
||||
///
|
||||
/// This should normally be a private key. However, if --signing-helper is
|
||||
/// used, then it should be a public key instead.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
key: PathBuf,
|
||||
|
||||
/// Certificate for signing key.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
cert: PathBuf,
|
||||
|
||||
/// Environment variable containing private key passphrase.
|
||||
#[arg(long, value_name = "ENV_VAR", value_parser, group = "pass")]
|
||||
pass_env_var: Option<OsString>,
|
||||
|
||||
/// File containing private key passphrase.
|
||||
#[arg(long, value_name = "FILE", value_parser, group = "pass")]
|
||||
pass_file: Option<PathBuf>,
|
||||
|
||||
/// External program for signing.
|
||||
///
|
||||
/// If this option is specified, then --key must refer to a public key. The
|
||||
/// program will be invoked as:
|
||||
///
|
||||
/// <program> <algo> <public key> [file <pass file>|env <pass env>]
|
||||
#[arg(long, value_name = "PROGRAM", value_parser)]
|
||||
signing_helper: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct ZipModeGroup {
|
||||
/// Zip creation mode for the output OTA zip.
|
||||
///
|
||||
/// The streaming mode produces zip files that contain data descriptors.
|
||||
/// The zip file is hashed as it is being written. This mode is the default
|
||||
/// and works with the vast majority of devices.
|
||||
///
|
||||
/// The seekable mode produces zip files that do not use data descriptors.
|
||||
/// The zip file is reread and hashed after it has been fully written. The
|
||||
/// output file is more likely to be compatible with devices that have
|
||||
/// broken zip file parsers.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "MODE",
|
||||
default_value_t = ZipMode::Streaming,
|
||||
)]
|
||||
zip_mode: ZipMode,
|
||||
}
|
||||
|
||||
/// Unpack an OTA zip.
|
||||
#[derive(Debug, Parser)]
|
||||
struct UnpackCli {
|
||||
/// Path to input OTA zip.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
input: PathBuf,
|
||||
|
||||
/// Path to output info TOML.
|
||||
#[arg(long, value_name = "FILE", value_parser, default_value = "ota.toml")]
|
||||
output_info: PathBuf,
|
||||
|
||||
/// Path to output files directory.
|
||||
#[arg(long, value_name = "DIR", value_parser, default_value = "ota_files")]
|
||||
output_files: PathBuf,
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// The new OTA zip will *only* contain files listed in the info TOML file.
|
||||
/// Extra files in the input files directory that aren't listed will be silently
|
||||
/// ignored.
|
||||
///
|
||||
/// WARNING: This subcommand is for raw file manipulation and does not perform
|
||||
/// any validation of the contents of the payload binary. For normal usage, use
|
||||
/// `avbroot ota patch` instead.
|
||||
#[derive(Debug, Parser)]
|
||||
struct PackCli {
|
||||
/// Path to output OTA zip.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
output: PathBuf,
|
||||
|
||||
/// Path to input info TOML.
|
||||
#[arg(long, value_name = "FILE", value_parser, default_value = "ota.toml")]
|
||||
input_info: PathBuf,
|
||||
|
||||
/// Path to output info TOML.
|
||||
///
|
||||
/// If specified, the OTA info containing all recomputed fields will be
|
||||
/// written to this file. This can point to the same file as --input-info.
|
||||
#[arg(long, value_name = "FILE", value_parser)]
|
||||
output_info: Option<PathBuf>,
|
||||
|
||||
/// Path to input files directory.
|
||||
#[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,
|
||||
|
||||
#[command(flatten)]
|
||||
zip_mode: ZipModeGroup,
|
||||
}
|
||||
|
||||
/// Repack an OTA zip.
|
||||
///
|
||||
/// This command is equivalent to running `unpack` and `pack`, except without
|
||||
/// storing the unpacked data to disk first.
|
||||
#[derive(Debug, Parser)]
|
||||
struct RepackCli {
|
||||
/// Path to input OTA zip.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
input: PathBuf,
|
||||
|
||||
/// Path to output OTA zip.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
output: PathBuf,
|
||||
|
||||
#[command(flatten)]
|
||||
key: KeyGroup,
|
||||
|
||||
#[command(flatten)]
|
||||
zip_mode: ZipModeGroup,
|
||||
}
|
||||
|
||||
/// Display OTA zip information.
|
||||
#[derive(Debug, Parser)]
|
||||
struct InfoCli {
|
||||
/// Path to input OTA zip.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
input: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum ZipCommand {
|
||||
Unpack(UnpackCli),
|
||||
Pack(PackCli),
|
||||
Repack(RepackCli),
|
||||
Info(InfoCli),
|
||||
}
|
||||
|
||||
/// Pack, unpack, and inspect OTA zips.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ZipCli {
|
||||
#[command(subcommand)]
|
||||
command: ZipCommand,
|
||||
|
||||
/// Don't print OTA metadata information.
|
||||
#[arg(short, long, global = true)]
|
||||
quiet: bool,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2026 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(clippy::nursery)]
|
||||
#![allow(clippy::pedantic)]
|
||||
@@ -13,3 +16,82 @@ pub mod build {
|
||||
pub mod chromeos_update_engine {
|
||||
include!(concat!(env!("OUT_DIR"), "/chromeos_update_engine.rs"));
|
||||
}
|
||||
|
||||
/// Implement [`serde::Serialize`] and [`serde::Deserialize`] for prost enum
|
||||
/// type and create a bridge to use with `#[serde(with = "...")]` since enum
|
||||
/// fields are stored as their underlying repr. `$type` must be a fully
|
||||
/// qualified type.
|
||||
macro_rules! serde_derive_prost_enum {
|
||||
($type:ty, $module:ident $(,)?) => {
|
||||
impl serde::Serialize for $type {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.as_str_name().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for $type {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct EnumVisitor;
|
||||
|
||||
impl serde::de::Visitor<'_> for EnumVisitor {
|
||||
type Value = $type;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "a string containing a valid enum variant")
|
||||
}
|
||||
|
||||
fn visit_str<R>(self, v: &str) -> Result<Self::Value, R>
|
||||
where
|
||||
R: serde::de::Error,
|
||||
{
|
||||
match <$type>::from_str_name(v) {
|
||||
Some(e) => Ok(e),
|
||||
None => Err(serde::de::Error::custom(format!(
|
||||
"invalid enum variant: {v}",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(EnumVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
mod $module {
|
||||
pub fn serialize<S, T>(data: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
T: Copy + std::fmt::Display,
|
||||
$type: TryFrom<T>,
|
||||
<$type as TryFrom<T>>::Error: std::fmt::Display,
|
||||
{
|
||||
let value = <$type>::try_from(*data).map_err(|e| {
|
||||
serde::ser::Error::custom(format!("invalid enum value: {data}: {e}"))
|
||||
})?;
|
||||
|
||||
serde::Serialize::serialize(&value, serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
T: From<$type>,
|
||||
{
|
||||
let value: $type = serde::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
Ok(value.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
serde_derive_prost_enum!(
|
||||
crate::protobuf::build::tools::releasetools::ota_metadata::OtaType,
|
||||
ota_type,
|
||||
);
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ ring = "0.17.14"
|
||||
rsa = { version = "0.9.6", features = ["hazmat"] }
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
tempfile = "3.8.0"
|
||||
toml = { version = "0.9.11", features = ["serde"] }
|
||||
toml = { version = "1.0.1", features = ["serde"] }
|
||||
topological-sort = "0.2.2"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
|
||||
+2
-2
@@ -53,11 +53,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
|
||||
|
||||
[profile.pixel_v4_gki.hashes_streaming]
|
||||
original = "ea96196191e3a4133db4aff45d47aa3468514e29e0a724faac8081cbf4adf808"
|
||||
patched = "179923431ff94148186d8b0a457636c8a60057d047d55a4fc4da3a162e73eeed"
|
||||
patched = "c073cbb271099aaf338a8c70f3565ec70db47a87b4c9bb3efffd96ccee1a0c27"
|
||||
|
||||
[profile.pixel_v4_gki.hashes_seekable]
|
||||
original = "f6615ae355eba38689d24aa535981d09175d4832e7c65c04cdd89aa95d21f09d"
|
||||
patched = "b006858b78cddef0e3db1d77454cd945111ce15708121e3a483ff711abedec8b"
|
||||
patched = "65ce91a95574a308ffc5279696efe7f6f7e4e847795dc9ae43d9ffd012a7ce3f"
|
||||
|
||||
# Google Pixel 6a
|
||||
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
|
||||
|
||||
+9
-7
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023 Pascal Roeleven
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
@@ -68,11 +68,11 @@ use crate::{
|
||||
fn hash_file(path: &Path, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
|
||||
info!("Calculating hash: {path:?}");
|
||||
|
||||
let raw_reader =
|
||||
File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
let buf_reader = BufReader::new(raw_reader);
|
||||
let raw_reader = File::open(path)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
let context = ring::digest::Context::new(&ring::digest::SHA256);
|
||||
let mut hashing_reader = HashingReader::new(buf_reader, context);
|
||||
let mut hashing_reader = HashingReader::new(raw_reader, context);
|
||||
|
||||
stream::copy(&mut hashing_reader, io::sink(), cancel_signal)?;
|
||||
|
||||
@@ -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)]
|
||||
@@ -1067,6 +1067,8 @@ fn patch_image(
|
||||
OsStr::new("--replace"),
|
||||
OsStr::new("system"),
|
||||
system_image_file.as_os_str(),
|
||||
OsStr::new("--re-sign"),
|
||||
OsStr::new("boot"),
|
||||
OsStr::new("--key-avb"),
|
||||
avb_key_file.as_os_str(),
|
||||
OsStr::new("--pass-avb-file"),
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ rust-version.workspace = true
|
||||
anyhow = "1.0.75"
|
||||
clap = { version = "4.4.1", features = ["derive"] }
|
||||
regex = { version = "1.9.4", default-features = false, features = ["perf", "std"] }
|
||||
toml_edit = "0.24.0"
|
||||
toml_edit = "0.25.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user