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 <accounts+github@chiller3.com>
This commit is contained in:
Andrew Gunnerson
2023-10-01 16:16:04 -04:00
parent 72be84b2fc
commit ddb8911efd
5 changed files with 85 additions and 39 deletions
+1 -1
View File
@@ -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 {
+5 -7
View File
@@ -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.
+56 -8
View File
@@ -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<u8>),
#[error("Entry of type {0} should not have data: {:?}", .1.as_bstr())]
EntryHasData(CpioEntryType, Vec<u8>),
#[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<W: Write> CpioWriter<W> {
pub fn finish(mut self) -> Result<W> {
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>, 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(
Binary file not shown.
+23 -23
View File
@@ -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"