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 data = vec![1u8; 0x10];
22//! let iov = libc::iovec {
23//!     iov_base: data.as_ptr() as *mut _,
24//!     iov_len: data.len(),
25//! };
26//!
27//! assert!(file.write(&iov, 0).is_ok());
28//! assert!(file.sync().is_ok());
29//!
30//! let mut buf = vec![0u8; data.len()];
31//! let mut read_iov = libc::iovec {
32//!     iov_base: buf.as_mut_ptr() as *mut _,
33//!     iov_len: buf.len(),
34//! };
35//!
36//! assert!(file.read(&mut read_iov, 0).is_ok());
37//! assert_eq!(buf, data);
38//!
39//! assert!(FrozenFile::new(cfg.clone()).is_err());
40//!
41//! assert!(file.delete().is_ok());
42//! assert!(!path.exists());
43//!
44//! drop(file);
45//! assert!(FrozenFile::new(cfg).is_ok());
46//! ```
47
48#[cfg(any(target_os = "linux", target_os = "macos"))]
49mod posix;
50
51use crate::error::{FrozenErr, FrozenRes};
52
53/// file descriptor for [`FrozenFile`]
54#[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
60/// Domain Id for [`FrozenFile`] is **17**
61const ERRDOMAIN: u8 = 0x11;
62
63/// module id used for [`FrozenErr`]
64static mut MODULE_ID: u8 = 0;
65
66/// Error codes for [`FrozenFile`]
67#[repr(u16)]
68pub enum FFileErrRes {
69    /// (256) internal fuck up (hault and catch fire)
70    Hcf = 0x100,
71
72    /// (257) unknown error (fallback)
73    Unk = 0x101,
74
75    /// (258) no more space available
76    Nsp = 0x102,
77
78    /// (259) syncing error
79    Syn = 0x103,
80
81    /// (260) no write/read perm
82    Prm = 0x104,
83
84    /// (261) invalid path
85    Inv = 0x105,
86
87    /// (262) corrupted file
88    Cpt = 0x106,
89
90    /// (265) unable to grow
91    Grw = 0x107,
92
93    /// (266) unable to lock
94    Lck = 0x108,
95
96    /// (267) locks exhausted (mainly on nfs)
97    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/// Config for [`FrozenFile`]
139#[derive(Debug, Clone)]
140pub struct FFCfg {
141    /// Module id used for error logging
142    pub mid: u8,
143
144    /// Path for the file
145    ///
146    /// *NOTE:* The caller must make sure that the parent directory exists
147    pub path: std::path::PathBuf,
148
149    /// Size (in bytes) of a single chunk on fs
150    ///
151    /// A chunk is a smalled fixed size allocation and addressing unit used by
152    /// [`FrozenFile`] for all the write/read ops, which are operated by index
153    /// of the chunk and not the offset of the byte
154    pub chunk_size: usize,
155
156    /// Number of chunks to pre-allocate when [`FrozenFile`] is initialized
157    ///
158    /// Initial file length will be `chunk_size * initial_chunk_amount` (bytes)
159    pub initial_chunk_amount: usize,
160}
161
162/// Custom implementation of `std::fs::File`
163///
164/// ## Example
165///
166/// ```
167/// use frozen_core::ffile::{FrozenFile, FFCfg};
168///
169/// let dir = tempfile::tempdir().unwrap();
170/// let path = dir.path().join("tmp_frozen_file");
171///
172/// let cfg = FFCfg {
173///     mid: 0x00,
174///     chunk_size: 0x10,
175///     path: path.to_path_buf(),
176///     initial_chunk_amount: 0x0A,
177/// };
178///
179/// let file = FrozenFile::new(cfg.clone()).unwrap();
180/// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
181///
182/// let data = vec![1u8; 0x10];
183/// let iov = libc::iovec {
184///     iov_base: data.as_ptr() as *mut _,
185///     iov_len: data.len(),
186/// };
187///
188/// assert!(file.write(&iov, 0).is_ok());
189/// assert!(file.sync().is_ok());
190///
191/// let mut buf = vec![0u8; data.len()];
192/// let mut read_iov = libc::iovec {
193///     iov_base: buf.as_mut_ptr() as *mut _,
194///     iov_len: buf.len(),
195/// };
196///
197/// assert!(file.read(&mut read_iov, 0).is_ok());
198/// assert_eq!(buf, data);
199///
200/// assert!(FrozenFile::new(cfg.clone()).is_err());
201///
202/// assert!(file.delete().is_ok());
203/// assert!(!path.exists());
204///
205/// drop(file);
206/// assert!(FrozenFile::new(cfg).is_ok());
207/// ```
208#[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    /// Read current length of [`FrozenFile`]
219    #[inline]
220    pub fn length(&self) -> FrozenRes<usize> {
221        unsafe { self.get_file().length() }
222    }
223
224    /// Get file descriptor for [`FrozenFile`]
225    #[inline]
226    #[cfg(any(target_os = "linux", target_os = "macos"))]
227    pub fn fd(&self) -> i32 {
228        self.get_file().fd()
229    }
230
231    /// Check if [`FrozenFile`] exists on the fs
232    pub fn exists(&self) -> FrozenRes<bool> {
233        unsafe { TFile::exists(&self.cfg.path) }
234    }
235
236    /// Create a new or open an existing [`FrozenFile`]
237    ///
238    /// ## [`FFCfg`]
239    ///
240    /// All configs for [`FrozenFile`] are stored in [`FFCfg`]
241    ///
242    /// ## Important
243    ///
244    /// The `cfg` must not change any of its properties for the entire life of [`FrozenFile`],
245    /// one must use config stores like [`Rta`](https://crates.io/crates/rta) to store config
246    ///
247    /// ## Multiple Instances
248    ///
249    /// We acquire an exclusive lock for the entire file, this protects against operating with
250    /// multiple simultenious instance of [`FrozenFile`], when trying to call [`FrozenFile::new`]
251    /// when already called, [`FFileErrRes::Lck`] error will be thrown
252    ///
253    /// ## Example
254    ///
255    /// ```
256    /// use frozen_core::ffile::{FrozenFile, FFCfg};
257    ///
258    /// let dir = tempfile::tempdir().unwrap();
259    /// let path = dir.path().join("tmp_frozen_file");
260    ///
261    /// let cfg = FFCfg {
262    ///     mid: 0x00,
263    ///     chunk_size: 0x10,
264    ///     path: path.to_path_buf(),
265    ///     initial_chunk_amount: 0x0A,
266    /// };
267    ///
268    /// let file = FrozenFile::new(cfg).unwrap();
269    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
270    ///```
271    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        // INFO: right after open is successful, we must obtain an exclusive lock on the
281        // entire file, hence when another instance of [`FrozenFile`], when trying to access
282        // the same file, would correctly fail, while again obtaining the lock
283        unsafe { file.flock() }?;
284
285        // NOTE: we only set it the module_id once, right after an exclusive lock for the entire file is
286        // acquired, hence it'll be only set once per instance and is only used for error logging
287        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                // NOTE: we can treat this invariants as errors only because, our system guarantees,
296                // whenever file size is updated, i.e. has grown, it'll always be a multiple of `chunk_size`,
297                // and will have minimum of `chunk_size * initial_chunk_amount` (bytes) as the length, although
298                // it only holds true when any of params in `cfg` are never updated after the file is created
299                if (curr_len < init_len) || (curr_len % cfg.chunk_size != 0) {
300                    // INFO:
301                    // - close the file to avoid resource leaks
302                    // - we supress the close error, as we are already in an errored state
303                    let _ = unsafe { file.close() };
304                    return new_err_default(FFileErrRes::Cpt);
305                }
306            }
307        }
308
309        Ok(slf)
310    }
311
312    /// Grow file size of [`FrozenFile`] by given `count` of chunks
313    ///
314    /// ## Example
315    ///
316    /// ```
317    /// use frozen_core::ffile::{FrozenFile, FFCfg};
318    ///
319    /// let dir = tempfile::tempdir().unwrap();
320    /// let path = dir.path().join("tmp_frozen_file");
321    ///
322    /// let cfg = FFCfg {
323    ///     mid: 0x00,
324    ///     chunk_size: 0x10,
325    ///     path: path.to_path_buf(),
326    ///     initial_chunk_amount: 0x0A,
327    /// };
328    ///
329    /// let file = FrozenFile::new(cfg).unwrap();
330    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
331    ///
332    /// file.grow(0x20).unwrap();
333    /// assert_eq!(file.length().unwrap(), 0x10 * (0x0A + 0x20));
334    ///```    
335    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    /// Syncs in-mem data on the storage device
343    pub fn sync(&self) -> FrozenRes<()> {
344        let file = self.get_file();
345        unsafe { file.sync() }
346    }
347
348    /// Initiates writeback (best-effort) of dirty pages in the specified range
349    #[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    /// Delete [`FrozenFile`] from fs
359    ///
360    /// ## Example
361    ///
362    /// ```
363    /// use frozen_core::ffile::{FrozenFile, FFCfg};
364    ///
365    /// let dir = tempfile::tempdir().unwrap();
366    /// let path = dir.path().join("tmp_frozen_file");
367    ///
368    /// let cfg = FFCfg {
369    ///     mid: 0x00,
370    ///     chunk_size: 0x10,
371    ///     path: path.to_path_buf(),
372    ///     initial_chunk_amount: 0x0A,
373    /// };
374    ///
375    /// let file = FrozenFile::new(cfg).unwrap();
376    /// assert!(file.exists().unwrap());
377    ///
378    /// file.delete().unwrap();
379    /// assert!(!file.exists().unwrap());
380    ///```
381    pub fn delete(&self) -> FrozenRes<()> {
382        let file = self.get_file();
383        unsafe { file.unlink(&self.cfg.path) }
384    }
385
386    /// Read a single `iovec` chunk at given `index` w/ `pread` syscall
387    #[inline(always)]
388    #[cfg(any(target_os = "linux", target_os = "macos"))]
389    pub fn read(&self, buf: &mut libc::iovec, index: usize) -> FrozenRes<()> {
390        // sanity check
391        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    /// Write a single `iovec` chunk at given `index` w/ `pwrite` syscall
400    #[inline(always)]
401    #[cfg(any(target_os = "linux", target_os = "macos"))]
402    pub fn write(&self, buf: &libc::iovec, index: usize) -> FrozenRes<()> {
403        // sanity check
404        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    /// Read multiple `iovec` chunks starting from given `index` till `iovs.len()` w/ `preadv` syscall
413    #[inline(always)]
414    #[cfg(any(target_os = "linux", target_os = "macos"))]
415    pub fn preadv(&self, iovs: &mut [libc::iovec], index: usize) -> FrozenRes<()> {
416        // sanity check
417        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    /// Write multiple `iovec` chunks starting from given `index` till `iovs.len()` w/ `pwritev` syscall
426    #[inline(always)]
427    #[cfg(any(target_os = "linux", target_os = "macos"))]
428    pub fn pwritev(&self, iovs: &mut [libc::iovec], index: usize) -> FrozenRes<()> {
429        // sanity check
430        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        // guard for when delete is called (or drop on drop if its somehow possible)
447        #[cfg(any(target_os = "linux", target_os = "macos"))]
448        if self.fd() == posix::CLOSED_FD {
449            return;
450        }
451
452        // sync if dirty & close
453        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            // must be dropped to release the exclusive lock
513            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            // updated cfg
527            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            // index > curr_chunks
882            let err = file.read(&mut iov, 0x100).unwrap_err();
883            assert!(err.cmp(FFileErrRes::Hcf as u16));
884        }
885    }
886}