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,
54 ReadOnly = 3,
56 OutOfBounds = 4,
58 Custom = 5,
60 NullArg = 6,
62 Panic = 7,
64 BadString = 8,
66}
67
68impl FsCoreErrorCode {
69 fn from_error(e: &Error) -> Self {
70 match e {
71 Error::Io(_) => FsCoreErrorCode::Io,
72 Error::ShortRead { .. } => FsCoreErrorCode::ShortRead,
73 Error::ReadOnly => FsCoreErrorCode::ReadOnly,
74 Error::OutOfBounds { .. } => FsCoreErrorCode::OutOfBounds,
75 Error::Custom(_) => FsCoreErrorCode::Custom,
76 }
77 }
78}
79
80thread_local! {
85 static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
86}
87
88pub fn set_last_error(message: impl Into<String>) {
91 let s = message.into();
92 let cs = CString::new(s.replace('\0', "?")).expect("contains no NUL after replace");
93 LAST_ERROR.with(|slot| {
94 *slot.borrow_mut() = Some(cs);
95 });
96}
97
98fn clear_last_error() {
99 LAST_ERROR.with(|slot| {
100 *slot.borrow_mut() = None;
101 });
102}
103
104#[unsafe(no_mangle)]
108pub extern "C" fn fs_core_last_error_message() -> *const c_char {
109 LAST_ERROR.with(|slot| {
110 slot.borrow()
111 .as_ref()
112 .map(|cs| cs.as_ptr())
113 .unwrap_or(ptr::null())
114 })
115}
116
117pub fn ffi_guard<F>(body: F) -> FsCoreErrorCode
120where
121 F: FnOnce() -> Result<(), Error>,
122{
123 clear_last_error();
124 match std::panic::catch_unwind(AssertUnwindSafe(body)) {
125 Ok(Ok(())) => FsCoreErrorCode::Ok,
126 Ok(Err(e)) => {
127 let code = FsCoreErrorCode::from_error(&e);
128 set_last_error(e.to_string());
129 code
130 }
131 Err(panic) => {
132 set_last_error(panic_message(&panic));
133 FsCoreErrorCode::Panic
134 }
135 }
136}
137
138fn panic_message(panic: &Box<dyn std::any::Any + Send>) -> String {
139 if let Some(s) = panic.downcast_ref::<&'static str>() {
140 return (*s).to_string();
141 }
142 if let Some(s) = panic.downcast_ref::<String>() {
143 return s.clone();
144 }
145 "panic in FFI".to_string()
146}
147
148pub struct FsCoreDevice {
155 inner: Arc<dyn BlockDevice>,
156}
157
158impl FsCoreDevice {
159 pub fn into_handle(inner: Arc<dyn BlockDevice>) -> *mut FsCoreDevice {
164 Box::into_raw(Box::new(FsCoreDevice { inner }))
165 }
166
167 pub fn inner(&self) -> &Arc<dyn BlockDevice> {
171 &self.inner
172 }
173}
174
175#[unsafe(no_mangle)]
177pub unsafe extern "C" fn fs_core_device_close(handle: *mut FsCoreDevice) {
178 if handle.is_null() {
179 return;
180 }
181 let _ = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
182 drop(Box::from_raw(handle));
183 }));
184}
185
186#[unsafe(no_mangle)]
188pub unsafe extern "C" fn fs_core_device_size_bytes(handle: *const FsCoreDevice) -> u64 {
189 if handle.is_null() {
190 return 0;
191 }
192 std::panic::catch_unwind(AssertUnwindSafe(|| unsafe { (*handle).inner.size_bytes() }))
193 .unwrap_or(0)
194}
195
196#[unsafe(no_mangle)]
198pub unsafe extern "C" fn fs_core_device_is_writable(handle: *const FsCoreDevice) -> bool {
199 if handle.is_null() {
200 return false;
201 }
202 std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
203 (*handle).inner.is_writable()
204 }))
205 .unwrap_or(false)
206}
207
208#[unsafe(no_mangle)]
211pub unsafe extern "C" fn fs_core_device_read_at(
212 handle: *const FsCoreDevice,
213 offset: u64,
214 buf: *mut u8,
215 len: usize,
216) -> FsCoreErrorCode {
217 if handle.is_null() || (buf.is_null() && len > 0) {
218 return FsCoreErrorCode::NullArg;
219 }
220 ffi_guard(|| {
221 let slice_buf = unsafe { slice::from_raw_parts_mut(buf, len) };
222 unsafe { (*handle).inner.read_at(offset, slice_buf) }
223 })
224}
225
226#[unsafe(no_mangle)]
229pub unsafe extern "C" fn fs_core_device_write_at(
230 handle: *const FsCoreDevice,
231 offset: u64,
232 buf: *const u8,
233 len: usize,
234) -> FsCoreErrorCode {
235 if handle.is_null() || (buf.is_null() && len > 0) {
236 return FsCoreErrorCode::NullArg;
237 }
238 ffi_guard(|| {
239 let slice_buf = unsafe { slice::from_raw_parts(buf, len) };
240 unsafe { (*handle).inner.write_at(offset, slice_buf) }
241 })
242}
243
244#[unsafe(no_mangle)]
246pub unsafe extern "C" fn fs_core_device_flush(handle: *const FsCoreDevice) -> FsCoreErrorCode {
247 if handle.is_null() {
248 return FsCoreErrorCode::NullArg;
249 }
250 ffi_guard(|| unsafe { (*handle).inner.flush() })
251}
252
253#[unsafe(no_mangle)]
262pub unsafe extern "C" fn fs_core_file_open(
263 path: *const c_char,
264 writable: bool,
265) -> *mut FsCoreDevice {
266 if path.is_null() {
267 set_last_error("path is null");
268 return ptr::null_mut();
269 }
270 let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
271 let cstr = unsafe { std::ffi::CStr::from_ptr(path) };
272 let s = match cstr.to_str() {
273 Ok(s) => s,
274 Err(_) => {
275 set_last_error("path is not valid UTF-8");
276 return ptr::null_mut();
277 }
278 };
279 let dev = if writable {
280 crate::file_device::FileDevice::open_rw(s)
281 } else {
282 crate::file_device::FileDevice::open(s)
283 };
284 match dev {
285 Ok(d) => FsCoreDevice::into_handle(Arc::new(d)),
286 Err(e) => {
287 set_last_error(e.to_string());
288 ptr::null_mut()
289 }
290 }
291 }));
292 match res {
293 Ok(p) => p,
294 Err(panic) => {
295 set_last_error(panic_message(&panic));
296 ptr::null_mut()
297 }
298 }
299}
300
301pub type FsCoreReadCb =
311 Option<unsafe extern "C" fn(ctx: *mut c_void, offset: u64, buf: *mut u8, len: usize) -> c_int>;
312
313pub type FsCoreWriteCb = Option<
315 unsafe extern "C" fn(ctx: *mut c_void, offset: u64, buf: *const u8, len: usize) -> c_int,
316>;
317
318pub type FsCoreFlushCb = Option<unsafe extern "C" fn(ctx: *mut c_void) -> c_int>;
320
321#[repr(C)]
323pub struct FsCoreCallbackCfg {
324 pub read: FsCoreReadCb,
325 pub write: FsCoreWriteCb,
326 pub flush: FsCoreFlushCb,
327 pub ctx: *mut c_void,
328 pub size: u64,
329}
330
331fn cb_io_err(rc: c_int, op: &str) -> io::Error {
337 io::Error::other(format!("callback {op} returned {rc}"))
338}
339
340#[unsafe(no_mangle)]
348pub unsafe extern "C" fn fs_core_device_from_callbacks(
349 cfg: *const FsCoreCallbackCfg,
350) -> *mut FsCoreDevice {
351 if cfg.is_null() {
352 set_last_error("cfg is null");
353 return ptr::null_mut();
354 }
355 let res = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
356 let cfg = &*cfg;
357 let read_fn = match cfg.read {
358 Some(f) => f,
359 None => {
360 set_last_error("cfg.read is null");
361 return ptr::null_mut();
362 }
363 };
364 let write_fn = cfg.write;
365 let flush_fn = cfg.flush;
366 let ctx_addr = cfg.ctx as usize;
367 let size = cfg.size;
368
369 let read_cb: crate::callback_device::ReadCb = Box::new(move |off, buf| {
370 let ctx = ctx_addr as *mut c_void;
371 let rc = read_fn(ctx, off, buf.as_mut_ptr(), buf.len());
372 if rc == 0 {
373 Ok(())
374 } else {
375 Err(cb_io_err(rc, "read"))
376 }
377 });
378 let write_cb: Option<crate::callback_device::WriteCb> = write_fn.map(|f| {
379 Box::new(move |off, buf: &[u8]| {
380 let ctx = ctx_addr as *mut c_void;
381 let rc = f(ctx, off, buf.as_ptr(), buf.len());
382 if rc == 0 {
383 Ok(())
384 } else {
385 Err(cb_io_err(rc, "write"))
386 }
387 }) as crate::callback_device::WriteCb
388 });
389 let flush_cb: Option<crate::callback_device::FlushCb> = flush_fn.map(|f| {
390 Box::new(move || {
391 let ctx = ctx_addr as *mut c_void;
392 let rc = f(ctx);
393 if rc == 0 {
394 Ok(())
395 } else {
396 Err(cb_io_err(rc, "flush"))
397 }
398 }) as crate::callback_device::FlushCb
399 });
400
401 let dev = CallbackDevice {
402 size,
403 read: read_cb,
404 write: write_cb,
405 flush: flush_cb,
406 };
407 FsCoreDevice::into_handle(Arc::new(dev))
408 }));
409 match res {
410 Ok(p) => p,
411 Err(panic) => {
412 set_last_error(panic_message(&panic));
413 ptr::null_mut()
414 }
415 }
416}
417
418#[unsafe(no_mangle)]
429pub unsafe extern "C" fn fs_core_device_slice_ro(
430 parent: *const FsCoreDevice,
431 start: u64,
432 length: u64,
433) -> *mut FsCoreDevice {
434 if parent.is_null() {
435 set_last_error("parent is null");
436 return ptr::null_mut();
437 }
438 let res = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
439 let parent_arc = (*parent).inner().clone();
440 let parent_read: Arc<dyn crate::block::BlockRead> = parent_arc;
443 let slice = crate::slice::OwnedSlice::new(parent_read, start, length);
444 FsCoreDevice::into_handle(Arc::new(slice))
445 }));
446 match res {
447 Ok(p) => p,
448 Err(panic) => {
449 set_last_error(panic_message(&panic));
450 ptr::null_mut()
451 }
452 }
453}
454
455#[unsafe(no_mangle)]
460pub unsafe extern "C" fn fs_core_device_slice_rw(
461 parent: *const FsCoreDevice,
462 start: u64,
463 length: u64,
464) -> *mut FsCoreDevice {
465 if parent.is_null() {
466 set_last_error("parent is null");
467 return ptr::null_mut();
468 }
469 let res = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
470 let parent_arc = (*parent).inner().clone();
471 let slice = crate::slice::OwnedRwSlice::new(parent_arc, start, length);
472 FsCoreDevice::into_handle(Arc::new(slice))
473 }));
474 match res {
475 Ok(p) => p,
476 Err(panic) => {
477 set_last_error(panic_message(&panic));
478 ptr::null_mut()
479 }
480 }
481}
482
483#[cfg(test)]
489mod tests {
490 use super::*;
491 use std::fs::File;
492 use std::io::Write;
493
494 fn tmp_image(bytes: &[u8]) -> String {
495 use std::sync::atomic::{AtomicU32, Ordering};
496 static C: AtomicU32 = AtomicU32::new(0);
497 let n = C.fetch_add(1, Ordering::Relaxed);
498 let p = std::env::temp_dir()
499 .join(format!("fs_core_ffi_{}_{n}.img", std::process::id()))
500 .to_string_lossy()
501 .into_owned();
502 File::create(&p).unwrap().write_all(bytes).unwrap();
503 p
504 }
505
506 #[test]
507 fn open_read_close_round_trip() {
508 let path = tmp_image(b"hello, fs-core ffi");
509 let cpath = CString::new(path.as_str()).unwrap();
510 let h = unsafe { fs_core_file_open(cpath.as_ptr(), false) };
511 assert!(!h.is_null(), "open failed");
512
513 unsafe {
514 assert_eq!(fs_core_device_size_bytes(h), 18);
515 assert!(!fs_core_device_is_writable(h));
516
517 let mut buf = [0u8; 5];
518 let rc = fs_core_device_read_at(h, 0, buf.as_mut_ptr(), buf.len());
519 assert_eq!(rc, FsCoreErrorCode::Ok);
520 assert_eq!(&buf, b"hello");
521
522 let rc = fs_core_device_write_at(h, 0, b"x".as_ptr(), 1);
524 assert_eq!(rc, FsCoreErrorCode::ReadOnly);
525
526 fs_core_device_close(h);
527 }
528 let _ = std::fs::remove_file(&path);
529 }
530
531 #[test]
532 fn null_args_return_null_arg() {
533 let mut buf = [0u8; 4];
534 let rc = unsafe { fs_core_device_read_at(ptr::null(), 0, buf.as_mut_ptr(), buf.len()) };
535 assert_eq!(rc, FsCoreErrorCode::NullArg);
536 let rc = unsafe { fs_core_device_flush(ptr::null()) };
537 assert_eq!(rc, FsCoreErrorCode::NullArg);
538 }
539
540 #[test]
541 fn last_error_populated_on_open_failure() {
542 let cpath = CString::new("/path/that/does/not/exist/we/hope").unwrap();
543 let h = unsafe { fs_core_file_open(cpath.as_ptr(), false) };
544 assert!(h.is_null());
545 let msg = fs_core_last_error_message();
546 assert!(!msg.is_null());
547 let s = unsafe { std::ffi::CStr::from_ptr(msg).to_string_lossy().into_owned() };
548 assert!(!s.is_empty(), "expected an error message");
549 }
550
551 use std::sync::{Arc as StdArc, Mutex as StdMutex};
554
555 struct CbState {
556 data: Vec<u8>,
557 flushed: u32,
558 }
559
560 unsafe extern "C" fn t_read(ctx: *mut c_void, offset: u64, buf: *mut u8, len: usize) -> c_int {
562 let st = unsafe { &mut *(ctx as *mut CbState) };
563 let off = offset as usize;
564 if off + len > st.data.len() {
565 return 5; }
567 unsafe {
568 std::ptr::copy_nonoverlapping(st.data.as_ptr().add(off), buf, len);
569 }
570 0
571 }
572 unsafe extern "C" fn t_write(
573 ctx: *mut c_void,
574 offset: u64,
575 buf: *const u8,
576 len: usize,
577 ) -> c_int {
578 let st = unsafe { &mut *(ctx as *mut CbState) };
579 let off = offset as usize;
580 if off + len > st.data.len() {
581 return 5;
582 }
583 unsafe {
584 std::ptr::copy_nonoverlapping(buf, st.data.as_mut_ptr().add(off), len);
585 }
586 0
587 }
588 unsafe extern "C" fn t_flush(ctx: *mut c_void) -> c_int {
589 let st = unsafe { &mut *(ctx as *mut CbState) };
590 st.flushed += 1;
591 0
592 }
593
594 #[test]
595 fn callback_device_round_trip_rw() {
596 let mut st = Box::new(CbState {
597 data: vec![0u8; 32],
598 flushed: 0,
599 });
600 for (i, b) in st.data.iter_mut().enumerate() {
601 *b = i as u8;
602 }
603 let ctx = &mut *st as *mut CbState as *mut c_void;
604
605 let cfg = FsCoreCallbackCfg {
606 read: Some(t_read),
607 write: Some(t_write),
608 flush: Some(t_flush),
609 ctx,
610 size: 32,
611 };
612 let h = unsafe { fs_core_device_from_callbacks(&cfg) };
613 assert!(!h.is_null(), "device_from_callbacks returned NULL");
614
615 unsafe {
616 assert_eq!(fs_core_device_size_bytes(h), 32);
617 assert!(fs_core_device_is_writable(h));
618
619 let mut buf = [0u8; 4];
620 let rc = fs_core_device_read_at(h, 4, buf.as_mut_ptr(), buf.len());
621 assert_eq!(rc, FsCoreErrorCode::Ok);
622 assert_eq!(buf, [4, 5, 6, 7]);
623
624 let payload = [0xDE, 0xAD, 0xBE, 0xEF];
625 let rc = fs_core_device_write_at(h, 8, payload.as_ptr(), payload.len());
626 assert_eq!(rc, FsCoreErrorCode::Ok);
627
628 let rc = fs_core_device_flush(h);
629 assert_eq!(rc, FsCoreErrorCode::Ok);
630
631 let mut readback = [0u8; 4];
632 let rc = fs_core_device_read_at(h, 8, readback.as_mut_ptr(), readback.len());
633 assert_eq!(rc, FsCoreErrorCode::Ok);
634 assert_eq!(readback, payload);
635
636 fs_core_device_close(h);
637 }
638 assert_eq!(st.flushed, 1);
639 assert_eq!(&st.data[8..12], &[0xDE, 0xAD, 0xBE, 0xEF]);
640 }
641
642 #[test]
643 fn callback_device_readonly_when_write_null() {
644 let mut st = Box::new(CbState {
645 data: vec![0xAAu8; 16],
646 flushed: 0,
647 });
648 let ctx = &mut *st as *mut CbState as *mut c_void;
649 let cfg = FsCoreCallbackCfg {
650 read: Some(t_read),
651 write: None,
652 flush: None,
653 ctx,
654 size: 16,
655 };
656 let h = unsafe { fs_core_device_from_callbacks(&cfg) };
657 assert!(!h.is_null());
658 unsafe {
659 assert!(!fs_core_device_is_writable(h));
660 let rc = fs_core_device_write_at(h, 0, [1u8].as_ptr(), 1);
661 assert_eq!(rc, FsCoreErrorCode::ReadOnly);
662 assert_eq!(fs_core_device_flush(h), FsCoreErrorCode::Ok);
664 fs_core_device_close(h);
665 }
666 let _ = StdArc::new(StdMutex::new(0u8));
668 }
669
670 #[test]
671 fn callback_device_null_cfg_returns_null() {
672 let h = unsafe { fs_core_device_from_callbacks(ptr::null()) };
673 assert!(h.is_null());
674 let msg = fs_core_last_error_message();
675 assert!(!msg.is_null());
676 }
677}