diff --git a/avbroot/src/boot.rs b/avbroot/src/boot.rs index 900ab71..3b8d556 100644 --- a/avbroot/src/boot.rs +++ b/avbroot/src/boot.rs @@ -373,7 +373,7 @@ impl BootImagePatcher for MagiskRootPatcher { // Repack ramdisk. cpio::sort(&mut entries); - cpio::reassign_inodes(&mut entries); + cpio::assign_inodes(&mut entries, false)?; let new_ramdisk = save_ramdisk(&entries, ramdisk_format, cancel_signal)?; match boot_image { diff --git a/avbroot/src/cli/cpio.rs b/avbroot/src/cli/cpio.rs index 32a718c..1b65d28 100644 --- a/avbroot/src/cli/cpio.rs +++ b/avbroot/src/cli/cpio.rs @@ -202,7 +202,6 @@ fn unpack_subcommand( fn pack_subcommand(cpio_cli: &CpioCli, cli: &PackCli, cancel_signal: &AtomicBool) -> Result<()> { let mut info = read_info(&cli.input_info)?; let mut writer = open_writer(&cli.output, info.format)?; - let mut inode = 300000; display_format(cpio_cli, info.format); @@ -210,14 +209,13 @@ fn pack_subcommand(cpio_cli: &CpioCli, cli: &PackCli, cancel_signal: &AtomicBool cpio::sort(&mut info.entries); } + cpio::assign_inodes(&mut info.entries, true)?; + let authority = ambient_authority(); let tree = Dir::open_ambient_dir(&cli.input_tree, authority) .with_context(|| format!("Failed to open directory: {:?}", cli.input_tree))?; for entry in &mut info.entries { - entry.inode = inode; - inode += 1; - let out = open_tree_file(&tree, entry)?; if let Some((_, file_size)) = &out { @@ -321,9 +319,9 @@ struct UnpackCli { /// silently ignored. Entries are added to the archive in the order that they /// are listed unless --sort is specified. /// -/// All fields inside the info TOML are used as-is, except for the inode -/// numbers, which will be regenerated. If any fields for an entry are missing, -/// they will be set to 0. +/// All fields inside the info TOML are used as-is. Missing fields in entries +/// are set to 0, aside from the inode number, which will be assigned a unique +/// value. #[derive(Debug, Parser)] struct PackCli { /// Path to output cpio file. diff --git a/avbroot/src/format/cpio.rs b/avbroot/src/format/cpio.rs index f56bfe1..4d3b82e 100644 --- a/avbroot/src/format/cpio.rs +++ b/avbroot/src/format/cpio.rs @@ -4,6 +4,7 @@ */ use std::{ + collections::{HashMap, HashSet}, fmt, io::{self, Cursor, Read, Write}, ops::Range, @@ -53,6 +54,8 @@ pub enum Error { HardLinksNotSupported(Vec), #[error("Entry of type {0} should not have data: {:?}", .1.as_bstr())] EntryHasData(CpioEntryType, Vec), + #[error("No inodes available for device {0:x},{1:x}")] + DeviceFull(u32, u32), #[error("{0:?} field exceeds integer bounds")] IntegerTooLarge(&'static str), #[error("I/O error")] @@ -637,10 +640,7 @@ impl CpioWriter { pub fn finish(mut self) -> Result { self.finish_entry()?; - let mut trailer = CpioEntry::new_trailer(); - trailer.inode = self.max_inode.wrapping_add(1); - - self.start_entry(&trailer)?; + self.start_entry(&CpioEntry::new_trailer())?; // Pad until the end of the block. if self.pad_to_block_size { @@ -703,13 +703,61 @@ pub fn sort(entries: &mut [CpioEntry]) { entries.sort_by(|a, b| a.path.cmp(&b.path)); } -pub fn reassign_inodes(entries: &mut [CpioEntry]) { - let mut inode = 300000; +/// Assign inodes to entries. If `missing_only` is true, then inodes are only +/// assigned if the inode field in an entry is set to 0. +/// +/// New inodes are assigned starting immediately after the highest inode number +/// for a given ([`CpioEntry::dev_maj`], [`CpioEntry::dev_min`]) pair. If there +/// are no existing inodes assigned for a device, then the numbers begin at +/// 300000. +pub fn assign_inodes(entries: &mut [CpioEntry], missing_only: bool) -> Result<()> { + fn next_non_zero(i: u32) -> u32 { + if i == u32::MAX { + 1 + } else { + i.wrapping_add(1) + } + } + + // (dev maj, dev min) -> (inode set, last assigned inode) + let mut inodes: HashMap<(u32, u32), (HashSet, u32)> = HashMap::new(); + + if missing_only { + for entry in &mut *entries { + if entry.inode != 0 { + let key = (entry.dev_maj, entry.dev_min); + let (set, last) = inodes.entry(key).or_default(); + + set.insert(entry.inode); + *last = (*last).max(entry.inode); + } + } + } for entry in entries { - entry.inode = inode; - inode += 1; + if entry.inode == 0 { + let key = (entry.dev_maj, entry.dev_min); + let (set, last) = inodes + .entry(key) + .or_insert_with(|| (HashSet::new(), 299999)); + + let mut unused = next_non_zero(*last); + + while set.contains(&unused) { + if unused == *last { + return Err(Error::DeviceFull(entry.dev_maj, entry.dev_min)); + } + + unused = next_non_zero(unused); + } + + entry.inode = unused; + set.insert(unused); + *last = unused; + } } + + Ok(()) } pub fn save( diff --git a/avbroot/tests/data/archive.cpio b/avbroot/tests/data/archive.cpio index 1d90a25..e6c0a6c 100644 Binary files a/avbroot/tests/data/archive.cpio and b/avbroot/tests/data/archive.cpio differ diff --git a/e2e/e2e.toml b/e2e/e2e.toml index d568a89..c644601 100644 --- a/e2e/e2e.toml +++ b/e2e/e2e.toml @@ -15,13 +15,13 @@ sections = [ ] hash.original.full = "6b881553f012d582080642d660e1cf5c9e6fe41e9f1c6ab12ae87fab7894e307" hash.original.stripped = "9befd7887a125ebd8e9ae0555469dababe6bc04b0aa41aa2562036782a6d87e0" -hash.patched.full = "9a16823737ec3586977106dbddfce3dc64acfcff4d7bc275989c6b78bd5b096e" -hash.patched.stripped = "0d3e5610a0215c94183a6ff15e4a44ceef20d476b3c1d3399fb1498e615c4623" -hash.avb_images."init_boot.img" = "a0f057ef2c2085a8fff2f4df98753e6db7721eb5f06e4011acdb3d10b1f75f85" -hash.avb_images."vbmeta.img" = "488d6a2e9941de44abd4bdf74dec463d5d6fdbf879f6dd3445a58bb7872a412e" +hash.patched.full = "6a08ae5c08e42b29ffc476511b6c60de8b77ba3f06c469a0a640e84cb95b9db9" +hash.patched.stripped = "479b4e203d432148baaf4eaa69205c2291ec10e8541c9a78e6d9b799e994f1e3" +hash.avb_images."init_boot.img" = "3bedb41be98c46241f11219021dfbb799a6d5c89e6e00d45a66f9a5b42e7dfbc" +hash.avb_images."vbmeta.img" = "e8e6e898ca73807edb43af0a0e86d4a94b14256def89581970287a9b1bf7a3ee" hash.avb_images."vbmeta_system.img" = "dbb63e08f26f46ccda501d99058d513ff71e3d6302c14d587442b666ff08862a" hash.avb_images."vbmeta_vendor.img" = "6ffa0a10e72c3371653be80de1380832b4d7f8bbf38a2bd861d44a4097a57117" -hash.avb_images."vendor_boot.img" = "9662194df18e78549f3fb212e26926ecac8baecaa5a5e63af7ae12a9d56b87fd" +hash.avb_images."vendor_boot.img" = "8f9407435e1ea532e55be418d58e1b3a4f8e0794e3cf4e562e0a0414058c6108" # Google Pixel 6a # What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks) @@ -36,13 +36,13 @@ sections = [ ] hash.original.full = "1f1f0abe67a6f6f47287be6dafec2c12628de6a715b82ca7beddaf67ad22aca5" hash.original.stripped = "38b15f5efdc7e056bc799859ba72ef9a73e93c61292c59f85fb4b9c31acc5f82" -hash.patched.full = "5904f5eaeccd8bb6bb62919eeedef9bacbafd0261d88d2f86844cce5aac14da0" -hash.patched.stripped = "e3facf3f17fb35733f8e53611926f436880fa85d62a1655da8cee1aa6a672da4" -hash.avb_images."boot.img" = "2ecab6d2c261dd52f55b9d275429343472be6bdc360efe14ef3336a9fdce3778" -hash.avb_images."vbmeta.img" = "0ad8baf243194e8cd1b52a498a3b4c8c0c5e2bb5a399af616c171406eac2c5b6" +hash.patched.full = "20f3eb522c45f8185c8c121ef2f6a18214345b85f3021d05841e1b002d348b3e" +hash.patched.stripped = "105f4305f886a79c03fcabd296a44ef498e2b9e602970c0ef6d8599b74a704b0" +hash.avb_images."boot.img" = "a19cb4d4fcc7f3e7d3046c3d19e2f243fb02513ca848ff92ad70d1ada55c4e65" +hash.avb_images."vbmeta.img" = "2d817e35f7b6cdc2edce58ef249a966fc677085b70a4aced4c829320aeef0be2" hash.avb_images."vbmeta_system.img" = "98a050f0d53a016fbb78147b1b4a9bca3fde615aa4da34bf62c2e07a395104b5" hash.avb_images."vbmeta_vendor.img" = "fac530f47f237e76f3c7c3cdfe96308170dd8e8f0b227d81114a489c69ba763c" -hash.avb_images."vendor_boot.img" = "f4691800d32dc12eb1e2a9ace9d1e036b844043953ab56b986ce9c04054d3656" +hash.avb_images."vendor_boot.img" = "6b66d8ea772dafc85e8745e88c9e05ff0401d062010c4d1ae0914b93cf3510aa" # Google Pixel 4a 5G # What's unique: boot (boot v3) + vendor_boot (vendor v3) @@ -57,12 +57,12 @@ sections = [ ] hash.original.full = "6d107ffac1cd3da2c972112acc75957ed725e5c13d57ca724d9bcca5404fcebd" hash.original.stripped = "5b889bdab3bb12ddcd3c243a56e1c58bedada8831069f49d56fe5098fb141e35" -hash.patched.full = "d101bb5376eea859b69ea78cb4162753b69cff81c14b4f244021f27ec0ce58d0" -hash.patched.stripped = "04c4d8f2e5dc744fd5a8295a00349e661260ccd2dd0750201dedc1e2294b7962" -hash.avb_images."boot.img" = "e6bd56ad12013ed2e4cf26451d5e769c40bab05feee39f9a741d15ecf272cfbe" -hash.avb_images."vbmeta.img" = "f5079baa3aec734c3f8b346958a3fce9e7641e3ea2f1f9bbf13031b81d2a00a6" +hash.patched.full = "c2866d8959fea55884de6281403e7daf435cded62477181354eb76332446af28" +hash.patched.stripped = "a9ccefe44818560144f073bef8ae1c2c840cf8fff9dafb0e589bd5fd40cf0fea" +hash.avb_images."boot.img" = "8e7278a2e8ae44ffc5475717eb0e1aa56bfb7650aef34375b0fe92f790835f95" +hash.avb_images."vbmeta.img" = "b036132b867f52a86eef79716261f35b1eb50e843dc4fe42f72cce67b24ae2db" hash.avb_images."vbmeta_system.img" = "9a7c6fd654e7a92aeffbdbd55ea0d87eee36f4c235e1b505423ad8a13a751a00" -hash.avb_images."vendor_boot.img" = "3cc7f110e69d3b32e33ecaac654f4189807d6cb8ad479b05054cec5c0372fafe" +hash.avb_images."vendor_boot.img" = "e774aa770fd9c0d19a509f212309f20b08da411f8830a7383646a65571437933" # Google Pixel 4a # What's unique: boot (boot v2) @@ -76,10 +76,10 @@ sections = [ ] hash.original.full = "01fd34b206152a3559039161c9874ab03df37da4268b86a9e0be899de5fc0af7" hash.original.stripped = "cc311b5bd46e06cfdefbade794d33aa9bc3ceda4ad4f38bfe9f0dfc17033d207" -hash.patched.full = "e06bef2fbf5d58d058d4b181a7f54cba6bc19b66986eb4c11519381f34b37622" -hash.patched.stripped = "709d2de4463df439349b85567bcda4a440685f5b82472ad098dbbcbdfefd05c6" -hash.avb_images."boot.img" = "ac504c793b095a66d79638295f2a6458c3ea775328238c8bb363a913d62d412a" -hash.avb_images."vbmeta.img" = "52a0b55a46ece965e58cbea199baa888f42d0789f3ddb24e740487d606732870" +hash.patched.full = "b788d2d86008a3923734a40fb89ca98533116e51e10f35c0447beb956b236bf5" +hash.patched.stripped = "b174f530d63bfe753ab9435a910b01a1abb2e08a8628b50101f876ed2313e536" +hash.avb_images."boot.img" = "7cdb8e4a9b79d9c2b4d5a513c219082c0609cf6cab8cf07e122dcaaa2bd1ed63" +hash.avb_images."vbmeta.img" = "3679c7224e3e3e0793b4d1a031e116098460a6b4c5f3f88d5a3a0a4c65b21582" hash.avb_images."vbmeta_system.img" = "1d3efa00fd1d44a594c7317072468fa95c23d83d2759d6d6e757783ceeabc594" # OnePlus 10 Pro @@ -107,10 +107,10 @@ sections = [ ] hash.original.full = "929f892fbd70699cf7f118a119aac1ae1b86351e1ada17715666fa4401e63472" hash.original.stripped = "4eabaf79b6c2b5df305e3ecdc2b9570c0dd27350b4e8d6434584000c4989ff3d" -hash.patched.full = "ca06e7d2905c421935e177d82535cee8e346facfa060b9dd5394387a758f8fdb" -hash.patched.stripped = "a1e2af8c50682b7cc02bd7ecc9b303cfc2f7dc540d5b5d43380075d13f54de0a" -hash.avb_images."boot.img" = "5bdf0f13343e52711eee493aeada0281c17a3b5204808228a564aa45c3a1871d" -hash.avb_images."recovery.img" = "a42c0bf4f023cd24394184a33ee113783a9c89a7cc4c0c582a5f72cc23b72309" +hash.patched.full = "ec576b7430e5a2788e89f8bbe37ee81b116386d9b101865d3b75eb9c51a0db4c" +hash.patched.stripped = "ddd0cf1b42564f8c2980320c14f8e0eb125412eb7a13ae8fb52d35424546c235" +hash.avb_images."boot.img" = "480d7cf519326fcaa5106ecfbbeb907309068635f7f6a25e5e4571d525c4926a" +hash.avb_images."recovery.img" = "eeb0f67e2084174fced510f4b59a82022559ffbe1c20d2c0ea4757fcd989a3af" hash.avb_images."vbmeta.img" = "c022cf79da301a8430af5c49704944c490707fa0306031fe3ea22c39ce4734f6" hash.avb_images."vbmeta_system.img" = "749616b7f04487c05e9e363ad2071a0ab3bae29d497daf1f1a7695f7c8cfa82a" hash.avb_images."vbmeta_vendor.img" = "a6037fce745384425fb12745b8568386b84fb57ca6f94f6e47bcf754de341ae4"