1#![allow(clippy::missing_safety_doc)]
26
27use crate::block::BlockDevice;
28use crate::callback_device::CallbackDevice;
29use crate::error::Error;
30use std::cell::RefCell;
31use std::ffi::{c_char, c_int, c_void, CString};
32use std::io;
33use std::panic::AssertUnwindSafe;
34use std::ptr;
35use std::slice;
36use std::sync::Arc;
37
38#[repr(i32)]
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum FsCoreErrorCode {
48 Ok = 0,
50 Io = 1,
52 ShortRead = 2,
56 ReadOnly = 3,
58 OutOfBounds = 4,
72 Custom = 5,
74 NullArg = 6,
76 Panic = 7,
78 BadString = 8,
92}
93
94impl FsCoreErrorCode {
95 fn from_error(e: &Error) -> Self {
96 match e {
97 Error::Io(_) => FsCoreErrorCode::Io,
98 Error::ShortRead { .. } => FsCoreErrorCode::ShortRead,
99 Error::ReadOnly => FsCoreErrorCode::ReadOnly,
100 Error::OutOfBounds { .. } => FsCoreErrorCode::OutOfBounds,
101 Error::Custom(_) => FsCoreErrorCode::Custom,
102 }
103 }
104}
105
106thread_local! {
111 static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
112}
113
114pub fn set_last_error(message: impl Into<String>) {
117 let s = message.into();
118 let cs = CString::new(s.replace('\0', "?")).expect("contains no NUL after replace");
119 LAST_ERROR.with(|slot| {
120 *slot.borrow_mut() = Some(cs);
121 });
122}
123
124fn clear_last_error() {
125 LAST_ERROR.with(|slot| {
126 *slot.borrow_mut() = None;
127 });
128}
129
130#[unsafe(no_mangle)]
134pub extern "C" fn fs_core_last_error_message() -> *const c_char {
135 LAST_ERROR.with(|slot| {
136 slot.borrow()
137 .as_ref()
138 .map(|cs| cs.as_ptr())
139 .unwrap_or(ptr::null())
140 })
141}
142
143pub fn ffi_guard<F>(body: F) -> FsCoreErrorCode
146where
147 F: FnOnce() -> Result<(), Error>,
148{
149 clear_last_error();
150 match std::panic::catch_unwind(AssertUnwindSafe(body)) {
151 Ok(Ok(())) => FsCoreErrorCode::Ok,
152 Ok(Err(e)) => {
153 let code = FsCoreErrorCode::from_error(&e);
154 set_last_error(e.to_string());
155 code
156 }
157 Err(panic) => {
158 set_last_error(panic_message(&panic));
159 FsCoreErrorCode::Panic
160 }
161 }
162}
163
164pub fn ffi_guard_or<T, F>(fail: T, body: F) -> T
201where
202 F: FnOnce() -> T,
203{
204 clear_last_error();
205 match std::panic::catch_unwind(AssertUnwindSafe(body)) {
206 Ok(value) => value,
207 Err(panic) => {
208 set_last_error(panic_message(&panic));
209 fail
210 }
211 }
212}
213
214fn panic_message(panic: &Box<dyn std::any::Any + Send>) -> String {
215 if let Some(s) = panic.downcast_ref::<&'static str>() {
216 return (*s).to_string();
217 }
218 if let Some(s) = panic.downcast_ref::<String>() {
219 return s.clone();
220 }
221 "panic in FFI".to_string()
222}
223
224pub struct FsCoreDevice {
231 inner: Arc<dyn BlockDevice>,
232}
233
234impl FsCoreDevice {
235 pub fn into_handle(inner: Arc<dyn BlockDevice>) -> *mut FsCoreDevice {
240 Box::into_raw(Box::new(FsCoreDevice { inner }))
241 }
242
243 pub fn inner(&self) -> &Arc<dyn BlockDevice> {
247 &self.inner
248 }
249}
250
251#[unsafe(no_mangle)]
253pub unsafe extern "C" fn fs_core_device_close(handle: *mut FsCoreDevice) {
254 if handle.is_null() {
255 return;
256 }
257 ffi_guard_or((), || unsafe {
258 drop(Box::from_raw(handle));
259 });
260}
261
262#[unsafe(no_mangle)]
264pub unsafe extern "C" fn fs_core_device_size_bytes(handle: *const FsCoreDevice) -> u64 {
265 if handle.is_null() {
266 return 0;
267 }
268 ffi_guard_or(0, || unsafe { (*handle).inner.size_bytes() })
269}
270
271#[unsafe(no_mangle)]
273pub unsafe extern "C" fn fs_core_device_is_writable(handle: *const FsCoreDevice) -> bool {
274 if handle.is_null() {
275 return false;
276 }
277 ffi_guard_or(false, || unsafe { (*handle).inner.is_writable() })
278}
279
280#[unsafe(no_mangle)]
283pub unsafe extern "C" fn fs_core_device_read_at(
284 handle: *const FsCoreDevice,
285 offset: u64,
286 buf: *mut u8,
287 len: usize,
288) -> FsCoreErrorCode {
289 if handle.is_null() || buf.is_null() {
295 return FsCoreErrorCode::NullArg;
296 }
297 ffi_guard(|| {
298 let slice_buf = unsafe { slice::from_raw_parts_mut(buf, len) };
299 unsafe { (*handle).inner.read_at(offset, slice_buf) }
300 })
301}
302
303#[unsafe(no_mangle)]
306pub unsafe extern "C" fn fs_core_device_write_at(
307 handle: *const FsCoreDevice,
308 offset: u64,
309 buf: *const u8,
310 len: usize,
311) -> FsCoreErrorCode {
312 if handle.is_null() || buf.is_null() {
314 return FsCoreErrorCode::NullArg;
315 }
316 ffi_guard(|| {
317 let slice_buf = unsafe { slice::from_raw_parts(buf, len) };
318 unsafe { (*handle).inner.write_at(offset, slice_buf) }
319 })
320}
321
322#[unsafe(no_mangle)]
324pub unsafe extern "C" fn fs_core_device_flush(handle: *const FsCoreDevice) -> FsCoreErrorCode {
325 if handle.is_null() {
326 return FsCoreErrorCode::NullArg;
327 }
328 ffi_guard(|| unsafe { (*handle).inner.flush() })
329}
330
331#[unsafe(no_mangle)]
340pub unsafe extern "C" fn fs_core_file_open(
341 path: *const c_char,
342 writable: bool,
343) -> *mut FsCoreDevice {
344 if path.is_null() {
345 set_last_error("path is null");
346 return ptr::null_mut();
347 }
348 ffi_guard_or(ptr::null_mut(), || {
349 let cstr = unsafe { std::ffi::CStr::from_ptr(path) };
350 let s = match cstr.to_str() {
351 Ok(s) => s,
352 Err(_) => {
353 set_last_error("path is not valid UTF-8");
354 return ptr::null_mut();
355 }
356 };
357 let dev = if writable {
358 crate::file_device::FileDevice::open_rw(s)
359 } else {
360 crate::file_device::FileDevice::open(s)
361 };
362 match dev {
363 Ok(d) => FsCoreDevice::into_handle(Arc::new(d)),
364 Err(e) => {
365 set_last_error(e.to_string());
366 ptr::null_mut()
367 }
368 }
369 })
370}
371
372pub type FsCoreReadCb =
382 Option<unsafe extern "C" fn(ctx: *mut c_void, offset: u64, buf: *mut u8, len: usize) -> c_int>;
383
384pub type FsCoreWriteCb = Option<
386 unsafe extern "C" fn(ctx: *mut c_void, offset: u64, buf: *const u8, len: usize) -> c_int,
387>;
388
389pub type FsCoreFlushCb = Option<unsafe extern "C" fn(ctx: *mut c_void) -> c_int>;
391
392#[repr(C)]
394pub struct FsCoreCallbackCfg {
395 pub read: FsCoreReadCb,
396 pub write: FsCoreWriteCb,
397 pub flush: FsCoreFlushCb,
398 pub ctx: *mut c_void,
399 pub size: u64,
400}
401
402fn cb_io_err(rc: c_int, op: &str) -> io::Error {
404 io::Error::other(format!("callback {op} returned {rc}"))
405}
406
407fn cb_result(rc: c_int, op: &'static str) -> io::Result<()> {
418 if rc == 0 {
419 Ok(())
420 } else {
421 Err(cb_io_err(rc, op))
422 }
423}
424
425#[unsafe(no_mangle)]
433pub unsafe extern "C" fn fs_core_device_from_callbacks(
434 cfg: *const FsCoreCallbackCfg,
435) -> *mut FsCoreDevice {
436 if cfg.is_null() {
437 set_last_error("cfg is null");
438 return ptr::null_mut();
439 }
440 ffi_guard_or(ptr::null_mut(), || unsafe {
441 let cfg = &*cfg;
442 let read_fn = match cfg.read {
443 Some(f) => f,
444 None => {
445 set_last_error("cfg.read is null");
446 return ptr::null_mut();
447 }
448 };
449 let write_fn = cfg.write;
450 let flush_fn = cfg.flush;
451 let ctx_addr = cfg.ctx as usize;
458 let size = cfg.size;
459
460 let read_cb: crate::callback_device::ReadCb = Box::new(move |off, buf| {
461 let ctx = ctx_addr as *mut c_void;
462 cb_result(read_fn(ctx, off, buf.as_mut_ptr(), buf.len()), "read")
463 });
464 let write_cb: Option<crate::callback_device::WriteCb> = write_fn.map(|f| {
465 Box::new(move |off, buf: &[u8]| {
466 let ctx = ctx_addr as *mut c_void;
467 cb_result(f(ctx, off, buf.as_ptr(), buf.len()), "write")
468 }) as crate::callback_device::WriteCb
469 });
470 let flush_cb: Option<crate::callback_device::FlushCb> = flush_fn.map(|f| {
471 Box::new(move || {
472 let ctx = ctx_addr as *mut c_void;
473 cb_result(f(ctx), "flush")
474 }) as crate::callback_device::FlushCb
475 });
476
477 let dev = CallbackDevice {
478 size,
479 read: read_cb,
480 write: write_cb,
481 flush: flush_cb,
482 };
483 FsCoreDevice::into_handle(Arc::new(dev))
484 })
485}
486
487#[unsafe(no_mangle)]
498pub unsafe extern "C" fn fs_core_device_slice_ro(
499 parent: *const FsCoreDevice,
500 start: u64,
501 length: u64,
502) -> *mut FsCoreDevice {
503 if parent.is_null() {
504 set_last_error("parent is null");
505 return ptr::null_mut();
506 }
507 ffi_guard_or(ptr::null_mut(), || unsafe {
508 let parent_arc = (*parent).inner().clone();
509 let parent_read: Arc<dyn crate::block::BlockRead> = parent_arc;
512 let slice = crate::slice::OwnedSlice::new(parent_read, start, length);
513 FsCoreDevice::into_handle(Arc::new(slice))
514 })
515}
516
517#[unsafe(no_mangle)]
522pub unsafe extern "C" fn fs_core_device_slice_rw(
523 parent: *const FsCoreDevice,
524 start: u64,
525 length: u64,
526) -> *mut FsCoreDevice {
527 if parent.is_null() {
528 set_last_error("parent is null");
529 return ptr::null_mut();
530 }
531 ffi_guard_or(ptr::null_mut(), || unsafe {
532 let parent_arc = (*parent).inner().clone();
533 let slice = crate::slice::OwnedRwSlice::new(parent_arc, start, length);
534 FsCoreDevice::into_handle(Arc::new(slice))
535 })
536}
537
538#[cfg(test)]
544mod tests {
545 use super::*;
546 use std::fs::File;
547 use std::io::Write;
548
549 fn tmp_image(bytes: &[u8]) -> String {
550 use std::sync::atomic::{AtomicU32, Ordering};
551 static C: AtomicU32 = AtomicU32::new(0);
552 let n = C.fetch_add(1, Ordering::Relaxed);
553 let p = std::env::temp_dir()
554 .join(format!("fs_core_ffi_{}_{n}.img", std::process::id()))
555 .to_string_lossy()
556 .into_owned();
557 File::create(&p).unwrap().write_all(bytes).unwrap();
558 p
559 }
560
561 #[test]
562 fn open_read_close_round_trip() {
563 let path = tmp_image(b"hello, fs-core ffi");
564 let cpath = CString::new(path.as_str()).unwrap();
565 let h = unsafe { fs_core_file_open(cpath.as_ptr(), false) };
566 assert!(!h.is_null(), "open failed");
567
568 unsafe {
569 assert_eq!(fs_core_device_size_bytes(h), 18);
570 assert!(!fs_core_device_is_writable(h));
571
572 let mut buf = [0u8; 5];
573 let rc = fs_core_device_read_at(h, 0, buf.as_mut_ptr(), buf.len());
574 assert_eq!(rc, FsCoreErrorCode::Ok);
575 assert_eq!(&buf, b"hello");
576
577 let rc = fs_core_device_write_at(h, 0, b"x".as_ptr(), 1);
579 assert_eq!(rc, FsCoreErrorCode::ReadOnly);
580
581 fs_core_device_close(h);
582 }
583 let _ = std::fs::remove_file(&path);
584 }
585
586 #[test]
587 fn null_args_return_null_arg() {
588 let mut buf = [0u8; 4];
589 let rc = unsafe { fs_core_device_read_at(ptr::null(), 0, buf.as_mut_ptr(), buf.len()) };
590 assert_eq!(rc, FsCoreErrorCode::NullArg);
591 let rc = unsafe { fs_core_device_flush(ptr::null()) };
592 assert_eq!(rc, FsCoreErrorCode::NullArg);
593 }
594
595 #[test]
596 fn last_error_populated_on_open_failure() {
597 let cpath = CString::new("/path/that/does/not/exist/we/hope").unwrap();
598 let h = unsafe { fs_core_file_open(cpath.as_ptr(), false) };
599 assert!(h.is_null());
600 let msg = fs_core_last_error_message();
601 assert!(!msg.is_null());
602 let s = unsafe { std::ffi::CStr::from_ptr(msg).to_string_lossy().into_owned() };
603 assert!(!s.is_empty(), "expected an error message");
604 }
605
606 use std::sync::{Arc as StdArc, Mutex as StdMutex};
609
610 struct CbState {
611 data: Vec<u8>,
612 flushed: u32,
613 }
614
615 unsafe extern "C" fn t_read(ctx: *mut c_void, offset: u64, buf: *mut u8, len: usize) -> c_int {
617 let st = unsafe { &mut *(ctx as *mut CbState) };
618 let off = offset as usize;
619 if off + len > st.data.len() {
620 return 5; }
622 unsafe {
623 std::ptr::copy_nonoverlapping(st.data.as_ptr().add(off), buf, len);
624 }
625 0
626 }
627 unsafe extern "C" fn t_write(
628 ctx: *mut c_void,
629 offset: u64,
630 buf: *const u8,
631 len: usize,
632 ) -> c_int {
633 let st = unsafe { &mut *(ctx as *mut CbState) };
634 let off = offset as usize;
635 if off + len > st.data.len() {
636 return 5;
637 }
638 unsafe {
639 std::ptr::copy_nonoverlapping(buf, st.data.as_mut_ptr().add(off), len);
640 }
641 0
642 }
643 unsafe extern "C" fn t_flush(ctx: *mut c_void) -> c_int {
644 let st = unsafe { &mut *(ctx as *mut CbState) };
645 st.flushed += 1;
646 0
647 }
648
649 #[test]
650 fn callback_device_round_trip_rw() {
651 let mut st = Box::new(CbState {
652 data: vec![0u8; 32],
653 flushed: 0,
654 });
655 for (i, b) in st.data.iter_mut().enumerate() {
656 *b = i as u8;
657 }
658 let ctx = &mut *st as *mut CbState as *mut c_void;
659
660 let cfg = FsCoreCallbackCfg {
661 read: Some(t_read),
662 write: Some(t_write),
663 flush: Some(t_flush),
664 ctx,
665 size: 32,
666 };
667 let h = unsafe { fs_core_device_from_callbacks(&cfg) };
668 assert!(!h.is_null(), "device_from_callbacks returned NULL");
669
670 unsafe {
671 assert_eq!(fs_core_device_size_bytes(h), 32);
672 assert!(fs_core_device_is_writable(h));
673
674 let mut buf = [0u8; 4];
675 let rc = fs_core_device_read_at(h, 4, buf.as_mut_ptr(), buf.len());
676 assert_eq!(rc, FsCoreErrorCode::Ok);
677 assert_eq!(buf, [4, 5, 6, 7]);
678
679 let payload = [0xDE, 0xAD, 0xBE, 0xEF];
680 let rc = fs_core_device_write_at(h, 8, payload.as_ptr(), payload.len());
681 assert_eq!(rc, FsCoreErrorCode::Ok);
682
683 let rc = fs_core_device_flush(h);
684 assert_eq!(rc, FsCoreErrorCode::Ok);
685
686 let mut readback = [0u8; 4];
687 let rc = fs_core_device_read_at(h, 8, readback.as_mut_ptr(), readback.len());
688 assert_eq!(rc, FsCoreErrorCode::Ok);
689 assert_eq!(readback, payload);
690
691 fs_core_device_close(h);
692 }
693 assert_eq!(st.flushed, 1);
694 assert_eq!(&st.data[8..12], &[0xDE, 0xAD, 0xBE, 0xEF]);
695 }
696
697 #[test]
698 fn callback_device_readonly_when_write_null() {
699 let mut st = Box::new(CbState {
700 data: vec![0xAAu8; 16],
701 flushed: 0,
702 });
703 let ctx = &mut *st as *mut CbState as *mut c_void;
704 let cfg = FsCoreCallbackCfg {
705 read: Some(t_read),
706 write: None,
707 flush: None,
708 ctx,
709 size: 16,
710 };
711 let h = unsafe { fs_core_device_from_callbacks(&cfg) };
712 assert!(!h.is_null());
713 unsafe {
714 assert!(!fs_core_device_is_writable(h));
715 let rc = fs_core_device_write_at(h, 0, [1u8].as_ptr(), 1);
716 assert_eq!(rc, FsCoreErrorCode::ReadOnly);
717 assert_eq!(fs_core_device_flush(h), FsCoreErrorCode::Ok);
719 fs_core_device_close(h);
720 }
721 let _ = StdArc::new(StdMutex::new(0u8));
723 }
724
725 #[test]
726 fn callback_device_null_cfg_returns_null() {
727 let h = unsafe { fs_core_device_from_callbacks(ptr::null()) };
728 assert!(h.is_null());
729 let msg = fs_core_last_error_message();
730 assert!(!msg.is_null());
731 }
732}
733
734#[cfg(test)]
735mod panic_message_tests {
736 use super::*;
737 use crate::block::{BlockDevice, BlockRead};
738
739 struct Panicking;
746
747 impl BlockRead for Panicking {
748 fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> Result<(), Error> {
749 panic!("read_at exploded")
750 }
751 fn size_bytes(&self) -> u64 {
752 panic!("size_bytes exploded")
753 }
754 }
755 impl BlockDevice for Panicking {
756 fn is_writable(&self) -> bool {
757 panic!("is_writable exploded")
758 }
759 }
760
761 fn handle() -> *mut FsCoreDevice {
762 FsCoreDevice::into_handle(std::sync::Arc::new(Panicking))
763 }
764
765 fn last_error() -> Option<String> {
766 let p = fs_core_last_error_message();
767 if p.is_null() {
768 return None;
769 }
770 Some(
771 unsafe { std::ffi::CStr::from_ptr(p) }
772 .to_string_lossy()
773 .into_owned(),
774 )
775 }
776
777 #[test]
785 fn a_panic_computing_the_size_is_reported_not_just_swallowed() {
786 clear_last_error();
787 let h = handle();
788 let size = unsafe { fs_core_device_size_bytes(h) };
789 assert_eq!(size, 0, "the fallback value is still returned");
790 let msg = last_error().expect("a caught panic must leave a message");
791 assert!(
792 msg.contains("size_bytes exploded"),
793 "the message should carry the panic's own text, got: {msg}"
794 );
795 unsafe { fs_core_device_close(h) };
796 }
797
798 #[test]
801 fn a_panic_probing_writability_is_reported() {
802 clear_last_error();
803 let h = handle();
804 let writable = unsafe { fs_core_device_is_writable(h) };
805 assert!(!writable, "the fallback value is still returned");
806 assert!(
807 last_error().is_some(),
808 "a caught panic must leave a message"
809 );
810 unsafe { fs_core_device_close(h) };
811 }
812
813 #[test]
816 fn a_successful_call_clears_the_previous_error() {
817 let h = handle();
818 let _ = unsafe { fs_core_device_size_bytes(h) };
819 assert!(last_error().is_some(), "setup: an error is recorded");
820 unsafe { fs_core_device_close(h) };
821
822 struct Sixteen;
823 impl BlockRead for Sixteen {
824 fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> Result<(), Error> {
825 Ok(())
826 }
827 fn size_bytes(&self) -> u64 {
828 16
829 }
830 }
831 impl BlockDevice for Sixteen {}
832 let h2 = FsCoreDevice::into_handle(std::sync::Arc::new(Sixteen));
833 assert_eq!(unsafe { fs_core_device_size_bytes(h2) }, 16);
834 assert!(
835 last_error().is_none(),
836 "a call that worked must not leave the previous panic's message in place"
837 );
838 unsafe { fs_core_device_close(h2) };
839 }
840}