Skip to main content

CowFile

Struct CowFile 

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

A copy-on-write file abstraction backed by memory or a file.

Writes accumulate in a pending log and are applied to the committed buffer on commit. The committed buffer is accessible as &[u8] via data, while read and typed I/O methods composite pending writes over the committed state.

§Architecture

 Committed Buffer               Pending Log
+---------------------+      +-------------------------+
| Vec<u8> or MmapMut  | <--- | Vec<PendingWrite>       |
| (OS-level CoW)      |      | (applied on commit)     |
+---------------------+      +-------------------------+

For memory-mapped files, the buffer is created with map_copy, which uses MAP_PRIVATE on Unix and PAGE_WRITECOPY on Windows. Only pages touched by commit are copied into anonymous memory — the rest of the file remains demand-paged from disk.

§Examples

use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 100]);

// Writes go to the pending log
pf.write(10, &[0xFF, 0xFE]).unwrap();

// data() returns committed state
assert_eq!(pf.data()[10], 0x00);

// read() composites pending writes
assert_eq!(pf.read_byte(10).unwrap(), 0xFF);

// Commit applies pending to the buffer
let mut pf = pf;
pf.commit().unwrap();
assert_eq!(pf.data()[10], 0xFF);

Implementations§

Source§

impl CowFile

Source

pub fn from_vec(data: Vec<u8>) -> Self

Creates a CowFile from an owned byte vector.

The provided bytes become the committed buffer. No copies are made during construction — the vector is moved into the CowFile.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0x4D, 0x5A, 0x90, 0x00]);
assert_eq!(pf.len(), 4);
Source

pub fn open(path: impl AsRef<Path>) -> Result<Self>

Creates a CowFile by memory-mapping a file from the given path.

The file is mapped with copy-on-write semantics (MAP_PRIVATE on Unix, PAGE_WRITECOPY on Windows). The original file is never modified. Only pages touched by commit are copied into anonymous memory — the rest of the file remains demand-paged from disk.

§Errors

Returns Error::Io if the file cannot be opened or memory-mapped.

§Examples
use cowfile::CowFile;

let pf = CowFile::open("binary.exe").unwrap();
println!("File size: {} bytes", pf.len());
Source

pub fn from_file(file: File) -> Result<Self>

Creates a CowFile from an already-opened std::fs::File.

The file is mapped with copy-on-write semantics. The original file is never modified.

Empty files (0 bytes) are handled by using a Vec backend instead of mmap, since memory-mapping an empty file is not supported on all platforms.

§Errors

Returns Error::Io if the file cannot be memory-mapped.

§Examples
use cowfile::CowFile;

let file = std::fs::File::open("binary.exe").unwrap();
let pf = CowFile::from_file(file).unwrap();
println!("File size: {} bytes", pf.len());
Source

pub fn data(&self) -> &[u8]

Returns the committed buffer as a byte slice.

This is a true zero-cost &[u8] reference into the committed buffer. For mmap-backed files, only accessed pages are loaded into physical memory by the OS.

Pending writes are not visible through this method. Use read or read_le for a view that composites pending writes, or call commit first.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![1, 2, 3]);
let data: &[u8] = pf.data();
assert_eq!(data, &[1, 2, 3]);
Source

pub fn len(&self) -> usize

Returns the total length of the data in bytes.

Source

pub fn is_empty(&self) -> bool

Returns true if the data is empty (zero bytes).

Source

pub fn has_pending(&self) -> bool

Returns true if there are uncommitted pending writes.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 10]);
assert!(!pf.has_pending());

pf.write(0, &[0xFF]).unwrap();
assert!(pf.has_pending());
Source

pub fn read(&self, offset: usize, length: usize) -> Result<Vec<u8>>

Reads length bytes starting at offset, compositing pending writes.

The returned bytes reflect pending writes applied over the committed buffer. When there are no pending writes, this is equivalent to slicing data.

§Errors

Returns Error::OutOfBounds if the requested range exceeds the data size.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![1, 2, 3, 4, 5]);
pf.write(2, &[0xFF]).unwrap();

let data = pf.read(1, 3).unwrap();
assert_eq!(data, vec![2, 0xFF, 4]);
Source

pub fn read_byte(&self, offset: usize) -> Result<u8>

Reads a single byte at the given offset, compositing pending writes.

§Errors

Returns Error::OutOfBounds if the offset is beyond the data size.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0xAA, 0xBB, 0xCC]);
assert_eq!(pf.read_byte(1).unwrap(), 0xBB);

