1use std::ffi::{CStr, CString};
17use std::os::raw::c_char;
18use std::ptr;
19use std::sync::Arc;
20
21use crate::block_store::{DeviceBlockStore, DiskBlockStore, MemoryBlockStore};
22use crate::cached_store::CachedBlockStore;
23use crate::crypto::ChaChaEngine;
24use crate::error::FsErrorCode;
25use crate::fs::FilesystemCore;
26use crate::model::DEFAULT_BLOCK_SIZE;
27use crate::network_store::NetworkBlockStoreConfig;
28
29pub struct FsHandle {
31 core: FilesystemCore,
32}
33
34#[no_mangle]
47pub unsafe extern "C" fn fs_create(
48 total_blocks: u64,
49 master_key: *const u8,
50 master_key_len: usize,
51) -> *mut FsHandle {
52 if master_key.is_null() || master_key_len == 0 {
53 return ptr::null_mut();
54 }
55 let key_slice = unsafe { std::slice::from_raw_parts(master_key, master_key_len) };
56
57 let store = Arc::new(MemoryBlockStore::new(DEFAULT_BLOCK_SIZE, total_blocks));
58 let crypto = match ChaChaEngine::new(key_slice) {
59 Ok(c) => Arc::new(c),
60 Err(_) => return ptr::null_mut(),
61 };
62
63 let core = FilesystemCore::new(store, crypto);
64 let handle = Box::new(FsHandle { core });
65 Box::into_raw(handle)
66}
67
68#[no_mangle]
84pub unsafe extern "C" fn fs_create_disk(
85 path: *const c_char,
86 total_blocks: u64,
87 block_size: u32,
88 create_new: i32,
89 master_key: *const u8,
90 master_key_len: usize,
91) -> *mut FsHandle {
92 if master_key.is_null() || master_key_len == 0 {
93 return ptr::null_mut();
94 }
95 let path_str = match unsafe { unsafe_cstr_to_str(path) } {
96 Some(s) => s,
97 None => return ptr::null_mut(),
98 };
99 let key_slice = unsafe { std::slice::from_raw_parts(master_key, master_key_len) };
100
101 let bs = if block_size == 0 {
102 DEFAULT_BLOCK_SIZE
103 } else {
104 block_size as usize
105 };
106
107 let store = if create_new != 0 {
108 match DiskBlockStore::create(path_str, bs, total_blocks) {
109 Ok(s) => Arc::new(s),
110 Err(_) => return ptr::null_mut(),
111 }
112 } else {
113 match DiskBlockStore::open(path_str, bs, total_blocks) {
114 Ok(s) => Arc::new(s),
115 Err(_) => return ptr::null_mut(),
116 }
117 };
118
119 let crypto = match ChaChaEngine::new(key_slice) {
120 Ok(c) => Arc::new(c),
121 Err(_) => return ptr::null_mut(),
122 };
123
124 let core = FilesystemCore::new(store, crypto);
125 let handle = Box::new(FsHandle { core });
126 Box::into_raw(handle)
127}
128
129#[no_mangle]
145pub unsafe extern "C" fn fs_create_device(
146 path: *const c_char,
147 total_blocks: u64,
148 block_size: u32,
149 initialize: i32,
150 master_key: *const u8,
151 master_key_len: usize,
152) -> *mut FsHandle {
153 if master_key.is_null() || master_key_len == 0 {
154 return ptr::null_mut();
155 }
156 let path_str = match unsafe { unsafe_cstr_to_str(path) } {
157 Some(s) => s,
158 None => return ptr::null_mut(),
159 };
160 let key_slice = unsafe { std::slice::from_raw_parts(master_key, master_key_len) };
161
162 let bs = if block_size == 0 {
163 DEFAULT_BLOCK_SIZE
164 } else {
165 block_size as usize
166 };
167
168 let store = if initialize != 0 {
169 match DeviceBlockStore::initialize(path_str, bs, total_blocks) {
170 Ok(s) => Arc::new(s),
171 Err(_) => return ptr::null_mut(),
172 }
173 } else {
174 match DeviceBlockStore::open(path_str, bs, total_blocks) {
175 Ok(s) => Arc::new(s),
176 Err(_) => return ptr::null_mut(),
177 }
178 };
179
180 let crypto = match ChaChaEngine::new(key_slice) {
181 Ok(c) => Arc::new(c),
182 Err(_) => return ptr::null_mut(),
183 };
184
185 let core = FilesystemCore::new(store, crypto);
186 let handle = Box::new(FsHandle { core });
187 Box::into_raw(handle)
188}
189
190#[no_mangle]
221pub unsafe extern "C" fn fs_create_network(
222 addr: *const c_char,
223 server_name: *const c_char,
224 ca_cert_path: *const c_char,
225 cache_blocks: u32,
226 auth_token: *const u8,
227 auth_token_len: usize,
228 master_key: *const u8,
229 master_key_len: usize,
230) -> *mut FsHandle {
231 if master_key.is_null() || master_key_len == 0 {
232 return ptr::null_mut();
233 }
234 let addr_str = match unsafe { unsafe_cstr_to_str(addr) } {
235 Some(s) => s,
236 None => return ptr::null_mut(),
237 };
238 let sni_str = match unsafe { unsafe_cstr_to_str(server_name) } {
239 Some(s) => s,
240 None => return ptr::null_mut(),
241 };
242 let ca_str = match unsafe { unsafe_cstr_to_str(ca_cert_path) } {
243 Some(s) => s,
244 None => return ptr::null_mut(),
245 };
246 let key_slice = unsafe { std::slice::from_raw_parts(master_key, master_key_len) };
247
248 let config = if !auth_token.is_null() && auth_token_len == 32 {
249 let token_slice = unsafe { std::slice::from_raw_parts(auth_token, 32) };
250 let mut token = [0u8; 32];
251 token.copy_from_slice(token_slice);
252 NetworkBlockStoreConfig::new(addr_str, sni_str)
253 .ca_cert(ca_str)
254 .auth_token_raw(token)
255 } else {
256 NetworkBlockStoreConfig::new(addr_str, sni_str)
257 .ca_cert(ca_str)
258 .auth_token(key_slice)
259 };
260
261 let net_store = match crate::network_store::NetworkBlockStore::from_config(config) {
262 Ok(s) => s,
263 Err(_) => return ptr::null_mut(),
264 };
265
266 let cap = if cache_blocks == 0 {
267 256
268 } else {
269 cache_blocks as usize
270 };
271 let store = Arc::new(CachedBlockStore::new(net_store, cap));
272
273 let crypto = match ChaChaEngine::new(key_slice) {
274 Ok(c) => Arc::new(c),
275 Err(_) => return ptr::null_mut(),
276 };
277
278 let core = FilesystemCore::new(store, crypto);
279 let handle = Box::new(FsHandle { core });
280 Box::into_raw(handle)
281}
282
283#[no_mangle]
289pub unsafe extern "C" fn fs_destroy(handle: *mut FsHandle) {
290 if !handle.is_null() {
291 unsafe {
292 drop(Box::from_raw(handle));
293 }
294 }
295}
296
297#[no_mangle]
304pub unsafe extern "C" fn fs_init_filesystem(handle: *mut FsHandle) -> i32 {
305 let Some(h) = (unsafe { handle.as_mut() }) else {
306 return FsErrorCode::InvalidArgument as i32;
307 };
308 match h.core.init_filesystem() {
309 Ok(()) => FsErrorCode::Ok as i32,
310 Err(ref e) => FsErrorCode::from(e) as i32,
311 }
312}
313
314#[no_mangle]
319pub unsafe extern "C" fn fs_open(handle: *mut FsHandle) -> i32 {
320 let Some(h) = (unsafe { handle.as_mut() }) else {
321 return FsErrorCode::InvalidArgument as i32;
322 };
323 match h.core.open() {
324 Ok(()) => FsErrorCode::Ok as i32,
325 Err(ref e) => FsErrorCode::from(e) as i32,
326 }
327}
328
329#[no_mangle]
337pub unsafe extern "C" fn fs_create_file(handle: *mut FsHandle, name: *const c_char) -> i32 {
338 let (h, name_str) = match validate_handle_and_name(handle, name) {
339 Ok(v) => v,
340 Err(code) => return code,
341 };
342 match h.core.create_file(name_str) {
343 Ok(()) => FsErrorCode::Ok as i32,
344 Err(ref e) => FsErrorCode::from(e) as i32,
345 }
346}
347
348#[no_mangle]
354pub unsafe extern "C" fn fs_write_file(
355 handle: *mut FsHandle,
356 name: *const c_char,
357 offset: u64,
358 data: *const u8,
359 data_len: usize,
360) -> i32 {
361 let (h, name_str) = match validate_handle_and_name(handle, name) {
362 Ok(v) => v,
363 Err(code) => return code,
364 };
365 if data.is_null() && data_len > 0 {
366 return FsErrorCode::InvalidArgument as i32;
367 }
368 let slice = if data_len > 0 {
369 unsafe { std::slice::from_raw_parts(data, data_len) }
370 } else {
371 &[]
372 };
373 match h.core.write_file(name_str, offset, slice) {
374 Ok(()) => FsErrorCode::Ok as i32,
375 Err(ref e) => FsErrorCode::from(e) as i32,
376 }
377}
378
379#[no_mangle]
389pub unsafe extern "C" fn fs_read_file(
390 handle: *mut FsHandle,
391 name: *const c_char,
392 offset: u64,
393 len: usize,
394 out_buf: *mut u8,
395 out_len: *mut usize,
396) -> i32 {
397 let (h, name_str) = match validate_handle_and_name(handle, name) {
398 Ok(v) => v,
399 Err(code) => return code,
400 };
401 if out_buf.is_null() || out_len.is_null() {
402 return FsErrorCode::InvalidArgument as i32;
403 }
404
405 match h.core.read_file(name_str, offset, len) {
406 Ok(data) => {
407 let buf_capacity = len;
408 if data.len() > buf_capacity {
409 unsafe { *out_len = data.len() };
410 return FsErrorCode::BufferTooSmall as i32;
411 }
412 unsafe {
413 ptr::copy_nonoverlapping(data.as_ptr(), out_buf, data.len());
414 *out_len = data.len();
415 }
416 FsErrorCode::Ok as i32
417 }
418 Err(ref e) => FsErrorCode::from(e) as i32,
419 }
420}
421
422#[no_mangle]
433pub unsafe extern "C" fn fs_list_root(handle: *mut FsHandle, out_error: *mut i32) -> *mut c_char {
434 let Some(h) = (unsafe { handle.as_mut() }) else {
435 if !out_error.is_null() {
436 unsafe { *out_error = FsErrorCode::InvalidArgument as i32 };
437 }
438 return ptr::null_mut();
439 };
440
441 match h.core.list_directory("") {
442 Ok(entries) => {
443 let json = match serde_json::to_string(&entries) {
444 Ok(j) => j,
445 Err(_) => {
446 if !out_error.is_null() {
447 unsafe { *out_error = FsErrorCode::InternalError as i32 };
448 }
449 return ptr::null_mut();
450 }
451 };
452 if !out_error.is_null() {
453 unsafe { *out_error = FsErrorCode::Ok as i32 };
454 }
455 match CString::new(json) {
456 Ok(cs) => cs.into_raw(),
457 Err(_) => {
458 if !out_error.is_null() {
459 unsafe { *out_error = FsErrorCode::InternalError as i32 };
460 }
461 ptr::null_mut()
462 }
463 }
464 }
465 Err(ref e) => {
466 if !out_error.is_null() {
467 unsafe { *out_error = FsErrorCode::from(e) as i32 };
468 }
469 ptr::null_mut()
470 }
471 }
472}
473
474#[no_mangle]
486pub unsafe extern "C" fn fs_list_dir(
487 handle: *mut FsHandle,
488 path: *const c_char,
489 out_error: *mut i32,
490) -> *mut c_char {
491 let Some(h) = (unsafe { handle.as_mut() }) else {
492 if !out_error.is_null() {
493 unsafe { *out_error = FsErrorCode::InvalidArgument as i32 };
494 }
495 return ptr::null_mut();
496 };
497 let path_str = match unsafe { unsafe_cstr_to_str(path) } {
498 Some(s) => s,
499 None => {
500 if !out_error.is_null() {
501 unsafe { *out_error = FsErrorCode::InvalidArgument as i32 };
502 }
503 return ptr::null_mut();
504 }
505 };
506
507 match h.core.list_directory(path_str) {
508 Ok(entries) => {
509 let json = match serde_json::to_string(&entries) {
510 Ok(j) => j,
511 Err(_) => {
512 if !out_error.is_null() {
513 unsafe { *out_error = FsErrorCode::InternalError as i32 };
514 }
515 return ptr::null_mut();
516 }
517 };
518 if !out_error.is_null() {
519 unsafe { *out_error = FsErrorCode::Ok as i32 };
520 }
521 match CString::new(json) {
522 Ok(cs) => cs.into_raw(),
523 Err(_) => {
524 if !out_error.is_null() {
525 unsafe { *out_error = FsErrorCode::InternalError as i32 };
526 }
527 ptr::null_mut()
528 }
529 }
530 }
531 Err(ref e) => {
532 if !out_error.is_null() {
533 unsafe { *out_error = FsErrorCode::from(e) as i32 };
534 }
535 ptr::null_mut()
536 }
537 }
538}
539
540#[no_mangle]
548pub unsafe extern "C" fn fs_create_dir(handle: *mut FsHandle, name: *const c_char) -> i32 {
549 let (h, name_str) = match validate_handle_and_name(handle, name) {
550 Ok(v) => v,
551 Err(code) => return code,
552 };
553 match h.core.create_directory(name_str) {
554 Ok(()) => FsErrorCode::Ok as i32,
555 Err(ref e) => FsErrorCode::from(e) as i32,
556 }
557}
558
559#[no_mangle]
564pub unsafe extern "C" fn fs_remove_file(handle: *mut FsHandle, name: *const c_char) -> i32 {
565 let (h, name_str) = match validate_handle_and_name(handle, name) {
566 Ok(v) => v,
567 Err(code) => return code,
568 };
569 match h.core.remove_file(name_str) {
570 Ok(()) => FsErrorCode::Ok as i32,
571 Err(ref e) => FsErrorCode::from(e) as i32,
572 }
573}
574
575#[no_mangle]
580pub unsafe extern "C" fn fs_rename(
581 handle: *mut FsHandle,
582 old_name: *const c_char,
583 new_name: *const c_char,
584) -> i32 {
585 let Some(h) = (unsafe { handle.as_mut() }) else {
586 return FsErrorCode::InvalidArgument as i32;
587 };
588 let old_str = match unsafe_cstr_to_str(old_name) {
589 Some(s) => s,
590 None => return FsErrorCode::InvalidArgument as i32,
591 };
592 let new_str = match unsafe_cstr_to_str(new_name) {
593 Some(s) => s,
594 None => return FsErrorCode::InvalidArgument as i32,
595 };
596 match h.core.rename(old_str, new_str) {
597 Ok(()) => FsErrorCode::Ok as i32,
598 Err(ref e) => FsErrorCode::from(e) as i32,
599 }
600}
601
602#[no_mangle]
610pub unsafe extern "C" fn fs_flush(handle: *mut FsHandle) -> i32 {
611 let Some(h) = (unsafe { handle.as_mut() }) else {
612 return FsErrorCode::InvalidArgument as i32;
613 };
614 match h.core.flush() {
615 Ok(()) => FsErrorCode::Ok as i32,
616 Err(ref e) => FsErrorCode::from(e) as i32,
617 }
618}
619
620#[no_mangle]
625pub unsafe extern "C" fn fs_sync(handle: *mut FsHandle) -> i32 {
626 let Some(h) = (unsafe { handle.as_mut() }) else {
627 return FsErrorCode::InvalidArgument as i32;
628 };
629 match h.core.sync() {
630 Ok(()) => FsErrorCode::Ok as i32,
631 Err(ref e) => FsErrorCode::from(e) as i32,
632 }
633}
634
635#[no_mangle]
647pub unsafe extern "C" fn fs_stat(
648 handle: *mut FsHandle,
649 name: *const c_char,
650 out_size: *mut u64,
651 out_kind: *mut i32,
652 out_inode_id: *mut u64,
653) -> i32 {
654 let (h, name_str) = match validate_handle_and_name(handle, name) {
655 Ok(v) => v,
656 Err(code) => return code,
657 };
658 if out_size.is_null() || out_kind.is_null() || out_inode_id.is_null() {
659 return FsErrorCode::InvalidArgument as i32;
660 }
661 match h.core.stat(name_str) {
662 Ok(entry) => {
663 unsafe {
664 *out_size = entry.size;
665 *out_kind = match entry.kind {
666 crate::model::InodeKind::File => 0,
667 crate::model::InodeKind::Directory => 1,
668 };
669 *out_inode_id = entry.inode_id;
670 }
671 FsErrorCode::Ok as i32
672 }
673 Err(ref e) => FsErrorCode::from(e) as i32,
674 }
675}
676
677#[no_mangle]
682pub unsafe extern "C" fn fs_scrub_free_blocks(handle: *mut FsHandle) -> i32 {
683 let Some(h) = (unsafe { handle.as_mut() }) else {
684 return FsErrorCode::InvalidArgument as i32;
685 };
686 match h.core.scrub_free_blocks() {
687 Ok(()) => FsErrorCode::Ok as i32,
688 Err(ref e) => FsErrorCode::from(e) as i32,
689 }
690}
691
692#[no_mangle]
701pub unsafe extern "C" fn fs_blake3(data: *const u8, data_len: usize, out: *mut u8) -> i32 {
702 if data.is_null() || out.is_null() {
703 return FsErrorCode::InvalidArgument as i32;
704 }
705 let input = unsafe { std::slice::from_raw_parts(data, data_len) };
706 let hash = blake3::hash(input);
707 unsafe { std::ptr::copy_nonoverlapping(hash.as_bytes().as_ptr(), out, 32) };
708 FsErrorCode::Ok as i32
709}
710
711#[no_mangle]
716pub unsafe extern "C" fn fs_free_string(s: *mut c_char) {
717 if !s.is_null() {
718 unsafe {
719 drop(CString::from_raw(s));
720 }
721 }
722}
723
724unsafe fn unsafe_cstr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {
729 if ptr.is_null() {
730 return None;
731 }
732 unsafe { CStr::from_ptr(ptr) }.to_str().ok()
733}
734
735unsafe fn validate_handle_and_name<'a>(
736 handle: *mut FsHandle,
737 name: *const c_char,
738) -> Result<(&'a mut FsHandle, &'a str), i32> {
739 let h = unsafe { handle.as_mut() }.ok_or(FsErrorCode::InvalidArgument as i32)?;
740 let name_str =
741 unsafe { unsafe_cstr_to_str(name) }.ok_or(FsErrorCode::InvalidArgument as i32)?;
742 Ok((h, name_str))
743}