Skip to main content

initramfs_builder/initramfs/
cpio.rs

1use anyhow::{Context, Result};
2use std::fs::{self};
3use std::io::Write;
4use std::os::unix::fs::{MetadataExt, PermissionsExt};
5use std::path::Path;
6use tracing::debug;
7use walkdir::WalkDir;
8
9pub struct CpioArchive {
10    entries: Vec<CpioEntry>,
11}
12
13struct CpioEntry {
14    path: String,
15    mode: u32,
16    uid: u32,
17    gid: u32,
18    nlink: u32,
19    mtime: u32,
20    data: Vec<u8>,
21    dev_major: u32,
22    dev_minor: u32,
23    rdev_major: u32,
24    rdev_minor: u32,
25}
26
27impl CpioArchive {
28    pub fn new() -> Self {
29        Self {
30            entries: Vec::new(),
31        }
32    }
33
34    /// Build a CPIO archive from a directory
35    pub fn from_directory(root: &Path) -> Result<Self> {
36        let mut archive = Self::new();
37
38        for entry in WalkDir::new(root).follow_links(false) {
39            let entry = entry?;
40            let full_path = entry.path();
41
42            let rel_path = full_path.strip_prefix(root).unwrap_or(full_path);
43
44            if rel_path.as_os_str().is_empty() {
45                continue;
46            }
47
48            let archive_path = format!("{}", rel_path.display());
49
50            archive.add_path(full_path, &archive_path)?;
51        }
52
53        Ok(archive)
54    }
55
56    /// Add a file or directory to the archive
57    fn add_path(&mut self, source_path: &Path, archive_path: &str) -> Result<()> {
58        let metadata = fs::symlink_metadata(source_path)
59            .with_context(|| format!("Failed to read metadata for {:?}", source_path))?;
60
61        let file_type = metadata.file_type();
62        let mode = metadata.permissions().mode();
63
64        let data = if file_type.is_file() {
65            fs::read(source_path)?
66        } else if file_type.is_symlink() {
67            let target = fs::read_link(source_path)?;
68            target.to_string_lossy().as_bytes().to_vec()
69        } else {
70            Vec::new()
71        };
72
73        debug!(
74            "Adding to cpio: {} (mode: {:o}, size: {})",
75            archive_path,
76            mode,
77            data.len()
78        );
79
80        self.entries.push(CpioEntry {
81            path: archive_path.to_string(),
82            mode,
83            uid: metadata.uid(),
84            gid: metadata.gid(),
85            nlink: metadata.nlink() as u32,
86            mtime: metadata.mtime() as u32,
87            data,
88            dev_major: 0,
89            dev_minor: 0,
90            rdev_major: 0,
91            rdev_minor: 0,
92        });
93
94        Ok(())
95    }
96
97    /// Write the archive to a file
98    pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
99        let mut ino = 1u32;
100
101        for entry in &self.entries {
102            self.write_entry(writer, entry, ino)?;
103            ino += 1;
104        }
105
106        // Write trailer
107        self.write_trailer(writer)?;
108
109        Ok(())
110    }
111
112    /// Write a single entry in newc format
113    fn write_entry<W: Write>(&self, writer: &mut W, entry: &CpioEntry, ino: u32) -> Result<()> {
114        let namesize = entry.path.len() + 1; // +1 for null terminator
115        let filesize = entry.data.len();
116
117        // newc header format (110 bytes of ASCII hex)
118        let header = format!(
119            "{}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}",
120            "070701",         // magic
121            ino,              // inode
122            entry.mode,       // mode
123            entry.uid,        // uid
124            entry.gid,        // gid
125            entry.nlink,      // nlink
126            entry.mtime,      // mtime
127            filesize,         // filesize
128            entry.dev_major,  // dev major
129            entry.dev_minor,  // dev minor
130            entry.rdev_major, // rdev major
131            entry.rdev_minor, // rdev minor
132            namesize,         // namesize
133            0u32,             // checksum (always 0 for newc)
134        );
135
136        writer.write_all(header.as_bytes())?;
137        writer.write_all(entry.path.as_bytes())?;
138        writer.write_all(&[0])?; // null terminator
139
140        // Pad to 4-byte boundary after header+name
141        let header_plus_name = 110 + namesize;
142        let padding = (4 - (header_plus_name % 4)) % 4;
143        writer.write_all(&vec![0u8; padding])?;
144
145        writer.write_all(&entry.data)?;
146
147        // Pad data to 4-byte boundary
148        let data_padding = (4 - (filesize % 4)) % 4;
149        writer.write_all(&vec![0u8; data_padding])?;
150
151        Ok(())
152    }
153
154    /// Write the TRAILER!!! entry
155    fn write_trailer<W: Write>(&self, writer: &mut W) -> Result<()> {
156        let trailer_name = "TRAILER!!!";
157        let namesize = trailer_name.len() + 1;
158
159        let header = format!(
160            "{}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}",
161            "070701", 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, namesize, 0
162        );
163
164        writer.write_all(header.as_bytes())?;
165        writer.write_all(trailer_name.as_bytes())?;
166        writer.write_all(&[0])?;
167
168        // Pad to 4-byte boundary
169        let header_plus_name = 110 + namesize;
170        let padding = (4 - (header_plus_name % 4)) % 4;
171        writer.write_all(&vec![0u8; padding])?;
172
173        Ok(())
174    }
175
176    /// Get the number of entries
177    pub fn len(&self) -> usize {
178        self.entries.len()
179    }
180
181    pub fn is_empty(&self) -> bool {
182        self.entries.is_empty()
183    }
184}
185
186impl Default for CpioArchive {
187    fn default() -> Self {
188        Self::new()
189    }
190}