pf.write_byte(1, 0xFF).unwrap();
assert_eq!(pf.read_byte(1).unwrap(), 0xFF);
Source

pub fn write(&self, offset: usize, data: &[u8]) -> Result<()>

Writes data at the given offset into the pending log.

The committed buffer is not modified. Pending writes are composited into reads via read and applied to the buffer on commit.

Empty writes (zero-length data) are silently ignored.

§Errors

Returns Error::OutOfBounds if the write extends beyond the data size.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 100]);
pf.write(50, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();

let data = pf.read(50, 4).unwrap();
assert_eq!(data, vec![0xDE, 0xAD, 0xBE, 0xEF]);
Source

pub fn write_byte(&self, offset: usize, byte: u8) -> Result<()>

Writes a single byte at the given offset into the pending log.

§Errors

Returns Error::OutOfBounds if the offset is beyond the data size.

Source

pub fn commit(&mut self) -> Result<()>

Applies all pending writes to the committed buffer and clears the log.

For mmap-backed files, only the OS pages touched by writes are copied into anonymous memory (MAP_PRIVATE CoW). The rest of the file remains demand-paged from disk.

§Errors

Returns Error::OutOfBounds if any pending write is out of bounds (should not happen if writes were bounds-checked).

§Examples
use cowfile::CowFile;

let mut pf = CowFile::from_vec(vec![0u8; 10]);

pf.write(0, &[0xAA]).unwrap();
assert_eq!(pf.data()[0], 0x00); // Not yet committed

pf.commit().unwrap();
assert_eq!(pf.data()[0], 0xAA); // Now committed
assert!(!pf.has_pending());
Source

pub fn discard(&mut self) -> Result<()>

Discards all pending writes without applying them.

§Errors

Returns Error::LockPoisoned if the internal lock was poisoned.

§Examples
use cowfile::CowFile;

let mut pf = CowFile::from_vec(vec![0u8; 10]);
pf.write(0, &[0xFF]).unwrap();
assert!(pf.has_pending());

pf.discard().unwrap();
assert!(!pf.has_pending());
assert_eq!(pf.data()[0], 0x00);
Source

pub fn read_le<T: Primitive>(&self, offset: usize) -> Result<T>

Reads a primitive value in little-endian byte order at the given offset.

Composites pending writes over the committed state.

§Errors

Returns Error::OutOfBounds if there are not enough bytes at offset.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0xEF, 0xBE, 0xAD, 0xDE, 0, 0, 0, 0]);
assert_eq!(pf.read_le::<u32>(0).unwrap(), 0xDEADBEEF);
Source

pub fn read_be<T: Primitive>(&self, offset: usize) -> Result<T>

Reads a primitive value in big-endian byte order at the given offset.

Composites pending writes over the committed state.

§Errors

Returns Error::OutOfBounds if there are not enough bytes at offset.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0]);
assert_eq!(pf.read_be::<u32>(0).unwrap(), 0xDEADBEEF);
Source

pub fn write_le<T: Primitive>(&self, offset: usize, value: T) -> Result<()>

Writes a primitive value in little-endian byte order at the given offset.

The write goes to the pending log.

§Errors

Returns Error::OutOfBounds if there are not enough bytes at offset.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 8]);
pf.write_le::<u32>(0, 0xDEADBEEF).unwrap();
assert_eq!(pf.read(0, 4).unwrap(), vec![0xEF, 0xBE, 0xAD, 0xDE]);
Source

pub fn write_be<T: Primitive>(&self, offset: usize, value: T) -> Result<()>

Writes a primitive value in big-endian byte order at the given offset.

The write goes to the pending log.

§Errors

Returns Error::OutOfBounds if there are not enough bytes at offset.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 8]);
pf.write_be::<u32>(0, 0xDEADBEEF).unwrap();
assert_eq!(pf.read(0, 4).unwrap(), vec![0xDE, 0xAD, 0xBE, 0xEF]);
Source

pub fn read_type<T: ReadFrom>(&self, offset: usize) -> Result<T>

Reads a user-defined type implementing ReadFrom at the given offset.

Composites pending writes over the committed state.

§Errors

Returns any error produced by the ReadFrom implementation.

§Examples
use cowfile::{CowFile, ReadFrom, Result};

struct Pair { a: u16, b: u16 }

impl ReadFrom for Pair {
    fn read_from(pf: &CowFile, offset: usize) -> Result<Self> {
        Ok(Pair {
            a: pf.read_le::<u16>(offset)?,
            b: pf.read_le::<u16>(offset + 2)?,
        })
    }
}

