1#![allow(non_camel_case_types)]
44#![allow(clippy::missing_safety_doc)]
48
49use crate::block_io::{AlignedDevice, BlockDevice, CallbackDevice, FileDevice};
50use crate::dir::{self, DirEntryType};
51use crate::error::errno::{EINVAL, EISDIR, ENAMETOOLONG, ENOENT, ENOSYS, ERANGE};
52use crate::error::{Error, Result};
53use crate::fs::{Filesystem, InodeRef};
54use crate::inode::{Inode, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK};
55use crate::xattr;
56use std::cell::RefCell;
57use std::ffi::{CStr, CString};
58use std::os::raw::{c_char, c_int, c_void};
59use std::panic::AssertUnwindSafe;
60use std::sync::Arc;
61
62thread_local! {
67 static LAST_ERROR: RefCell<CString> = RefCell::new(CString::new("").unwrap());
68 static LAST_ERRNO: RefCell<c_int> = const { RefCell::new(0) };
69}
70
71fn set_last_error<E: std::fmt::Display>(e: E) {
72 let msg = format!("{e}");
73 LAST_ERROR.with(|cell| {
74 *cell.borrow_mut() =
75 CString::new(msg).unwrap_or_else(|_| CString::new("unknown error").unwrap());
76 });
77}
78
79fn set_last_errno(errno: c_int) {
80 LAST_ERRNO.with(|cell| *cell.borrow_mut() = errno);
81}
82
83fn set_err_from(err: &Error, context: &str) {
86 set_last_error(format!("{context}: {err}"));
87 set_last_errno(err.to_errno());
88}
89
90fn set_err_msg(msg: &str, errno: c_int) {
93 set_last_error(msg);
94 set_last_errno(errno);
95}
96
97fn clear_last_error() {
98 LAST_ERROR.with(|cell| {
99 *cell.borrow_mut() = CString::new("").unwrap();
100 });
101 LAST_ERRNO.with(|cell| *cell.borrow_mut() = 0);
102}
103
104fn ffi_guard<T>(fail: T, body: impl FnOnce() -> T + std::panic::UnwindSafe) -> T {
108 match std::panic::catch_unwind(body) {
109 Ok(v) => v,
110 Err(panic) => {
111 let msg = if let Some(s) = panic.downcast_ref::<&'static str>() {
112 format!("panic: {s}")
113 } else if let Some(s) = panic.downcast_ref::<String>() {
114 format!("panic: {s}")
115 } else {
116 "panic: (non-string payload)".to_string()
117 };
118 set_err_msg(&msg, crate::error::errno::EIO);
119 fail
120 }
121 }
122}
123
124#[no_mangle]
127pub extern "C" fn fs_ext4_last_error() -> *const c_char {
128 LAST_ERROR.with(|cell| cell.borrow().as_ptr())
129}
130
131#[no_mangle]
136pub extern "C" fn fs_ext4_last_errno() -> c_int {
137 LAST_ERRNO.with(|cell| *cell.borrow())
138}
139
140#[repr(C)]
146#[derive(Copy, Clone, Debug)]
147pub enum fs_ext4_file_type_t {
148 Unknown = 0,
149 RegFile = 1,
150 Dir = 2,
151 ChrDev = 3,
152 BlkDev = 4,
153 Fifo = 5,
154 Sock = 6,
155 Symlink = 7,
156}
157
158#[repr(C)]
160pub struct fs_ext4_attr_t {
161 pub inode: u32,
162 pub mode: u16,
163 pub uid: u32,
164 pub gid: u32,
165 pub size: u64,
166 pub atime: i64,
174 pub mtime: i64,
175 pub ctime: i64,
176 pub crtime: i64,
177 pub link_count: u16,
178 pub file_type: fs_ext4_file_type_t,
179 pub atime_nsec: u32,
181 pub mtime_nsec: u32,
182 pub ctime_nsec: u32,
183 pub crtime_nsec: u32,
184 pub inode_flags: u32,
186 pub generation: u32,
188 pub blocks_512: u64,
190}
191
192#[repr(C)]
194pub struct fs_ext4_dirent_t {
195 pub inode: u32,
196 pub file_type: u8,
197 pub name_len: u8,
198 pub name: [c_char; 256],
199}
200
201#[repr(C)]
208pub struct fs_ext4_volume_info_t {
209 pub volume_name: [c_char; 17],
213 pub uuid: [u8; 16],
216 pub last_mounted: [c_char; 64],
219
220 pub block_size: u32,
222 pub total_blocks: u64,
223 pub free_blocks: u64,
224 pub reserved_blocks: u64,
229 pub total_inodes: u32,
230 pub free_inodes: u32,
231 pub inode_size: u16,
232 pub first_inode: u32,
235 pub blocks_per_group: u32,
236 pub inodes_per_group: u32,
237
238 pub creator_os: u32,
241 pub rev_level: u32,
242 pub minor_rev_level: u16,
243 pub feature_compat: u32,
244 pub feature_incompat: u32,
245 pub feature_ro_compat: u32,
246 pub desc_size: u16,
249 pub default_hash_version: u8,
250
251 pub state: u16,
256 pub errors_behavior: u16,
259 pub last_mount_time: u32,
261 pub last_write_time: u32,
263 pub last_check_time: u32,
265 pub check_interval: u32,
268 pub mount_count: u16,
270 pub max_mount_count: u16,
273 pub def_resuid: u16,
274 pub def_resgid: u16,
275
276 pub mounted_dirty: u8,
281}
282
283pub type fs_ext4_read_fn = Option<
285 unsafe extern "C" fn(context: *mut c_void, buf: *mut c_void, offset: u64, length: u64) -> c_int,
286>;
287
288pub type fs_ext4_write_fn = Option<
291 unsafe extern "C" fn(
292 context: *mut c_void,
293 buf: *const c_void,
294 offset: u64,
295 length: u64,
296 ) -> c_int,
297>;
298
299pub type fs_ext4_flush_fn = Option<unsafe extern "C" fn(context: *mut c_void) -> c_int>;
302
303#[repr(C)]
309pub struct fs_ext4_blockdev_cfg_t {
310 pub read: fs_ext4_read_fn,
311 pub context: *mut c_void,
312 pub size_bytes: u64,
313 pub block_size: u32,
319 pub write: fs_ext4_write_fn,
320 pub flush: fs_ext4_flush_fn,
321}
322
323pub struct fs_ext4_fs_t {
329 fs: Filesystem,
330}
331
332pub struct fs_ext4_dir_iter_t {
334 entries: Vec<fs_ext4_dirent_t>,
336 position: usize,
338 current: fs_ext4_dirent_t,
340}
341
342pub(crate) const FFI_PATH_MAX: usize = 4096;
350
351unsafe fn cstr_to_path<'a>(p: *const c_char, what: &str) -> Option<&'a [u8]> {
371 if p.is_null() {
372 set_err_msg(&format!("null {what}"), EINVAL);
373 return None;
374 }
375 let bytes = unsafe { CStr::from_ptr(p) }.to_bytes();
376 if bytes.len() > FFI_PATH_MAX {
377 set_err_msg(
378 &format!("{what} is longer than {FFI_PATH_MAX} bytes"),
379 ENAMETOOLONG,
380 );
381 return None;
382 }
383 Some(bytes)
384}
385
386unsafe fn cstr_to_name<'a>(p: *const c_char, what: &str) -> Option<&'a str> {
390 let bytes = unsafe { cstr_to_path(p, what) }?;
391 match std::str::from_utf8(bytes) {
392 Ok(s) => Some(s),
393 Err(_) => {
394 set_err_msg(
395 &format!("{what} is not valid UTF-8: {:?}", shown(bytes)),
396 EINVAL,
397 );
398 None
399 }
400 }
401}
402
403fn shown(path: &[u8]) -> std::borrow::Cow<'_, str> {
406 String::from_utf8_lossy(path)
407}
408
409unsafe fn cstr_to_str<'a>(p: *const c_char) -> &'a str {
416 if p.is_null() {
417 return "";
418 }
419 let cstr = CStr::from_ptr(p);
420 if cstr.to_bytes().len() > FFI_PATH_MAX {
421 return "";
422 }
423 cstr.to_str().unwrap_or("")
424}
425
426#[allow(dead_code)]
432unsafe fn cstr_to_str_strict<'a>(p: *const c_char) -> std::result::Result<&'a str, &'static str> {
433 if p.is_null() {
434 return Err("null pointer");
435 }
436 let cstr = CStr::from_ptr(p);
437 if cstr.to_bytes().len() > FFI_PATH_MAX {
438 return Err("string exceeds FFI_PATH_MAX");
439 }
440 cstr.to_str().map_err(|_| "invalid UTF-8")
441}
442
443fn mode_to_file_type(mode: u16) -> fs_ext4_file_type_t {
445 match mode & S_IFMT {
446 S_IFREG => fs_ext4_file_type_t::RegFile,
447 S_IFDIR => fs_ext4_file_type_t::Dir,
448 S_IFLNK => fs_ext4_file_type_t::Symlink,
449 S_IFCHR => fs_ext4_file_type_t::ChrDev,
450 S_IFBLK => fs_ext4_file_type_t::BlkDev,
451 S_IFIFO => fs_ext4_file_type_t::Fifo,
452 S_IFSOCK => fs_ext4_file_type_t::Sock,
453 _ => fs_ext4_file_type_t::Unknown,
454 }
455}
456
457fn fill_attr(out: &mut fs_ext4_attr_t, ino: u32, inode: &Inode) {
459 out.inode = ino;
460 out.mode = inode.mode & 0x0FFF; out.uid = inode.uid;
462 out.gid = inode.gid;
463 out.size = inode.size;
464 out.atime = inode.atime;
465 out.mtime = inode.mtime;
466 out.ctime = inode.ctime;
467 out.crtime = inode.crtime;
468 out.link_count = inode.links_count;
469 out.file_type = mode_to_file_type(inode.mode);
470 out.atime_nsec = inode.atime_nsec;
471 out.mtime_nsec = inode.mtime_nsec;
472 out.ctime_nsec = inode.ctime_nsec;
473 out.crtime_nsec = inode.crtime_nsec;
474 out.inode_flags = inode.flags;
475 out.generation = inode.generation;
476 out.blocks_512 = inode.blocks;
477}
478
479#[no_mangle]
485pub unsafe extern "C" fn fs_ext4_mount(device_path: *const c_char) -> *mut fs_ext4_fs_t {
486 ffi_guard(
487 std::ptr::null_mut(),
488 AssertUnwindSafe(|| {
489 clear_last_error();
490 let path = cstr_to_str(device_path);
491 if path.is_empty() {
492 set_err_msg("null or empty device_path", EINVAL);
493 return std::ptr::null_mut();
494 }
495
496 let dev = match FileDevice::open(path) {
497 Ok(d) => Arc::new(d) as Arc<dyn BlockDevice>,
498 Err(e) => {
499 set_err_from(&e, &format!("open {path}"));
500 return std::ptr::null_mut();
501 }
502 };
503
504 match Filesystem::mount(dev) {
505 Ok(fs) => Box::into_raw(Box::new(fs_ext4_fs_t { fs })),
506 Err(e) => {
507 set_err_from(&e, &format!("mount {path}"));
508 std::ptr::null_mut()
509 }
510 }
511 }),
512 )
513}
514
515const MAX_SECTOR_SIZE: u32 = 65536;
518
519fn callback_mount_device(
528 dev: CallbackDevice,
529 cfg: &fs_ext4_blockdev_cfg_t,
530) -> std::result::Result<Arc<dyn BlockDevice>, String> {
531 let sector = cfg.block_size;
532 if sector <= 1 {
533 return Ok(Arc::new(dev));
534 }
535 if !sector.is_power_of_two() || sector > MAX_SECTOR_SIZE {
536 return Err(format!(
537 "cfg.block_size {sector} is not a power of two in 2..={MAX_SECTOR_SIZE}"
538 ));
539 }
540 if !cfg.size_bytes.is_multiple_of(sector as u64) {
541 return Err(format!(
542 "cfg.block_size {sector} does not divide cfg.size_bytes {}",
543 cfg.size_bytes
544 ));
545 }
546 let dev = AlignedDevice::new(dev, sector);
547 let mut sb = [0u8; 1024];
551 if dev.read_at(1024, &mut sb).is_ok() && u16::from_le_bytes([sb[0x38], sb[0x39]]) == 0xEF53 {
552 let log = u32::from_le_bytes([sb[0x18], sb[0x19], sb[0x1A], sb[0x1B]]);
553 if let Some(fs_block) = 1024u32.checked_shl(log).filter(|_| log < 22) {
554 if sector > fs_block {
555 return Err(format!(
556 "cfg.block_size {sector} is larger than the filesystem block size {fs_block}"
557 ));
558 }
559 }
560 }
561 Ok(Arc::new(dev))
562}
563
564#[no_mangle]
566pub unsafe extern "C" fn fs_ext4_mount_with_callbacks(
567 cfg: *const fs_ext4_blockdev_cfg_t,
568) -> *mut fs_ext4_fs_t {
569 ffi_guard(
570 std::ptr::null_mut(),
571 AssertUnwindSafe(|| mount_with_callbacks_inner(cfg)),
572 )
573}
574
575unsafe fn mount_with_callbacks_inner(cfg: *const fs_ext4_blockdev_cfg_t) -> *mut fs_ext4_fs_t {
576 clear_last_error();
577 if cfg.is_null() {
578 set_err_msg("null cfg", EINVAL);
579 return std::ptr::null_mut();
580 }
581 let cfg = &*cfg;
582 let Some(read_fn) = cfg.read else {
583 set_err_msg("cfg.read is null", EINVAL);
584 return std::ptr::null_mut();
585 };
586
587 let ctx_addr = cfg.context as usize;
593 let size = cfg.size_bytes;
594
595 let dev = CallbackDevice {
596 size,
597 read: Box::new(move |offset, buf| {
598 let rc = unsafe {
599 read_fn(
600 ctx_addr as *mut c_void,
601 buf.as_mut_ptr() as *mut c_void,
602 offset,
603 buf.len() as u64,
604 )
605 };
606 if rc != 0 {
607 Err(std::io::Error::other(format!("callback returned {rc}")))
608 } else {
609 Ok(())
610 }
611 }),
612 write: None,
616 flush: None,
617 };
618
619 let dev = match callback_mount_device(dev, cfg) {
620 Ok(dev) => dev,
621 Err(msg) => {
622 set_err_msg(&msg, EINVAL);
623 return std::ptr::null_mut();
624 }
625 };
626 match Filesystem::mount(dev) {
627 Ok(fs) => Box::into_raw(Box::new(fs_ext4_fs_t { fs })),
628 Err(e) => {
629 set_err_from(&e, "mount (callback)");
630 std::ptr::null_mut()
631 }
632 }
633}
634
635#[no_mangle]
647pub unsafe extern "C" fn fs_ext4_mount_with_fs_core_device(
648 handle: *mut fs_core::ffi::FsCoreDevice,
649) -> *mut fs_ext4_fs_t {
650 mount_fs_core_handle(handle, false)
651}
652
653#[no_mangle]
658pub unsafe extern "C" fn fs_ext4_mount_with_fs_core_device_lazy(
659 handle: *mut fs_core::ffi::FsCoreDevice,
660) -> *mut fs_ext4_fs_t {
661 mount_fs_core_handle(handle, true)
662}
663
664unsafe fn mount_fs_core_handle(
671 handle: *mut fs_core::ffi::FsCoreDevice,
672 lazy: bool,
673) -> *mut fs_ext4_fs_t {
674 ffi_guard(
675 std::ptr::null_mut(),
676 AssertUnwindSafe(|| {
677 clear_last_error();
678 if handle.is_null() {
679 set_err_msg("null fs_core handle", EINVAL);
680 return std::ptr::null_mut();
681 }
682 let inner = (*handle).inner().clone();
686 let adapter = crate::fs_core_bridge::CoreDevice::new(inner);
687 let dev: Arc<dyn BlockDevice> = Arc::new(adapter);
688
689 let (mounted, what) = if lazy {
690 (Filesystem::mount_lazy(dev), "mount_lazy via fs_core handle")
691 } else {
692 (Filesystem::mount(dev), "mount via fs_core handle")
693 };
694 match mounted {
695 Ok(fs) => Box::into_raw(Box::new(fs_ext4_fs_t { fs })),
696 Err(e) => {
697 set_err_from(&e, what);
698 std::ptr::null_mut()
699 }
700 }
701 }),
702 )
703}
704
705#[no_mangle]
715pub unsafe extern "C" fn fs_ext4_mount_rw_with_callbacks(
716 cfg: *const fs_ext4_blockdev_cfg_t,
717) -> *mut fs_ext4_fs_t {
718 ffi_guard(
719 std::ptr::null_mut(),
720 AssertUnwindSafe(|| mount_rw_with_callbacks_inner(cfg)),
721 )
722}
723
724unsafe fn mount_rw_with_callbacks_inner(cfg: *const fs_ext4_blockdev_cfg_t) -> *mut fs_ext4_fs_t {
725 clear_last_error();
726 if cfg.is_null() {
727 set_err_msg("null cfg", EINVAL);
728 return std::ptr::null_mut();
729 }
730 let cfg = &*cfg;
731 let Some(read_fn) = cfg.read else {
732 set_err_msg("cfg.read is null", EINVAL);
733 return std::ptr::null_mut();
734 };
735 let Some(write_fn) = cfg.write else {
736 set_err_msg("cfg.write is null (required for RW callback mount)", EINVAL);
737 return std::ptr::null_mut();
738 };
739 let flush_fn = cfg.flush; let ctx_addr = cfg.context as usize;
746 let size = cfg.size_bytes;
747
748 let read_closure = move |offset: u64, buf: &mut [u8]| -> std::io::Result<()> {
749 let rc = unsafe {
750 read_fn(
751 ctx_addr as *mut c_void,
752 buf.as_mut_ptr() as *mut c_void,
753 offset,
754 buf.len() as u64,
755 )
756 };
757 if rc != 0 {
758 Err(std::io::Error::other(format!(
759 "read callback returned {rc}"
760 )))
761 } else {
762 Ok(())
763 }
764 };
765 let write_closure = move |offset: u64, buf: &[u8]| -> std::io::Result<()> {
766 let rc = unsafe {
767 write_fn(
768 ctx_addr as *mut c_void,
769 buf.as_ptr() as *const c_void,
770 offset,
771 buf.len() as u64,
772 )
773 };
774 if rc != 0 {
775 Err(std::io::Error::other(format!(
776 "write callback returned {rc}"
777 )))
778 } else {
779 Ok(())
780 }
781 };
782 let flush_closure: Option<crate::block_io::FlushCb> = flush_fn.map(|f| {
783 let cb: crate::block_io::FlushCb = Box::new(move || -> std::io::Result<()> {
784 let rc = unsafe { f(ctx_addr as *mut c_void) };
785 if rc != 0 {
786 Err(std::io::Error::other(format!(
787 "flush callback returned {rc}"
788 )))
789 } else {
790 Ok(())
791 }
792 });
793 cb
794 });
795
796 let dev = CallbackDevice {
797 size,
798 read: Box::new(read_closure),
799 write: Some(Box::new(write_closure)),
800 flush: flush_closure,
801 };
802
803 let dev = match callback_mount_device(dev, cfg) {
804 Ok(dev) => dev,
805 Err(msg) => {
806 set_err_msg(&msg, EINVAL);
807 return std::ptr::null_mut();
808 }
809 };
810 match Filesystem::mount(dev) {
811 Ok(fs) => Box::into_raw(Box::new(fs_ext4_fs_t { fs })),
812 Err(e) => {
813 set_err_from(&e, "mount_rw (callback)");
814 std::ptr::null_mut()
815 }
816 }
817}
818
819#[no_mangle]
829pub unsafe extern "C" fn fs_ext4_mount_rw_with_callbacks_lazy(
830 cfg: *const fs_ext4_blockdev_cfg_t,
831) -> *mut fs_ext4_fs_t {
832 ffi_guard(
833 std::ptr::null_mut(),
834 AssertUnwindSafe(|| mount_rw_with_callbacks_lazy_inner(cfg)),
835 )
836}
837
838unsafe fn mount_rw_with_callbacks_lazy_inner(
839 cfg: *const fs_ext4_blockdev_cfg_t,
840) -> *mut fs_ext4_fs_t {
841 clear_last_error();
842 if cfg.is_null() {
843 set_err_msg("null cfg", EINVAL);
844 return std::ptr::null_mut();
845 }
846 let cfg = &*cfg;
847 let Some(read_fn) = cfg.read else {
848 set_err_msg("cfg.read is null", EINVAL);
849 return std::ptr::null_mut();
850 };
851 let Some(write_fn) = cfg.write else {
852 set_err_msg("cfg.write is null (required for RW callback mount)", EINVAL);
853 return std::ptr::null_mut();
854 };
855 let flush_fn = cfg.flush;
856
857 let ctx_addr = cfg.context as usize;
858 let size = cfg.size_bytes;
859
860 let read_closure = move |offset: u64, buf: &mut [u8]| -> std::io::Result<()> {
861 let rc = unsafe {
862 read_fn(
863 ctx_addr as *mut c_void,
864 buf.as_mut_ptr() as *mut c_void,
865 offset,
866 buf.len() as u64,
867 )
868 };
869 if rc != 0 {
870 Err(std::io::Error::other(format!(
871 "read callback returned {rc}"
872 )))
873 } else {
874 Ok(())
875 }
876 };
877 let write_closure = move |offset: u64, buf: &[u8]| -> std::io::Result<()> {
878 let rc = unsafe {
879 write_fn(
880 ctx_addr as *mut c_void,
881 buf.as_ptr() as *const c_void,
882 offset,
883 buf.len() as u64,
884 )
885 };
886 if rc != 0 {
887 Err(std::io::Error::other(format!(
888 "write callback returned {rc}"
889 )))
890 } else {
891 Ok(())
892 }
893 };
894 let flush_closure: Option<crate::block_io::FlushCb> = flush_fn.map(|f| {
895 let cb: crate::block_io::FlushCb = Box::new(move || -> std::io::Result<()> {
896 let rc = unsafe { f(ctx_addr as *mut c_void) };
897 if rc != 0 {
898 Err(std::io::Error::other(format!(
899 "flush callback returned {rc}"
900 )))
901 } else {
902 Ok(())
903 }
904 });
905 cb
906 });
907
908 let dev = CallbackDevice {
909 size,
910 read: Box::new(read_closure),
911 write: Some(Box::new(write_closure)),
912 flush: flush_closure,
913 };
914
915 let dev = match callback_mount_device(dev, cfg) {
916 Ok(dev) => dev,
917 Err(msg) => {
918 set_err_msg(&msg, EINVAL);
919 return std::ptr::null_mut();
920 }
921 };
922 match Filesystem::mount_lazy(dev) {
923 Ok(fs) => Box::into_raw(Box::new(fs_ext4_fs_t { fs })),
924 Err(e) => {
925 set_err_from(&e, "mount_rw_lazy (callback)");
926 std::ptr::null_mut()
927 }
928 }
929}
930
931#[no_mangle]
938pub unsafe extern "C" fn fs_ext4_replay_journal_if_dirty(fs: *mut fs_ext4_fs_t) -> c_int {
939 ffi_guard(
940 -1,
941 AssertUnwindSafe(|| {
942 clear_last_error();
943 if fs.is_null() {
944 set_err_msg("null fs handle", EINVAL);
945 return -1;
946 }
947 let fs_ref = &mut (*fs).fs;
948 match fs_ref.replay_journal_if_dirty() {
949 Ok(_) => 0,
950 Err(e) => {
951 set_err_from(&e, "replay_journal_if_dirty");
952 -1
953 }
954 }
955 }),
956 )
957}
958
959#[no_mangle]
966pub unsafe extern "C" fn fs_ext4_flush(fs: *mut fs_ext4_fs_t) -> c_int {
967 ffi_guard(
968 -1,
969 AssertUnwindSafe(|| {
970 clear_last_error();
971 if fs.is_null() {
972 set_err_msg("null fs handle", EINVAL);
973 return -1;
974 }
975 match (*fs).fs.flush() {
976 Ok(()) => 0,
977 Err(e) => {
978 set_err_from(&e, "flush");
979 -1
980 }
981 }
982 }),
983 )
984}
985
986#[no_mangle]
991pub unsafe extern "C" fn fs_ext4_fresh_read(fs: *mut fs_ext4_fs_t) -> c_int {
992 ffi_guard(
993 -1,
994 AssertUnwindSafe(|| {
995 clear_last_error();
996 if fs.is_null() {
997 set_err_msg("null fs handle", EINVAL);
998 return -1;
999 }
1000 match (*fs).fs.fresh_read() {
1001 Ok(()) => 0,
1002 Err(e) => {
1003 set_err_from(&e, "fresh_read");
1004 -1
1005 }
1006 }
1007 }),
1008 )
1009}
1010
1011#[no_mangle]
1013pub unsafe extern "C" fn fs_ext4_umount(fs: *mut fs_ext4_fs_t) {
1014 ffi_guard(
1015 (),
1016 AssertUnwindSafe(|| {
1017 if !fs.is_null() {
1018 drop(Box::from_raw(fs));
1019 }
1020 }),
1021 )
1022}
1023
1024#[no_mangle]
1030pub unsafe extern "C" fn fs_ext4_get_volume_info(
1031 fs: *mut fs_ext4_fs_t,
1032 info: *mut fs_ext4_volume_info_t,
1033) -> c_int {
1034 ffi_guard(
1035 -1,
1036 AssertUnwindSafe(|| {
1037 clear_last_error();
1038 if fs.is_null() || info.is_null() {
1039 set_err_msg("null fs or info", EINVAL);
1040 return -1;
1041 }
1042 let fs = &(*fs).fs;
1043 let info = &mut *info;
1044
1045 std::ptr::write_bytes(info as *mut fs_ext4_volume_info_t, 0, 1);
1049
1050 let name_bytes = fs.sb.volume_name.as_bytes();
1054 let copy_len = name_bytes.len().min(16);
1055 for (i, &b) in name_bytes[..copy_len].iter().enumerate() {
1056 info.volume_name[i] = b as c_char;
1057 }
1058 info.volume_name[copy_len] = 0;
1059 info.uuid = fs.sb.uuid;
1060 let lm_bytes = fs.sb.last_mounted.as_bytes();
1063 let lm_copy = lm_bytes.len().min(63);
1064 for (i, &b) in lm_bytes[..lm_copy].iter().enumerate() {
1065 info.last_mounted[i] = b as c_char;
1066 }
1067 info.last_mounted[lm_copy] = 0;
1068
1069 info.block_size = fs.sb.block_size();
1071 info.total_blocks = fs.sb.blocks_count;
1072 info.free_blocks = fs.sb.free_blocks_count;
1073 info.reserved_blocks = fs.sb.r_blocks_count;
1074 info.total_inodes = fs.sb.inodes_count;
1075 info.free_inodes = fs.sb.free_inodes_count;
1076 info.inode_size = fs.sb.inode_size;
1077 info.first_inode = fs.sb.first_inode;
1078 info.blocks_per_group = fs.sb.blocks_per_group;
1079 info.inodes_per_group = fs.sb.inodes_per_group;
1080
1081 info.creator_os = fs.sb.creator_os;
1083 info.rev_level = fs.sb.rev_level;
1084 info.minor_rev_level = fs.sb.minor_rev_level;
1085 info.feature_compat = fs.sb.feature_compat;
1086 info.feature_incompat = fs.sb.feature_incompat;
1087 info.feature_ro_compat = fs.sb.feature_ro_compat;
1088 info.desc_size = fs.sb.desc_size;
1089 info.default_hash_version = fs.sb.default_hash_version;
1090
1091 info.state = fs.sb.state;
1093 info.errors_behavior = fs.sb.errors_behavior;
1094 info.last_mount_time = fs.sb.mtime;
1095 info.last_write_time = fs.sb.wtime;
1096 info.last_check_time = fs.sb.lastcheck;
1097 info.check_interval = fs.sb.checkinterval;
1098 info.mount_count = fs.sb.mnt_count;
1099 info.max_mount_count = fs.sb.max_mnt_count;
1100 info.def_resuid = fs.sb.def_resuid;
1101 info.def_resgid = fs.sb.def_resgid;
1102
1103 info.mounted_dirty = if fs.sb.is_clean() { 0 } else { 1 };
1104
1105 0
1106 }),
1107 )
1108}
1109
1110#[no_mangle]
1117pub unsafe extern "C" fn fs_ext4_set_volume_label(
1118 fs: *mut fs_ext4_fs_t,
1119 label: *const c_char,
1120) -> c_int {
1121 ffi_guard(
1122 -1,
1123 AssertUnwindSafe(|| {
1124 clear_last_error();
1125 if fs.is_null() || label.is_null() {
1126 set_err_msg("null fs or label", EINVAL);
1127 return -1;
1128 }
1129 let bytes = CStr::from_ptr(label).to_bytes();
1130 match (*fs).fs.set_volume_label(bytes) {
1131 Ok(()) => 0,
1132 Err(e) => {
1133 set_err_from(&e, "set_volume_label");
1134 -1
1135 }
1136 }
1137 }),
1138 )
1139}
1140
1141fn resolve_path(fs: &Filesystem, path: &[u8]) -> Result<u32> {
1151 let ino = fs.lookup_path_bytes(path)?;
1152
1153 if path.ends_with(b"/") && path != b"/" {
1158 let (inode, _raw) = fs.read_inode_verified(ino)?;
1159 if !inode.is_dir() {
1160 return Err(Error::NotADirectory);
1161 }
1162 }
1163 Ok(ino)
1164}
1165
1166#[no_mangle]
1168pub unsafe extern "C" fn fs_ext4_stat(
1169 fs: *mut fs_ext4_fs_t,
1170 path: *const c_char,
1171 attr: *mut fs_ext4_attr_t,
1172) -> c_int {
1173 ffi_guard(
1174 -1,
1175 AssertUnwindSafe(|| {
1176 clear_last_error();
1177 if fs.is_null() || path.is_null() || attr.is_null() {
1178 set_err_msg("null fs, path, or attr", EINVAL);
1179 return -1;
1180 }
1181 let fs = &(*fs).fs;
1182 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1183 return -1;
1184 };
1185 let path = shown(path_b);
1186 let attr = &mut *attr;
1187
1188 match resolve_path(fs, path_b).and_then(|ino| stat_into(fs, ino.into(), attr)) {
1189 Ok(()) => 0,
1190 Err(e) => {
1191 set_err_from(&e, &format!("stat {path}"));
1192 -1
1193 }
1194 }
1195 }),
1196 )
1197}
1198
1199#[no_mangle]
1201pub unsafe extern "C" fn fs_ext4_dir_open(
1202 fs: *mut fs_ext4_fs_t,
1203 path: *const c_char,
1204) -> *mut fs_ext4_dir_iter_t {
1205 ffi_guard(
1206 std::ptr::null_mut(),
1207 AssertUnwindSafe(|| {
1208 clear_last_error();
1209 if fs.is_null() || path.is_null() {
1210 set_err_msg("null fs or path", EINVAL);
1211 return std::ptr::null_mut();
1212 }
1213 let fs_ref = &(*fs).fs;
1214 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1215 return std::ptr::null_mut();
1216 };
1217 let path_str = shown(path_b);
1218
1219 match resolve_path(fs_ref, path_b).and_then(|ino| dir_iter(fs_ref, ino.into())) {
1220 Ok(iter) => iter,
1221 Err(e) => {
1222 set_err_from(&e, &format!("dir_open {path_str}"));
1223 std::ptr::null_mut()
1224 }
1225 }
1226 }),
1227 )
1228}
1229
1230fn dir_iter(fs: &Filesystem, dir: InodeRef) -> Result<*mut fs_ext4_dir_iter_t> {
1233 let entries = fs
1234 .read_dir_ino(dir)?
1235 .iter()
1236 .map(dir_entry_to_bridge)
1237 .collect();
1238 Ok(Box::into_raw(Box::new(fs_ext4_dir_iter_t {
1239 entries,
1240 position: 0,
1241 current: unsafe { std::mem::zeroed() },
1243 })))
1244}
1245
1246fn stat_into(fs: &Filesystem, r: InodeRef, attr: &mut fs_ext4_attr_t) -> Result<()> {
1249 let inode = fs.stat_ino(r)?;
1250 fill_attr(attr, r.ino, &inode);
1251 Ok(())
1252}
1253
1254unsafe fn read_into(
1258 fs: &Filesystem,
1259 r: InodeRef,
1260 buf: *mut c_void,
1261 offset: u64,
1262 length: u64,
1263) -> Result<i64> {
1264 let inode = fs.stat_ino(r)?;
1265 if !inode.is_file() {
1266 return Err(Error::InvalidArgument("not a regular file"));
1267 }
1268 let length = length.min(inode.size).min(usize::MAX as u64);
1274 let out = std::slice::from_raw_parts_mut(buf as *mut u8, length as usize);
1275 Ok(fs.read_ino(r, offset, out)? as i64)
1276}
1277
1278unsafe fn readlink_into(target: &[u8], buf: *mut c_char, bufsize: usize, what: &str) -> c_int {
1282 let needed = target.len() + 1;
1283 if bufsize < needed {
1284 set_err_msg(
1285 &format!(
1286 "readlink {what}: buffer of {bufsize} bytes is too small, \
1287 {needed} needed (target and NUL)"
1288 ),
1289 ERANGE,
1290 );
1291 return -1;
1292 }
1293 let out = std::slice::from_raw_parts_mut(buf.cast::<u8>(), needed);
1294 out[..target.len()].copy_from_slice(target);
1295 out[target.len()] = 0;
1296 target.len() as c_int
1298}
1299
1300fn dir_entry_to_bridge(e: &dir::DirEntry) -> fs_ext4_dirent_t {
1302 let mut name = [0 as c_char; 256];
1303 let copy_len = e.name.len().min(255);
1304 for (i, &b) in e.name[..copy_len].iter().enumerate() {
1305 name[i] = b as c_char;
1306 }
1307 name[copy_len] = 0;
1308
1309 let file_type = match e.file_type {
1310 DirEntryType::RegFile => 1u8,
1311 DirEntryType::Directory => 2,
1312 DirEntryType::CharDev => 3,
1313 DirEntryType::BlockDev => 4,
1314 DirEntryType::Fifo => 5,
1315 DirEntryType::Socket => 6,
1316 DirEntryType::Symlink => 7,
1317 DirEntryType::Unknown => 0,
1318 };
1319
1320 fs_ext4_dirent_t {
1321 inode: e.inode,
1322 file_type,
1323 name_len: copy_len as u8,
1324 name,
1325 }
1326}
1327
1328#[no_mangle]
1330pub unsafe extern "C" fn fs_ext4_dir_next(
1331 iter: *mut fs_ext4_dir_iter_t,
1332) -> *const fs_ext4_dirent_t {
1333 ffi_guard(
1334 std::ptr::null(),
1335 AssertUnwindSafe(|| {
1336 if iter.is_null() {
1337 return std::ptr::null();
1338 }
1339 let iter = &mut *iter;
1340 if iter.position >= iter.entries.len() {
1341 return std::ptr::null();
1342 }
1343 iter.current = fs_ext4_dirent_t {
1346 inode: iter.entries[iter.position].inode,
1347 file_type: iter.entries[iter.position].file_type,
1348 name_len: iter.entries[iter.position].name_len,
1349 name: iter.entries[iter.position].name,
1350 };
1351 iter.position += 1;
1352 &iter.current
1353 }),
1354 )
1355}
1356
1357#[no_mangle]
1359pub unsafe extern "C" fn fs_ext4_dir_close(iter: *mut fs_ext4_dir_iter_t) {
1360 ffi_guard(
1361 (),
1362 AssertUnwindSafe(|| {
1363 if !iter.is_null() {
1364 drop(Box::from_raw(iter));
1365 }
1366 }),
1367 )
1368}
1369
1370#[no_mangle]
1372pub unsafe extern "C" fn fs_ext4_read_file(
1373 fs: *mut fs_ext4_fs_t,
1374 path: *const c_char,
1375 buf: *mut c_void,
1376 offset: u64,
1377 length: u64,
1378) -> i64 {
1379 ffi_guard(
1380 -1,
1381 AssertUnwindSafe(|| {
1382 clear_last_error();
1383 if fs.is_null() || path.is_null() || buf.is_null() {
1384 set_err_msg("null fs, path, or buf", EINVAL);
1385 return -1;
1386 }
1387 let fs_ref = &(*fs).fs;
1388 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1389 return -1;
1390 };
1391 let path_str = shown(path_b);
1392
1393 let read = resolve_path(fs_ref, path_b)
1394 .and_then(|ino| read_into(fs_ref, ino.into(), buf, offset, length));
1395 match read {
1396 Ok(n) => n,
1397 Err(e) => {
1398 set_err_from(&e, &format!("read_file {path_str}"));
1399 -1
1400 }
1401 }
1402 }),
1403 )
1404}
1405
1406#[no_mangle]
1415pub unsafe extern "C" fn fs_ext4_readlink(
1416 fs: *mut fs_ext4_fs_t,
1417 path: *const c_char,
1418 buf: *mut c_char,
1419 bufsize: usize,
1420) -> c_int {
1421 ffi_guard(
1422 -1,
1423 AssertUnwindSafe(|| {
1424 clear_last_error();
1425 if fs.is_null() || path.is_null() || buf.is_null() {
1428 set_err_msg("null fs/path/buf", EINVAL);
1429 return -1;
1430 }
1431 let fs_ref = &(*fs).fs;
1432 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1433 return -1;
1434 };
1435 let path_str = shown(path_b);
1436
1437 let target = match resolve_path(fs_ref, path_b).and_then(|ino| fs_ref.read_link(ino)) {
1438 Ok(t) => t,
1439 Err(e) => {
1440 set_err_from(&e, &format!("readlink {path_str}"));
1441 return -1;
1442 }
1443 };
1444
1445 readlink_into(&target, buf, bufsize, &path_str)
1446 }),
1447 )
1448}
1449
1450#[no_mangle]
1457pub unsafe extern "C" fn fs_ext4_listxattr(
1458 fs: *mut fs_ext4_fs_t,
1459 path: *const c_char,
1460 buf: *mut c_char,
1461 bufsize: usize,
1462) -> i64 {
1463 ffi_guard(
1464 -1,
1465 AssertUnwindSafe(|| {
1466 clear_last_error();
1467 if fs.is_null() || path.is_null() {
1468 set_err_msg("null fs or path", EINVAL);
1469 return -1;
1470 }
1471 let fs_ref = &(*fs).fs;
1472 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1473 return -1;
1474 };
1475 let path_str = shown(path_b);
1476
1477 let ino = match resolve_path(fs_ref, path_b) {
1478 Ok(n) => n,
1479 Err(e) => {
1480 set_err_from(&e, &format!("listxattr {path_str}"));
1481 return -1;
1482 }
1483 };
1484 let (inode, inode_raw) = match fs_ref.read_inode_verified(ino) {
1485 Ok(p) => p,
1486 Err(e) => {
1487 set_err_from(&e, &format!("read inode {ino}"));
1488 return -1;
1489 }
1490 };
1491
1492 let names = match xattr::list_names(fs_ref, &inode, &inode_raw) {
1495 Ok(v) => v,
1496 Err(e) => {
1497 set_err_from(&e, &format!("listxattr {path_str}"));
1498 return -1;
1499 }
1500 };
1501
1502 let required: usize = names.iter().map(|n| n.len() + 1).sum();
1503
1504 if !buf.is_null() && bufsize > 0 {
1505 let out = std::slice::from_raw_parts_mut(buf.cast::<u8>(), bufsize);
1506 let mut pos = 0;
1507 for name in &names {
1508 let name_bytes = name.as_bytes();
1509 let needed = name_bytes.len() + 1;
1510 if pos + needed > bufsize {
1511 break;
1512 }
1513 out[pos..pos + name_bytes.len()].copy_from_slice(name_bytes);
1514 out[pos + name_bytes.len()] = 0;
1515 pos += needed;
1516 }
1517 }
1518
1519 required as i64
1520 }),
1521 )
1522}
1523
1524#[no_mangle]
1527pub unsafe extern "C" fn fs_ext4_getxattr(
1528 fs: *mut fs_ext4_fs_t,
1529 path: *const c_char,
1530 name: *const c_char,
1531 buf: *mut c_void,
1532 bufsize: usize,
1533) -> i64 {
1534 ffi_guard(
1535 -1,
1536 AssertUnwindSafe(|| {
1537 clear_last_error();
1538 if fs.is_null() || path.is_null() || name.is_null() {
1539 set_err_msg("null fs, path, or name", EINVAL);
1540 return -1;
1541 }
1542 let fs_ref = &(*fs).fs;
1543 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1544 return -1;
1545 };
1546 let path_str = shown(path_b);
1547 let Some(name_str) = (unsafe { cstr_to_name(name, "name") }) else {
1548 return -1;
1549 };
1550
1551 let ino = match resolve_path(fs_ref, path_b) {
1552 Ok(n) => n,
1553 Err(e) => {
1554 set_err_from(&e, &format!("getxattr {path_str}"));
1555 return -1;
1556 }
1557 };
1558 let (inode, inode_raw) = match fs_ref.read_inode_verified(ino) {
1559 Ok(p) => p,
1560 Err(e) => {
1561 set_err_from(&e, &format!("read inode {ino}"));
1562 return -1;
1563 }
1564 };
1565
1566 let value = match xattr::get_resolved(fs_ref, &inode, &inode_raw, name_str) {
1567 Ok(Some(v)) => v,
1568 Ok(None) => {
1569 set_err_msg(
1570 &format!("getxattr {path_str}: {name_str} not found"),
1571 ENOENT,
1572 );
1573 return -1;
1574 }
1575 Err(e) => {
1576 set_err_from(&e, &format!("getxattr {path_str} {name_str}"));
1577 return -1;
1578 }
1579 };
1580
1581 if !buf.is_null() && bufsize > 0 {
1582 let copy_len = value.len().min(bufsize);
1583 let out = std::slice::from_raw_parts_mut(buf as *mut u8, bufsize);
1584 out[..copy_len].copy_from_slice(&value[..copy_len]);
1585 }
1586
1587 value.len() as i64
1588 }),
1589 )
1590}
1591
1592#[no_mangle]
1613pub unsafe extern "C" fn fs_ext4_truncate(
1614 fs: *mut fs_ext4_fs_t,
1615 path: *const c_char,
1616 new_size: u64,
1617) -> c_int {
1618 ffi_guard(
1619 -1,
1620 AssertUnwindSafe(|| {
1621 clear_last_error();
1622 if fs.is_null() || path.is_null() {
1623 set_err_msg("null fs/path", EINVAL);
1624 return -1;
1625 }
1626 let fs_ref = &(*fs).fs;
1627 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1628 return -1;
1629 };
1630 let path_str = shown(path_b);
1631 let res = resolve_path(fs_ref, path_b)
1634 .and_then(|ino| fs_ref.apply_truncate_ino(ino, new_size));
1635 match res {
1636 Ok(()) => 0,
1637 Err(e) => {
1638 set_err_from(&e, &format!("truncate {path_str} -> {new_size}"));
1639 -1
1640 }
1641 }
1642 }),
1643 )
1644}
1645
1646pub const FS_EXT4_FALLOC_FL_KEEP_SIZE: c_int = 0x01;
1650pub const FS_EXT4_FALLOC_FL_PUNCH_HOLE: c_int = 0x02;
1651pub const FS_EXT4_FALLOC_FL_ZERO_RANGE: c_int = 0x10;
1652
1653#[no_mangle]
1668pub unsafe extern "C" fn fs_ext4_fallocate(
1669 fs: *mut fs_ext4_fs_t,
1670 path: *const c_char,
1671 offset: u64,
1672 len: u64,
1673 flags: c_int,
1674) -> c_int {
1675 ffi_guard(
1676 -1,
1677 AssertUnwindSafe(|| {
1678 clear_last_error();
1679 if fs.is_null() || path.is_null() {
1680 set_err_msg("null fs/path", EINVAL);
1681 return -1;
1682 }
1683 let fs_ref = &(*fs).fs;
1684 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1685 return -1;
1686 };
1687 let path_str = shown(path_b);
1688 let ino = match resolve_path(fs_ref, path_b) {
1689 Ok(n) => n,
1690 Err(e) => {
1691 set_err_from(&e, &format!("fallocate {path_str}"));
1692 return -1;
1693 }
1694 };
1695 let is_punch = flags & FS_EXT4_FALLOC_FL_PUNCH_HOLE != 0;
1698 let is_zero = flags & FS_EXT4_FALLOC_FL_ZERO_RANGE != 0;
1699 let is_keep = flags & FS_EXT4_FALLOC_FL_KEEP_SIZE != 0;
1700 let result = if is_zero {
1701 fs_ref.apply_fallocate_zero_range(ino, offset, len)
1702 } else if is_punch {
1703 if !is_keep {
1704 set_err_msg("fallocate: PUNCH_HOLE requires KEEP_SIZE", EINVAL);
1705 return -1;
1706 }
1707 fs_ref.apply_fallocate_punch_hole(ino, offset, len)
1708 } else if flags == FS_EXT4_FALLOC_FL_KEEP_SIZE {
1709 fs_ref.apply_fallocate_keep_size(ino, offset, len)
1710 } else {
1711 set_err_msg(
1712 "fallocate: unsupported flag combination (KEEP_SIZE / PUNCH_HOLE+KEEP_SIZE / ZERO_RANGE only)",
1713 ENOSYS,
1714 );
1715 return -1;
1716 };
1717 match result {
1718 Ok(()) => 0,
1719 Err(e) => {
1720 set_err_from(&e, &format!("fallocate {path_str} @{offset}+{len}"));
1721 -1
1722 }
1723 }
1724 }),
1725 )
1726}
1727
1728#[no_mangle]
1738pub unsafe extern "C" fn fs_ext4_unlink(fs: *mut fs_ext4_fs_t, path: *const c_char) -> c_int {
1739 ffi_guard(
1740 -1,
1741 AssertUnwindSafe(|| {
1742 clear_last_error();
1743 if fs.is_null() || path.is_null() {
1744 set_err_msg("null fs/path", EINVAL);
1745 return -1;
1746 }
1747 let fs_ref = &(*fs).fs;
1748 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1749 return -1;
1750 };
1751 let path_str = shown(path_b);
1752 match fs_ref.apply_unlink_bytes(path_b) {
1753 Ok(()) => 0,
1754 Err(e) => {
1755 set_err_from(&e, &format!("unlink {path_str}"));
1756 -1
1757 }
1758 }
1759 }),
1760 )
1761}
1762
1763#[no_mangle]
1771pub unsafe extern "C" fn fs_ext4_create(
1772 fs: *mut fs_ext4_fs_t,
1773 path: *const c_char,
1774 mode: u16,
1775) -> u32 {
1776 ffi_guard(
1777 0u32,
1778 AssertUnwindSafe(|| {
1779 clear_last_error();
1780 if fs.is_null() || path.is_null() {
1781 set_err_msg("null fs/path", EINVAL);
1782 return 0u32;
1783 }
1784 let fs_ref = &(*fs).fs;
1785 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1786 return 0u32;
1787 };
1788 let path_str = shown(path_b);
1789 match fs_ref.apply_create_bytes(path_b, mode) {
1790 Ok(ino) => ino,
1791 Err(e) => {
1792 set_err_from(&e, &format!("create {path_str}"));
1793 0u32
1794 }
1795 }
1796 }),
1797 )
1798}
1799
1800#[no_mangle]
1809pub unsafe extern "C" fn fs_ext4_write_file(
1810 fs: *mut fs_ext4_fs_t,
1811 path: *const c_char,
1812 data: *const c_void,
1813 len: u64,
1814) -> i64 {
1815 ffi_guard(
1816 -1i64,
1817 AssertUnwindSafe(|| {
1818 clear_last_error();
1819 if fs.is_null() || path.is_null() {
1820 set_err_msg("null fs/path", EINVAL);
1821 return -1;
1822 }
1823 if data.is_null() && len > 0 {
1824 set_err_msg("null data with non-zero len", EINVAL);
1825 return -1;
1826 }
1827 const MAX_WRITE_LEN: u64 = 1 << 30;
1834 if len > MAX_WRITE_LEN {
1835 set_err_msg(
1836 &format!("write_file: len {len} exceeds {MAX_WRITE_LEN}"),
1837 EINVAL,
1838 );
1839 return -1;
1840 }
1841 let fs_ref = &(*fs).fs;
1842 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1843 return -1i64;
1844 };
1845 let path_str = shown(path_b);
1846 let ino = match resolve_path(fs_ref, path_b) {
1850 Ok(n) => n,
1851 Err(e) => {
1852 set_err_from(&e, &format!("write_file {path_str}"));
1853 return -1;
1854 }
1855 };
1856 let inode = match fs_ref.read_inode_verified(ino) {
1857 Ok((i, _)) => i,
1858 Err(e) => {
1859 set_err_from(&e, &format!("read inode {ino}"));
1860 return -1;
1861 }
1862 };
1863 if inode.is_dir() {
1864 set_err_msg(&format!("write_file {path_str}: is a directory"), EISDIR);
1865 return -1;
1866 }
1867 if !inode.is_file() {
1868 set_err_msg(
1869 &format!("write_file {path_str}: not a regular file"),
1870 EINVAL,
1871 );
1872 return -1;
1873 }
1874 let slice: &[u8] = if len == 0 {
1875 &[]
1876 } else {
1877 std::slice::from_raw_parts(data as *const u8, len as usize)
1878 };
1879 match fs_ref.apply_replace_file_content_bytes(path_b, slice) {
1880 Ok(new_size) => new_size as i64,
1881 Err(e) => {
1882 set_err_from(&e, &format!("write_file {path_str} ({len} bytes)"));
1883 -1
1884 }
1885 }
1886 }),
1887 )
1888}
1889
1890#[no_mangle]
1911pub unsafe extern "C" fn fs_ext4_pwrite(
1912 fs: *mut fs_ext4_fs_t,
1913 path: *const c_char,
1914 data: *const c_void,
1915 len: u64,
1916 offset: u64,
1917) -> i64 {
1918 ffi_guard(
1919 -1i64,
1920 AssertUnwindSafe(|| {
1921 clear_last_error();
1922 if fs.is_null() || path.is_null() {
1923 set_err_msg("null fs/path", EINVAL);
1924 return -1;
1925 }
1926 let slice = match write_arg(data, len, offset) {
1927 Ok(s) => s,
1928 Err(e) => {
1929 set_err_from(&e, "pwrite");
1930 return -1;
1931 }
1932 };
1933 let fs_ref = &(*fs).fs;
1934 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
1935 return -1i64;
1936 };
1937 let path_str = shown(path_b);
1938 match fs_ref.apply_pwrite_bytes(path_b, offset, slice) {
1939 Ok(new_size) => new_size as i64,
1940 Err(e) => {
1941 set_err_from(&e, &format!("pwrite {path_str} @{offset}+{len}"));
1942 -1
1943 }
1944 }
1945 }),
1946 )
1947}
1948
1949unsafe fn write_arg<'a>(data: *const c_void, len: u64, offset: u64) -> Result<&'a [u8]> {
1952 if data.is_null() && len > 0 {
1953 return Err(Error::InvalidArgument("null data with non-zero len"));
1954 }
1955 const MAX_PWRITE_LEN: u64 = 1 << 30;
1960 if len > MAX_PWRITE_LEN {
1961 return Err(Error::InvalidArgument("len exceeds the 1 GiB per-call cap"));
1962 }
1963 if offset.checked_add(len).is_none() {
1966 return Err(Error::InvalidArgument("offset+len overflow"));
1967 }
1968 Ok(if len == 0 {
1969 &[]
1970 } else {
1971 std::slice::from_raw_parts(data as *const u8, len as usize)
1972 })
1973}
1974
1975#[no_mangle]
1979pub unsafe extern "C" fn fs_ext4_mount_rw(device_path: *const c_char) -> *mut fs_ext4_fs_t {
1980 ffi_guard(
1981 std::ptr::null_mut(),
1982 AssertUnwindSafe(|| {
1983 clear_last_error();
1984 let path = cstr_to_str(device_path);
1985 if path.is_empty() {
1986 set_err_msg("null or empty device_path", EINVAL);
1987 return std::ptr::null_mut();
1988 }
1989 let dev = match FileDevice::open_rw(path) {
1990 Ok(d) => Arc::new(d) as Arc<dyn BlockDevice>,
1991 Err(e) => {
1992 set_err_from(&e, &format!("open_rw {path}"));
1993 return std::ptr::null_mut();
1994 }
1995 };
1996 match Filesystem::mount(dev) {
1997 Ok(fs) => Box::into_raw(Box::new(fs_ext4_fs_t { fs })),
1998 Err(e) => {
1999 set_err_from(&e, &format!("mount_rw {path}"));
2000 std::ptr::null_mut()
2001 }
2002 }
2003 }),
2004 )
2005}
2006
2007#[no_mangle]
2012pub unsafe extern "C" fn fs_ext4_link(
2013 fs: *mut fs_ext4_fs_t,
2014 src: *const c_char,
2015 dst: *const c_char,
2016) -> c_int {
2017 ffi_guard(
2018 -1,
2019 AssertUnwindSafe(|| {
2020 clear_last_error();
2021 if fs.is_null() || src.is_null() || dst.is_null() {
2022 set_err_msg("null fs/src/dst", EINVAL);
2023 return -1;
2024 }
2025 let fs_ref = &(*fs).fs;
2026 let Some(src_b) = (unsafe { cstr_to_path(src, "src") }) else {
2027 return -1;
2028 };
2029 let src_str = shown(src_b);
2030 let Some(dst_b) = (unsafe { cstr_to_path(dst, "dst") }) else {
2031 return -1;
2032 };
2033 let dst_str = shown(dst_b);
2034 match fs_ref.apply_link_bytes(src_b, dst_b) {
2035 Ok(()) => 0,
2036 Err(e) => {
2037 set_err_from(&e, &format!("link {src_str} -> {dst_str}"));
2038 -1
2039 }
2040 }
2041 }),
2042 )
2043}
2044
2045#[no_mangle]
2051pub unsafe extern "C" fn fs_ext4_rename(
2052 fs: *mut fs_ext4_fs_t,
2053 src: *const c_char,
2054 dst: *const c_char,
2055) -> c_int {
2056 ffi_guard(
2057 -1,
2058 AssertUnwindSafe(|| {
2059 clear_last_error();
2060 if fs.is_null() || src.is_null() || dst.is_null() {
2061 set_err_msg("null fs/src/dst", EINVAL);
2062 return -1;
2063 }
2064 let fs_ref = &(*fs).fs;
2065 let Some(src_b) = (unsafe { cstr_to_path(src, "src") }) else {
2066 return -1;
2067 };
2068 let src_str = shown(src_b);
2069 let Some(dst_b) = (unsafe { cstr_to_path(dst, "dst") }) else {
2070 return -1;
2071 };
2072 let dst_str = shown(dst_b);
2073 match fs_ref.apply_rename_bytes(src_b, dst_b, false) {
2074 Ok(()) => 0,
2075 Err(e) => {
2076 set_err_from(&e, &format!("rename {src_str} -> {dst_str}"));
2077 -1
2078 }
2079 }
2080 }),
2081 )
2082}
2083
2084pub const FS_EXT4_RENAME_REPLACE: c_int = 0x01;
2091
2092fn rename_flags(flags: c_int) -> Result<bool> {
2096 if flags & !FS_EXT4_RENAME_REPLACE != 0 {
2097 return Err(Error::InvalidArgument("unknown rename flag bits"));
2098 }
2099 Ok(flags & FS_EXT4_RENAME_REPLACE != 0)
2100}
2101
2102#[no_mangle]
2112pub unsafe extern "C" fn fs_ext4_rename2(
2113 fs: *mut fs_ext4_fs_t,
2114 src: *const c_char,
2115 dst: *const c_char,
2116 flags: c_int,
2117) -> c_int {
2118 ffi_guard(
2119 -1,
2120 AssertUnwindSafe(|| {
2121 clear_last_error();
2122 if fs.is_null() || src.is_null() || dst.is_null() {
2123 set_err_msg("null fs/src/dst", EINVAL);
2124 return -1;
2125 }
2126 let replace = match rename_flags(flags) {
2127 Ok(r) => r,
2128 Err(e) => {
2129 set_err_from(&e, "rename2");
2130 return -1;
2131 }
2132 };
2133 let fs_ref = &(*fs).fs;
2134 let Some(src_b) = (unsafe { cstr_to_path(src, "src") }) else {
2135 return -1;
2136 };
2137 let src_str = shown(src_b);
2138 let Some(dst_b) = (unsafe { cstr_to_path(dst, "dst") }) else {
2139 return -1;
2140 };
2141 let dst_str = shown(dst_b);
2142 match fs_ref.apply_rename_bytes(src_b, dst_b, replace) {
2143 Ok(()) => 0,
2144 Err(e) => {
2145 set_err_from(&e, &format!("rename2 {src_str} -> {dst_str}"));
2146 -1
2147 }
2148 }
2149 }),
2150 )
2151}
2152
2153#[no_mangle]
2157pub unsafe extern "C" fn fs_ext4_mkdir(
2158 fs: *mut fs_ext4_fs_t,
2159 path: *const c_char,
2160 mode: u16,
2161) -> u32 {
2162 ffi_guard(
2163 0u32,
2164 AssertUnwindSafe(|| {
2165 clear_last_error();
2166 if fs.is_null() || path.is_null() {
2167 set_err_msg("null fs/path", EINVAL);
2168 return 0u32;
2169 }
2170 let fs_ref = &(*fs).fs;
2171 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2172 return 0u32;
2173 };
2174 let path_str = shown(path_b);
2175 match fs_ref.apply_mkdir_bytes(path_b, mode) {
2176 Ok(ino) => ino,
2177 Err(e) => {
2178 set_err_from(&e, &format!("mkdir {path_str}"));
2179 0u32
2180 }
2181 }
2182 }),
2183 )
2184}
2185
2186#[no_mangle]
2190pub unsafe extern "C" fn fs_ext4_rmdir(fs: *mut fs_ext4_fs_t, path: *const c_char) -> c_int {
2191 ffi_guard(
2192 -1,
2193 AssertUnwindSafe(|| {
2194 clear_last_error();
2195 if fs.is_null() || path.is_null() {
2196 set_err_msg("null fs/path", EINVAL);
2197 return -1;
2198 }
2199 let fs_ref = &(*fs).fs;
2200 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2201 return -1;
2202 };
2203 let path_str = shown(path_b);
2204 match fs_ref.apply_rmdir_bytes(path_b) {
2205 Ok(()) => 0,
2206 Err(e) => {
2207 set_err_from(&e, &format!("rmdir {path_str}"));
2208 -1
2209 }
2210 }
2211 }),
2212 )
2213}
2214
2215#[no_mangle]
2221pub unsafe extern "C" fn fs_ext4_chmod(
2222 fs: *mut fs_ext4_fs_t,
2223 path: *const c_char,
2224 mode: u16,
2225) -> c_int {
2226 ffi_guard(
2227 -1,
2228 AssertUnwindSafe(|| {
2229 clear_last_error();
2230 if fs.is_null() || path.is_null() {
2231 set_err_msg("null fs/path", EINVAL);
2232 return -1;
2233 }
2234 let fs_ref = &(*fs).fs;
2235 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2236 return -1;
2237 };
2238 let path_str = shown(path_b);
2239 match fs_ref.apply_chmod_bytes(path_b, mode) {
2240 Ok(()) => 0,
2241 Err(e) => {
2242 set_err_from(&e, &format!("chmod {path_str}"));
2243 -1
2244 }
2245 }
2246 }),
2247 )
2248}
2249
2250#[no_mangle]
2256pub unsafe extern "C" fn fs_ext4_chown(
2257 fs: *mut fs_ext4_fs_t,
2258 path: *const c_char,
2259 uid: u32,
2260 gid: u32,
2261) -> c_int {
2262 ffi_guard(
2263 -1,
2264 AssertUnwindSafe(|| {
2265 clear_last_error();
2266 if fs.is_null() || path.is_null() {
2267 set_err_msg("null fs/path", EINVAL);
2268 return -1;
2269 }
2270 let fs_ref = &(*fs).fs;
2271 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2272 return -1;
2273 };
2274 let path_str = shown(path_b);
2275 match fs_ref.apply_chown_bytes(path_b, uid, gid) {
2276 Ok(()) => 0,
2277 Err(e) => {
2278 set_err_from(&e, &format!("chown {path_str}"));
2279 -1
2280 }
2281 }
2282 }),
2283 )
2284}
2285
2286#[no_mangle]
2295pub unsafe extern "C" fn fs_ext4_mknod(
2296 fs: *mut fs_ext4_fs_t,
2297 path: *const c_char,
2298 mode: u16,
2299 major: u32,
2300 minor: u32,
2301) -> u32 {
2302 ffi_guard(
2303 0u32,
2304 AssertUnwindSafe(|| {
2305 clear_last_error();
2306 if fs.is_null() || path.is_null() {
2307 set_err_msg("null fs/path", EINVAL);
2308 return 0u32;
2309 }
2310 let fs_ref = &(*fs).fs;
2311 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2312 return 0u32;
2313 };
2314 let path_str = shown(path_b);
2315 match fs_ref.apply_mknod_bytes(path_b, mode, major, minor) {
2316 Ok(ino) => ino,
2317 Err(e) => {
2318 set_err_from(&e, &format!("mknod {path_str}"));
2319 0u32
2320 }
2321 }
2322 }),
2323 )
2324}
2325
2326#[no_mangle]
2336pub unsafe extern "C" fn fs_ext4_set_flags(
2337 fs: *mut fs_ext4_fs_t,
2338 path: *const c_char,
2339 flags: u32,
2340) -> c_int {
2341 ffi_guard(
2342 -1,
2343 AssertUnwindSafe(|| {
2344 clear_last_error();
2345 if fs.is_null() || path.is_null() {
2346 set_err_msg("null fs/path", EINVAL);
2347 return -1;
2348 }
2349 let fs_ref = &(*fs).fs;
2350 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2351 return -1;
2352 };
2353 let path_str = shown(path_b);
2354 match fs_ref.apply_set_flags_bytes(path_b, flags) {
2355 Ok(()) => 0,
2356 Err(e) => {
2357 set_err_from(&e, &format!("set_flags {path_str}"));
2358 -1
2359 }
2360 }
2361 }),
2362 )
2363}
2364
2365#[no_mangle]
2384pub unsafe extern "C" fn fs_ext4_utimens(
2385 fs: *mut fs_ext4_fs_t,
2386 path: *const c_char,
2387 atime_sec: i64,
2388 atime_nsec: u32,
2389 mtime_sec: i64,
2390 mtime_nsec: u32,
2391) -> c_int {
2392 ffi_guard(
2393 -1,
2394 AssertUnwindSafe(|| {
2395 clear_last_error();
2396 if fs.is_null() || path.is_null() {
2397 set_err_msg("null fs/path", EINVAL);
2398 return -1;
2399 }
2400 let fs_ref = &(*fs).fs;
2401 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2402 return -1;
2403 };
2404 let path_str = shown(path_b);
2405 match fs_ref.apply_utimens_bytes(path_b, atime_sec, atime_nsec, mtime_sec, mtime_nsec) {
2406 Ok(()) => 0,
2407 Err(e) => {
2408 set_err_from(&e, &format!("utimens {path_str}"));
2409 -1
2410 }
2411 }
2412 }),
2413 )
2414}
2415
2416#[no_mangle]
2432pub unsafe extern "C" fn fs_ext4_symlink(
2433 fs: *mut fs_ext4_fs_t,
2434 target: *const c_char,
2435 linkpath: *const c_char,
2436) -> u32 {
2437 ffi_guard(
2438 0u32,
2439 AssertUnwindSafe(|| {
2440 clear_last_error();
2441 if fs.is_null() || target.is_null() || linkpath.is_null() {
2442 set_err_msg("null fs/target/linkpath", EINVAL);
2443 return 0u32;
2444 }
2445 let fs_ref = &(*fs).fs;
2449 let Some(target_b) = (unsafe { cstr_to_path(target, "target") }) else {
2450 return 0u32;
2451 };
2452 let target_str = shown(target_b);
2453 let Some(linkpath_b) = (unsafe { cstr_to_path(linkpath, "linkpath") }) else {
2454 return 0u32;
2455 };
2456 let linkpath_str = shown(linkpath_b);
2457 match fs_ref.apply_symlink_bytes(target_b, linkpath_b) {
2458 Ok(ino) => ino,
2459 Err(e) => {
2460 set_err_from(&e, &format!("symlink {linkpath_str} -> {target_str}"));
2461 0u32
2462 }
2463 }
2464 }),
2465 )
2466}
2467
2468#[no_mangle]
2478pub unsafe extern "C" fn fs_ext4_removexattr(
2479 fs: *mut fs_ext4_fs_t,
2480 path: *const c_char,
2481 name: *const c_char,
2482) -> c_int {
2483 ffi_guard(
2484 -1,
2485 AssertUnwindSafe(|| {
2486 clear_last_error();
2487 if fs.is_null() || path.is_null() || name.is_null() {
2488 set_err_msg("null fs/path/name", EINVAL);
2489 return -1;
2490 }
2491 let fs_ref = &(*fs).fs;
2492 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2493 return -1;
2494 };
2495 let path_str = shown(path_b);
2496 let Some(name_str) = (unsafe { cstr_to_name(name, "name") }) else {
2497 return -1;
2498 };
2499 match fs_ref.apply_removexattr_bytes(path_b, name_str) {
2500 Ok(()) => 0,
2501 Err(e) => {
2502 set_err_from(&e, &format!("removexattr {path_str} {name_str}"));
2503 -1
2504 }
2505 }
2506 }),
2507 )
2508}
2509
2510#[no_mangle]
2522pub unsafe extern "C" fn fs_ext4_setxattr(
2523 fs: *mut fs_ext4_fs_t,
2524 path: *const c_char,
2525 name: *const c_char,
2526 value: *const c_void,
2527 value_len: usize,
2528) -> c_int {
2529 ffi_guard(
2530 -1,
2531 AssertUnwindSafe(|| {
2532 clear_last_error();
2533 if fs.is_null() || path.is_null() || name.is_null() {
2534 set_err_msg("null fs/path/name", EINVAL);
2535 return -1;
2536 }
2537 if value.is_null() && value_len > 0 {
2538 set_err_msg("null value with nonzero len", EINVAL);
2539 return -1;
2540 }
2541 const MAX_XATTR_VALUE_LEN: usize = 64 * 1024;
2547 if value_len > MAX_XATTR_VALUE_LEN {
2548 set_err_msg(
2549 &format!("setxattr: value_len {value_len} exceeds {MAX_XATTR_VALUE_LEN}"),
2550 EINVAL,
2551 );
2552 return -1;
2553 }
2554 let fs_ref = &(*fs).fs;
2555 let Some(path_b) = (unsafe { cstr_to_path(path, "path") }) else {
2556 return -1;
2557 };
2558 let path_str = shown(path_b);
2559 let Some(name_str) = (unsafe { cstr_to_name(name, "name") }) else {
2560 return -1;
2561 };
2562 let value_bytes = if value_len == 0 {
2563 &[][..]
2564 } else {
2565 std::slice::from_raw_parts(value as *const u8, value_len)
2566 };
2567 match fs_ref.apply_setxattr_bytes(path_b, name_str, value_bytes) {
2568 Ok(()) => 0,
2569 Err(e) => {
2570 set_err_from(&e, &format!("setxattr {path_str} {name_str}"));
2571 -1
2572 }
2573 }
2574 }),
2575 )
2576}
2577
2578pub const FS_EXT4_GEN_ANY: u32 = u32::MAX;
2601
2602fn inode_ref(ino: u32, generation: u32) -> InodeRef {
2603 if generation == FS_EXT4_GEN_ANY {
2604 InodeRef::any(ino)
2605 } else {
2606 InodeRef::new(ino, generation)
2607 }
2608}
2609
2610fn ino_call<T: Copy>(
2613 fs: *mut fs_ext4_fs_t,
2614 fail: T,
2615 what: &dyn Fn() -> String,
2616 body: impl FnOnce(&Filesystem) -> Result<T>,
2617) -> T {
2618 ffi_guard(
2619 fail,
2620 AssertUnwindSafe(|| {
2621 clear_last_error();
2622 if fs.is_null() {
2623 set_err_msg(&format!("{}: null fs", what()), EINVAL);
2624 return fail;
2625 }
2626 match body(unsafe { &(*fs).fs }) {
2628 Ok(v) => v,
2629 Err(e) => {
2630 set_err_from(&e, &what());
2631 fail
2632 }
2633 }
2634 }),
2635 )
2636}
2637
2638unsafe fn name_arg<'a>(name: *const c_char, name_len: usize) -> Result<&'a [u8]> {
2640 if name.is_null() {
2641 return Err(Error::InvalidArgument("null name"));
2642 }
2643 if name_len > FFI_PATH_MAX {
2646 return Err(Error::NameTooLong);
2647 }
2648 Ok(std::slice::from_raw_parts(name.cast::<u8>(), name_len))
2649}
2650
2651unsafe fn name_for_msg(name: *const c_char, name_len: usize) -> String {
2653 match name_arg(name, name_len) {
2654 Ok(n) => String::from_utf8_lossy(n).into_owned(),
2655 Err(_) => "<invalid name>".into(),
2656 }
2657}
2658
2659fn attr_out(fs: &Filesystem, ino: u32, attr: *mut fs_ext4_attr_t) -> Result<c_int> {
2662 if !attr.is_null() {
2663 stat_into(fs, InodeRef::any(ino), unsafe { &mut *attr })?;
2665 }
2666 Ok(0)
2667}
2668
2669#[no_mangle]
2673pub unsafe extern "C" fn fs_ext4_lookup_at(
2674 fs: *mut fs_ext4_fs_t,
2675 dir_ino: u32,
2676 dir_generation: u32,
2677 name: *const c_char,
2678 name_len: usize,
2679 attr: *mut fs_ext4_attr_t,
2680) -> c_int {
2681 let what = || format!("lookup_at {dir_ino}/{}", name_for_msg(name, name_len));
2682 ino_call(fs, -1, &what, |fs| {
2683 if attr.is_null() {
2684 return Err(Error::InvalidArgument("null attr"));
2685 }
2686 let ino = fs.lookup_at(
2687 inode_ref(dir_ino, dir_generation),
2688 name_arg(name, name_len)?,
2689 )?;
2690 stat_into(fs, InodeRef::any(ino), &mut *attr)?;
2691 Ok(0)
2692 })
2693}
2694
2695#[no_mangle]
2698pub unsafe extern "C" fn fs_ext4_stat_ino(
2699 fs: *mut fs_ext4_fs_t,
2700 ino: u32,
2701 generation: u32,
2702 attr: *mut fs_ext4_attr_t,
2703) -> c_int {
2704 ino_call(fs, -1, &|| format!("stat_ino {ino}"), |fs| {
2705 if attr.is_null() {
2706 return Err(Error::InvalidArgument("null attr"));
2707 }
2708 stat_into(fs, inode_ref(ino, generation), &mut *attr)?;
2709 Ok(0)
2710 })
2711}
2712
2713#[no_mangle]
2715pub unsafe extern "C" fn fs_ext4_dir_open_ino(
2716 fs: *mut fs_ext4_fs_t,
2717 ino: u32,
2718 generation: u32,
2719) -> *mut fs_ext4_dir_iter_t {
2720 ino_call(
2721 fs,
2722 std::ptr::null_mut(),
2723 &|| format!("dir_open_ino {ino}"),
2724 |fs| dir_iter(fs, inode_ref(ino, generation)),
2725 )
2726}
2727
2728#[no_mangle]
2731pub unsafe extern "C" fn fs_ext4_pread_ino(
2732 fs: *mut fs_ext4_fs_t,
2733 ino: u32,
2734 generation: u32,
2735 buf: *mut c_void,
2736 offset: u64,
2737 length: u64,
2738) -> i64 {
2739 ino_call(fs, -1, &|| format!("pread_ino {ino}"), |fs| {
2740 if buf.is_null() {
2741 return Err(Error::InvalidArgument("null buf"));
2742 }
2743 read_into(fs, inode_ref(ino, generation), buf, offset, length)
2744 })
2745}
2746
2747#[no_mangle]
2751pub unsafe extern "C" fn fs_ext4_readlink_ino(
2752 fs: *mut fs_ext4_fs_t,
2753 ino: u32,
2754 generation: u32,
2755 buf: *mut c_char,
2756 bufsize: usize,
2757) -> c_int {
2758 ino_call(fs, -1, &|| format!("readlink_ino {ino}"), |fs| {
2759 if buf.is_null() {
2760 return Err(Error::InvalidArgument("null buf"));
2761 }
2762 let target = fs.read_link_ino(inode_ref(ino, generation))?;
2763 Ok(readlink_into(
2764 &target,
2765 buf,
2766 bufsize,
2767 &format!("inode {ino}"),
2768 ))
2769 })
2770}
2771
2772#[no_mangle]
2774pub unsafe extern "C" fn fs_ext4_pwrite_ino(
2775 fs: *mut fs_ext4_fs_t,
2776 ino: u32,
2777 generation: u32,
2778 data: *const c_void,
2779 len: u64,
2780 offset: u64,
2781) -> i64 {
2782 ino_call(
2783 fs,
2784 -1,
2785 &|| format!("pwrite_ino {ino} @{offset}+{len}"),
2786 |fs| {
2787 let data = write_arg(data, len, offset)?;
2788 Ok(fs.apply_pwrite_ino(inode_ref(ino, generation), offset, data)? as i64)
2789 },
2790 )
2791}
2792
2793#[no_mangle]
2795pub unsafe extern "C" fn fs_ext4_truncate_ino(
2796 fs: *mut fs_ext4_fs_t,
2797 ino: u32,
2798 generation: u32,
2799 new_size: u64,
2800) -> c_int {
2801 ino_call(
2802 fs,
2803 -1,
2804 &|| format!("truncate_ino {ino} -> {new_size}"),
2805 |fs| {
2806 fs.apply_truncate_ino(inode_ref(ino, generation), new_size)?;
2807 Ok(0)
2808 },
2809 )
2810}
2811
2812#[no_mangle]
2814pub unsafe extern "C" fn fs_ext4_chmod_ino(
2815 fs: *mut fs_ext4_fs_t,
2816 ino: u32,
2817 generation: u32,
2818 mode: u16,
2819) -> c_int {
2820 ino_call(fs, -1, &|| format!("chmod_ino {ino}"), |fs| {
2821 fs.apply_chmod_ino(inode_ref(ino, generation), mode)?;
2822 Ok(0)
2823 })
2824}
2825
2826#[no_mangle]
2828pub unsafe extern "C" fn fs_ext4_chown_ino(
2829 fs: *mut fs_ext4_fs_t,
2830 ino: u32,
2831 generation: u32,
2832 uid: u32,
2833 gid: u32,
2834) -> c_int {
2835 ino_call(fs, -1, &|| format!("chown_ino {ino}"), |fs| {
2836 fs.apply_chown_ino(inode_ref(ino, generation), uid, gid)?;
2837 Ok(0)
2838 })
2839}
2840
2841#[no_mangle]
2843#[allow(clippy::too_many_arguments)]
2844pub unsafe extern "C" fn fs_ext4_utimens_ino(
2845 fs: *mut fs_ext4_fs_t,
2846 ino: u32,
2847 generation: u32,
2848 atime_sec: i64,
2849 atime_nsec: u32,
2850 mtime_sec: i64,
2851 mtime_nsec: u32,
2852) -> c_int {
2853 ino_call(fs, -1, &|| format!("utimens_ino {ino}"), |fs| {
2854 fs.apply_utimens_ino(
2855 inode_ref(ino, generation),
2856 atime_sec,
2857 atime_nsec,
2858 mtime_sec,
2859 mtime_nsec,
2860 )?;
2861 Ok(0)
2862 })
2863}
2864
2865#[no_mangle]
2868pub unsafe extern "C" fn fs_ext4_create_at(
2869 fs: *mut fs_ext4_fs_t,
2870 dir_ino: u32,
2871 dir_generation: u32,
2872 name: *const c_char,
2873 name_len: usize,
2874 mode: u16,
2875 attr: *mut fs_ext4_attr_t,
2876) -> c_int {
2877 let what = || format!("create_at {dir_ino}/{}", name_for_msg(name, name_len));
2878 ino_call(fs, -1, &what, |fs| {
2879 let dir = inode_ref(dir_ino, dir_generation);
2880 let ino = fs.apply_create_at(dir, name_arg(name, name_len)?, mode)?;
2881 attr_out(fs, ino, attr)
2882 })
2883}
2884
2885#[no_mangle]
2888pub unsafe extern "C" fn fs_ext4_mkdir_at(
2889 fs: *mut fs_ext4_fs_t,
2890 dir_ino: u32,
2891 dir_generation: u32,
2892 name: *const c_char,
2893 name_len: usize,
2894 mode: u16,
2895 attr: *mut fs_ext4_attr_t,
2896) -> c_int {
2897 let what = || format!("mkdir_at {dir_ino}/{}", name_for_msg(name, name_len));
2898 ino_call(fs, -1, &what, |fs| {
2899 let dir = inode_ref(dir_ino, dir_generation);
2900 let ino = fs.apply_mkdir_at(dir, name_arg(name, name_len)?, mode)?;
2901 attr_out(fs, ino, attr)
2902 })
2903}
2904
2905#[no_mangle]
2908#[allow(clippy::too_many_arguments)]
2909pub unsafe extern "C" fn fs_ext4_mknod_at(
2910 fs: *mut fs_ext4_fs_t,
2911 dir_ino: u32,
2912 dir_generation: u32,
2913 name: *const c_char,
2914 name_len: usize,
2915 mode: u16,
2916 major: u32,
2917 minor: u32,
2918 attr: *mut fs_ext4_attr_t,
2919) -> c_int {
2920 let what = || format!("mknod_at {dir_ino}/{}", name_for_msg(name, name_len));
2921 ino_call(fs, -1, &what, |fs| {
2922 let dir = inode_ref(dir_ino, dir_generation);
2923 let ino = fs.apply_mknod_at(dir, name_arg(name, name_len)?, mode, major, minor)?;
2924 attr_out(fs, ino, attr)
2925 })
2926}
2927
2928#[no_mangle]
2932#[allow(clippy::too_many_arguments)]
2933pub unsafe extern "C" fn fs_ext4_symlink_at(
2934 fs: *mut fs_ext4_fs_t,
2935 dir_ino: u32,
2936 dir_generation: u32,
2937 name: *const c_char,
2938 name_len: usize,
2939 target: *const c_char,
2940 attr: *mut fs_ext4_attr_t,
2941) -> c_int {
2942 let what = || format!("symlink_at {dir_ino}/{}", name_for_msg(name, name_len));
2943 ino_call(fs, -1, &what, |fs| {
2944 if target.is_null() {
2945 return Err(Error::InvalidArgument("null target"));
2946 }
2947 let target = CStr::from_ptr(target).to_bytes();
2948 if target.len() > FFI_PATH_MAX {
2949 return Err(Error::NameTooLong);
2950 }
2951 let dir = inode_ref(dir_ino, dir_generation);
2952 let ino = fs.apply_symlink_at(dir, name_arg(name, name_len)?, target)?;
2953 attr_out(fs, ino, attr)
2954 })
2955}
2956
2957#[no_mangle]
2960#[allow(clippy::too_many_arguments)]
2961pub unsafe extern "C" fn fs_ext4_link_at(
2962 fs: *mut fs_ext4_fs_t,
2963 ino: u32,
2964 generation: u32,
2965 dir_ino: u32,
2966 dir_generation: u32,
2967 name: *const c_char,
2968 name_len: usize,
2969 attr: *mut fs_ext4_attr_t,
2970) -> c_int {
2971 let what = || {
2972 format!(
2973 "link_at {ino} -> {dir_ino}/{}",
2974 name_for_msg(name, name_len)
2975 )
2976 };
2977 ino_call(fs, -1, &what, |fs| {
2978 fs.apply_link_at(
2979 inode_ref(ino, generation),
2980 inode_ref(dir_ino, dir_generation),
2981 name_arg(name, name_len)?,
2982 )?;
2983 attr_out(fs, ino, attr)
2984 })
2985}
2986
2987#[no_mangle]
2990pub unsafe extern "C" fn fs_ext4_unlink_at(
2991 fs: *mut fs_ext4_fs_t,
2992 dir_ino: u32,
2993 dir_generation: u32,
2994 name: *const c_char,
2995 name_len: usize,
2996) -> c_int {
2997 let what = || format!("unlink_at {dir_ino}/{}", name_for_msg(name, name_len));
2998 ino_call(fs, -1, &what, |fs| {
2999 fs.apply_unlink_at(
3000 inode_ref(dir_ino, dir_generation),
3001 name_arg(name, name_len)?,
3002 )?;
3003 Ok(0)
3004 })
3005}
3006
3007#[no_mangle]
3010pub unsafe extern "C" fn fs_ext4_rmdir_at(
3011 fs: *mut fs_ext4_fs_t,
3012 dir_ino: u32,
3013 dir_generation: u32,
3014 name: *const c_char,
3015 name_len: usize,
3016) -> c_int {
3017 let what = || format!("rmdir_at {dir_ino}/{}", name_for_msg(name, name_len));
3018 ino_call(fs, -1, &what, |fs| {
3019 fs.apply_rmdir_at(
3020 inode_ref(dir_ino, dir_generation),
3021 name_arg(name, name_len)?,
3022 )?;
3023 Ok(0)
3024 })
3025}
3026
3027#[no_mangle]
3030#[allow(clippy::too_many_arguments)]
3031pub unsafe extern "C" fn fs_ext4_rename_at(
3032 fs: *mut fs_ext4_fs_t,
3033 src_dir_ino: u32,
3034 src_dir_generation: u32,
3035 src_name: *const c_char,
3036 src_name_len: usize,
3037 dst_dir_ino: u32,
3038 dst_dir_generation: u32,
3039 dst_name: *const c_char,
3040 dst_name_len: usize,
3041 flags: c_int,
3042) -> c_int {
3043 let what = || {
3044 format!(
3045 "rename_at {src_dir_ino}/{} -> {dst_dir_ino}/{}",
3046 name_for_msg(src_name, src_name_len),
3047 name_for_msg(dst_name, dst_name_len)
3048 )
3049 };
3050 ino_call(fs, -1, &what, |fs| {
3051 let replace = rename_flags(flags)?;
3052 fs.apply_rename_at(
3053 inode_ref(src_dir_ino, src_dir_generation),
3054 name_arg(src_name, src_name_len)?,
3055 inode_ref(dst_dir_ino, dst_dir_generation),
3056 name_arg(dst_name, dst_name_len)?,
3057 replace,
3058 )?;
3059 Ok(0)
3060 })
3061}
3062
3063#[no_mangle]
3076pub unsafe extern "C" fn fs_ext4_mkfs(
3077 cfg: *const fs_ext4_blockdev_cfg_t,
3078 label: *const c_char,
3079 uuid: *const u8,
3080) -> c_int {
3081 ffi_guard(
3082 -EINVAL,
3083 AssertUnwindSafe(|| {
3084 clear_last_error();
3085 if cfg.is_null() {
3086 set_err_msg("null cfg", EINVAL);
3087 return -EINVAL;
3088 }
3089 let cfg = &*cfg;
3090 let Some(read_fn) = cfg.read else {
3091 set_err_msg("cfg.read is null", EINVAL);
3092 return -EINVAL;
3093 };
3094 let Some(write_fn) = cfg.write else {
3095 set_err_msg("cfg.write is null (mkfs needs writes)", EINVAL);
3096 return -EINVAL;
3097 };
3098 let flush_fn = cfg.flush; if cfg.size_bytes == 0 {
3100 set_err_msg("cfg.size_bytes is 0", EINVAL);
3101 return -EINVAL;
3102 }
3103 let block_size = if cfg.block_size == 0 {
3104 crate::mkfs::DEFAULT_BLOCK_SIZE
3105 } else {
3106 cfg.block_size
3107 };
3108
3109 let ctx_addr = cfg.context as usize;
3110
3111 let read_closure = move |offset: u64, buf: &mut [u8]| -> std::io::Result<()> {
3112 let rc = unsafe {
3113 read_fn(
3114 ctx_addr as *mut c_void,
3115 buf.as_mut_ptr() as *mut c_void,
3116 offset,
3117 buf.len() as u64,
3118 )
3119 };
3120 if rc != 0 {
3121 Err(std::io::Error::other(format!("read cb {rc}")))
3122 } else {
3123 Ok(())
3124 }
3125 };
3126 let write_closure = move |offset: u64, buf: &[u8]| -> std::io::Result<()> {
3127 let rc = unsafe {
3128 write_fn(
3129 ctx_addr as *mut c_void,
3130 buf.as_ptr() as *const c_void,
3131 offset,
3132 buf.len() as u64,
3133 )
3134 };
3135 if rc != 0 {
3136 Err(std::io::Error::other(format!("write cb {rc}")))
3137 } else {
3138 Ok(())
3139 }
3140 };
3141 let flush_closure: Option<crate::block_io::FlushCb> = flush_fn.map(|f| {
3142 let cb: crate::block_io::FlushCb = Box::new(move || -> std::io::Result<()> {
3143 let rc = unsafe { f(ctx_addr as *mut c_void) };
3144 if rc != 0 {
3145 Err(std::io::Error::other(format!("flush cb {rc}")))
3146 } else {
3147 Ok(())
3148 }
3149 });
3150 cb
3151 });
3152
3153 let dev = CallbackDevice {
3154 size: cfg.size_bytes,
3155 read: Box::new(read_closure),
3156 write: Some(Box::new(write_closure)),
3157 flush: flush_closure,
3158 };
3159
3160 let label_str: Option<&str> = if label.is_null() {
3161 None
3162 } else {
3163 Some(cstr_to_str(label))
3164 };
3165 let uuid_arr: Option<[u8; 16]> = if uuid.is_null() {
3166 None
3167 } else {
3168 let mut tmp = [0u8; 16];
3169 std::ptr::copy_nonoverlapping(uuid, tmp.as_mut_ptr(), 16);
3170 Some(tmp)
3171 };
3172
3173 match crate::mkfs::format_filesystem(
3174 &dev,
3175 label_str,
3176 uuid_arr,
3177 cfg.size_bytes,
3178 block_size,
3179 ) {
3180 Ok(()) => 0,
3181 Err(e) => {
3182 let errno = e.to_errno();
3183 set_err_from(&e, "mkfs");
3184 -errno
3185 }
3186 }
3187 }),
3188 )
3189}
3190
3191#[repr(C)]
3211#[derive(Copy, Clone)]
3212pub enum fs_ext4_fsck_phase_t {
3213 Superblock = 0,
3214 Journal = 1,
3215 Directory = 2,
3216 Inodes = 3,
3217 Finalize = 4,
3218}
3219
3220pub type fs_ext4_fsck_progress_fn = Option<
3222 unsafe extern "C" fn(
3223 context: *mut c_void,
3224 phase: fs_ext4_fsck_phase_t,
3225 phase_name: *const c_char,
3226 done: u64,
3227 total: u64,
3228 ),
3229>;
3230
3231pub type fs_ext4_fsck_finding_fn = Option<
3233 unsafe extern "C" fn(
3234 context: *mut c_void,
3235 kind: *const c_char,
3236 inode: u32,
3237 detail: *const c_char,
3238 ),
3239>;
3240
3241#[repr(C)]
3243pub struct fs_ext4_fsck_options_t {
3244 pub read_only: u8,
3247 pub replay_journal: u8,
3251 pub max_dirs: u32,
3253 pub max_entries_per_dir: u32,
3255 pub on_progress: fs_ext4_fsck_progress_fn,
3258 pub on_finding: fs_ext4_fsck_finding_fn,
3260 pub context: *mut c_void,
3263 pub repair: u8,
3267}
3268
3269#[repr(C)]
3271pub struct fs_ext4_fsck_report_t {
3272 pub inodes_visited: u64,
3273 pub directories_scanned: u64,
3274 pub entries_scanned: u64,
3275 pub anomalies_found: u64,
3279 pub was_dirty: u8,
3283 pub dirty_cleared: u8,
3285 pub repaired_count: u64,
3287 pub initial_anomalies_count: u64,
3295}
3296
3297fn anomaly_to_capi(a: &crate::fsck::Anomaly) -> (&'static str, u32, String) {
3300 use crate::fsck::Anomaly;
3301 match a {
3302 &Anomaly::LinkCountTooLow {
3303 ino,
3304 stored,
3305 observed,
3306 } => (
3307 "link_count_low",
3308 ino,
3309 format!("stored={stored} observed={observed}"),
3310 ),
3311 &Anomaly::LinkCountTooHigh {
3312 ino,
3313 stored,
3314 observed,
3315 } => (
3316 "link_count_high",
3317 ino,
3318 format!("stored={stored} observed={observed}"),
3319 ),
3320 &Anomaly::DanglingEntry {
3321 parent_ino,
3322 child_ino,
3323 observed,
3324 } => (
3325 "dangling_entry",
3326 child_ino,
3327 format!("parent_ino={parent_ino} observed={observed}"),
3328 ),
3329 &Anomaly::WrongDotDot {
3330 dir_ino,
3331 claims,
3332 actual_parent,
3333 } => (
3334 "wrong_dotdot",
3335 dir_ino,
3336 format!("claims={claims} actual_parent={actual_parent}"),
3337 ),
3338 Anomaly::BogusEntry {
3339 parent_ino,
3340 child_ino,
3341 name,
3342 } => (
3343 "bogus_entry",
3344 *child_ino,
3345 format!(
3346 "parent_ino={parent_ino} name={}",
3347 String::from_utf8_lossy(name)
3348 ),
3349 ),
3350 &Anomaly::BlockGroupFreeCountDrift {
3351 group_index,
3352 stored_blocks,
3353 observed_blocks,
3354 stored_inodes,
3355 observed_inodes,
3356 } => (
3357 "block_group_free_count_drift",
3358 group_index,
3359 format!(
3360 "stored_blocks={stored_blocks} observed_blocks={observed_blocks} \
3361 stored_inodes={stored_inodes} observed_inodes={observed_inodes}"
3362 ),
3363 ),
3364 &Anomaly::SuperblockFreeCountDrift {
3365 stored_blocks,
3366 observed_blocks,
3367 stored_inodes,
3368 observed_inodes,
3369 } => (
3370 "superblock_free_count_drift",
3371 0,
3372 format!(
3373 "stored_blocks={stored_blocks} observed_blocks={observed_blocks} \
3374 stored_inodes={stored_inodes} observed_inodes={observed_inodes}"
3375 ),
3376 ),
3377 Anomaly::DuplicateDirentForDirInode { ino, dirents } => {
3384 let detail = dirents
3385 .iter()
3386 .map(|(p, n)| format!("{p}:{n}"))
3387 .collect::<Vec<_>>()
3388 .join(",");
3389 ("duplicate_dir_inode", *ino, format!("aliases={detail}"))
3390 }
3391 &Anomaly::DirBlockChecksumMismatch {
3392 dir_ino,
3393 logical_block,
3394 htree,
3395 } => (
3396 "dir_block_checksum",
3397 dir_ino,
3398 format!("logical_block={logical_block} htree={}", u8::from(htree)),
3399 ),
3400 Anomaly::UnreadableDirectory { ino, reason } => {
3401 ("unreadable_directory", *ino, format!("reason={reason}"))
3402 }
3403 }
3404}
3405
3406#[no_mangle]
3415pub unsafe extern "C" fn fs_ext4_fsck_run(
3416 fs: *mut fs_ext4_fs_t,
3417 opts: *const fs_ext4_fsck_options_t,
3418 report: *mut fs_ext4_fsck_report_t,
3419) -> c_int {
3420 ffi_guard(
3421 -1,
3422 AssertUnwindSafe(|| {
3423 clear_last_error();
3424 if fs.is_null() || opts.is_null() || report.is_null() {
3425 set_err_msg("null fs/opts/report", EINVAL);
3426 return -1;
3427 }
3428 let fs_ref = &mut (*fs).fs;
3429 let opts_ref = &*opts;
3430 if opts_ref.read_only > 1 {
3434 set_err_msg("fsck: opts.read_only must be 0 or 1", EINVAL);
3435 return -1;
3436 }
3437 if opts_ref.replay_journal > 1 {
3438 set_err_msg("fsck: opts.replay_journal must be 0 or 1", EINVAL);
3439 return -1;
3440 }
3441 if opts_ref.repair > 1 {
3442 set_err_msg("fsck: opts.repair must be 0 or 1", EINVAL);
3443 return -1;
3444 }
3445 if opts_ref.read_only == 1 && opts_ref.repair == 1 {
3446 set_err_msg("fsck: opts.repair = 1 requires opts.read_only = 0", EINVAL);
3447 return -1;
3448 }
3449 let repair_requested = opts_ref.repair == 1;
3450
3451 std::ptr::write_bytes(report, 0, 1);
3454 let report_out = &mut *report;
3455
3456 report_out.was_dirty = if fs_ref.sb.is_clean() { 0 } else { 1 };
3458
3459 let progress_cb = opts_ref.on_progress;
3461 let finding_cb = opts_ref.on_finding;
3462 let ctx = opts_ref.context;
3463
3464 let emit_progress = |phase: crate::fsck::FsckPhase, done: u64, total: u64| {
3466 if let Some(cb) = progress_cb {
3467 let phase_c = match phase {
3468 crate::fsck::FsckPhase::Superblock => fs_ext4_fsck_phase_t::Superblock,
3469 crate::fsck::FsckPhase::Journal => fs_ext4_fsck_phase_t::Journal,
3470 crate::fsck::FsckPhase::Directory => fs_ext4_fsck_phase_t::Directory,
3471 crate::fsck::FsckPhase::Inodes => fs_ext4_fsck_phase_t::Inodes,
3472 crate::fsck::FsckPhase::Finalize => fs_ext4_fsck_phase_t::Finalize,
3473 };
3474 let name = CString::new(phase.name()).unwrap();
3477 cb(ctx, phase_c, name.as_ptr(), done, total);
3478 }
3479 };
3480
3481 if opts_ref.replay_journal != 0 {
3485 emit_progress(crate::fsck::FsckPhase::Journal, 0, 1);
3486 if let Err(e) = fs_ref.replay_journal_if_dirty() {
3487 set_err_from(&e, "fsck: replay_journal_if_dirty");
3488 return -1;
3489 }
3490 emit_progress(crate::fsck::FsckPhase::Journal, 1, 1);
3491 }
3492
3493 let max_dirs = if opts_ref.max_dirs == 0 {
3495 u32::MAX
3496 } else {
3497 opts_ref.max_dirs
3498 };
3499 let max_entries = if opts_ref.max_entries_per_dir == 0 {
3500 u32::MAX
3501 } else {
3502 opts_ref.max_entries_per_dir
3503 };
3504
3505 let emit_finding = |a: &crate::fsck::Anomaly| {
3507 if let Some(cb) = finding_cb {
3508 let (kind, ino, detail) = anomaly_to_capi(a);
3509 let kind_c = CString::new(kind).unwrap_or_else(|_| CString::new("?").unwrap());
3513 let detail_c =
3514 CString::new(detail).unwrap_or_else(|_| CString::new("").unwrap());
3515 cb(ctx, kind_c.as_ptr(), ino, detail_c.as_ptr());
3518 }
3519 };
3520
3521 let result = crate::fsck::audit_with_repair(
3522 fs_ref,
3523 max_dirs,
3524 max_entries,
3525 emit_progress,
3526 emit_finding,
3527 repair_requested,
3528 );
3529
3530 match result {
3531 Ok(audit_report) => {
3532 report_out.inodes_visited = audit_report.inodes_visited as u64;
3533 report_out.directories_scanned = audit_report.directories_scanned as u64;
3534 report_out.entries_scanned = audit_report.entries_scanned;
3535 report_out.anomalies_found = audit_report.anomalies_count;
3536 report_out.initial_anomalies_count = audit_report.initial_anomalies_count;
3537 report_out.repaired_count = audit_report.repaired_count;
3538 let post_sb_clean = if repair_requested && report_out.was_dirty == 1 {
3548 crate::superblock::Superblock::read(fs_ref.dev.as_ref())
3549 .map(|sb| sb.is_clean())
3550 .unwrap_or(false)
3551 } else {
3552 false
3553 };
3554 report_out.dirty_cleared = if post_sb_clean { 1 } else { 0 };
3555 0
3556 }
3557 Err(e) => {
3558 set_err_from(&e, "fsck: audit");
3559 -1
3560 }
3561 }
3562 }),
3563 )
3564}