1use bitflags::bitflags;
2use eyre::{eyre, Result};
3use lazy_static::lazy_static;
4use log::*;
5use uuid::Uuid;
6
7use std::ffi::{CStr, CString};
8use std::fs::OpenOptions;
9use std::mem::MaybeUninit;
10use std::os::unix::ffi::OsStrExt;
11use std::path::{Path, PathBuf};
12use std::sync::{Arc, RwLock};
13use std::time::SystemTime;
14
15use self::block::*;
16use self::file::*;
17use self::inode::*;
18use self::io::*;
19use self::messages::*;
20
21pub mod block;
22pub mod facade;
23pub mod file;
24pub mod inode;
25pub mod io;
26pub mod messages;
27
28#[derive(Debug, Clone)]
29pub struct ExtFilesystem(Arc<RwLock<libe2fs_sys::ext2_filsys>>, PathBuf);
30unsafe impl Send for ExtFilesystem {}
34unsafe impl Sync for ExtFilesystem {}
35
36lazy_static! {
37 static ref DEFAULT_IO_MANAGER: IoManager = {
38 #[cfg(not(target_os = "windows"))]
39 IoManager(Arc::new(RwLock::new(unsafe {
40 *libe2fs_sys::unix_io_manager
41 })))
42 };
43}
44
45impl ExtFilesystem {
46 pub const ROOT_INODE: u32 = libe2fs_sys::EXT2_ROOT_INO;
47 pub const LPF_INODE: u32 = 11;
48
49 pub fn create<P: Into<PathBuf>>(path: P, size_bytes: u64) -> Result<Self> {
50 let path = path.into();
52 debug!(
53 "creating ext filesystem at {path:?} of size {size_bytes}",
54 path = path,
55 size_bytes = size_bytes
56 );
57 let file = OpenOptions::new()
58 .write(true)
59 .create(true)
60 .read(true)
61 .create_new(true)
62 .open(&path)?;
63 file.set_len(size_bytes)?;
64
65 debug!("initialising superblock...");
67 let (err, fs) = unsafe {
68 let mut fs = MaybeUninit::uninit();
69 let block_size = 1_024;
70 let inode_ratio = 8_192;
71 let blocks_count = size_bytes / block_size;
72 let path = CString::new(path.to_string_lossy().as_bytes())?;
73
74 let mut lsector_size = 0;
76 let mut psector_size = 0;
77 let err = libe2fs_sys::ext2fs_get_device_sectsize(path.as_ptr(), &mut lsector_size);
78 if err != 0 {
79 return report(err);
80 }
81 let err =
82 libe2fs_sys::ext2fs_get_device_phys_sectsize(path.as_ptr(), &mut psector_size);
83 if err != 0 {
84 return report(err);
85 }
86
87 let mut superblock = libe2fs_sys::ext2_super_block {
88 s_rev_level: 1,
89 s_log_block_size: 0,
90 s_blocks_per_group: 0,
92 s_blocks_count: blocks_count as u32,
93 s_blocks_count_hi: (blocks_count >> 32) as u32,
94 s_first_meta_bg: 0,
95 s_log_cluster_size: 0,
96 s_desc_size: libe2fs_sys::EXT2_MIN_DESC_SIZE_64BIT as u16,
97 s_inode_size: 0,
99 s_inodes_count: (blocks_count * block_size / inode_ratio).try_into()?,
100 s_r_blocks_count: 5,
101
102 s_algorithm_usage_bitmap: 0,
103 s_backup_bgs: [0, 0],
104 s_checksum_seed: 0,
105 s_checksum: 0,
106 s_creator_os: libe2fs_sys::EXT2_OS_LINUX,
107 s_block_group_nr: 0,
108 s_checkinterval: 0,
109 s_checksum_type: 0,
110 s_clusters_per_group: 0,
111 s_def_hash_version: 0,
112 s_def_resgid: 0,
113 s_def_resuid: 0,
114 s_default_mount_opts: 0,
115 s_encoding: 0,
116 s_encoding_flags: 0,
117 s_encrypt_algos: [0, 0, 0, 0],
118 s_encrypt_pw_salt: [0; 16],
119 s_encryption_level: 0,
120 s_error_count: 0,
121 s_errors: 0,
122 s_feature_compat: 0,
123 s_feature_incompat: libe2fs_sys::EXT4_FEATURE_INCOMPAT_64BIT
124 | libe2fs_sys::EXT3_FEATURE_INCOMPAT_EXTENTS,
125 s_feature_ro_compat: libe2fs_sys::EXT2_FEATURE_RO_COMPAT_LARGE_FILE
126 | libe2fs_sys::EXT4_FEATURE_RO_COMPAT_HUGE_FILE
127 | libe2fs_sys::EXT4_FEATURE_RO_COMPAT_DIR_NLINK,
128 s_first_data_block: 0,
129 s_first_error_block: 0,
130 s_first_error_errcode: 0,
131 s_first_error_func: [0; 32],
132 s_first_error_ino: 0,
133 s_first_error_line: 0,
134 s_first_error_time: 0,
135 s_first_error_time_hi: 0,
136 s_first_ino: 0,
137 s_flags: 0,
138 s_free_blocks_count: 0,
139 s_free_blocks_hi: 0,
140 s_free_inodes_count: 0,
141 s_grp_quota_inum: 0,
142 s_hash_seed: [0; 4],
143 s_kbytes_written: 0,
144 s_last_error_block: 0,
145 s_last_error_errcode: 0,
146 s_last_error_func: [0; 32],
147 s_last_error_ino: 0,
148 s_last_error_line: 0,
149 s_last_error_time: 0,
150 s_last_error_time_hi: 0,
151 s_last_mounted: [0; 64],
152 s_last_orphan: 0,
153 s_inodes_per_group: 0,
154 s_jnl_backup_type: 0,
155 s_jnl_blocks: [0; 17],
156 s_journal_dev: 0,
157 s_journal_inum: 0,
158 s_journal_uuid: [0; 16],
159 s_lastcheck: 0,
160 s_lastcheck_hi: 0,
161 s_log_groups_per_flex: 0,
162 s_max_mnt_count: 0,
163 s_mmp_block: 0,
164 s_mmp_update_interval: 0,
165 s_mtime: 0,
166 s_mtime_hi: 0,
167 s_mkfs_time: 0,
168 s_mkfs_time_hi: 0,
169 s_mount_opts: [0; 64],
170 s_prealloc_blocks: 0,
171 s_prealloc_dir_blocks: 0,
172 s_lpf_ino: 0,
173 s_magic: libe2fs_sys::EXT2_SUPER_MAGIC.try_into()?,
174 s_min_extra_isize: 0,
175 s_minor_rev_level: 0,
176 s_mnt_count: 0,
177 s_orphan_file_inum: 0,
178 s_overhead_clusters: 0,
179 s_prj_quota_inum: 0,
180 s_r_blocks_count_hi: 0,
181 s_raid_stride: 0,
182 s_raid_stripe_width: 0,
183 s_reserved: [0; 94],
184 s_reserved_gdt_blocks: 0,
185 s_reserved_pad: 0,
186 s_snapshot_id: 0,
187 s_snapshot_inum: 0,
188 s_snapshot_list: 0,
189 s_snapshot_r_blocks_count: 0,
190 s_state: 0,
191 s_usr_quota_inum: 0,
192 s_uuid: [0; 16],
193 s_volume_name: [0; 16],
194 s_want_extra_isize: 0,
195 s_wtime: 0,
196 s_wtime_hi: 0,
197 };
198 let io_manager = DEFAULT_IO_MANAGER.clone().0;
199 let mut io_manager = io_manager.write().unwrap();
200 let err = libe2fs_sys::ext2fs_initialize(
201 path.as_ptr(),
202 (libe2fs_sys::EXT2_FLAG_EXCLUSIVE
203 | libe2fs_sys::EXT2_FLAG_64BITS
204 | libe2fs_sys::EXT2_FLAG_SKIP_MMP
205 | libe2fs_sys::EXT2_FLAG_RW) as i32,
206 &mut superblock as *mut _,
207 &mut *io_manager,
208 fs.as_mut_ptr(),
209 );
210 (err, fs)
211 };
212
213 if err != 0 {
214 return report(err);
215 }
216
217 let fs: libe2fs_sys::ext2_filsys = unsafe { fs.assume_init() };
218
219 debug!("updating superblock accounting...");
223 unsafe { *(*fs).super_ }.s_kbytes_written = 1;
224
225 debug!("generating uuid...");
226 let uuid = Uuid::new_v4();
227 let uuid = uuid.as_bytes();
228 unsafe { *(*fs).super_ }.s_uuid = *uuid;
229
230 debug!("setting creatoros...");
233 let creatoros = libe2fs_sys::EXT2_OS_LINUX;
234 unsafe { *(*fs).super_ }.s_creator_os = creatoros;
235
236 debug!("setting volume label...");
237 unsafe { *(*fs).super_ }.s_volume_name = [
238 'f'.try_into()?,
239 'l'.try_into()?,
240 'a'.try_into()?,
241 'i'.try_into()?,
242 'l'.try_into()?,
243 0,
244 0,
245 0,
246 0,
247 0,
248 0,
249 0,
250 0,
251 0,
252 0,
253 0,
254 ];
255
256 debug!("setting checksum...");
257 unsafe { *(*fs).super_ }.s_checksum_type = libe2fs_sys::EXT2_CRC32C_CHKSUM as u8;
258
259 debug!("allocating group tables...");
260 let err = unsafe { libe2fs_sys::ext2fs_allocate_tables(fs) };
261 if err != 0 {
262 return report(err);
263 }
264
265 debug!("converting subcluster bitmaps...");
268 let err =
269 unsafe { libe2fs_sys::ext2fs_convert_subcluster_bitmap(fs, &mut (*fs).block_map) };
270 if err != 0 {
271 return report(err);
272 }
273
274 debug!("calculating overhead...");
276 let mut overhead: u64 = 0;
277 let err = unsafe {
278 libe2fs_sys::ext2fs_count_used_clusters(
279 fs,
280 (*(*fs).super_).s_first_data_block as u64,
281 libe2fs_sys::ext2fs_blocks_count((*fs).super_) - 1,
282 &mut overhead,
283 )
284 };
285 if err != 0 {
286 return report(err);
287 }
288
289 unsafe { *(*fs).super_ }.s_overhead_clusters = overhead as u32;
293
294 debug!("updating accounting...");
295 unsafe { *(*fs).super_ }.s_checkinterval = 0;
296 unsafe { *(*fs).super_ }.s_max_mnt_count = 1;
297
298 debug!("flushing!");
299 let err = unsafe { libe2fs_sys::ext2fs_flush(fs) };
300 if err != 0 {
301 return report(err);
302 }
303
304 debug!("creating root dir...");
305 let err = unsafe {
306 libe2fs_sys::ext2fs_mkdir(fs, Self::ROOT_INODE, Self::ROOT_INODE, std::ptr::null_mut())
307 };
308 if err != 0 {
309 return report(err);
310 }
311
312 debug!("creating l+f...");
313 let lpf_name = CString::new("lost+found")?;
314 let err = unsafe { libe2fs_sys::ext2fs_mkdir(fs, Self::ROOT_INODE, 0, lpf_name.as_ptr()) };
315 if err != 0 {
316 return report(err);
317 }
318
319 debug!("reserving inodes...");
320 for i in Self::ROOT_INODE + 1..unsafe { *(*fs).super_ }.s_first_ino {
321 unsafe {
322 libe2fs_sys::ext2fs_inode_alloc_stats2(fs, i, 1, 0);
323 }
324 }
325 unsafe {
326 (*fs).flags |=
327 (libe2fs_sys::EXT2_FLAG_IB_DIRTY | libe2fs_sys::EXT2_FLAG_CHANGED) as i32;
328 }
329
330 debug!("creating bad block inode...");
331 unsafe {
332 libe2fs_sys::ext2fs_mark_generic_bmap(
333 (*fs).inode_map,
334 libe2fs_sys::EXT2_BAD_INO as u64,
335 );
336 libe2fs_sys::ext2fs_inode_alloc_stats2(fs, libe2fs_sys::EXT2_BAD_INO, 1, 0);
337 let err = libe2fs_sys::ext2fs_update_bb_inode(fs, std::ptr::null_mut());
338 if err != 0 {
339 return report(err);
340 }
341 }
342
343 debug!("flushing!");
344 let err = unsafe { libe2fs_sys::ext2fs_flush(fs) };
345 if err != 0 {
346 return report(err);
347 }
348
349 Ok(Self(Arc::new(RwLock::new(fs)), path))
350 }
351
352 pub fn open<P: Into<PathBuf> + std::fmt::Debug>(
353 name: P,
354 block_size: Option<u32>,
355 flags: Option<ExtFilesystemOpenFlags>,
356 ) -> Result<Self> {
357 let mut fs = MaybeUninit::uninit();
375 let name = name.into().canonicalize()?;
376 let (err, fs) = unsafe {
377 debug!("preparing to open ext filesystem...");
378 debug!("input = {name:#?}");
379 debug!("opening ext filesystem at '{name:?}'");
380 let name = CString::new(name.to_string_lossy().as_bytes())?;
381 let io_manager = DEFAULT_IO_MANAGER.clone().0;
382 let mut io_manager = io_manager.write().unwrap();
383 debug!("got io manager");
384 let err = libe2fs_sys::ext2fs_open(
385 name.as_ptr(),
386 flags.unwrap_or(ExtFilesystemOpenFlags::OPEN_64BIT).bits(),
387 0,
388 block_size.unwrap_or(0),
389 &mut *io_manager,
390 fs.as_mut_ptr(),
391 );
392 (err, fs)
393 };
394
395 if err == 0 {
396 let fs = unsafe { fs.assume_init() };
397 let out = Self(Arc::new(RwLock::new(fs)), name);
398 debug!("@ starting setup @");
399 out.read_bitmaps()?;
400
401 let lpf_inode = out.read_inode(Self::LPF_INODE);
402 if lpf_inode.is_err() {
403 debug!("creating missing /lost+found...");
404 out.mkdir("/", "lost+found")?;
405 }
406
407 debug!("@ finished setup @");
408
409 Ok(out)
410 } else {
411 report(err)
412 }
413 }
414
415 pub fn iterate_dir<F, P: Into<PathBuf>>(&self, dir: P, mut f: F) -> Result<()>
416 where
417 F: FnMut(*mut libe2fs_sys::ext2_dir_entry, i32, i32, &str, &[i8]) -> Result<i32>,
418 {
419 let dir = dir.into();
420 debug!("iterate dir at {dir:?}");
421 let inode = self.find_inode(&dir)?;
422 debug!("found inode {}", inode.0);
423 let fs = self.0.read().unwrap();
424
425 debug!("creating trampoline...");
426 let iterator = get_dir_iterator_trampoline(&f);
427 debug!("boing!");
428
429 let err = unsafe {
430 debug!("iterating {dir:?} with user-provided iterator...");
431 libe2fs_sys::ext2fs_dir_iterate(
432 *fs,
433 inode.num(),
434 0,
435 &mut [0u8; 4_096] as *mut _ as *mut i8,
436 Some(iterator),
437 &mut f as *mut _ as *mut ::std::ffi::c_void,
438 )
439 };
440 if err == 0 {
441 Ok(())
442 } else {
443 report(err)
444 }
445 }
446
447 pub fn root_inode(&self) -> Result<ExtInode> {
448 self.read_inode(Self::ROOT_INODE)
449 }
450
451 pub fn read_inode(&self, inode: u32) -> Result<ExtInode> {
452 debug!("reading inode {inode}...");
453 let mut inode_ptr = MaybeUninit::uninit();
454 let err = unsafe {
455 libe2fs_sys::ext2fs_read_inode(
456 self.0.read().unwrap().as_mut().unwrap(),
457 inode,
458 inode_ptr.as_mut_ptr(),
459 )
460 };
461 if err == 0 {
462 Ok(unsafe { ExtInode(inode, *inode_ptr.assume_init_mut()) })
463 } else {
464 report(err)
465 }
466 }
467
468 pub fn find_inode<P: Into<PathBuf>>(&self, path: P) -> Result<ExtInode> {
469 let path = path.into();
470 debug!("finding inode for {path:?}...");
471 let path = CString::new(path.to_str().unwrap())?;
472 let mut inode = MaybeUninit::uninit();
473 let err = unsafe {
474 debug!("naming inode at {path:?}");
475 let fs = self.0.read().unwrap();
476 libe2fs_sys::ext2fs_namei(
477 *fs,
478 libe2fs_sys::EXT2_ROOT_INO,
479 libe2fs_sys::EXT2_ROOT_INO,
480 path.as_ptr(),
481 inode.as_mut_ptr(),
482 )
483 };
484 if err == 0 {
485 debug!("found inode, reading...");
486 self.read_inode(unsafe { *inode.assume_init_mut() })
487 } else {
488 report(err)
489 }
490 }
491
492 pub fn find_inode_follow<P: Into<PathBuf>>(&self, path: P) -> Result<ExtInode> {
493 let path = path.into();
494 debug!("finding inode for {path:?}...");
495 let path = CString::new(path.to_str().unwrap())?;
496 let mut inode = MaybeUninit::uninit();
497 let err = unsafe {
498 debug!("naming inode at {path:?}");
499 let fs = self.0.read().unwrap();
500 libe2fs_sys::ext2fs_namei_follow(
501 *fs,
502 libe2fs_sys::EXT2_ROOT_INO,
503 libe2fs_sys::EXT2_ROOT_INO,
504 path.as_ptr(),
505 inode.as_mut_ptr(),
506 )
507 };
508 if err == 0 {
509 debug!("found inode, reading...");
510 self.read_inode(unsafe { *inode.assume_init_mut() })
511 } else {
512 report(err)
513 }
514 }
515
516 pub fn lookup<P: Into<PathBuf> + Clone>(&self, dir: P, name: &str) -> Result<ExtInode> {
517 {
518 let dir = dir.clone();
519 debug!("looking up {name} in {:?}...", dir.into());
520 }
521 let dir_inode_number = self.find_inode(dir)?.0;
522 debug!("found dir inode: {dir_inode_number}");
523
524 let name = match name.strip_prefix('/') {
525 Some(name) => name,
526 None => name,
527 };
528 let name = CString::new(name)?;
529
530 let mut inode = MaybeUninit::uninit();
531 let err = unsafe {
532 let fs = self.0.read().unwrap();
533 debug!("looking up {name:?} in {dir_inode_number} via ext2fs_lookup...");
534 libe2fs_sys::ext2fs_lookup(
535 *fs,
536 dir_inode_number,
537 name.as_ptr(),
538 name.as_bytes().len().try_into()?,
539 std::ptr::null_mut(),
540 inode.as_mut_ptr(),
541 )
542 };
543 if err == 0 {
544 self.read_inode(unsafe { inode.assume_init() })
545 } else {
546 report(err)
547 }
548 }
549
550 pub fn get_pathname(&self, inode: u32) -> Result<String> {
551 debug!("reading pathname for inode {}", inode);
552 let mut name = MaybeUninit::<&[i8]>::uninit();
553 let err = unsafe {
554 libe2fs_sys::ext2fs_get_pathname(
555 self.0.read().unwrap().as_mut().unwrap(),
556 libe2fs_sys::EXT2_ROOT_INO,
557 inode,
558 name.as_mut_ptr() as *mut *mut ::std::ffi::c_char,
559 )
560 };
561 let name = unsafe { name.assume_init() };
562 debug!("received {} byte(s)", name.len());
563 if err == 0 {
564 Ok(String::from_utf8(name.iter().map(|i| *i as u8).collect())?)
565 } else {
566 report(err)
567 }
568 }
569
570 pub fn open_file(&self, inode: u32, flags: Option<ExtFileOpenFlags>) -> Result<ExtFile> {
571 let mut file = MaybeUninit::uninit();
572 let err = unsafe {
573 libe2fs_sys::ext2fs_file_open2(
574 self.0.read().unwrap().as_mut().unwrap(),
575 inode,
576 std::ptr::null_mut(),
577 flags.unwrap_or(ExtFileOpenFlags::empty()).bits(),
578 file.as_mut_ptr(),
579 )
580 };
581
582 if err == 0 {
583 Ok(ExtFile(unsafe { file.assume_init() }, ExtFileState::Open))
584 } else {
585 report(err)
586 }
587 }
588
589 pub fn close_file(&self, file: &mut ExtFile) -> Result<()> {
590 if file.1 == ExtFileState::Closed {
591 return Err(eyre!("file already closed!"));
592 }
593
594 let err = unsafe { libe2fs_sys::ext2fs_file_close(file.0) };
595 if err == 0 {
596 file.1 = ExtFileState::Closed;
597 Ok(())
598 } else {
599 report(err)
600 }
601 }
602
603 pub fn get_inode(&self, file: &ExtFile) -> Result<ExtInode> {
604 let inode = unsafe { libe2fs_sys::ext2fs_file_get_inode(file.0) };
605 let inode_num = unsafe { libe2fs_sys::ext2fs_file_get_inode_num(file.0) };
606 if inode.is_null() {
607 Err(ExtError::ENOENT.into())
608 } else {
609 Ok(ExtInode(inode_num, unsafe { *inode }))
610 }
611 }
612
613 pub fn get_inode_number(&self, file: &ExtFile) -> Result<u32> {
614 let inode = unsafe { libe2fs_sys::ext2fs_file_get_inode_num(file.0) };
615 if inode == 0 {
616 Err(ExtError::ENOENT.into())
617 } else {
618 Ok(inode)
619 }
620 }
621
622 pub fn read_file(&self, file: &ExtFile, buf: &mut [u8]) -> Result<usize> {
623 debug!("reading up to {} bytes from {file:?}", buf.len());
624 let mut got = MaybeUninit::uninit();
625 let err = unsafe {
626 libe2fs_sys::ext2fs_file_read(
627 file.0,
628 buf.as_mut_ptr() as *mut ::std::ffi::c_void,
629 buf.len() as u32,
630 got.as_mut_ptr(),
631 )
632 };
633 let bytes_read = unsafe { got.assume_init() };
634 debug!("read {bytes_read} bytes");
635 if bytes_read != buf.len() as u32 {
636 warn!("read {} bytes, expected {}", bytes_read, buf.len());
637 }
638 if err == 0 {
639 Ok(bytes_read as usize)
640 } else {
641 report(err)
642 }
643 }
644
645 pub fn write_file(&self, file: &ExtFile, buf: &[u8]) -> Result<usize> {
646 let mut written = MaybeUninit::uninit();
647 debug!("attempting to write {} bytes to {file:?}", buf.len());
648 let ext_file = file.0 as *mut libe2fs_sys::real_ext2_file;
649 let err = unsafe {
650 libe2fs_sys::ext2fs_file_write(
651 ext_file as *mut libe2fs_sys::ext2_file,
652 buf.as_ptr() as *const ::std::ffi::c_void,
653 buf.len() as u32,
654 written.as_mut_ptr(),
655 )
656 };
657
658 if err != 0 {
659 return report(err);
660 }
661
662 let written = unsafe { written.assume_init() };
663
664 unsafe {
666 let mut inode = self.read_inode((*ext_file).ino)?;
667 debug!("final inode size: {}", inode.1.i_size);
668 let err = libe2fs_sys::ext2fs_write_inode(
669 self.0.read().unwrap().as_mut().unwrap(),
670 (*ext_file).ino,
671 &mut inode.1,
672 );
673
674 if err != 0 {
675 return report(err);
676 }
677 }
678
679 let err =
680 unsafe { libe2fs_sys::ext2fs_file_flush(ext_file as *mut libe2fs_sys::ext2_file) };
681 if err == 0 {
682 self.flush()?;
683 debug!("write succeeded");
685 Ok(written as usize)
686 } else {
687 report(err)
688 }
689 }
690
691 pub fn flush_file(&self, file: &ExtFile) -> Result<()> {
692 let err = unsafe { libe2fs_sys::ext2fs_file_flush(file.0) };
693 if err == 0 {
694 Ok(())
695 } else {
696 report(err)
697 }
698 }
699
700 pub fn new_inode(&self, dir: u32, mode: u16) -> Result<ExtInode> {
701 let mut inode = MaybeUninit::uninit();
702 let fs = *self.0.read().unwrap();
703
704 debug!("creating new inode in dir {dir} with mode {mode}");
705 let err = unsafe {
706 libe2fs_sys::ext2fs_new_inode(
707 fs,
708 dir,
709 libe2fs_sys::LINUX_S_IFREG as i32 | 0o0600,
710 (*fs).inode_map,
711 inode.as_mut_ptr(),
712 )
713 };
714
715 if err == 0 {
716 let inum = unsafe { inode.assume_init() };
717 debug!("created inode: {inum}");
719 let mut inode = libe2fs_sys::ext2_inode {
721 i_mode: mode | libe2fs_sys::LINUX_S_IFREG as u16,
722 i_uid: 0,
723 i_size: 0,
724 i_atime: 0,
725 i_ctime: 0,
726 i_mtime: 0,
727 i_dtime: 0,
728 i_gid: 0,
729 i_links_count: 0,
730 i_blocks: unsafe { (*fs).blocksize / 512 },
731 i_flags: libe2fs_sys::EXT4_EXTENTS_FL,
733 osd1: libe2fs_sys::ext2_inode__bindgen_ty_1 {
734 linux1: libe2fs_sys::ext2_inode__bindgen_ty_1__bindgen_ty_1 { l_i_version: 0 },
735 },
736 i_block: [0; 15],
737 i_generation: 0,
738 i_file_acl: 0,
739 i_size_high: 0,
740 i_faddr: 0,
741 osd2: libe2fs_sys::ext2_inode__bindgen_ty_2 {
742 linux2: libe2fs_sys::ext2_inode__bindgen_ty_2__bindgen_ty_1 {
743 l_i_blocks_hi: 0,
744 l_i_file_acl_high: 0,
745 l_i_uid_high: 0,
746 l_i_gid_high: 0,
747 l_i_checksum_lo: 0,
748 l_i_reserved: 0,
749 },
750 },
751 };
752
753 unsafe {
754 let err =
755 libe2fs_sys::ext2fs_iblk_set(fs, &mut inode as *mut libe2fs_sys::ext2_inode, 1);
756 if err != 0 {
757 return report(err);
758 }
759 debug!("iblk_set");
760 }
761
762 debug!("attaching data block...");
763 let data_block = self.new_block(&mut ExtInode(inum, inode))?;
766 debug!("data block: {data_block}");
767 debug!("adding data block to extents tree...");
772
773 unsafe {
774 let mut handle = MaybeUninit::uninit();
775 let err =
776 libe2fs_sys::ext2fs_extent_open2(fs, inum, &mut inode, handle.as_mut_ptr());
777 if err != 0 {
778 return report(err);
779 }
780 let err =
781 libe2fs_sys::ext2fs_extent_set_bmap(handle.assume_init(), 0, data_block, 0);
782 if err != 0 {
783 return report(err);
784 }
785 }
786
787 debug!("uses {} 512b-i_blocks", inode.i_blocks);
788
789 debug!("writing new inode...");
791 unsafe {
794 libe2fs_sys::ext2fs_inode_alloc_stats2(fs, inum, 1, 0);
795 libe2fs_sys::ext2fs_block_alloc_stats2(fs, data_block, 1);
796 }
797
798 let err = unsafe { libe2fs_sys::ext2fs_write_new_inode(fs, inum, &mut inode) };
799 if err == 0 {
800 self.flush()?;
801 Ok(ExtInode(inum, inode))
802 } else {
803 report(err)
804 }
805 } else {
806 report(err)
807 }
808 }
809
810 pub fn new_block(&self, inode: &mut ExtInode) -> Result<u64> {
811 let mut block = MaybeUninit::uninit();
812 let fs = *self.0.read().unwrap();
813 let err = unsafe {
814 libe2fs_sys::ext2fs_new_block2(
815 fs,
816 self.find_inode_goal(inode)?.0,
817 std::ptr::null_mut(),
818 block.as_mut_ptr(),
819 )
820 };
821 if err == 0 {
822 let block = unsafe { block.assume_init() };
823 debug!("created block {block}");
824 Ok(block)
825 } else {
826 report(err)
827 }
828 }
829
830 pub fn find_inode_goal(&self, inode: &mut ExtInode) -> Result<ExtBlock> {
831 let fs = *self.0.read().unwrap();
832 debug!("finding goal for inode {}", inode.0);
833 let block_number =
834 unsafe { libe2fs_sys::ext2fs_find_inode_goal(fs, inode.0, std::ptr::null_mut(), 0) };
835 Ok(ExtBlock(block_number))
836 }
837
838 pub fn next_free_block(&self) -> Result<u64> {
839 let fs = *self.0.read().unwrap();
840 let mut out = MaybeUninit::<u64>::uninit();
841 let res = unsafe {
842 let fs = *fs;
845 let superblock = *fs.super_;
846 libe2fs_sys::ext2fs_find_first_zero_generic_bmap(
847 fs.block_map,
848 superblock.s_first_data_block as u64,
849 (superblock.s_blocks_count - superblock.s_first_data_block) as u64,
850 out.as_mut_ptr(),
851 )
852 };
853 if res != 0 {
854 return report(res);
855 }
856 let out = unsafe { out.assume_init() };
857 debug!("found next free block: block #{out}");
858 Ok(out)
859 }
860
861 pub fn inode_bitmap(&self) -> ExtInodeBitmap {
862 let fs = *self.0.read().unwrap();
863 ExtInodeBitmap(unsafe { *fs }.inode_map)
864 }
865
866 pub fn block_bitmap(&self) -> ExtBlockBitmap {
867 let fs = *self.0.read().unwrap();
868 ExtBlockBitmap(unsafe { *fs }.block_map)
869 }
870
871 pub fn mkdir<P: Into<PathBuf>, S: Into<String>>(&self, parent: P, name: S) -> Result<()> {
872 let parent = parent.into();
873 let name = name.into();
874 debug!(
875 "mkdir {}/{name}",
876 parent.display().to_string().trim_end_matches('/')
877 );
878 let name = CString::new(name)?;
879 let parent_inode = self.find_inode(&parent)?;
880 debug!("parent_inode: {}", parent_inode.0);
881 debug!("creating: {:?}", name);
882
883 let err = unsafe {
884 let fs = self.0.write().unwrap();
885 libe2fs_sys::ext2fs_mkdir(
888 *fs,
889 parent_inode.0,
890 0,
891 name.as_bytes_with_nul().as_ptr() as *mut _,
892 )
893 };
894 if err == 0 {
895 self.flush()?;
896 debug!("mkdir: success");
897 Ok(())
898 } else {
899 report(err)
900 }
901 }
902
903 pub fn read_bitmaps(&self) -> Result<()> {
904 let err =
905 unsafe { libe2fs_sys::ext2fs_read_bitmaps(self.0.read().unwrap().as_mut().unwrap()) };
906 if err == 0 {
907 Ok(())
908 } else {
909 report(err)
910 }
911 }
912
913 pub fn write_bitmaps(&self) -> Result<()> {
914 let err = unsafe {
915 let fs = *self.0.write().unwrap();
917 debug!("writing inode bitmap...");
918 let err = libe2fs_sys::ext2fs_write_inode_bitmap(&mut *fs as libe2fs_sys::ext2_filsys);
919 if err == 0 {
920 debug!("writing block bitmap...");
921 let err =
922 libe2fs_sys::ext2fs_write_block_bitmap(&mut *fs as libe2fs_sys::ext2_filsys);
923 if err == 0 {
924 debug!("done writing bitmaps");
925 0
926 } else {
927 err
928 }
929 } else {
930 err
931 }
932 };
933 if err == 0 {
934 Ok(())
935 } else {
936 debug!("writing bitmap failed with error {err}");
937 report(err)
938 }
939 }
940
941 pub fn flush(&self) -> Result<()> {
942 let fs = *self.0.write().unwrap();
943 unsafe {
944 (*fs).flags |= (libe2fs_sys::EXT2_FLAG_DIRTY | libe2fs_sys::EXT2_FLAG_CHANGED) as i32;
945 };
946 let err = unsafe { libe2fs_sys::ext2fs_flush(fs) };
947 if err == 0 {
948 Ok(())
949 } else {
950 dbg!(err);
951 report(err)
952 }
953 }
954
955 pub fn touch<P: Into<PathBuf>>(&self, path: P, mode: u16) -> Result<ExtFile> {
956 let fs = *self.0.write().unwrap();
957 let path = path.into();
958
959 let inum = unsafe {
960 let mut inum = MaybeUninit::<u32>::uninit();
961 let err = libe2fs_sys::ext2fs_namei(
962 fs,
963 Self::ROOT_INODE,
964 Self::ROOT_INODE,
965 CString::new(path.to_string_lossy().as_bytes())?.as_ptr(),
966 inum.as_mut_ptr(),
967 );
968 if err != 0 {
969 debug!("touch: could not find inum, allocating new inode");
970 self.new_inode(Self::ROOT_INODE, mode)?.0
971 } else {
972 inum.assume_init()
973 }
974 };
975
976 unsafe {
977 let file = {
978 let mut file = MaybeUninit::<libe2fs_sys::ext2_file_t>::uninit();
979 let err = libe2fs_sys::ext2fs_file_open2(
980 fs,
981 inum,
982 std::ptr::null_mut(),
983 (ExtFileOpenFlags::CREATE | ExtFileOpenFlags::WRITE).bits(),
984 file.as_mut_ptr(),
985 );
986 if err != 0 {
987 return report(err);
988 }
989 file.assume_init()
990 };
991
992 let fs = *self.0.write().unwrap();
993 let mut inode = self.get_inode(&ExtFile(file, ExtFileState::Open))?;
994 debug!("inode size: {}", inode.1.i_size);
995
996 inode.1.i_links_count = 1;
997
998 let err = libe2fs_sys::ext2fs_write_inode(fs, inum, &mut inode.1);
1000 if err != 0 {
1001 return report(err);
1002 }
1003 debug!("wrote inode");
1004
1005 let parent_inum = self.find_inode(path.parent().unwrap())?.0;
1007 let file_name = path.file_name().unwrap();
1008 debug!("linking {file_name:?} @ {inum} to parent inode {parent_inum}");
1009 let file_name = CString::new(file_name.as_bytes())?;
1010 let err = libe2fs_sys::ext2fs_link(
1011 fs,
1012 parent_inum,
1013 file_name.as_ptr(),
1014 inum,
1015 libe2fs_sys::EXT2_FT_REG_FILE.try_into()?,
1016 );
1017 if err != 0 {
1018 return report(err);
1019 }
1020 }
1021
1022 self.flush()?;
1023
1024 let file = self.open_file(self.find_inode(path)?.0, Some(ExtFileOpenFlags::WRITE))?;
1025
1026 Ok(file)
1027 }
1028
1029 pub fn write_to_file<P: Into<PathBuf>>(&self, path: P, buf: &[u8]) -> Result<usize> {
1030 let fs = *self.0.write().unwrap();
1031 let path = path.into();
1032
1033 let inum = unsafe {
1034 let mut inum = MaybeUninit::<u32>::uninit();
1035 let err = libe2fs_sys::ext2fs_namei(
1036 fs,
1037 Self::ROOT_INODE,
1038 Self::ROOT_INODE,
1039 CString::new(path.to_string_lossy().as_bytes())?.as_ptr(),
1040 inum.as_mut_ptr(),
1041 );
1042 if err != 0 {
1043 debug!("write_to_file: could not find inum, allocating new inode");
1044 self.new_inode(Self::ROOT_INODE, 0)?.0
1045 } else {
1046 inum.assume_init()
1047 }
1048 };
1049
1050 let file = unsafe {
1051 let mut file = MaybeUninit::<libe2fs_sys::ext2_file_t>::uninit();
1052 let err = libe2fs_sys::ext2fs_file_open2(
1053 fs,
1054 inum,
1055 std::ptr::null_mut(),
1056 (ExtFileOpenFlags::CREATE | ExtFileOpenFlags::WRITE).bits(),
1057 file.as_mut_ptr(),
1058 );
1059 if err != 0 {
1060 return report(err);
1061 }
1062 file.assume_init()
1063 };
1064
1065 let mut written = 0;
1067 let err = unsafe {
1068 libe2fs_sys::ext2fs_file_write(
1069 file,
1070 buf.as_ptr() as *const libc::c_void,
1071 buf.len() as u32,
1072 &mut written,
1073 )
1074 };
1075 if err != 0 {
1076 return report(err);
1077 }
1078
1079 unsafe {
1080 let fs = *self.0.write().unwrap();
1081 let mut inode = self.get_inode(&ExtFile(file, ExtFileState::Open))?;
1082 debug!("inode size: {}", inode.1.i_size);
1085
1086 inode.1.i_links_count = 1;
1087
1088 let err = libe2fs_sys::ext2fs_write_inode(fs, inum, &mut inode.1);
1090 if err != 0 {
1091 return report(err);
1092 }
1093 debug!("wrote inode");
1094
1095 let parent_inum = self.find_inode(path.parent().unwrap())?.0;
1097 let file_name = path.file_name().unwrap();
1098 debug!("linking {file_name:?} @ {inum} to parent inode {parent_inum}");
1099 let file_name = CString::new(file_name.as_bytes())?;
1100 let err = libe2fs_sys::ext2fs_link(
1101 fs,
1102 parent_inum,
1103 file_name.as_ptr(),
1104 inum,
1105 libe2fs_sys::EXT2_FT_REG_FILE.try_into()?,
1106 );
1107 if err != 0 {
1108 return report(err);
1109 }
1110 }
1111
1112 self.flush()?;
1113
1114 Ok(written as usize)
1115 }
1116
1117 pub fn unlink<P: Into<PathBuf>>(&self, path: P) -> Result<()> {
1118 let fs = *self.0.write().unwrap();
1119 let path = path.into();
1120 let file_name = path
1121 .file_name()
1122 .expect("cannot unlink files without a name");
1123
1124 let inode = self.find_inode(&path)?;
1125 let parent_inum = self
1126 .find_inode(path.parent().unwrap_or(PathBuf::from("/").as_path()))?
1127 .0;
1128
1129 debug!("unlinking {file_name:?}...");
1130 let err = unsafe {
1131 libe2fs_sys::ext2fs_unlink(
1132 fs,
1133 parent_inum,
1134 CString::from_vec_unchecked(file_name.as_bytes().to_vec()).as_ptr(),
1135 inode.0,
1136 0,
1137 )
1138 };
1139
1140 if err != 0 {
1141 return report(err);
1142 }
1143
1144 Ok(())
1145 }
1146
1147 pub fn link<P: Into<PathBuf>>(&self, path: P, new_path: P) -> Result<()> {
1148 let fs = *self.0.write().unwrap();
1149 let path = path.into();
1150 let new_path = new_path.into();
1151 let file_name = path
1152 .file_name()
1153 .expect("cannot unlink files without a name");
1154
1155 let mut inode = self.find_inode(&path)?;
1156 let new_parent_inum = self
1157 .find_inode(new_path.parent().unwrap_or(PathBuf::from("/").as_path()))?
1158 .0;
1159
1160 debug!("linking {file_name:?}...");
1161 let err = unsafe {
1162 libe2fs_sys::ext2fs_link(
1163 fs,
1164 new_parent_inum,
1165 CString::from_vec_unchecked(file_name.as_bytes().to_vec()).as_ptr(),
1166 inode.0,
1167 libe2fs_sys::EXT2_FT_REG_FILE.try_into()?,
1168 )
1169 };
1170
1171 if err != 0 {
1172 return report(err);
1173 }
1174
1175 inode.1.i_links_count += 1;
1176 let err = unsafe { libe2fs_sys::ext2fs_write_inode(fs, inode.0, &mut inode.1) };
1177 if err != 0 {
1178 return report(err);
1179 }
1180
1181 Ok(())
1182 }
1183
1184 pub fn delete<P: Into<PathBuf>>(&self, path: P) -> Result<()> {
1185 let fs = *self.0.write().unwrap();
1186 let path = path.into();
1187 let file_name = path
1188 .file_name()
1189 .expect("cannot unlink files without a name");
1190
1191 let mut inode = self.find_inode(&path)?;
1192 let parent_inum = self
1194 .find_inode(path.parent().unwrap_or(PathBuf::from("/").as_path()))?
1195 .0;
1196
1197 debug!("unlinking {file_name:?}...");
1198 let err = unsafe {
1199 libe2fs_sys::ext2fs_unlink(
1200 fs,
1201 parent_inum,
1202 CString::from_vec_unchecked(file_name.as_bytes().to_vec()).as_ptr(),
1203 inode.0,
1204 0,
1205 )
1206 };
1207 if err != 0 {
1208 return report(err);
1209 }
1210
1211 inode.1.i_links_count -= 1;
1212 inode.1.i_dtime = SystemTime::now()
1213 .duration_since(SystemTime::UNIX_EPOCH)
1214 .unwrap()
1215 .as_secs() as u32;
1216
1217 let err = unsafe { libe2fs_sys::ext2fs_write_inode(fs, inode.0, &mut inode.1) };
1218 if err != 0 {
1219 return report(err);
1220 }
1221
1222 if unsafe { libe2fs_sys::ext2fs_inode_has_valid_blocks2(fs, &mut inode.1 as *mut _) != 0 } {
1224 let err = unsafe {
1225 libe2fs_sys::ext2fs_punch(
1226 fs,
1227 inode.0,
1228 &mut inode.1 as *mut _,
1229 std::ptr::null_mut(),
1230 0,
1231 u64::MAX,
1232 )
1233 };
1234 if err != 0 {
1235 return report(err);
1236 }
1237 }
1238
1239 unsafe {
1240 libe2fs_sys::ext2fs_inode_alloc_stats2(fs, inode.0, -1, 0);
1242 }
1243
1244 self.flush()?;
1245
1246 Ok(())
1247 }
1248
1249 pub fn write_inode(&self, inode: &mut ExtInode) -> Result<()> {
1250 let err = unsafe {
1251 libe2fs_sys::ext2fs_write_inode(
1252 self.0.read().unwrap().as_mut().unwrap(),
1253 inode.0,
1254 &mut inode.1,
1255 )
1256 };
1257
1258 if err != 0 {
1259 report(err)
1260 } else {
1261 Ok(())
1262 }
1263 }
1264
1265 pub fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(
1266 &self,
1267 symlink_parent_dir: &ExtInode,
1268 symlink_inode: Option<&ExtInode>,
1269 symlink_name: P1,
1270 symlink_target_path: P2,
1271 ) -> Result<()> {
1272 let symlink_name = symlink_name.as_ref();
1273 let symlink_target_path = symlink_target_path.as_ref();
1274
1275 let symlink_target_path = CString::new(
1276 symlink_target_path
1277 .as_os_str()
1278 .to_string_lossy()
1279 .to_string(),
1280 )
1281 .unwrap();
1282 let symlink_name =
1283 CString::new(symlink_name.as_os_str().to_string_lossy().to_string()).unwrap();
1284
1285 unsafe {
1286 libe2fs_sys::ext2fs_symlink(
1287 self.0.read().unwrap().as_mut().unwrap(),
1288 symlink_parent_dir.0,
1289 symlink_inode.map(|i| i.0).unwrap_or(0),
1290 symlink_name.as_ptr(),
1291 symlink_target_path.as_ptr(),
1292 );
1293 };
1294
1295 Ok(())
1296 }
1297
1298 pub fn seek(&self, file: &ExtFile, offset: u64, direction: i32) -> Result<()> {
1299 debug!("seeking to {offset}");
1300 let err = unsafe {
1301 libe2fs_sys::ext2fs_file_llseek(file.0, offset, direction, std::ptr::null_mut())
1302 };
1303 if err == 0 {
1304 debug!("seek ok");
1305 Ok(())
1306 } else {
1307 report(err)
1308 }
1309 }
1310
1311 }
1321
1322impl Drop for ExtFilesystem {
1323 fn drop(&mut self) {
1324 unsafe {
1325 debug!("drop: writing bitmaps...");
1326 self.write_bitmaps().unwrap();
1327 let fs = self.0.write().unwrap();
1328 debug!("closing fs...");
1329 let err = libe2fs_sys::ext2fs_close(fs.as_mut().unwrap());
1330 if err != 0 {
1331 Err::<(), ExtError>(ExtError::from(err as u32)).unwrap();
1332 }
1333 }
1334 }
1335}
1336
1337pub trait ExtBitmap {
1338 fn is_32bit(&self) -> bool;
1339 fn is_64bit(&self) -> bool;
1340}
1341
1342pub(crate) fn report<T>(error: i64) -> Result<T> {
1343 if error > 100_000 {
1344 let err: ExtEtMessage = error.into();
1345 Err(err.into())
1346 } else {
1347 let err: ExtError = (error as u32).into();
1348 Err(err.into())
1349 }
1350}
1351
1352pub type DirIteratorCallback = unsafe extern "C" fn(
1353 *mut libe2fs_sys::ext2_dir_entry,
1354 i32,
1355 i32,
1356 *mut i8,
1357 *mut ::std::ffi::c_void,
1358) -> i32;
1359
1360unsafe extern "C" fn dir_iterator_trampoline<F>(
1361 dir_entry: *mut libe2fs_sys::ext2_dir_entry,
1362 offset: i32,
1363 block_size: i32,
1364 buf: *mut i8,
1365 user_data: *mut ::std::ffi::c_void,
1366) -> i32
1367where
1368 F: FnMut(*mut libe2fs_sys::ext2_dir_entry, i32, i32, &str, &[i8]) -> Result<i32>,
1369{
1370 let name = CStr::from_ptr(unsafe { *dir_entry }.name.as_ptr())
1371 .to_str()
1372 .unwrap();
1373 debug!("got dir entry: {name}");
1374 let buf = std::slice::from_raw_parts(buf, block_size as usize);
1375 debug!("built buf!");
1376 let user_data = &mut *(user_data as *mut F);
1377 debug!("invoking user fn!");
1378 user_data(dir_entry, offset, block_size, name, buf).unwrap()
1379}
1380
1381fn get_dir_iterator_trampoline<F>(_closure: &F) -> DirIteratorCallback
1382where
1383 F: FnMut(*mut libe2fs_sys::ext2_dir_entry, i32, i32, &str, &[i8]) -> Result<i32>,
1384{
1385 dir_iterator_trampoline::<F>
1386}
1387
1388bitflags! {
1389 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1390 pub struct ExtFilesystemOpenFlags: i32 {
1391 const OPEN_RW = libe2fs_sys::EXT2_FLAG_RW as i32;
1392 const FORCE = libe2fs_sys::EXT2_FLAG_FORCE as i32;
1393 const JOURNAL_DEV_OK = libe2fs_sys::EXT2_FLAG_JOURNAL_DEV_OK as i32;
1394 const SKIP_MMP = libe2fs_sys::EXT2_FLAG_SKIP_MMP as i32;
1395 const OPEN_64BIT = libe2fs_sys::EXT2_FLAG_64BITS as i32;
1396 }
1397
1398 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1399 pub struct ExtFileOpenFlags: i32 {
1400 const WRITE = libe2fs_sys::EXT2_FILE_WRITE as i32;
1401 const CREATE = libe2fs_sys::EXT2_FILE_CREATE as i32;
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use std::fs;
1408 use std::path::Path;
1409
1410 use pretty_assertions::{assert_eq, assert_ne};
1411
1412 use super::*;
1413
1414 use eyre::Result;
1415
1416 #[ctor::ctor]
1417 fn initialize() {
1418 pretty_env_logger::init();
1419 }
1420
1421 pub struct TempImage(PathBuf, TempDir);
1422
1423 impl TempImage {
1424 pub fn new<P: Into<PathBuf>>(path: P) -> Result<Self> {
1425 let path = path.into();
1426 let tmp = TempDir::new()?;
1427 let mut tmp_path = tmp.path_view();
1428 tmp_path.push(path.file_name().unwrap());
1429 fs::copy(&path, &tmp_path)?;
1430
1431 Ok(Self(tmp_path, tmp))
1432 }
1433
1434 #[allow(unused)]
1435 pub fn path_view(&self) -> &Path {
1436 &self.0
1437 }
1438 }
1439
1440 impl Drop for TempImage {
1441 fn drop(&mut self) {
1442 std::fs::remove_file(&self.0).unwrap();
1443 }
1444 }
1445
1446 pub struct TempDir {
1447 path: PathBuf,
1448 }
1449
1450 impl TempDir {
1451 pub fn new() -> Result<TempDir> {
1452 let mut path = std::env::temp_dir();
1453 path.push(format!("flail-workdir-{}", rand::random::<u64>()));
1454 fs::create_dir_all(&path)?;
1455
1456 Ok(TempDir { path })
1457 }
1458
1459 pub fn path_view(&self) -> PathBuf {
1460 self.path.clone()
1461 }
1462 }
1463
1464 impl Drop for TempDir {
1465 fn drop(&mut self) {
1466 if self.path.exists() {
1467 std::fs::remove_dir_all(&self.path).unwrap();
1468 }
1469 }
1470 }
1471
1472 #[test]
1473 pub fn test_reading_directories_works() -> Result<()> {
1474 let fs = ExtFilesystem::open(
1475 "./fixtures/hello-world.ext4",
1476 None,
1477 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1478 )?;
1479
1480 fs.iterate_dir(
1481 "/",
1482 |dir_entry: *mut libe2fs_sys::ext2_dir_entry,
1483 _offset,
1484 _block_size,
1485 name: &str,
1486 _priv_data| {
1487 assert_ne!((unsafe { *dir_entry }).inode, 0);
1488 debug!("reading inode {}", unsafe { *dir_entry }.inode);
1489 debug!("got path: {name}!!!");
1490 assert_ne!(name.len(), 0);
1491 Ok(0)
1492 },
1493 )?;
1494
1495 Ok(())
1496 }
1497
1498 #[test]
1499 pub fn test_read_write_works() -> Result<()> {
1500 let img = TempImage::new("./fixtures/empty.ext4")?;
1501
1502 let fs = ExtFilesystem::open(
1503 img.path_view(),
1504 None,
1505 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1506 )?;
1507
1508 let data = "hello flail!";
1509
1510 let inode = fs.new_inode(ExtFilesystem::ROOT_INODE, 0o700)?;
1511 let written = {
1512 let file = fs.open_file(
1513 inode.num(),
1514 Some(ExtFileOpenFlags::CREATE | ExtFileOpenFlags::WRITE),
1515 )?;
1516 debug!("write data: '{data}'");
1517 let written = fs.write_file(&file, data.as_bytes())?;
1518 assert_eq!(data.len(), written);
1519 debug!("wrote {written} bytes");
1520 written
1521 };
1522
1523 {
1524 let file = fs.open_file(inode.num(), None)?;
1525 let mut out_buffer = vec![0u8; data.len()];
1526 let read = fs.read_file(&file, &mut out_buffer)?;
1527 assert_eq!(written, read);
1528 debug!("read {read} bytes");
1529 assert_eq!(data.as_bytes(), out_buffer.as_slice());
1530 }
1531
1532 Ok(())
1533 }
1534
1535 #[test]
1536 pub fn test_mkdir_works() -> Result<()> {
1537 let img = TempImage::new("./fixtures/empty.ext4")?;
1538
1539 let fs = ExtFilesystem::open(
1540 img.path_view(),
1541 None,
1542 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1543 )?;
1544
1545 fs.mkdir("/", "foo")?;
1546
1547 let inode = fs.find_inode("/foo")?;
1548 assert_eq!(true, inode.0 > 0);
1549
1550 Ok(())
1551 }
1552
1553 #[test]
1554 pub fn test_passes_fsck() -> Result<()> {
1555 {
1556 let img = TempImage::new("./fixtures/empty.ext4")?;
1557
1558 {
1559 let fs = ExtFilesystem::open(
1560 img.path_view(),
1561 None,
1562 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1563 )?;
1564
1565 fs.mkdir("/", "foo")?;
1566 }
1567
1568 let fsck = std::process::Command::new("fsck.ext4")
1569 .arg("-f")
1570 .arg("-n")
1571 .arg(img.path_view())
1572 .spawn()?
1573 .wait()?;
1574
1575 assert!(fsck.success());
1576 }
1577
1578 {
1579 let img = TempImage::new("./fixtures/empty.ext4")?;
1580
1581 {
1582 let fs = ExtFilesystem::open(
1584 img.path_view(),
1585 None,
1586 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1587 )?;
1588
1589 let data = "hello flail";
1590
1591 debug!("write data: '{data}'");
1592 let written = fs.write_to_file("/test.txt", data.as_bytes())?;
1593
1594 assert_eq!(data.len(), written);
1595 debug!("wrote {written} bytes");
1596 }
1597
1598 let fsck = std::process::Command::new("fsck.ext4")
1599 .arg("-f")
1600 .arg("-n")
1601 .arg(img.path_view())
1602 .spawn()?
1603 .wait()?;
1604
1605 assert!(fsck.success());
1606
1607 {
1608 let fs = ExtFilesystem::open(
1610 img.path_view(),
1611 None,
1612 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1613 )?;
1614
1615 let mut out_buffer = vec![0u8; 11];
1616
1617 let inode = fs.lookup("/", "/test.txt")?;
1618 let file = fs.open_file(inode.0, None)?;
1619 let read = fs.read_file(&file, &mut out_buffer)?;
1620
1621 assert_eq!(11, read);
1622 debug!("read {read} bytes");
1623 assert_eq!("hello flail", std::str::from_utf8(&out_buffer)?);
1624 }
1625
1626 let fsck = std::process::Command::new("fsck.ext4")
1627 .arg("-f")
1628 .arg("-n")
1629 .arg(img.path_view())
1630 .spawn()?
1631 .wait()?;
1632
1633 assert!(fsck.success());
1634
1635 {
1636 let fs = ExtFilesystem::open(
1638 img.path_view(),
1639 None,
1640 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1641 )?;
1642
1643 fs.delete("/test.txt")?;
1644 }
1645
1646 let fsck = std::process::Command::new("fsck.ext4")
1647 .arg("-f")
1648 .arg("-n")
1649 .arg(img.path_view())
1650 .spawn()?
1651 .wait()?;
1652
1653 assert!(fsck.success());
1654 }
1655
1656 Ok(())
1657 }
1658
1659 #[test]
1660 pub fn test_making_new_fs_works() -> Result<()> {
1661 let temp = TempDir::new()?;
1662 let img = temp.path_view().join("test.img");
1663 dbg!(&img);
1664
1665 {
1667 let fs = ExtFilesystem::create(&img, 16 * 1024 * 1024)?;
1668 let data = "hello flail";
1669
1670 debug!("write data: '{data}'");
1671 let written = fs.write_to_file("/test.txt", data.as_bytes())?;
1672
1673 assert_eq!(data.len(), written);
1674 debug!("wrote {written} bytes");
1675 }
1676
1677 let fsck = std::process::Command::new("fsck.ext4")
1678 .arg("-f")
1679 .arg("-n")
1680 .arg(img.clone())
1681 .spawn()?
1682 .wait()?;
1683
1684 assert!(fsck.success());
1685
1686 {
1687 let fs = ExtFilesystem::open(
1689 &img,
1690 None,
1691 Some(ExtFilesystemOpenFlags::OPEN_64BIT | ExtFilesystemOpenFlags::OPEN_RW),
1692 )?;
1693
1694 let mut out_buffer = vec![0u8; 11];
1695
1696 let inode = fs.lookup("/", "/test.txt")?;
1697 let file = fs.open_file(inode.0, None)?;
1698 let read = fs.read_file(&file, &mut out_buffer)?;
1699
1700 assert_eq!(11, read);
1701 debug!("read {read} bytes");
1702 assert_eq!("hello flail", std::str::from_utf8(&out_buffer)?);
1703 }
1704
1705 Ok(())
1706 }
1707}