let pf = CowFile::from_vec(vec![0x01, 0x00, 0x02, 0x00]);
let pair: Pair = pf.read_type(0).unwrap();
assert_eq!(pair.a, 1);
assert_eq!(pair.b, 2);
Source

pub fn write_type<T: WriteTo>(&self, offset: usize, value: &T) -> Result<()>

Writes a user-defined type implementing WriteTo at the given offset.

The write goes to the pending log.

§Errors

Returns any error produced by the WriteTo implementation.

§Examples
use cowfile::{CowFile, WriteTo, Result};

struct Pair { a: u16, b: u16 }

impl WriteTo for Pair {
    fn write_to(&self, pf: &CowFile, offset: usize) -> Result<()> {
        pf.write_le::<u16>(offset, self.a)?;
        pf.write_le::<u16>(offset + 2, self.b)?;
        Ok(())
    }
}

let pf = CowFile::from_vec(vec![0u8; 8]);
pf.write_type(0, &Pair { a: 1, b: 2 }).unwrap();
assert_eq!(pf.read(0, 4).unwrap(), vec![0x01, 0x00, 0x02, 0x00]);
Source

pub fn cursor(&self) -> CowFileCursor<'_>

Creates a cursor over this CowFile at position 0.

The returned CowFileCursor implements std::io::Read, std::io::Write, and std::io::Seek, allowing the CowFile to be used with any API that expects standard I/O traits.

Multiple cursors can exist over the same CowFile simultaneously, each with its own independent position.

§Examples
use std::io::{Read, Write, Seek, SeekFrom};
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 32]);
let mut cursor = pf.cursor();

cursor.write_all(&[1, 2, 3, 4]).unwrap();
cursor.seek(SeekFrom::Start(0)).unwrap();

let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).unwrap();
assert_eq!(buf, [1, 2, 3, 4]);
Source

pub fn source_path(&self) -> Option<&Path>

Returns the original file path for mmap-backed instances opened via open.

Returns None for vec-backed instances or those created via from_file.

§Examples
use cowfile::CowFile;

let pf = CowFile::open("binary.exe").unwrap();
assert!(pf.source_path().is_some());

let pf = CowFile::from_vec(vec![0u8; 10]);
assert!(pf.source_path().is_none());
Source

pub fn fork(&self) -> Result<CowFile>

Creates an independent copy of this CowFile.

For mmap-backed files with a known source path, re-opens the original file — a new MAP_PRIVATE mmap that shares physical read pages with the parent via OS-level copy-on-write. For vec-backed files or those without a source path, clones the data.

Pending writes are not carried over — the fork starts clean.

§Errors

Returns Error::Io if the source file cannot be reopened.

§Examples
use cowfile::CowFile;

let pf = CowFile::open("binary.exe").unwrap();
pf.write(0, &[0xFF]).unwrap();

let forked = pf.fork().unwrap();
// Fork starts clean — no pending writes
assert!(!forked.has_pending());
// But reads the same committed data
assert_eq!(forked.data()[0], pf.data()[0]);
Source

pub fn to_vec(&self) -> Result<Vec<u8>>

Produces a Vec<u8> with all pending writes composited over the committed buffer.

§Errors

Returns Error::LockPoisoned if the internal lock was poisoned.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![1, 2, 3, 4, 5]);
pf.write(0, &[0xFF]).unwrap();

let output = pf.to_vec().unwrap();
assert_eq!(output, vec![0xFF, 2, 3, 4, 5]);
Source

pub fn to_file(&self, path: impl AsRef<Path>) -> Result<()>

Writes the data with all pending writes applied to disk.

For files smaller than 64 MiB, this uses buffered I/O. For larger files, this uses a writable memory map for efficient output.

§Errors

Returns Error::Io if the file cannot be created or written.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![0u8; 1024]);
pf.write(0, &[0x4D, 0x5A]).unwrap();
pf.to_file("output.bin").unwrap();
Source

pub fn into_vec(self) -> Result<Vec<u8>>

Consumes the CowFile and returns the data as an owned Vec<u8>.

If there are no pending writes and the backend is a Vec, this is a zero-copy move. Otherwise, the data is materialized with pending writes applied.

§Errors

Returns Error::LockPoisoned if the internal lock was poisoned.

§Examples
use cowfile::CowFile;

let pf = CowFile::from_vec(vec![1, 2, 3]);
let data = pf.into_vec().unwrap();
assert_eq!(data, vec![1, 2, 3]);

Trait Implementations§

Source§

impl Debug for CowFile

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.