Skip to main content

frozen_core/ffile/
mod.rs

1//! Custom implementation of `std::fs::File`
2//!
3//! ## Example
4//!
5//! ```
6//! use frozen_core::ffile::{FrozenFile, FFCfg};
7//!
8//! let dir = tempfile::tempdir().unwrap();
9//! let path = dir.path().join("tmp_frozen_file");
10//!
11//! let cfg = FFCfg {
12//!     mid: 0x00,
13//!     chunk_size: 0x10,
14//!     path: path.to_path_buf(),
15//!     initial_chunk_amount: 0x0A,
16//! };
17//!
18//! let file = FrozenFile::new(cfg.clone()).unwrap();
19//! assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
20//!
21//! let mut data = vec![1u8; 0x10];
22//! assert!(file.pwrite(data.as_mut_ptr(), 0).is_ok());
23//! assert!(file.sync().is_ok());
24//!
25//! let mut buf = vec![0u8; data.len()];
26//! assert!(file.pread(buf.as_mut_ptr(), 0).is_ok());
27//! assert_eq!(buf, data);
28//!
29//! assert!(FrozenFile::new(cfg.clone()).is_err());
30//!
31//! assert!(file.delete().is_ok());
32//! assert!(!path.exists());
33//!
34//! drop(file);
35//! assert!(FrozenFile::new(cfg).is_ok());
36//! ```
37
38#[cfg(any(target_os = "linux", target_os = "macos"))]
39mod posix;
40
41use crate::error::{FrozenErr, FrozenRes};
42
43/// file descriptor for [`FrozenFile`]
44#[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
50/// Domain Id for [`FrozenFile`] is **17**
51const ERRDOMAIN: u8 = 0x11;
52
53/// module id used for [`FrozenErr`]
54static mut MODULE_ID: u8 = 0;
55
56/// Error codes for [`FrozenFile`]
57#[repr(u16)]
58pub enum FFileErr {
59    /// (256) internal fuck up (hault and catch fire)
60    Hcf = 0x100,
61
62    /// (257) unknown error (fallback)
63    Unk = 0x101,
64
65    /// (258) no more space available
66    Nsp = 0x102,
67
68    /// (259) syncing error
69    Syn = 0x103,
70
71    /// (260) no write/read perm
72    Prm = 0x104,
73
74    /// (261) invalid path
75    Inv = 0x105,
76
77    /// (262) corrupted file
78    Cpt = 0x106,
79
80    /// (265) unable to grow
81    Grw = 0x107,
82
83    /// (266) unable to lock
84    Lck = 0x108,
85
86    /// (267) locks exhausted (mainly on nfs)
87    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/// Config for [`FrozenFile`]
129#[derive(Debug, Clone)]
130pub struct FFCfg {
131    /// Module id used for error logging
132    pub mid: u8,
133
134    /// Path for the file
135    ///
136    /// *NOTE:* The caller must make sure that the parent directory exists
137    pub path: std::path::PathBuf,
138
139    /// Size (in bytes) of a single chunk on fs
140    ///
141    /// A chunk is a smalled fixed size allocation and addressing unit used by
142    /// [`FrozenFile`] for all the write/read ops, which are operated by index
143    /// of the chunk and not the offset of the byte
144    pub chunk_size: usize,
145
146    /// Number of chunks to pre-allocate when [`FrozenFile`] is initialized
147    ///
148    /// Initial file length will be `chunk_size * initial_chunk_amount` (bytes)
149    pub initial_chunk_amount: usize,
150}
151
152/// Custom implementation of `std::fs::File`
153///
154/// ## Example
155///
156/// ```
157/// use frozen_core::ffile::{FrozenFile, FFCfg};
158///
159/// let dir = tempfile::tempdir().unwrap();
160/// let path = dir.path().join("tmp_frozen_file");
161///
162/// let cfg = FFCfg {
163///     mid: 0x00,
164///     chunk_size: 0x10,
165///     path: path.to_path_buf(),
166///     initial_chunk_amount: 0x0A,
167/// };
168///
169/// let file = FrozenFile::new(cfg.clone()).unwrap();
170/// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
171///
172/// let mut data = vec![1u8; 0x10];
173/// assert!(file.pwrite(data.as_mut_ptr(), 0).is_ok());
174/// assert!(file.sync().is_ok());
175///
176/// let mut buf = vec![0u8; data.len()];
177/// assert!(file.pread(buf.as_mut_ptr(), 0).is_ok());
178/// assert_eq!(buf, data);
179///
180/// assert!(FrozenFile::new(cfg.clone()).is_err());
181///
182/// assert!(file.delete().is_ok());
183/// assert!(!path.exists());
184///
185/// drop(file);
186/// assert!(FrozenFile::new(cfg).is_ok());
187/// ```
188#[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    /// Fetch config used for [`FrozenFile`]
199    #[inline]
200    pub fn cfg(&self) -> &FFCfg {
201        &self.cfg
202    }
203
204    /// Read current length of [`FrozenFile`]
205    #[inline]
206    pub fn length(&self) -> FrozenRes<usize> {
207        unsafe { self.get_file().length() }
208    }
209
210    /// Get file descriptor for [`FrozenFile`]
211    #[inline]
212    #[cfg(any(target_os = "linux", target_os = "macos"))]
213    pub fn fd(&self) -> i32 {
214        self.get_file().fd()
215    }
216
217    /// Check if [`FrozenFile`] exists on the fs
218    pub fn exists(&self) -> FrozenRes<bool> {
219        unsafe { TFile::exists(&self.cfg.path) }
220    }
221
222    /// Create a new or open an existing [`FrozenFile`]
223    ///
224    /// ## [`FFCfg`]
225    ///
226    /// All configs for [`FrozenFile`] are stored in [`FFCfg`]
227    ///
228    /// ## Important
229    ///
230    /// The `cfg` must not change any of its properties for the entire life of [`FrozenFile`],
231    /// one must use config stores like [`Rta`](https://crates.io/crates/rta) to store config
232    ///
233    /// ## Multiple Instances
234    ///
235    /// We acquire an exclusive lock for the entire file, this protects against operating with
236    /// multiple simultenious instance of [`FrozenFile`], when trying to call [`FrozenFile::new`]
237    /// when already called, [`FFileErr::Lck`] error will be thrown
238    ///
239    /// ## Example
240    ///
241    /// ```
242    /// use frozen_core::ffile::{FrozenFile, FFCfg};
243    ///
244    /// let dir = tempfile::tempdir().unwrap();
245    /// let path = dir.path().join("tmp_frozen_file");
246    ///
247    /// let cfg = FFCfg {
248    ///     mid: 0x00,
249    ///     chunk_size: 0x10,
250    ///     path: path.to_path_buf(),
251    ///     initial_chunk_amount: 0x0A,
252    /// };
253    ///
254    /// let file = FrozenFile::new(cfg).unwrap();
255    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
256    ///```
257    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        // INFO: right after open is successful, we must obtain an exclusive lock on the
267        // entire file, hence when another instance of [`FrozenFile`], when trying to access
268        // the same file, would correctly fail, while again obtaining the lock
269        unsafe { file.flock() }?;
270
271        // NOTE: we only set it the module_id once, right after an exclusive lock for the entire file is
272        // acquired, hence it'll be only set once per instance and is only used for error logging
273        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                // NOTE: we can treat this invariants as errors only because, our system guarantees,
282                // whenever file size is updated, i.e. has grown, it'll always be a multiple of `chunk_size`,
283                // and will have minimum of `chunk_size * initial_chunk_amount` (bytes) as the length, although
284                // it only holds true when any of params in `cfg` are never updated after the file is created
285                if (curr_len < init_len) || (curr_len % cfg.chunk_size != 0) {
286                    // INFO:
287                    // - close the file to avoid resource leaks
288                    // - we supress the close error, as we are already in an errored state
289                    let _ = unsafe { file.close() };
290                    return new_err_default(FFileErr::Cpt);
291                }
292            }
293        }
294
295        Ok(slf)
296    }
297
298    /// Grow file size of [`FrozenFile`] by given `count` of chunks
299    ///
300    /// ## Example
301    ///
302    /// ```
303    /// use frozen_core::ffile::{FrozenFile, FFCfg};
304    ///
305    /// let dir = tempfile::tempdir().unwrap();
306    /// let path = dir.path().join("tmp_frozen_file");
307    ///
308    /// let cfg = FFCfg {
309    ///     mid: 0x00,
310    ///     chunk_size: 0x10,
311    ///     path: path.to_path_buf(),
312    ///     initial_chunk_amount: 0x0A,
313    /// };
314    ///
315    /// let file = FrozenFile::new(cfg).unwrap();
316    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
317    ///
318    /// file.grow(0x20).unwrap();
319    /// assert_eq!(file.length().unwrap(), 0x10 * (0x0A + 0x20));
320    ///```    
321    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    /// Syncs in-mem data on the storage device
329    pub fn sync(&self) -> FrozenRes<()> {
330        let file = self.get_file();
331        unsafe { file.sync() }
332    }
333
334    /// Initiates writeback (best-effort) of dirty pages in the specified range
335    #[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    /// Delete [`FrozenFile`] from fs
345    ///
346    /// ## Example
347    ///
348    /// ```
349    /// use frozen_core::ffile::{FrozenFile, FFCfg};
350    ///
351    /// let dir = tempfile::tempdir().unwrap();
352    /// let path = dir.path().join("tmp_frozen_file");
353    ///
354    /// let cfg = FFCfg {
355    ///     mid: 0x00,
356    ///     chunk_size: 0x10,
357    ///     path: path.to_path_buf(),
358    ///     initial_chunk_amount: 0x0A,
359    /// };
360    ///
361    /// let file = FrozenFile::new(cfg).unwrap();
362    /// assert!(file.exists().unwrap());
363    ///
364    /// file.delete().unwrap();
365    /// assert!(!file.exists().unwrap());
366    ///```
367    pub fn delete(&self) -> FrozenRes<()> {
368        let file = self.get_file();
369        unsafe { file.unlink(&self.cfg.path) }
370    }
371
372    /// Read a single chunk at given `index` w/ `pread` syscall
373    #[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    /// Write a single chunk at given `index` w/ `pwrite` syscall
384    #[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    /// Read multiple chunks starting from given `index` till `bufs.len()` w/ `preadv` syscall
395    #[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    /// Write multiple chunks starting from given `index` till `bufs.len()` w/ `pwritev` syscall
405    #[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        // guard for when delete is called (or drop on drop if its somehow possible)
423        #[cfg(any(target_os = "linux", target_os = "macos"))]
424        if self.fd() == posix::CLOSED_FD {
425            return;
426        }
427
428        // sync if dirty & close
429        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            // must be dropped to release the exclusive lock
489            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            // updated cfg
503            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            // index > curr_chunks
793            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}