1#[cfg(any(target_os = "linux", target_os = "macos"))]
39mod posix;
40
41use crate::error::{FrozenErr, FrozenRes};
42
43#[cfg(any(target_os = "linux", target_os = "macos"))]
45pub type FFId = libc::c_int;
46
47#[cfg(any(target_os = "linux", target_os = "macos"))]
48type TFile = posix::POSIXFile;
49
50const ERRDOMAIN: u8 = 0x11;
52
53static mut MODULE_ID: u8 = 0;
55
56#[repr(u16)]
58pub enum FFileErr {
59 Hcf = 0x100,
61
62 Unk = 0x101,
64
65 Nsp = 0x102,
67
68 Syn = 0x103,
70
71 Prm = 0x104,
73
74 Inv = 0x105,
76
77 Cpt = 0x106,
79
80 Grw = 0x107,
82
83 Lck = 0x108,
85
86 Lex = 0x109,
88}
89
90impl FFileErr {
91 #[inline]
92 fn default_message(&self) -> &'static [u8] {
93 match self {
94 Self::Inv => b"invalid file path",
95 Self::Unk => b"unknown error type",
96 Self::Hcf => b"hault and catch fire",
97 Self::Grw => b"unable to grow the file",
98 Self::Prm => b"missing write/read permissions",
99 Self::Nsp => b"no space left on storage device",
100 Self::Cpt => b"file is either invalid or corrupted",
101 Self::Syn => b"failed to sync/flush data to storage device",
102 Self::Lex => b"failed to obtain lock, as no more locks available",
103 Self::Lck => b"failed to obtain exclusive lock, file may already be open",
104 }
105 }
106}
107
108#[inline]
109pub(in crate::ffile) fn new_err<R>(res: FFileErr, message: Vec<u8>) -> FrozenRes<R> {
110 let detail = res.default_message();
111 let err = FrozenErr::new(unsafe { MODULE_ID }, ERRDOMAIN, res as u16, detail, message);
112 Err(err)
113}
114
115#[inline]
116pub(in crate::ffile) fn new_err_default<R>(res: FFileErr) -> FrozenRes<R> {
117 let detail = res.default_message();
118 let err = FrozenErr::new(
119 unsafe { MODULE_ID },
120 ERRDOMAIN,
121 res as u16,
122 detail,
123 Vec::with_capacity(0),
124 );
125 Err(err)
126}
127
128#[derive(Debug, Clone)]
130pub struct FFCfg {
131 pub mid: u8,
133
134 pub path: std::path::PathBuf,
138
139 pub chunk_size: usize,
145
146 pub initial_chunk_amount: usize,
150}
151
152#[derive(Debug)]
189pub struct FrozenFile {
190 cfg: FFCfg,
191 file: core::cell::UnsafeCell<core::mem::ManuallyDrop<TFile>>,
192}
193
194unsafe impl Send for FrozenFile {}
195unsafe impl Sync for FrozenFile {}
196
197impl FrozenFile {
198 #[inline]
200 pub fn cfg(&self) -> &FFCfg {
201 &self.cfg
202 }
203
204 #[inline]
206 pub fn length(&self) -> FrozenRes<usize> {
207 unsafe { self.get_file().length() }
208 }
209
210 #[inline]
212 #[cfg(any(target_os = "linux", target_os = "macos"))]
213 pub fn fd(&self) -> i32 {
214 self.get_file().fd()
215 }
216
217 pub fn exists(&self) -> FrozenRes<bool> {
219 unsafe { TFile::exists(&self.cfg.path) }
220 }
221
222 pub fn new(cfg: FFCfg) -> FrozenRes<Self> {
258 let raw_file = unsafe { posix::POSIXFile::new(&cfg.path) }?;
259 let slf = Self {
260 cfg: cfg.clone(),
261 file: core::cell::UnsafeCell::new(core::mem::ManuallyDrop::new(raw_file)),
262 };
263
264 let file = slf.get_file();
265
266 unsafe { file.flock() }?;
270
271 unsafe { MODULE_ID = cfg.mid };
274
275 let curr_len = slf.length()?;
276 let init_len = cfg.chunk_size * cfg.initial_chunk_amount;
277
278 match curr_len {
279 0 => slf.grow(cfg.initial_chunk_amount)?,
280 _ => {
281 if (curr_len < init_len) || (curr_len % cfg.chunk_size != 0) {
286 let _ = unsafe { file.close() };
290 return new_err_default(FFileErr::Cpt);
291 }
292 }
293 }
294
295 Ok(slf)
296 }
297
298 pub fn grow(&self, count: usize) -> FrozenRes<()> {
322 let curr_len = self.length()?;
323 let len_to_add = self.cfg.chunk_size * count;
324
325 unsafe { self.get_file().grow(curr_len, len_to_add) }
326 }
327
328 pub fn sync(&self) -> FrozenRes<()> {
330 let file = self.get_file();
331 unsafe { file.sync() }
332 }
333
334 #[cfg(target_os = "linux")]
336 pub fn sync_range(&self, index: usize, count: usize) -> FrozenRes<()> {
337 let offset = self.cfg.chunk_size * index;
338 let len_to_sync = self.cfg.chunk_size * count;
339 let file = self.get_file();
340
341 unsafe { file.sync_range(offset, len_to_sync) }
342 }
343
344 pub fn delete(&self) -> FrozenRes<()> {
368 let file = self.get_file();
369 unsafe { file.unlink(&self.cfg.path) }
370 }
371
372 #[inline(always)]
374 #[allow(clippy::not_unsafe_ptr_arg_deref)]
375 #[cfg(any(target_os = "linux", target_os = "macos"))]
376 pub fn pread(&self, buf: *mut u8, index: usize) -> FrozenRes<()> {
377 let offset = self.cfg.chunk_size * index;
378 let file = self.get_file();
379
380 unsafe { file.pread(buf, offset, self.cfg.chunk_size) }
381 }
382
383 #[inline(always)]
385 #[allow(clippy::not_unsafe_ptr_arg_deref)]
386 #[cfg(any(target_os = "linux", target_os = "macos"))]
387 pub fn pwrite(&self, buf: *mut u8, index: usize) -> FrozenRes<()> {
388 let offset = self.cfg.chunk_size * index;
389 let file = self.get_file();
390
391 unsafe { file.pwrite(buf, offset, self.cfg.chunk_size) }
392 }
393
394 #[inline(always)]
396 #[cfg(any(target_os = "linux", target_os = "macos"))]
397 pub fn preadv(&self, bufs: &[*mut u8], index: usize) -> FrozenRes<()> {
398 let offset = self.cfg.chunk_size * index;
399 let file = self.get_file();
400
401 unsafe { file.preadv(bufs, offset, self.cfg.chunk_size) }
402 }
403
404 #[inline(always)]
406 #[cfg(any(target_os = "linux", target_os = "macos"))]
407 pub fn pwritev(&self, bufs: &[*mut u8], index: usize) -> FrozenRes<()> {
408 let offset = self.cfg.chunk_size * index;
409 let file = self.get_file();
410
411 unsafe { file.pwritev(bufs, offset, self.cfg.chunk_size) }
412 }
413
414 #[inline]
415 fn get_file(&self) -> &core::mem::ManuallyDrop<TFile> {
416 unsafe { &*self.file.get() }
417 }
418}
419
420impl Drop for FrozenFile {
421 fn drop(&mut self) {
422 #[cfg(any(target_os = "linux", target_os = "macos"))]
424 if self.fd() == posix::CLOSED_FD {
425 return;
426 }
427
428 let _ = self.sync();
430 let _ = unsafe { self.get_file().close() };
431 }
432}
433
434impl core::fmt::Display for FrozenFile {
435 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
436 write!(
437 f,
438 "FrozenFile {{fd: {}, len: {}}}",
439 self.fd(),
440 self.length().unwrap_or(0),
441 )
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use crate::error::TEST_MID;
449 use std::sync::Arc;
450
451 const CHUNK_SIZE: usize = 0x10;
452 const INIT_CHUNKS: usize = 0x0A;
453
454 fn tmp_path() -> (tempfile::TempDir, FFCfg) {
455 let dir = tempfile::tempdir().unwrap();
456 let path = dir.path().join("tmp_ff_file");
457 let cfg = FFCfg {
458 path,
459 mid: TEST_MID,
460 chunk_size: CHUNK_SIZE,
461 initial_chunk_amount: INIT_CHUNKS,
462 };
463
464 (dir, cfg)
465 }
466
467 mod ff_lifecycle {
468 use super::*;
469
470 #[test]
471 fn ok_new_with_init_len() {
472 let (_dir, cfg) = tmp_path();
473 let file = FrozenFile::new(cfg).unwrap();
474
475 let exists = file.exists().unwrap();
476 assert!(exists);
477
478 assert_eq!(file.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
479 }
480
481 #[test]
482 fn ok_new_existing() {
483 let (_dir, cfg) = tmp_path();
484
485 let file = FrozenFile::new(cfg.clone()).unwrap();
486 assert_eq!(file.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
487
488 drop(file);
490
491 let reopened = FrozenFile::new(cfg.clone()).unwrap();
492 assert_eq!(reopened.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
493 }
494
495 #[test]
496 fn err_new_when_file_smaller_than_init_len() {
497 let (_dir, mut cfg) = tmp_path();
498
499 let file = FrozenFile::new(cfg.clone()).unwrap();
500 drop(file);
501
502 cfg.chunk_size *= 2;
504
505 let err = FrozenFile::new(cfg).unwrap_err();
506 assert!(err.compare(FFileErr::Cpt as u16));
507 }
508
509 #[test]
510 fn ok_exists_true_when_exists() {
511 let (_dir, cfg) = tmp_path();
512 let file = FrozenFile::new(cfg).unwrap();
513
514 let exists = file.exists().unwrap();
515 assert!(exists);
516 }
517
518 #[test]
519 fn ok_exists_false_when_missing() {
520 let (_dir, cfg) = tmp_path();
521 let file = FrozenFile::new(cfg).unwrap();
522 file.delete().unwrap();
523
524 let exists = file.exists().unwrap();
525 assert!(!exists);
526 }
527
528 #[test]
529 fn ok_delete_file() {
530 let (_dir, cfg) = tmp_path();
531
532 let file = FrozenFile::new(cfg).unwrap();
533 let exists = file.exists().unwrap();
534 assert!(exists);
535
536 file.delete().unwrap();
537 let exists = file.exists().unwrap();
538 assert!(!exists);
539 }
540
541 #[test]
542 fn err_delete_after_delete() {
543 let (_dir, cfg) = tmp_path();
544
545 let file = FrozenFile::new(cfg).unwrap();
546 file.delete().unwrap();
547
548 let err = file.delete().unwrap_err();
549 assert!(err.compare(FFileErr::Inv as u16));
550 }
551
552 #[test]
553 fn ok_drop_persists_without_explicit_sync() {
554 let mut data = [0x0Bu8; CHUNK_SIZE];
555 let (_dir, cfg) = tmp_path();
556
557 {
558 let file = FrozenFile::new(cfg.clone()).unwrap();
559 file.pwrite(data.as_mut_ptr(), 0).unwrap();
560 drop(file);
561 }
562
563 {
564 let reopened = FrozenFile::new(cfg).unwrap();
565 let mut buf = [0u8; CHUNK_SIZE];
566
567 reopened.pread(buf.as_mut_ptr(), 0).unwrap();
568 assert_eq!(buf, data);
569 }
570 }
571 }
572
573 mod ff_lock {
574 use super::*;
575
576 #[test]
577 fn err_new_when_already_open() {
578 let (_dir, cfg) = tmp_path();
579 let file = FrozenFile::new(cfg.clone()).unwrap();
580
581 let err = FrozenFile::new(cfg).unwrap_err();
582 assert!(err.compare(FFileErr::Lck as u16));
583
584 drop(file);
585 }
586
587 #[test]
588 fn ok_drop_releases_exclusive_lock() {
589 let (_dir, cfg) = tmp_path();
590
591 let file = FrozenFile::new(cfg.clone()).unwrap();
592 drop(file);
593
594 let _ = FrozenFile::new(cfg).expect("must not fail after drop");
595 }
596 }
597
598 mod ff_grow {
599 use super::*;
600
601 #[test]
602 fn ok_grow_updates_length() {
603 let (_dir, cfg) = tmp_path();
604
605 let file = FrozenFile::new(cfg).unwrap();
606 assert_eq!(file.length().unwrap(), CHUNK_SIZE * INIT_CHUNKS);
607
608 file.grow(0x20).unwrap();
609 assert_eq!(file.length().unwrap(), CHUNK_SIZE * (INIT_CHUNKS + 0x20));
610 }
611
612 #[test]
613 fn ok_grow_sync_cycle() {
614 let (_dir, cfg) = tmp_path();
615 let file = FrozenFile::new(cfg).unwrap();
616
617 for _ in 0..0x0A {
618 file.grow(0x100).unwrap();
619 file.sync().unwrap();
620 }
621
622 assert_eq!(file.length().unwrap(), CHUNK_SIZE * (INIT_CHUNKS + (0x0A * 0x100)));
623 }
624 }
625
626 mod ff_sync {
627 use super::*;
628
629 #[test]
630 fn ok_sync_after_sync() {
631 let (_dir, cfg) = tmp_path();
632 let file = FrozenFile::new(cfg).unwrap();
633
634 file.sync().unwrap();
635 file.sync().unwrap();
636 file.sync().unwrap();
637 }
638
639 #[test]
640 fn err_sync_after_delete() {
641 let (_dir, cfg) = tmp_path();
642 let file = FrozenFile::new(cfg).unwrap();
643 file.delete().unwrap();
644
645 let err = file.sync().unwrap_err();
646 assert!(err.compare(FFileErr::Hcf as u16));
647 }
648 }
649
650 mod ff_write_read {
651 use super::*;
652
653 #[test]
654 fn ok_single_write_read_cycle() {
655 let (_dir, cfg) = tmp_path();
656 let file = FrozenFile::new(cfg).unwrap();
657
658 let mut data = [0x0Bu8; CHUNK_SIZE];
659
660 file.pwrite(data.as_mut_ptr(), 4).unwrap();
661 file.sync().unwrap();
662
663 let mut buf = [0u8; CHUNK_SIZE];
664 file.pread(buf.as_mut_ptr(), 4).unwrap();
665 assert_eq!(buf, data);
666 }
667
668 #[test]
669 fn ok_vectored_write_read_cycle() {
670 let (_dir, cfg) = tmp_path();
671 let file = FrozenFile::new(cfg).unwrap();
672
673 let mut bufs = [[1u8; CHUNK_SIZE], [2u8; CHUNK_SIZE]];
674 let bufs: Vec<*mut u8> = bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
675
676 file.pwritev(&bufs, 0).unwrap();
677 file.sync().unwrap();
678
679 let mut read_bufs = [[0u8; CHUNK_SIZE], [0u8; CHUNK_SIZE]];
680 let rbufs: Vec<*mut u8> = read_bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
681 file.preadv(&rbufs, 0).unwrap();
682
683 assert!(read_bufs[0].iter().all(|b| *b == 1));
684 assert!(read_bufs[1].iter().all(|b| *b == 2));
685 }
686
687 #[test]
688 fn ok_write_concurrent_non_overlapping() {
689 let (_dir, mut cfg) = tmp_path();
690 cfg.initial_chunk_amount = 0x100;
691 let file = Arc::new(FrozenFile::new(cfg).unwrap());
692
693 let mut handles = vec![];
694 for i in 0..0x0A {
695 let f = file.clone();
696 handles.push(std::thread::spawn(move || {
697 let mut data = [i as u8; CHUNK_SIZE];
698 f.pwrite(data.as_mut_ptr(), i).unwrap();
699 }));
700 }
701
702 for h in handles {
703 h.join().unwrap();
704 }
705
706 file.sync().unwrap();
707
708 for i in 0..0x0A {
709 let mut buf = [0u8; CHUNK_SIZE];
710 file.pread(buf.as_mut_ptr(), i).unwrap();
711 assert!(buf.iter().all(|b| *b == i as u8));
712 }
713 }
714
715 #[test]
716 fn ok_concurrent_grow_and_write() {
717 let (_dir, cfg) = tmp_path();
718 let file = Arc::new(FrozenFile::new(cfg).unwrap());
719
720 let writer = {
721 let f = file.clone();
722 std::thread::spawn(move || {
723 for i in 0..INIT_CHUNKS {
724 let mut data = [i as u8; CHUNK_SIZE];
725 f.pwrite(data.as_mut_ptr(), i).unwrap();
726 }
727 })
728 };
729
730 let chunks_to_grow = 0x20;
731 let grower = {
732 let f = file.clone();
733 std::thread::spawn(move || {
734 f.grow(chunks_to_grow).unwrap();
735 })
736 };
737
738 writer.join().unwrap();
739 grower.join().unwrap();
740
741 file.sync().unwrap();
742 assert_eq!(file.length().unwrap(), CHUNK_SIZE * (INIT_CHUNKS + chunks_to_grow));
743
744 for i in 0..INIT_CHUNKS {
745 let mut buf = [0u8; CHUNK_SIZE];
746 file.pread(buf.as_mut_ptr(), i).unwrap();
747 assert!(buf.iter().all(|b| *b == i as u8));
748 }
749 }
750
751 #[test]
752 fn ok_concurrent_sync_and_write() {
753 let (_dir, cfg) = tmp_path();
754 let file = Arc::new(FrozenFile::new(cfg).unwrap());
755
756 let writer = {
757 let f = file.clone();
758 std::thread::spawn(move || {
759 for i in 0..INIT_CHUNKS {
760 let mut data = [i as u8; CHUNK_SIZE];
761 f.pwrite(data.as_mut_ptr(), i).unwrap();
762 }
763 })
764 };
765
766 let syncer = {
767 let f = file.clone();
768 std::thread::spawn(move || {
769 for _ in 0..0x0A {
770 f.sync().unwrap();
771 }
772 })
773 };
774
775 writer.join().unwrap();
776 syncer.join().unwrap();
777
778 file.sync().unwrap();
779
780 for i in 0..INIT_CHUNKS {
781 let mut buf = [0; CHUNK_SIZE];
782 file.pread(buf.as_mut_ptr(), i).unwrap();
783 assert!(buf.iter().all(|b| *b == i as u8));
784 }
785 }
786
787 #[test]
788 fn err_read_hcf_for_eof() {
789 let (_dir, cfg) = tmp_path();
790 let file = FrozenFile::new(cfg).unwrap();
791
792 let mut buf = [0; CHUNK_SIZE];
794 let err = file.pread(buf.as_mut_ptr(), 0x100).unwrap_err();
795 assert!(err.compare(FFileErr::Hcf as u16));
796 }
797 }
798}