Skip to main content

File

Struct File 

Source
pub struct File { /* private fields */ }
Expand description

Custom implementation of std::fs::File

Implementations§

Source§

impl File

Source

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,
};

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());
Source

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,
};

{
    let file = File::new(cfg.clone()).unwrap();
    assert_eq!(file.length(), 256);
}

let opened = File::open(cfg).unwrap();
assert_eq!(opened.length(), 256);
Source

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,
};

// 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);
Source

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 of cfg.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,
};

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);
Source

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 of cfg.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,
};

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());
Source

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,
};

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);
Source

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,
};

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());
Source

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,
};

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());
Source

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,
};

let file = File::new(cfg).unwrap();
assert_eq!(file.total_buffers().unwrap(), 8);
Source

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,
};

let file = File::new(cfg).unwrap();
assert_eq!(file.cfg().buffer_size, 64);
assert_eq!(file.cfg().initial_available_buffers, 2);
Source

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,
};

let file = File::new(cfg).unwrap();
assert_eq!(file.length(), 512);
Source

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,
};

let file = File::new(cfg).unwrap();
assert_eq!(file.current_epoch(), 0);

file.write(&[1u8; 16], 0).unwrap();
assert_eq!(file.current_epoch(), 1);
Source

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,
};

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);
Source

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,
};

let file = File::new(cfg).unwrap();
let completion = file.completion();
assert_eq!(completion.read_current_epoch(), 0);
Source

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,
};

let file = File::new(cfg).unwrap();
assert!(file.exists().unwrap());
Source

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,
};

let file = File::new(cfg).unwrap();
assert!(path.exists());

file.delete().unwrap();
assert!(!path.exists());
Source

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,
};

let file = File::new(cfg).unwrap();
#[cfg(unix)]
assert!(file.fd() >= 0);
#[cfg(windows)]
assert_ne!(file.fd(), -1isize);

Trait Implementations§

Source§

impl Debug for File

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for File

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl Send for File

Source§

impl Sync for File

Auto Trait Implementations§

§

impl !RefUnwindSafe for File

§

impl !UnwindSafe for File

§

impl Freeze for File

§

impl Unpin for File

§

impl UnsafeUnpin for File

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.