initramfs_builder/initramfs/
cpio.rs1use 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 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 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 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 self.write_trailer(writer)?;
108
109 Ok(())
110 }
111
112 fn write_entry<W: Write>(&self, writer: &mut W, entry: &CpioEntry, ino: u32) -> Result<()> {
114 let namesize = entry.path.len() + 1; let filesize = entry.data.len();
116
117 let header = format!(
119 "{}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}{:08X}",
120 "070701", ino, entry.mode, entry.uid, entry.gid, entry.nlink, entry.mtime, filesize, entry.dev_major, entry.dev_minor, entry.rdev_major, entry.rdev_minor, namesize, 0u32, );
135
136 writer.write_all(header.as_bytes())?;
137 writer.write_all(entry.path.as_bytes())?;
138 writer.write_all(&[0])?; 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 let data_padding = (4 - (filesize % 4)) % 4;
149 writer.write_all(&vec![0u8; data_padding])?;
150
151 Ok(())
152 }
153
154 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 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 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}