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, FrozenFileCfg};
7//!
8//! const MID: u8 = 0;
9//!
10//! let dir = tempfile::tempdir().unwrap();
11//! let path = dir.path().join("tmp_frozen_file");
12//!
13//! let cfg = FrozenFileCfg {
14//!     module_id: MID,
15//!     buffer_size: 0x10,
16//!     path: path.to_path_buf(),
17//!     initial_available_buffers: 0x0A,
18//! };
19//!
20//! let file = FrozenFile::new(cfg.clone()).unwrap();
21//! assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
22//!
23//! let mut data = vec![1u8; 0x10];
24//! assert!(file.pwrite(data.as_mut_ptr(), 0).is_ok());
25//! assert!(file.sync().is_ok());
26//!
27//! let mut buf = vec![0u8; data.len()];
28//! assert!(file.pread(buf.as_mut_ptr(), 0).is_ok());
29//! assert_eq!(buf, data);
30//!
31//! assert!(FrozenFile::new(cfg.clone()).is_err());
32//!
33//! assert!(file.delete().is_ok());
34//! assert!(!path.exists());
35//!
36//! drop(file);
37//! assert!(FrozenFile::new(cfg).is_ok());
38//! ```
39
40#[cfg(any(target_os = "linux", target_os = "macos"))]
41mod posix;
42
43use crate::error::FrozenResult;
44
45/// File descriptor of [`FrozenFile`]
46#[cfg(any(target_os = "linux", target_os = "macos"))]
47pub type TFileId = libc::c_int;
48
49#[cfg(any(target_os = "linux", target_os = "macos"))]
50type TFile = posix::POSIXFile;
51
52/// Error codes for [`FrozenFile`]
53pub(in crate::ffile) mod err {
54    use crate::error::{ErrCode, FrozenError, FrozenResult};
55
56    /// Domain Id for [`FrozenFile`] is **17**
57    const ERRDOMAIN: u8 = 0x11;
58
59    /// module id used for [`FrozenError`]
60    pub static MID: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
61
62    #[cfg(not(test))]
63    #[inline(always)]
64    pub fn mid() -> &'static u8 {
65        MID.get().unwrap()
66    }
67
68    #[cfg(test)]
69    #[inline(always)]
70    pub fn mid() -> &'static u8 {
71        MID.get_or_init(|| 0)
72    }
73
74    /// internal fuck up (hault and catch fire)
75    pub const HCF: ErrCode = ErrCode::new(0x02, "hault and catch fire");
76
77    /// unknown error (fallback)
78    pub const UNK: ErrCode = ErrCode::new(0x04, "unknown error");
79
80    /// no more space available
81    pub const NSP: ErrCode = ErrCode::new(0x08, "not enough space available on the storage device");
82
83    /// syncing error
84    pub const SYN: ErrCode = ErrCode::new(0x0A, "failed to sync/flush data to storage device");
85
86    /// no write perm
87    pub const WRT: ErrCode = ErrCode::new(0x0C, "missing permissions for write");
88
89    /// no read perm
90    pub const RED: ErrCode = ErrCode::new(0x0E, "missing permissions for read");
91
92    /// invalid file path
93    pub const INV: ErrCode = ErrCode::new(0x10, "invalid path to file");
94
95    /// corrupted file
96    pub const CPT: ErrCode = ErrCode::new(0x12, "file is either invalid or corrupted");
97
98    /// unable to grow
99    pub const GRW: ErrCode = ErrCode::new(0x14, "unable to zero-extend file");
100
101    /// locks exhausted (mainly on nfs)
102    pub const LEX: ErrCode = ErrCode::new(0x18, "failed to obtain lock, as no more locks available");
103
104    /// no write/read perm
105    pub const PRM: ErrCode = ErrCode::new(0x1A, "missing permissions for IO");
106
107    /// unable to obtain exclusive lock
108    pub const LCK: ErrCode = ErrCode::new(0x1C, "failed to obtain exclusive lock as file may already opened");
109
110    #[inline]
111    pub(in crate::ffile) fn new_err<R, E: std::fmt::Display>(code: ErrCode, error: E) -> FrozenResult<R> {
112        let err = FrozenError::new_raw(*mid(), ERRDOMAIN, code, error);
113        Err(err)
114    }
115
116    #[inline]
117    pub(in crate::ffile) fn new_err_default<R>(code: ErrCode) -> FrozenResult<R> {
118        let err = FrozenError::new(*mid(), ERRDOMAIN, code, "");
119        Err(err)
120    }
121}
122
123/// Config for [`FrozenFile`]
124#[derive(Debug, Clone)]
125pub struct FrozenFileCfg {
126    /// Identifier used for error propagation by [`frozen_core::error::FrozenError`]
127    pub module_id: u8,
128
129    /// Absolute path for/of the file
130    ///
131    /// *NOTE:* The caller must make sure that the path represents a file and all the parent
132    /// directories included in the path do exists.
133    pub path: std::path::PathBuf,
134
135    /// Size (in bytes) of a single chunk in file
136    ///
137    /// A chunk is a small fixed size allocation and addressing unit used by [`FrozenFile`] for all
138    /// the write/read ops. These ops are operated by index of the chunk and not the offset of the
139    /// byte.
140    ///
141    /// *NOTE:* Chunk size when power of 2, is cache efficient and good for performance
142    pub buffer_size: usize,
143
144    /// Number of chunks to pre-allocate on fs when [`FrozenFile`] is initialized
145    ///
146    /// Initial file length will be `buffer_size * initial_available_buffers` (bytes).
147    pub initial_available_buffers: usize,
148}
149
150/// Custom implementation of `std::fs::File`
151///
152/// ## Example
153///
154/// ```
155/// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
156///
157/// const MID: u8 = 0;
158///
159/// let dir = tempfile::tempdir().unwrap();
160/// let path = dir.path().join("tmp_frozen_file");
161///
162/// let cfg = FrozenFileCfg {
163///     module_id: MID,
164///     buffer_size: 0x10,
165///     path: path.to_path_buf(),
166///     initial_available_buffers: 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: FrozenFileCfg,
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    /// Read current length of [`FrozenFile`]
199    #[inline]
200    pub fn length(&self) -> FrozenResult<usize> {
201        unsafe { self.get_file().length() }
202    }
203
204    /// Get file descriptor for [`FrozenFile`]
205    #[inline]
206    #[cfg(any(target_os = "linux", target_os = "macos"))]
207    pub fn fd(&self) -> TFileId {
208        self.get_file().fd()
209    }
210
211    /// Check if the [`FrozenFile`] exists on the fs
212    pub fn exists(&self) -> FrozenResult<bool> {
213        unsafe { TFile::exists(&self.cfg.path) }
214    }
215
216    /// Create a new or open an existing [`FrozenFile`]
217    ///
218    /// ## [`FrozenFileCfg`]
219    ///
220    /// All configs for [`FrozenFile`] are stored in [`FrozenFileCfg`]
221    ///
222    /// ## Important
223    ///
224    /// The provided [`FrozenFileCfg`] must remain identical across all reopen cycles of the
225    /// [`FrozenFile`].
226    ///
227    /// Changing any of the feilds after initial creation, may violate internal layout invariants
228    /// and cause the file to be treated as corrupted.
229    ///
230    /// ## Multiple Instances
231    ///
232    /// Every instance of [`FrozenFile`] tries to acquire an exclusive lock, which protects against
233    /// operating with multiple simultenious instances.
234    ///
235    /// If trying to call [`FrozenFile::new`] when already called, [`FFileErr::Lck`] error will be
236    /// thrown.
237    ///
238    /// ## Example
239    ///
240    /// ```
241    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
242    ///
243    /// const MID: u8 = 0;
244    ///
245    /// let dir = tempfile::tempdir().unwrap();
246    /// let path = dir.path().join("tmp_frozen_file");
247    ///
248    /// let cfg = FrozenFileCfg {
249    ///     module_id: MID,
250    ///     buffer_size: 0x10,
251    ///     path: path.to_path_buf(),
252    ///     initial_available_buffers: 0x0A,
253    /// };
254    ///
255    /// let file = FrozenFile::new(cfg).unwrap();
256    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
257    /// ```
258    pub fn new(cfg: FrozenFileCfg) -> FrozenResult<Self> {
259        let raw_file = unsafe { posix::POSIXFile::new(&cfg.path) }?;
260        let slf = Self { cfg: cfg.clone(), file: core::cell::UnsafeCell::new(core::mem::ManuallyDrop::new(raw_file)) };
261
262        let file = slf.get_file();
263
264        // INFO: right after open is successful, we must obtain an exclusive lock on the entire file.
265        // So the another instance of [`FrozenFile`] will try to access the same lock, would
266        // correctly fail with [`FFileErr::Lck`] error.
267        unsafe { file.flock() }?;
268
269        // NOTE: The value is used for error logging and is initialized only once, as `OnceLock`
270        // guarantees that the first caller sets the value and all subsequent calls reuse it
271        let _ = err::MID.get_or_init(|| cfg.module_id);
272
273        let curr_len = slf.length()?;
274        let init_len = cfg.buffer_size * cfg.initial_available_buffers;
275
276        match curr_len {
277            0 => slf.grow(cfg.initial_available_buffers)?,
278            _ => {
279                // NOTE: we can treat these invariants as errors only because, our system guarantees,
280                // whenever file size is updated, i.e. has grown, it'll always be a multiple of
281                // `buffer_size`, and will have minimum of `buffer_size * initial_available_buffers`
282                // (bytes) as it's length, although it only holds true when any of the params in
283                // provided `cfg` are never updated after the initial creation of the file
284                //
285                // INFO: when true, we close the file to avoid resource leaks
286                if (curr_len < init_len) || (curr_len % cfg.buffer_size != 0) {
287                    // NOTE: we supress the close error as we are already in an errored state
288                    let _ = unsafe { file.close() };
289                    return err::new_err_default(err::CPT);
290                }
291            }
292        }
293
294        Ok(slf)
295    }
296
297    /// Syncs in-mem data on the storage device
298    pub fn sync(&self) -> FrozenResult<()> {
299        let file = self.get_file();
300        unsafe { file.sync() }
301    }
302
303    /// A best-effort call to prompt kernel to start flushing dirty pages in the specified range
304    ///
305    /// This call, by itself, does not guarantee any kind of durability, and must always be paired
306    /// with strong sync call i.e. [`FrozenFile::sync`]
307    #[cfg(target_os = "linux")]
308    pub fn sync_range(&self, index: usize, count: usize) -> FrozenResult<()> {
309        let offset = self.cfg.buffer_size * index;
310        let len_to_sync = self.cfg.buffer_size * count;
311        let file = self.get_file();
312
313        unsafe { file.sync_range(offset, len_to_sync) }
314    }
315
316    /// Delete [`FrozenFile`] from fs
317    ///
318    /// ## Example
319    ///
320    /// ```
321    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
322    ///
323    /// const MID: u8 = 0;
324    ///
325    /// let dir = tempfile::tempdir().unwrap();
326    /// let path = dir.path().join("tmp_frozen_file");
327    ///
328    /// let cfg = FrozenFileCfg {
329    ///     module_id: MID,
330    ///     buffer_size: 0x10,
331    ///     path: path.to_path_buf(),
332    ///     initial_available_buffers: 0x0A,
333    /// };
334    ///
335    /// let file = FrozenFile::new(cfg).unwrap();
336    /// assert!(file.exists().unwrap());
337    ///
338    /// file.delete().unwrap();
339    /// assert!(!file.exists().unwrap());
340    /// ```
341    pub fn delete(&self) -> FrozenResult<()> {
342        let file = self.get_file();
343        unsafe { file.unlink(&self.cfg.path) }
344    }
345
346    /// Read a single chunk at given `index` w/ `pread` syscall
347    ///
348    /// ## Example
349    ///
350    /// ```
351    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
352    ///
353    /// const MID: u8 = 0;
354    ///
355    /// let dir = tempfile::tempdir().unwrap();
356    /// let path = dir.path().join("tmp_frozen_file");
357    ///
358    /// let cfg = FrozenFileCfg {
359    ///     module_id: MID,
360    ///     buffer_size: 0x10,
361    ///     path: path.to_path_buf(),
362    ///     initial_available_buffers: 0x0A,
363    /// };
364    ///
365    /// let file = FrozenFile::new(cfg).unwrap();
366    ///
367    /// let mut data = [7u8; 0x10];
368    /// file.pwrite(data.as_mut_ptr(), 2).unwrap();
369    /// file.sync().unwrap();
370    ///
371    /// let mut buf = [0u8; 0x10];
372    /// file.pread(buf.as_mut_ptr(), 2).unwrap();
373    ///
374    /// assert_eq!(buf, data);
375    /// ```
376    #[inline(always)]
377    #[allow(clippy::not_unsafe_ptr_arg_deref)]
378    #[cfg(any(target_os = "linux", target_os = "macos"))]
379    pub fn pread(&self, buf: *mut u8, index: usize) -> FrozenResult<()> {
380        let offset = self.cfg.buffer_size * index;
381        let file = self.get_file();
382
383        unsafe { file.pread(buf, offset, self.cfg.buffer_size) }
384    }
385
386    /// Write a single chunk at given `index` w/ `pwrite` syscall
387    ///
388    /// ## Example
389    ///
390    /// ```
391    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
392    ///
393    /// const MID: u8 = 0;
394    ///
395    /// let dir = tempfile::tempdir().unwrap();
396    /// let path = dir.path().join("tmp_frozen_file");
397    ///
398    /// let cfg = FrozenFileCfg {
399    ///     module_id: MID,
400    ///     buffer_size: 0x10,
401    ///     path: path.to_path_buf(),
402    ///     initial_available_buffers: 0x0A,
403    /// };
404    ///
405    /// let file = FrozenFile::new(cfg).unwrap();
406    ///
407    /// let mut data = [9u8; 0x10];
408    /// file.pwrite(data.as_mut_ptr(), 4).unwrap();
409    /// file.sync().unwrap();
410    ///
411    /// let mut buf = [0u8; 0x10];
412    /// file.pread(buf.as_mut_ptr(), 4).unwrap();
413    ///
414    /// assert_eq!(buf, data);
415    /// ```
416    #[inline(always)]
417    #[allow(clippy::not_unsafe_ptr_arg_deref)]
418    #[cfg(any(target_os = "linux", target_os = "macos"))]
419    pub fn pwrite(&self, buf: *mut u8, index: usize) -> FrozenResult<()> {
420        let offset = self.cfg.buffer_size * index;
421        let file = self.get_file();
422
423        unsafe { file.pwrite(buf, offset, self.cfg.buffer_size) }
424    }
425
426    /// Read multiple chunks starting from given `index` till `bufs.len()` w/ `preadv` syscall
427    ///
428    /// ## Example
429    ///
430    /// ```
431    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
432    ///
433    /// const MID: u8 = 0;
434    ///
435    /// let dir = tempfile::tempdir().unwrap();
436    /// let path = dir.path().join("tmp_frozen_file");
437    ///
438    /// let cfg = FrozenFileCfg {
439    ///     module_id: MID,
440    ///     buffer_size: 0x10,
441    ///     path: path.to_path_buf(),
442    ///     initial_available_buffers: 0x0A,
443    /// };
444    ///
445    /// let file = FrozenFile::new(cfg).unwrap();
446    ///
447    /// let mut write_bufs = [[1u8; 0x10], [2u8; 0x10]];
448    /// let ptrs: Vec<*mut u8> = write_bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
449    ///
450    /// file.pwritev(&ptrs, 0).unwrap();
451    /// file.sync().unwrap();
452    ///
453    /// let mut read_bufs = [[0u8; 0x10], [0u8; 0x10]];
454    /// let rptrs: Vec<*mut u8> = read_bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
455    ///
456    /// file.preadv(&rptrs, 0).unwrap();
457    ///
458    /// assert!(read_bufs[0].iter().all(|b| *b == 1));
459    /// assert!(read_bufs[1].iter().all(|b| *b == 2));
460    /// ```
461    #[inline(always)]
462    #[cfg(any(target_os = "linux", target_os = "macos"))]
463    pub fn preadv(&self, bufs: &[*mut u8], index: usize) -> FrozenResult<()> {
464        let offset = self.cfg.buffer_size * index;
465        let file = self.get_file();
466
467        unsafe { file.preadv(bufs, offset, self.cfg.buffer_size) }
468    }
469
470    /// Write multiple chunks starting from given `index` till `bufs.len()` w/ `pwritev` syscall
471    ///
472    /// ## Example
473    ///
474    /// ```
475    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
476    ///
477    /// const MID: u8 = 0;
478    ///
479    /// let dir = tempfile::tempdir().unwrap();
480    /// let path = dir.path().join("tmp_frozen_file");
481    ///
482    /// let cfg = FrozenFileCfg {
483    ///     module_id: MID,
484    ///     buffer_size: 0x10,
485    ///     path: path.to_path_buf(),
486    ///     initial_available_buffers: 0x0A,
487    /// };
488    ///
489    /// let file = FrozenFile::new(cfg).unwrap();
490    ///
491    /// let mut bufs = [[3u8; 0x10], [4u8; 0x10]];
492    /// let ptrs: Vec<*mut u8> = bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
493    ///
494    /// file.pwritev(&ptrs, 2).unwrap();
495    /// file.sync().unwrap();
496    ///
497    /// let mut a = [0u8; 0x10];
498    /// let mut b = [0u8; 0x10];
499    ///
500    /// file.pread(a.as_mut_ptr(), 2).unwrap();
501    /// file.pread(b.as_mut_ptr(), 3).unwrap();
502    ///
503    /// assert!(a.iter().all(|v| *v == 3));
504    /// assert!(b.iter().all(|v| *v == 4));
505    /// ```
506    #[inline(always)]
507    #[cfg(any(target_os = "linux", target_os = "macos"))]
508    pub fn pwritev(&self, bufs: &[*mut u8], index: usize) -> FrozenResult<()> {
509        let offset = self.cfg.buffer_size * index;
510        let file = self.get_file();
511
512        unsafe { file.pwritev(bufs, offset, self.cfg.buffer_size) }
513    }
514
515    /// Grow file size of [`FrozenFile`] by given `count` of chunks
516    ///
517    /// After successful execution, updated file length will be `current_length + (count * buffer_size)`
518    ///
519    /// ## Example
520    ///
521    /// ```
522    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
523    ///
524    /// const MID: u8 = 0;
525    ///
526    /// let dir = tempfile::tempdir().unwrap();
527    /// let path = dir.path().join("tmp_frozen_file");
528    ///
529    /// let cfg = FrozenFileCfg {
530    ///     module_id: MID,
531    ///     buffer_size: 0x10,
532    ///     path: path.to_path_buf(),
533    ///     initial_available_buffers: 0x0A,
534    /// };
535    ///
536    /// let file = FrozenFile::new(cfg).unwrap();
537    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
538    ///
539    /// file.grow(0x20).unwrap();
540    /// assert_eq!(file.length().unwrap(), 0x10 * (0x0A + 0x20));
541    /// ```
542    pub fn grow(&self, count: usize) -> FrozenResult<()> {
543        let curr_len = self.length()?;
544        let len_to_add = self.cfg.buffer_size * count;
545
546        unsafe { self.get_file().grow(curr_len, len_to_add) }
547    }
548
549    /// Fetch total available chunks in [`FrozenFile`] from fs
550    ///
551    /// ## Working
552    ///
553    /// This call performs a syscall to fetch current length of [`FrozenFile`] from fs, as the
554    /// current length of the file is not cached anywhere in the pipeline to avoid TOCTAU race
555    /// conditions.
556    ///
557    /// ## Example
558    ///
559    /// ```
560    /// use frozen_core::ffile::{FrozenFile, FrozenFileCfg};
561    ///
562    /// const MID: u8 = 0;
563    ///
564    /// let dir = tempfile::tempdir().unwrap();
565    /// let path = dir.path().join("tmp_frozen_file");
566    ///
567    /// let cfg = FrozenFileCfg {
568    ///     module_id: MID,
569    ///     buffer_size: 0x10,
570    ///     path: path.to_path_buf(),
571    ///     initial_available_buffers: 0x0A,
572    /// };
573    ///
574    /// let file = FrozenFile::new(cfg).unwrap();
575    /// assert_eq!(file.length().unwrap(), 0x10 * 0x0A);
576    ///
577    /// file.grow(0x20).unwrap();
578    /// assert_eq!(file.total_chunks().unwrap(), 0x0A + 0x20);
579    /// ```
580    #[inline]
581    pub fn total_chunks(&self) -> FrozenResult<usize> {
582        let curr_len = self.length()?;
583        let buffer_size = self.cfg.buffer_size;
584
585        if crate::hints::unlikely(curr_len % buffer_size != 0) {
586            return err::new_err_default(err::CPT);
587        }
588
589        Ok(curr_len / buffer_size)
590    }
591
592    #[inline]
593    fn get_file(&self) -> &core::mem::ManuallyDrop<TFile> {
594        unsafe { &*self.file.get() }
595    }
596}
597
598impl Drop for FrozenFile {
599    fn drop(&mut self) {
600        // guard for when delete is called (or drop on drop if its somehow possible)
601        #[cfg(any(target_os = "linux", target_os = "macos"))]
602        if self.fd() == posix::CLOSED_FD {
603            return;
604        }
605
606        // sync if dirty & close
607        let _ = self.sync();
608        let _ = unsafe { self.get_file().close() };
609    }
610}
611
612impl core::fmt::Display for FrozenFile {
613    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
614        write!(f, "FrozenFile {{fd: {}, len: {}}}", self.fd(), self.length().unwrap_or(0),)
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621    use std::sync;
622
623    const MID: u8 = 0;
624    const INIT_BUFFERS: usize = 4;
625    const BUFFER_SIZE: usize = 0x10;
626
627    fn tmp_path() -> (tempfile::TempDir, FrozenFileCfg) {
628        let dir = tempfile::tempdir().unwrap();
629        let path = dir.path().join("tmp_ff_file");
630        let cfg =
631            FrozenFileCfg { path, module_id: MID, buffer_size: BUFFER_SIZE, initial_available_buffers: INIT_BUFFERS };
632
633        (dir, cfg)
634    }
635
636    mod ff_lifecycle {
637        use super::*;
638
639        #[test]
640        fn ok_new_with_init_len() {
641            let (_dir, cfg) = tmp_path();
642            let file = FrozenFile::new(cfg).unwrap();
643
644            let exists = file.exists().unwrap();
645            assert!(exists);
646
647            assert_eq!(file.length().unwrap(), BUFFER_SIZE * INIT_BUFFERS);
648        }
649
650        #[test]
651        fn ok_new_existing() {
652            let (_dir, cfg) = tmp_path();
653
654            let file = FrozenFile::new(cfg.clone()).unwrap();
655            assert_eq!(file.length().unwrap(), BUFFER_SIZE * INIT_BUFFERS);
656
657            // must be dropped to release the exclusive lock
658            drop(file);
659
660            let reopened = FrozenFile::new(cfg.clone()).unwrap();
661            assert_eq!(reopened.length().unwrap(), BUFFER_SIZE * INIT_BUFFERS);
662        }
663
664        #[test]
665        fn err_new_when_file_smaller_than_init_len() {
666            let (_dir, mut cfg) = tmp_path();
667
668            let file = FrozenFile::new(cfg.clone()).unwrap();
669            drop(file);
670
671            // updated cfg
672            cfg.buffer_size *= 2;
673
674            let err = FrozenFile::new(cfg).unwrap_err();
675            assert_eq!(err.reason, err::CPT.reason);
676        }
677
678        #[test]
679        fn ok_exists_true_when_exists() {
680            let (_dir, cfg) = tmp_path();
681            let file = FrozenFile::new(cfg).unwrap();
682
683            let exists = file.exists().unwrap();
684            assert!(exists);
685        }
686
687        #[test]
688        fn ok_exists_false_when_missing() {
689            let (_dir, cfg) = tmp_path();
690            let file = FrozenFile::new(cfg).unwrap();
691            file.delete().unwrap();
692
693            let exists = file.exists().unwrap();
694            assert!(!exists);
695        }
696
697        #[test]
698        fn ok_delete_file() {
699            let (_dir, cfg) = tmp_path();
700
701            let file = FrozenFile::new(cfg).unwrap();
702            let exists = file.exists().unwrap();
703            assert!(exists);
704
705            file.delete().unwrap();
706            let exists = file.exists().unwrap();
707            assert!(!exists);
708        }
709
710        #[test]
711        fn err_delete_after_delete() {
712            let (_dir, cfg) = tmp_path();
713
714            let file = FrozenFile::new(cfg).unwrap();
715            file.delete().unwrap();
716
717            let err = file.delete().unwrap_err();
718            assert_eq!(err.reason, err::INV.reason);
719        }
720
721        #[test]
722        fn ok_drop_persists_without_explicit_sync() {
723            let mut data = [0x0Bu8; BUFFER_SIZE];
724            let (_dir, cfg) = tmp_path();
725
726            {
727                let file = FrozenFile::new(cfg.clone()).unwrap();
728                file.pwrite(data.as_mut_ptr(), 0).unwrap();
729                drop(file);
730            }
731
732            {
733                let reopened = FrozenFile::new(cfg).unwrap();
734                let mut buf = [0u8; BUFFER_SIZE];
735
736                reopened.pread(buf.as_mut_ptr(), 0).unwrap();
737                assert_eq!(buf, data);
738            }
739        }
740    }
741
742    mod ff_lock {
743        use super::*;
744
745        #[test]
746        fn err_new_when_already_open() {
747            let (_dir, cfg) = tmp_path();
748            let file = FrozenFile::new(cfg.clone()).unwrap();
749
750            let err = FrozenFile::new(cfg).unwrap_err();
751            assert_eq!(err.reason, err::LCK.reason);
752
753            drop(file);
754        }
755
756        #[test]
757        fn ok_drop_releases_exclusive_lock() {
758            let (_dir, cfg) = tmp_path();
759
760            let file = FrozenFile::new(cfg.clone()).unwrap();
761            drop(file);
762
763            let _ = FrozenFile::new(cfg).expect("must not fail after drop");
764        }
765    }
766
767    mod ff_grow {
768        use super::*;
769
770        #[test]
771        fn ok_grow_updates_length() {
772            let (_dir, cfg) = tmp_path();
773
774            let file = FrozenFile::new(cfg).unwrap();
775            assert_eq!(file.length().unwrap(), BUFFER_SIZE * INIT_BUFFERS);
776
777            file.grow(0x20).unwrap();
778            assert_eq!(file.length().unwrap(), BUFFER_SIZE * (INIT_BUFFERS + 0x20));
779        }
780
781        #[test]
782        fn ok_grow_sync_cycle() {
783            let (_dir, cfg) = tmp_path();
784            let file = FrozenFile::new(cfg).unwrap();
785
786            for _ in 0..0x0A {
787                file.grow(0x100).unwrap();
788                file.sync().unwrap();
789            }
790
791            assert_eq!(file.length().unwrap(), BUFFER_SIZE * (INIT_BUFFERS + (0x0A * 0x100)));
792        }
793    }
794
795    mod ff_sync {
796        use super::*;
797
798        #[test]
799        fn ok_sync_after_sync() {
800            let (_dir, cfg) = tmp_path();
801            let file = FrozenFile::new(cfg).unwrap();
802
803            file.sync().unwrap();
804            file.sync().unwrap();
805            file.sync().unwrap();
806        }
807
808        #[test]
809        fn err_sync_after_delete() {
810            let (_dir, cfg) = tmp_path();
811            let file = FrozenFile::new(cfg).unwrap();
812            file.delete().unwrap();
813
814            let err = file.sync().unwrap_err();
815            assert_eq!(err.reason, err::HCF.reason);
816        }
817    }
818
819    mod ff_write_read {
820        use super::*;
821
822        #[test]
823        fn ok_single_write_read_cycle() {
824            let (_dir, cfg) = tmp_path();
825            let file = FrozenFile::new(cfg).unwrap();
826
827            let mut data = [0x0Bu8; BUFFER_SIZE];
828
829            file.pwrite(data.as_mut_ptr(), 4).unwrap();
830            file.sync().unwrap();
831
832            let mut buf = [0u8; BUFFER_SIZE];
833            file.pread(buf.as_mut_ptr(), 4).unwrap();
834            assert_eq!(buf, data);
835        }
836
837        #[test]
838        fn ok_vectored_write_read_cycle() {
839            let (_dir, cfg) = tmp_path();
840            let file = FrozenFile::new(cfg).unwrap();
841
842            let mut bufs = [[1u8; BUFFER_SIZE], [2u8; BUFFER_SIZE]];
843            let bufs: Vec<*mut u8> = bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
844
845            file.pwritev(&bufs, 0).unwrap();
846            file.sync().unwrap();
847
848            let mut read_bufs = [[0u8; BUFFER_SIZE], [0u8; BUFFER_SIZE]];
849            let rbufs: Vec<*mut u8> = read_bufs.iter_mut().map(|b| b.as_mut_ptr()).collect();
850            file.preadv(&rbufs, 0).unwrap();
851
852            assert!(read_bufs[0].iter().all(|b| *b == 1));
853            assert!(read_bufs[1].iter().all(|b| *b == 2));
854        }
855
856        #[test]
857        fn ok_write_concurrent_non_overlapping() {
858            let (_dir, mut cfg) = tmp_path();
859            cfg.initial_available_buffers = 0x100;
860            let file = sync::Arc::new(FrozenFile::new(cfg).unwrap());
861
862            let mut handles = vec![];
863            for i in 0..2 {
864                let f = file.clone();
865                handles.push(std::thread::spawn(move || {
866                    let mut data = [i as u8; BUFFER_SIZE];
867                    f.pwrite(data.as_mut_ptr(), i).unwrap();
868                }));
869            }
870
871            for h in handles {
872                h.join().unwrap();
873            }
874
875            file.sync().unwrap();
876
877            for i in 0..2 {
878                let mut buf = [0u8; BUFFER_SIZE];
879                file.pread(buf.as_mut_ptr(), i).unwrap();
880                assert!(buf.iter().all(|b| *b == i as u8));
881            }
882        }
883
884        #[test]
885        fn ok_concurrent_grow_and_write() {
886            let (_dir, cfg) = tmp_path();
887            let file = sync::Arc::new(FrozenFile::new(cfg).unwrap());
888
889            let writer = {
890                let f = file.clone();
891                std::thread::spawn(move || {
892                    for i in 0..INIT_BUFFERS {
893                        let mut data = [i as u8; BUFFER_SIZE];
894                        f.pwrite(data.as_mut_ptr(), i).unwrap();
895                    }
896                })
897            };
898
899            let chunks_to_grow = 0x20;
900            let grower = {
901                let f = file.clone();
902                std::thread::spawn(move || {
903                    f.grow(chunks_to_grow).unwrap();
904                })
905            };
906
907            writer.join().unwrap();
908            grower.join().unwrap();
909
910            file.sync().unwrap();
911            assert_eq!(file.length().unwrap(), BUFFER_SIZE * (INIT_BUFFERS + chunks_to_grow));
912
913            for i in 0..INIT_BUFFERS {
914                let mut buf = [0u8; BUFFER_SIZE];
915                file.pread(buf.as_mut_ptr(), i).unwrap();
916                assert!(buf.iter().all(|b| *b == i as u8));
917            }
918        }
919
920        #[test]
921        fn ok_concurrent_sync_and_write() {
922            let (_dir, cfg) = tmp_path();
923            let file = sync::Arc::new(FrozenFile::new(cfg).unwrap());
924
925            let writer = {
926                let f = file.clone();
927                std::thread::spawn(move || {
928                    for i in 0..INIT_BUFFERS {
929                        let mut data = [i as u8; BUFFER_SIZE];
930                        f.pwrite(data.as_mut_ptr(), i).unwrap();
931                    }
932                })
933            };
934
935            let syncer = {
936                let f = file.clone();
937                std::thread::spawn(move || {
938                    for _ in 0..2 {
939                        f.sync().unwrap();
940                    }
941                })
942            };
943
944            writer.join().unwrap();
945            syncer.join().unwrap();
946
947            file.sync().unwrap();
948
949            for i in 0..INIT_BUFFERS {
950                let mut buf = [0; BUFFER_SIZE];
951                file.pread(buf.as_mut_ptr(), i).unwrap();
952                assert!(buf.iter().all(|b| *b == i as u8));
953            }
954        }
955
956        #[test]
957        fn err_read_hcf_for_eof() {
958            let (_dir, cfg) = tmp_path();
959            let file = FrozenFile::new(cfg).unwrap();
960
961            // index > curr_chunks
962            let mut buf = [0; BUFFER_SIZE];
963            let err = file.pread(buf.as_mut_ptr(), 0x100).unwrap_err();
964            assert_eq!(err.reason, err::HCF.reason);
965        }
966    }
967}