From ddb8911efd76b0c052d7dbd70a3dc86105273777 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Sun, 1 Oct 2023 16:16:04 -0400 Subject: [PATCH] cpio: Only reassign inodes when missing This way, archives with hard links can pass through `cpio unpack` and `cpio pack`, even though there's no explicit support for hard links. This also changes the trailer entry logic to not set an inode number. AOSP's mkbootfs and magiskboot both start at 300000 and increment by one for each entry, including the trailer. However, GNU cpio, bsdcpio, busybox, and toybox all set it to 0, which makes more sense given that it doesn't represent anything on disk. Signed-off-by: Andrew Gunnerson --- avbroot/src/boot.rs | 2 +- avbroot/src/cli/cpio.rs | 12 +++--- avbroot/src/format/cpio.rs | 64 ++++++++++++++++++++++++++++---- avbroot/tests/data/archive.cpio | Bin 616 -> 616 bytes e2e/e2e.toml | 46 +++++++++++------------ 5 files changed, 85 insertions(+), 39 deletions(-) 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 1d90a25e1d5d71e4aa252c53a69cb880db47f77d..e6c0a6c6a2cc13b5407729d1cf8180690552ecae 100644 GIT binary patch delta 17 ZcmaFC@`7ckF?4ggD{2V4LE delta 22 ecmaFC@`7c