1#![allow(unsafe_code)]
12#![allow(
13 clippy::cast_possible_truncation,
14 clippy::cast_possible_wrap,
15 clippy::cast_sign_loss
16)]
17
18use std::ffi::CString;
19use std::path::Path;
20
21use crate::error::{Error, Result};
22use crate::sys;
23
24#[non_exhaustive]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[repr(u32)]
28pub enum BlockSize {
29 B1024 = 0,
31 B2048 = 1,
33 B4096 = 2,
35}
36
37impl BlockSize {
38 #[must_use]
40 pub const fn bytes(self) -> u32 {
41 match self {
42 Self::B1024 => 1024,
43 Self::B2048 => 2048,
44 Self::B4096 => 4096,
45 }
46 }
47}
48
49#[non_exhaustive]
51#[derive(Debug, Clone, Copy)]
52pub struct CreateOptions {
53 pub block_size: BlockSize,
55 pub reserved_ratio: u8,
57}
58
59impl Default for CreateOptions {
60 fn default() -> Self {
61 Self {
62 block_size: BlockSize::B4096,
63 reserved_ratio: 0,
64 }
65 }
66}
67
68#[non_exhaustive]
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[repr(i32)]
72pub enum FileType {
73 Unknown = 0,
75 RegularFile = 1,
77 Directory = 2,
79 CharDevice = 3,
81 BlockDevice = 4,
83 Fifo = 5,
85 Socket = 6,
87 Symlink = 7,
89}
90
91pub struct Filesystem {
112 inner: sys::ext2_filsys,
114}
115
116impl std::fmt::Debug for Filesystem {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.debug_struct("Filesystem")
119 .field("open", &!self.inner.is_null())
120 .finish()
121 }
122}
123
124impl Drop for Filesystem {
125 fn drop(&mut self) {
126 if !self.inner.is_null() {
127 unsafe {
131 let _ = sys::ext2fs_close(self.inner);
132 }
133 }
134 }
135}
136
137impl Filesystem {
138 pub fn create(path: &Path, size_bytes: u64, opts: &CreateOptions) -> Result<Self> {
142 let c_path = to_cstring(path)?;
143 let bs = opts.block_size;
144 let blocks = size_bytes / u64::from(bs.bytes());
145 let reserved = blocks * u64::from(opts.reserved_ratio) / 100;
146
147 unsafe {
148 let mut fs: sys::ext2_filsys = std::ptr::null_mut();
149 let mut param: sys::ext2_super_block = std::mem::zeroed();
150 param.s_blocks_count = blocks as u32;
151 param.s_log_block_size = bs as u32;
152 param.s_rev_level = sys::EXT2_DYNAMIC_REV;
153 param.s_r_blocks_count = reserved as u32;
154
155 check(
156 "ext2fs_initialize",
157 sys::ext2fs_initialize(
158 c_path.as_ptr(),
159 sys::EXT2_FLAG_EXCLUSIVE as i32,
160 std::ptr::from_mut(&mut param),
161 sys::unix_io_manager,
162 std::ptr::from_mut(&mut fs),
163 ),
164 )?;
165
166 let this = Self { inner: fs };
168 check(
169 "ext2fs_allocate_tables",
170 sys::ext2fs_allocate_tables(this.inner),
171 )?;
172 Ok(this)
173 }
174 }
175
176 pub fn open(path: &Path) -> Result<Self> {
178 let c_path = to_cstring(path)?;
179
180 unsafe {
181 let mut fs: sys::ext2_filsys = std::ptr::null_mut();
182 check(
183 "ext2fs_open",
184 sys::ext2fs_open(
185 c_path.as_ptr(),
186 sys::EXT2_FLAG_RW as i32,
187 0,
188 0,
189 sys::unix_io_manager,
190 std::ptr::from_mut(&mut fs),
191 ),
192 )?;
193 Ok(Self { inner: fs })
194 }
195 }
196
197 pub fn populate(&mut self, source_dir: &Path) -> Result<()> {
202 let c_src = to_cstring(source_dir)?;
203 unsafe {
204 check(
205 "populate_fs",
206 sys::populate_fs(
207 self.inner,
208 sys::EXT2_ROOT_INO,
209 c_src.as_ptr(),
210 sys::EXT2_ROOT_INO,
211 ),
212 )
213 }
214 }
215
216 pub fn flush(&mut self) -> Result<()> {
218 unsafe { check("ext2fs_flush", sys::ext2fs_flush(self.inner)) }
219 }
220
221 pub fn write_file(&mut self, host_path: &Path, guest_path: &str) -> Result<()> {
225 let c_host = to_cstring(host_path)?;
226 let c_guest = str_to_cstring(guest_path)?;
227 unsafe {
228 check(
229 "do_write_internal",
230 sys::do_write_internal(
231 self.inner,
232 sys::EXT2_ROOT_INO,
233 c_host.as_ptr(),
234 c_guest.as_ptr(),
235 sys::EXT2_ROOT_INO,
236 ),
237 )
238 }
239 }
240
241 pub fn mkdir(&mut self, name: &str) -> Result<()> {
243 let c_name = str_to_cstring(name)?;
244 unsafe {
245 check(
246 "do_mkdir_internal",
247 sys::do_mkdir_internal(
248 self.inner,
249 sys::EXT2_ROOT_INO,
250 c_name.as_ptr(),
251 sys::EXT2_ROOT_INO,
252 ),
253 )
254 }
255 }
256
257 pub fn symlink(&mut self, name: &str, target: &str) -> Result<()> {
259 let c_name = str_to_cstring(name)?;
260 let c_target = str_to_cstring(target)?;
261 unsafe {
262 check(
263 "do_symlink_internal",
264 sys::do_symlink_internal(
265 self.inner,
266 sys::EXT2_ROOT_INO,
267 c_name.as_ptr(),
268 c_target.as_ptr().cast_mut(),
269 sys::EXT2_ROOT_INO,
270 ),
271 )
272 }
273 }
274
275 pub fn add_journal(&mut self) -> Result<()> {
277 unsafe {
278 check(
279 "ext2fs_add_journal_inode",
280 sys::ext2fs_add_journal_inode(self.inner, 0, 0),
281 )
282 }
283 }
284
285 pub fn link(&mut self, dir: u32, name: &str, ino: u32, file_type: FileType) -> Result<()> {
287 let c_name = str_to_cstring(name)?;
288 unsafe {
289 check(
290 "ext2fs_link",
291 sys::ext2fs_link(self.inner, dir, c_name.as_ptr(), ino, file_type as i32),
292 )
293 }
294 }
295
296 pub fn read_inode(&self, ino: u32) -> Result<sys::ext2_inode> {
298 unsafe {
299 let mut inode: sys::ext2_inode = std::mem::zeroed();
300 check(
301 "ext2fs_read_inode",
302 sys::ext2fs_read_inode(self.inner, ino, &raw mut inode),
303 )?;
304 Ok(inode)
305 }
306 }
307
308 pub fn write_inode(&mut self, ino: u32, inode: &sys::ext2_inode) -> Result<()> {
310 unsafe {
311 let mut copy = *inode;
312 check(
313 "ext2fs_write_inode",
314 sys::ext2fs_write_inode(self.inner, ino, &raw mut copy),
315 )
316 }
317 }
318
319 pub fn write_new_inode(&mut self, ino: u32, inode: &sys::ext2_inode) -> Result<()> {
321 unsafe {
322 let mut copy = *inode;
323 check(
324 "ext2fs_write_new_inode",
325 sys::ext2fs_write_new_inode(self.inner, ino, &raw mut copy),
326 )
327 }
328 }
329
330 pub fn alloc_inode(&mut self, dir: u32, mode: u16) -> Result<u32> {
335 unsafe {
336 let mut ino: sys::ext2_ino_t = 0;
337 let map = (*self.inner).inode_map;
338 check(
339 "ext2fs_new_inode",
340 sys::ext2fs_new_inode(self.inner, dir, i32::from(mode), map, &raw mut ino),
341 )?;
342 let is_dir = i32::from(mode & 0o040_000 != 0);
343 sys::ext2fs_inode_alloc_stats2(self.inner, ino, 1, is_dir);
344 Ok(ino)
345 }
346 }
347
348 pub fn alloc_block(&mut self, goal: u64) -> Result<u64> {
353 unsafe {
354 let mut blk: sys::blk64_t = 0;
355 let map = (*self.inner).block_map;
356 check(
357 "ext2fs_new_block2",
358 sys::ext2fs_new_block2(self.inner, goal, map, &raw mut blk),
359 )?;
360 sys::ext2fs_block_alloc_stats2(self.inner, blk, 1);
361 Ok(blk)
362 }
363 }
364}
365
366pub fn create_from_dir(source_dir: &Path, output: &Path, size_bytes: u64) -> Result<()> {
384 let mut fs = Filesystem::create(output, size_bytes, &CreateOptions::default())?;
385 fs.populate(source_dir)?;
386 fs.add_journal()?;
387 Ok(())
388}
389
390pub fn inject_file(image: &Path, host_file: &Path, guest_path: &str) -> Result<()> {
394 let mut fs = Filesystem::open(image)?;
395 fs.write_file(host_file, guest_path)
396}
397
398pub fn estimate_image_size(dir: &Path) -> Result<u64> {
403 let mut total_bytes: u64 = 0;
404 let mut inode_count: u64 = 0;
405
406 walk(dir, &mut |meta| {
407 inode_count += 1;
408 if meta.is_file() {
409 total_bytes += (meta.len() + 4095) & !4095;
411 } else if meta.is_dir() {
412 total_bytes += 4096;
413 } else if meta.is_symlink() && meta.len() > 60 {
414 total_bytes += 4096;
417 }
418 })?;
419
420 let raw = total_bytes + inode_count * 256;
422 let sized = raw * 11 / 10 + 64 * 1024 * 1024;
423 Ok(sized.max(256 * 1024 * 1024))
424}
425
426const fn check(op: &'static str, code: sys::errcode_t) -> Result<()> {
428 if code == 0 {
429 Ok(())
430 } else {
431 Err(Error::Ext2fs { op, code })
432 }
433}
434
435fn to_cstring(path: &Path) -> Result<CString> {
437 let s = path
438 .to_str()
439 .ok_or_else(|| Error::InvalidPath(path.display().to_string()))?;
440 CString::new(s).map_err(|e| Error::InvalidPath(e.to_string()))
441}
442
443fn str_to_cstring(s: &str) -> Result<CString> {
445 CString::new(s).map_err(|e| Error::InvalidPath(e.to_string()))
446}
447
448fn walk(dir: &Path, f: &mut impl FnMut(&std::fs::Metadata)) -> Result<()> {
451 for entry in std::fs::read_dir(dir)? {
452 let path = entry?.path();
453 if let Ok(meta) = path.symlink_metadata() {
454 f(&meta);
455 if meta.is_dir() {
456 walk(&path, f)?;
457 }
458 }
459 }
460 Ok(())
461}