pub struct File { /* private fields */ }Expand description
Custom implementation of std::fs::File
Implementations§
Source§impl File
impl File
Sourcepub fn new(cfg: FileCfg) -> FrozenResult<Self>
pub fn new(cfg: FileCfg) -> FrozenResult<Self>
Creates and pre-allocates a new File at cfg.path
§TOCTAU Safe
If the file already exists, an error w/ err::EXS is returned to guard against TOCTOU
overwrites
§Exclusive Lock
Acquires an exclusive advisory lock via flock(LOCK_EX | LOCK_NB) immediately after
descriptor creation
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("new_file.bin"),
buffer_size: 64,
initial_available_buffers: 4,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg.clone()).unwrap();
assert_eq!(file.length(), 256);
// Creating another file at the same path fails
assert!(File::new(cfg).is_err());Sourcepub fn open(cfg: FileCfg) -> FrozenResult<Self>
pub fn open(cfg: FileCfg) -> FrozenResult<Self>
Open an existing File while validating its layout invariants
§Invariants
Following invariants are checked,
- Current file length is at least
buffer_size * initial_available_buffers - Current file length is a multiple of
buffer_size
If any invariant is violated, the file is closed and err::CPT is returned
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("open_file.bin"),
buffer_size: 64,
initial_available_buffers: 4,
sync_interval: None,
sequential_access_hint: false,
};
{
let file = File::new(cfg.clone()).unwrap();
assert_eq!(file.length(), 256);
}
let opened = File::open(cfg).unwrap();
assert_eq!(opened.length(), 256);Sourcepub fn open_or_create(cfg: FileCfg) -> FrozenResult<Self>
pub fn open_or_create(cfg: FileCfg) -> FrozenResult<Self>
Open an existing File or create it if it does not yet exist
§Semantics
Uses idempotent open-or-create (OPEN_ALWAYS on Windows, O_CREAT without O_EXCL on
POSIX)
If the file is newly created, it is grown to buffer_size * initial_available_buffers and
synced; and if the file already exists, it is opened and its layout invariants are validated
In both cases, an exclusive advisory lock is acquired on the file
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("idempotent.bin"),
buffer_size: 64,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
// First invocation creates and initializes the file
{
let file = File::open_or_create(cfg.clone()).unwrap();
assert_eq!(file.length(), 128);
}
// Subsequent invocation opens the existing file safely
let reopened = File::open_or_create(cfg).unwrap();
assert_eq!(reopened.length(), 128);Sourcepub fn read(&self, buf: &mut [u8], index: usize) -> FrozenResult<()>
pub fn read(&self, buf: &mut [u8], index: usize) -> FrozenResult<()>
Read bytes starting from buffer index into buf w/ pread syscall
§Durability Guarantee & Caller Responsibility
It is 100% the caller’s responsibility to ensure that they do not read newly written data
unless the older data has been confirmed durable
(e.g. by checking or waiting on the write’s AckTicket)
§Multiple Buffers
The input buf can span across a single or multiple buffers in memory
§Constraints
buf.len()must be a non-zero multiple ofcfg.buffer_size- Reading beyond current file length will return
err::HCF
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("read_test.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
let data = [7u8; 16];
let ticket = file.write(&data, 0).unwrap();
ticket.force().unwrap();
let mut out = [0u8; 16];
file.read(&mut out, 0).unwrap();
assert_eq!(out, data);Sourcepub fn write(&self, buf: &[u8], index: usize) -> FrozenResult<AckTicket>
pub fn write(&self, buf: &[u8], index: usize) -> FrozenResult<AckTicket>
Write bytes starting at buffer index from buf w/ pwrite syscall
Returns an AckTicket representing the durability acknowledgement of this write operation
§Multiple Buffers
The input buf can span across a single or multiple buffers in memory
§Constraints
buf.len()must be a non-zero multiple ofcfg.buffer_size- Writing beyond current file length will return
err::HCF
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("write_test.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
let data = [42u8; 16];
let ticket = file.write(&data, 1).unwrap();
assert_eq!(ticket.is_durable(), false);
file.sync().unwrap();
assert!(ticket.is_durable());Sourcepub fn grow(&self, count: usize) -> FrozenResult<()>
pub fn grow(&self, count: usize) -> FrozenResult<()>
Grow file size of File by given count of buffers
After successful execution, updated file length will be current_length + (count * buffer_size)
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("grow_test.bin"),
buffer_size: 32,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert_eq!(file.length(), 64);
file.grow(3).unwrap();
assert_eq!(file.length(), 160);
assert_eq!(file.total_buffers().unwrap(), 5);Sourcepub fn sync(&self) -> FrozenResult<()>
pub fn sync(&self) -> FrozenResult<()>
Syncs in-mem data to the storage device
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("sync_test.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
let ticket = file.write(&[9u8; 16], 0).unwrap();
assert!(!ticket.is_durable());
file.sync().unwrap();
assert!(ticket.is_durable());Sourcepub fn sync_range(&self, index: usize, count: usize) -> FrozenResult<()>
pub fn sync_range(&self, index: usize, count: usize) -> FrozenResult<()>
Best-effort call to prompt kernel to start flushing dirty pages in the specified chunk range
If count == 0, this call is a no-op and returns Ok(())
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("sync_range.bin"),
buffer_size: 64,
initial_available_buffers: 4,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
file.write(&[1u8; 64], 0).unwrap();
assert!(file.sync_range(0, 1).is_ok());
assert!(file.sync_range(0, 0).is_ok());Sourcepub fn total_buffers(&self) -> FrozenResult<usize>
pub fn total_buffers(&self) -> FrozenResult<usize>
Fetch total available buffers in File
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("buffers.bin"),
buffer_size: 32,
initial_available_buffers: 8,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert_eq!(file.total_buffers().unwrap(), 8);Sourcepub fn cfg(&self) -> &FileCfg
pub fn cfg(&self) -> &FileCfg
Get reference to configuration of File
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("cfg.bin"),
buffer_size: 64,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert_eq!(file.cfg().buffer_size, 64);
assert_eq!(file.cfg().initial_available_buffers, 2);Sourcepub fn length(&self) -> usize
pub fn length(&self) -> usize
Read current length (in bytes) of File
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("len.bin"),
buffer_size: 128,
initial_available_buffers: 4,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert_eq!(file.length(), 512);Sourcepub fn current_epoch(&self) -> TEpoch
pub fn current_epoch(&self) -> TEpoch
Fetch the latest assigned durability epoch
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("epoch.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert_eq!(file.current_epoch(), 0);
file.write(&[1u8; 16], 0).unwrap();
assert_eq!(file.current_epoch(), 1);Sourcepub fn durable_epoch(&self) -> TEpoch
pub fn durable_epoch(&self) -> TEpoch
Fetch the latest durable epoch
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("durable_epoch.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
file.write(&[1u8; 16], 0).unwrap();
assert_eq!(file.durable_epoch(), 0);
file.sync().unwrap();
assert_eq!(file.durable_epoch(), 1);Sourcepub fn completion(&self) -> &Arc<Completion> ⓘ
pub fn completion(&self) -> &Arc<Completion> ⓘ
Fetch reference to underlying durability Completion
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("completion.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
let completion = file.completion();
assert_eq!(completion.read_current_epoch(), 0);Sourcepub fn exists(&self) -> FrozenResult<bool>
pub fn exists(&self) -> FrozenResult<bool>
Check if File exists on storage device or not
§Access Semantics
Uses access(path, F_OK) to verify the existence of the file
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("exists.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert!(file.exists().unwrap());Sourcepub fn delete(self) -> FrozenResult<()>
pub fn delete(self) -> FrozenResult<()>
Deletes the File entry from the storage device
Consumes self by value to prevent any concurrent or post-deletion operations
Unlinks the file at path, closes the underlying descriptor, and syncs the parent directory
to guarantee crash-safe durability
§Caller Responsibilities
The caller must ensure that when calling delete, no write operations are pending for
durability and no write operations are invoked during or after delete is called
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("delete_test.bin");
let cfg = FileCfg {
module_id: 1,
path: path.clone(),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
assert!(path.exists());
file.delete().unwrap();
assert!(!path.exists());Sourcepub fn fd(&self) -> FileId
pub fn fd(&self) -> FileId
Get file descriptor or handle for File
§Example
use frozen_core::file::{File, FileCfg};
let dir = tempfile::tempdir().unwrap();
let cfg = FileCfg {
module_id: 1,
path: dir.path().join("fd_test.bin"),
buffer_size: 16,
initial_available_buffers: 2,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::new(cfg).unwrap();
#[cfg(unix)]
assert!(file.fd() >= 0);
#[cfg(windows)]
assert_ne!(file.fd(), -1isize);