1#[cfg(any(target_os = "linux", target_os = "macos"))]
49mod posix;
50
51use crate::error::{FrozenErr, FrozenRes};
52
53#[cfg(any(target_os = "linux", target_os = "macos"))]
55pub type FFId = libc::c_int;
56
57#[cfg(any(target_os = "linux", target_os = "macos"))]
58type TFile = posix::POSIXFile;
59
60const ERRDOMAIN: u8 = 0x11;
62
63static mut MODULE_ID: u8 = 0;
65
66#[repr(u16)]
68pub enum FFileErrRes {
69 Hcf = 0x100,
71
72 Unk = 0x101,
74
75 Nsp = 0x102,
77
78 Syn = 0x103,
80
81 Prm = 0x104,
83
84 Inv = 0x105,
86
87 Cpt = 0x106,
89
90 Grw = 0x107,
92
93 Lck = 0x108,
95
96 Lex = 0x109,
98}
99
100impl FFileErrRes {
101 #[inline]
102 fn default_message(&self) -> &'static [u8] {
103 match self {
104 Self::Inv => b"invalid file path",
105 Self::Unk => b"unknown error type",
106 Self::Hcf => b"hault and catch fire",
107 Self::Grw => b"unable to grow the file",
108 Self::Prm => b"missing write/read permissions",
109 Self::Nsp => b"no space left on storage device",
110 Self::Cpt => b"file is either invalid or corrupted",
111 Self::Syn => b"failed to sync/flush data to storage device",
112 Self::Lex => b"failed to obtain lock, as no more locks available",
113 Self::Lck => b"failed to obtain exclusive lock, file may already be open",
114 }
115 }
116}
117
118#[inline]
119pub(in crate::ffile) fn new_err<R>(res: FFileErrRes, message: Vec<u8>) -> FrozenRes<R> {
120 let detail = res.default_message();
121 let err = FrozenErr::new(unsafe { MODULE_ID }, ERRDOMAIN, res as u16, detail, message);
122 Err(err)
123}
124
125#[inline]
126pub(in crate::ffile) fn new_err_default<R>(res: FFileErrRes) -> FrozenRes<R> {
127 let detail = res.default_message();
128 let err = FrozenErr::new(
129 unsafe { MODULE_ID },
130 ERRDOMAIN,
131 res as u16,
132 detail,
133 Vec::with_capacity(0),
134 );
135 Err(err)
136}
137
138#[derive(Debug, Clone)]
140pub struct FFCfg {
141 pub mid: u8,
143
144 pub path: std::path::PathBuf,
148
149 pub chunk_size: usize,
155
156 pub initial_chunk_amount: usize,
160}
161
162#[derive(Debug)]
209pub struct FrozenFile {
210 cfg: FFCfg,
211 file: core::cell::UnsafeCell<core::mem::ManuallyDrop<TFile>>,
212}
213
214unsafe impl Send for FrozenFile {}
215unsafe impl Sync for FrozenFile {}
216
217impl FrozenFile {
218 #[inline]
220 pub fn length(&self) -> FrozenRes<usize> {
221 unsafe { self.get_file().length() }
222 }
223
224 #[inline]
226 #[cfg(any(target_os = "linux", target_os = "macos"))]
227 pub fn fd(&self) -> i32 {
228 self.get_file().fd()
229 }
230
231 pub fn exists(&self) -> FrozenRes<bool> {
233 unsafe { TFile::exists(&self.cfg.path) }
234 }
235
236 pub fn new(cfg: FFCfg) -> FrozenRes<Self> {
272 let raw_file = unsafe { posix::POSIXFile::new(&cfg.path) }?;
273 let slf = Self {
274 cfg: cfg.clone(),
275 file: core::cell::UnsafeCell::new(core::mem::ManuallyDrop::new(raw_file)),
276 };
277
278 let file = slf.get_file();
279
280 unsafe { file.flock() }?;
284
285 unsafe { MODULE_ID = cfg.mid };
288
289 let curr_len = slf.length()?;
290 let init_len = cfg.chunk_size * cfg.initial_chunk_amount;
291
292 match curr_len {
293 0 => slf.grow(cfg.initial_chunk_amount)?,
294 _ => {
295 if (curr_len < init_len) || (curr_len % cfg.chunk_size != 0) {
300 let _ = unsafe { file.close() };
304 return new_err_default(FFileErrRes::Cpt);
305 }
306 }
307 }
308
309 Ok(slf)
310 }
311
312 pub fn grow(&self, count: usize) -> FrozenRes<()> {
336 let curr_len = self.length()?;
337 let len_to_add = self.cfg.chunk_size * count;
338
339 unsafe { self.get_file().grow(curr_len, len_to_add) }
340 }
341
342 pub fn sync(&self) -> FrozenRes<()> {
344 let file = self.get_file();
345 unsafe { file.sync() }
346 }
347
348 #[cfg(any(target_os = "linux"))]
350 pub fn sync_range(&self, index: usize, count: usize) -> FrozenRes<()> {
351 let offset = self.cfg.chunk_size * index;
352 let len_to_sync = self.cfg.chunk_size * count;
353 let file = self.get_file();
354
355 unsafe { file.sync_range(offset, len_to_sync) }
356 }
357
358 pub fn delete(&self) -> FrozenRes<()> {
382 let file = self.get_file();
383 unsafe { file.unlink(&self.cfg.path) }
384 }
385
386 #[inline(always)]
388 #[cfg(any(target_os = "linux", target_os = "macos"))]
389 pub fn read(&self, buf: &mut libc::iovec, index: usize) -> FrozenRes<()> {
390 debug_assert_eq!(buf.iov_len, self.cfg.chunk_size);
392
393 let offset = self.cfg.chunk_size * index;
394 let file = self.get_file();
395
396 unsafe { file.pread(buf, offset) }
397 }
398
399 #[inline(always)]
401 #[cfg(any(target_os = "linux", target_os = "macos"))]
402 pub fn write(&self, buf: &libc::iovec, index: usize) -> FrozenRes<()> {
403 debug_assert_eq!(buf.iov_len, self.cfg.chunk_size);
405
406 let offset = self.cfg.chunk_size * index;
407 let file = self.get_file();
408
409 unsafe { file.pwrite(buf, offset) }
410 }
411
412 #[inline(always)]
414 #[cfg(any(target_os = "linux", target_os = "macos"))]
415 pub fn preadv(&self, iovs: &mut [libc::iovec], index: usize) -> FrozenRes<()> {
416 debug_assert!(iovs.iter().all(|i| i.iov_len == self.cfg.chunk_size));
418
419 let offset = self.cfg.chunk_size * index;
420 let file = self.get_file();
421
422 unsafe { file.preadv(iovs, offset) }
423 }
424
425 #[inline(always)]
427 #[cfg(any(target_os = "linux", target_os = "macos"))]
428 pub fn pwritev(&self, iovs: &mut [libc::iovec], index: usize) -> FrozenRes<()> {
429 debug_assert!(iovs.iter().all(|i| i.iov_len == self.cfg.chunk_size));
431
432 let offset = self.cfg.chunk_size * index;
433 let file = self.get_file();
434
435 unsafe { file.pwritev(iovs, offset) }
436 }
437
438 #[inline]
439 fn get_file(&self) -> &core::mem::ManuallyDrop<TFile> {
440 unsafe { &*self.file.get() }
441 }
442}
443
444impl Drop for FrozenFile {
445 fn drop(&mut self) {
446 #[cfg(any(target_os = "linux", target_os = "macos"))]
448 if self.fd() == posix::CLOSED_FD {
449 return;
450 }
451
452 let _ = self.sync();
454 let _ = unsafe { self.get_file().close() };
455 }
456}
457
458impl core::fmt::Display for FrozenFile {
459 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
460 write!(
461 f,
462 "FrozenFile {{fd: {}, len: {}}}",
463 self.fd(),
464 self.length().unwrap_or(0),
465 )
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use crate::error::TEST_MID;
473 use std::sync::Arc;
474
475 const CHUNK_SIZE: usize = 0x10;
476 const INIT_CHUNKS: usize = 0x0A;
477
478 fn tmp_path() -> (tempfile::TempDir, FFCfg) {
479 let dir = tempfile::tempdir().unwrap();
480 let path = dir.path().join("tmp_ff_file");
481 let cfg = FFCfg {
482 path: path,
483 mid: TEST_MID,
484 chunk_size: CHUNK_SIZE,
485 initial_chunk_amount: INIT_CHUNKS,
486 };
487
488 (dir, cfg)
489 }
490
491 mod ff_lifecycle {
492 use super::*;
493
494 #[test]
495 fn ok_new_with_init_len() {
496 let (_dir, cfg) = tmp_path();
497 let file = FrozenFile::new(cfg).unwrap();
498
499 let exists = file.exists().unwrap();
500 assert!(exists);
501
502 assert_eq!(file.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
503 }
504
505 #[test]
506 fn ok_new_existing() {
507 let (_dir, cfg) = tmp_path();
508
509 let file = FrozenFile::new(cfg.clone()).unwrap();
510 assert_eq!(file.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
511
512 drop(file);
514
515 let reopened = FrozenFile::new(cfg.clone()).unwrap();
516 assert_eq!(reopened.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
517 }
518
519 #[test]
520 fn err_new_when_file_smaller_than_init_len() {
521 let (_dir, mut cfg) = tmp_path();
522
523 let file = FrozenFile::new(cfg.clone()).unwrap();
524 drop(file);
525
526 cfg.chunk_size = cfg.chunk_size * 2;
528
529 let err = FrozenFile::new(cfg).unwrap_err();
530 assert!(err.cmp(FFileErrRes::Cpt as u16));
531 }
532
533 #[test]
534 fn ok_exists_true_when_exists() {
535 let (_dir, cfg) = tmp_path();
536 let file = FrozenFile::new(cfg).unwrap();
537
538 let exists = file.exists().unwrap();
539 assert!(exists);
540 }
541
542 #[test]
543 fn ok_exists_false_when_missing() {
544 let (_dir, cfg) = tmp_path();
545 let file = FrozenFile::new(cfg).unwrap();
546 file.delete().unwrap();
547
548 let exists = file.exists().unwrap();
549 assert!(!exists);
550 }
551
552 #[test]
553 fn ok_delete_file() {
554 let (_dir, cfg) = tmp_path();
555
556 let file = FrozenFile::new(cfg).unwrap();
557 let exists = file.exists().unwrap();
558 assert!(exists);
559
560 file.delete().unwrap();
561 let exists = file.exists().unwrap();
562 assert!(!exists);
563 }
564
565 #[test]
566 fn err_delete_after_delete() {
567 let (_dir, cfg) = tmp_path();
568
569 let file = FrozenFile::new(cfg).unwrap();
570 file.delete().unwrap();
571
572 let err = file.delete().unwrap_err();
573 assert!(err.cmp(FFileErrRes::Inv as u16));
574 }
575
576 #[test]
577 fn ok_drop_persists_without_explicit_sync() {
578 let data = [0x0Bu8; CHUNK_SIZE];
579 let (_dir, cfg) = tmp_path();
580
581 {
582 let file = FrozenFile::new(cfg.clone()).unwrap();
583 let iov = libc::iovec {
584 iov_base: data.as_ptr() as *mut _,
585 iov_len: data.len(),
586 };
587 file.write(&iov, 0).unwrap();
588 drop(file);
589 }
590
591 {
592 let reopened = FrozenFile::new(cfg).unwrap();
593 let mut buf = [0u8; CHUNK_SIZE];
594 let mut iov = libc::iovec {
595 iov_base: buf.as_mut_ptr() as *mut _,
596 iov_len: buf.len(),
597 };
598
599 reopened.read(&mut iov, 0).unwrap();
600 assert_eq!(buf, data);
601 }
602 }
603 }
604
605 mod ff_lock {
606 use super::*;
607
608 #[test]
609 fn err_new_when_already_open() {
610 let (_dir, cfg) = tmp_path();
611 let file = FrozenFile::new(cfg.clone()).unwrap();
612
613 let err = FrozenFile::new(cfg).unwrap_err();
614 assert!(err.cmp(FFileErrRes::Lck as u16));
615
616 drop(file);
617 }
618
619 #[test]
620 fn ok_drop_releases_exclusive_lock() {
621 let (_dir, cfg) = tmp_path();
622
623 let file = FrozenFile::new(cfg.clone()).unwrap();
624 drop(file);
625
626 let _ = FrozenFile::new(cfg).expect("must not fail after drop");
627 }
628 }
629
630 mod ff_grow {
631 use super::*;
632
633 #[test]
634 fn ok_grow_updates_length() {
635 let (_dir, cfg) = tmp_path();
636
637 let file = FrozenFile::new(cfg).unwrap();
638 assert_eq!(file.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
639
640 file.grow(0x20).unwrap();
641 assert_eq!(file.length().unwrap(), CHUNK_SIZE * (INIT_CHUNKS + 0x20));
642 }
643
644 #[test]
645 fn ok_grow_sync_cycle() {
646 let (_dir, cfg) = tmp_path();
647 let file = FrozenFile::new(cfg).unwrap();
648
649 for _ in 0..0x0A {
650 file.grow(0x100).unwrap();
651 file.sync().unwrap();
652 }
653
654 assert_eq!(file.length().unwrap(), CHUNK_SIZE * (INIT_CHUNKS + (0x0A * 0x100)));
655 }
656 }
657
658 mod ff_sync {
659 use super::*;
660
661 #[test]
662 fn ok_sync_after_sync() {
663 let (_dir, cfg) = tmp_path();
664 let file = FrozenFile::new(cfg).unwrap();
665
666 file.sync().unwrap();
667 file.sync().unwrap();
668 file.sync().unwrap();
669 }
670
671 #[test]
672 fn err_sync_after_delete() {
673 let (_dir, cfg) = tmp_path();
674 let file = FrozenFile::new(cfg).unwrap();
675 file.delete().unwrap();
676
677 let err = file.sync().unwrap_err();
678 assert!(err.cmp(FFileErrRes::Hcf as u16));
679 }
680 }
681
682 mod ff_write_read {
683 use super::*;
684
685 #[test]
686 fn ok_single_write_read_cycle() {
687 let (_dir, cfg) = tmp_path();
688 let file = FrozenFile::new(cfg).unwrap();
689
690 let data = [0x0Bu8; CHUNK_SIZE];
691 let iov = libc::iovec {
692 iov_base: data.as_ptr() as *mut _,
693 iov_len: data.len(),
694 };
695
696 file.write(&iov, 4).unwrap();
697 file.sync().unwrap();
698
699 let mut buf = [0u8; CHUNK_SIZE];
700 let mut read_iov = libc::iovec {
701 iov_base: buf.as_mut_ptr() as *mut _,
702 iov_len: buf.len(),
703 };
704
705 file.read(&mut read_iov, 4).unwrap();
706 assert_eq!(buf, data);
707 }
708
709 #[test]
710 fn ok_vectored_write_read_cycle() {
711 let (_dir, cfg) = tmp_path();
712 let file = FrozenFile::new(cfg).unwrap();
713
714 let mut bufs = [[1u8; CHUNK_SIZE], [2u8; CHUNK_SIZE]];
715 let mut iovs: Vec<libc::iovec> = bufs
716 .iter_mut()
717 .map(|b| libc::iovec {
718 iov_base: b.as_mut_ptr() as *mut _,
719 iov_len: b.len(),
720 })
721 .collect();
722
723 file.pwritev(&mut iovs, 0).unwrap();
724 file.sync().unwrap();
725
726 let mut read_bufs = [[0u8; CHUNK_SIZE], [0u8; CHUNK_SIZE]];
727 let mut read_iovs: Vec<libc::iovec> = read_bufs
728 .iter_mut()
729 .map(|b| libc::iovec {
730 iov_base: b.as_mut_ptr() as *mut _,
731 iov_len: b.len(),
732 })
733 .collect();
734
735 file.preadv(&mut read_iovs, 0).unwrap();
736 assert!(read_bufs[0].iter().all(|b| *b == 1));
737 assert!(read_bufs[1].iter().all(|b| *b == 2));
738 }
739
740 #[test]
741 fn ok_write_concurrent_non_overlapping() {
742 let (_dir, mut cfg) = tmp_path();
743 cfg.initial_chunk_amount = 0x100;
744 let file = Arc::new(FrozenFile::new(cfg).unwrap());
745
746 let mut handles = vec![];
747 for i in 0..0x0A {
748 let f = file.clone();
749 handles.push(std::thread::spawn(move || {
750 let data = [i as u8; CHUNK_SIZE];
751 let iov = libc::iovec {
752 iov_base: data.as_ptr() as *mut _,
753 iov_len: data.len(),
754 };
755
756 f.write(&iov, i).unwrap();
757 }));
758 }
759
760 for h in handles {
761 h.join().unwrap();
762 }
763
764 file.sync().unwrap();
765
766 for i in 0..0x0A {
767 let mut buf = [0u8; CHUNK_SIZE];
768 let mut iov = libc::iovec {
769 iov_base: buf.as_mut_ptr() as *mut _,
770 iov_len: buf.len(),
771 };
772
773 file.read(&mut iov, i).unwrap();
774 assert!(buf.iter().all(|b| *b == i as u8));
775 }
776 }
777
778 #[test]
779 fn ok_concurrent_grow_and_write() {
780 let (_dir, cfg) = tmp_path();
781 let file = Arc::new(FrozenFile::new(cfg).unwrap());
782
783 let writer = {
784 let f = file.clone();
785 std::thread::spawn(move || {
786 for i in 0..INIT_CHUNKS {
787 let data = [i as u8; CHUNK_SIZE];
788 let iov = libc::iovec {
789 iov_base: data.as_ptr() as *mut _,
790 iov_len: data.len(),
791 };
792
793 f.write(&iov, i).unwrap();
794 }
795 })
796 };
797
798 let chunks_to_grow = 0x20;
799 let grower = {
800 let f = file.clone();
801 std::thread::spawn(move || {
802 f.grow(chunks_to_grow).unwrap();
803 })
804 };
805
806 writer.join().unwrap();
807 grower.join().unwrap();
808
809 file.sync().unwrap();
810 assert_eq!(file.length().unwrap(), CHUNK_SIZE * (INIT_CHUNKS + chunks_to_grow));
811
812 for i in 0..INIT_CHUNKS {
813 let mut buf = [0u8; CHUNK_SIZE];
814 let mut iov = libc::iovec {
815 iov_base: buf.as_mut_ptr() as *mut _,
816 iov_len: buf.len(),
817 };
818
819 file.read(&mut iov, i).unwrap();
820 assert!(buf.iter().all(|b| *b == i as u8));
821 }
822 }
823
824 #[test]
825 fn ok_concurrent_sync_and_write() {
826 let (_dir, cfg) = tmp_path();
827 let file = Arc::new(FrozenFile::new(cfg).unwrap());
828
829 let writer = {
830 let f = file.clone();
831 std::thread::spawn(move || {
832 for i in 0..INIT_CHUNKS {
833 let data = [i as u8; CHUNK_SIZE];
834 let iov = libc::iovec {
835 iov_base: data.as_ptr() as *mut _,
836 iov_len: data.len(),
837 };
838
839 f.write(&iov, i).unwrap();
840 }
841 })
842 };
843
844 let syncer = {
845 let f = file.clone();
846 std::thread::spawn(move || {
847 for _ in 0..0x0A {
848 f.sync().unwrap();
849 }
850 })
851 };
852
853 writer.join().unwrap();
854 syncer.join().unwrap();
855
856 file.sync().unwrap();
857
858 for i in 0..INIT_CHUNKS {
859 let mut buf = [0; CHUNK_SIZE];
860 let mut iov = libc::iovec {
861 iov_base: buf.as_mut_ptr() as *mut _,
862 iov_len: buf.len(),
863 };
864
865 file.read(&mut iov, i).unwrap();
866 assert!(buf.iter().all(|b| *b == i as u8));
867 }
868 }
869
870 #[test]
871 fn err_read_hcf_for_eof() {
872 let (_dir, cfg) = tmp_path();
873 let file = FrozenFile::new(cfg).unwrap();
874
875 let mut buf = [0; CHUNK_SIZE];
876 let mut iov = libc::iovec {
877 iov_base: buf.as_mut_ptr() as *mut _,
878 iov_len: buf.len(),
879 };
880
881 let err = file.read(&mut iov, 0x100).unwrap_err();
883 assert!(err.cmp(FFileErrRes::Hcf as u16));
884 }
885 }
886}