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
impl CowFile
Sourcepub fn from_vec(data: Vec<u8>) -> Self
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);Sourcepub fn open(path: impl AsRef<Path>) -> Result<Self>
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());Sourcepub fn from_file(file: File) -> Result<Self>
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());Sourcepub fn data(&self) -> &[u8] ⓘ
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]);Sourcepub fn has_pending(&self) -> bool
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());Sourcepub fn read(&self, offset: usize, length: usize) -> Result<Vec<u8>>
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]);Sourcepub fn read_byte(&self, offset: usize) -> Result<u8>
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);Sourcepub fn write(&self, offset: usize, data: &[u8]) -> Result<()>
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]);Sourcepub fn write_byte(&self, offset: usize, byte: u8) -> Result<()>
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.
Sourcepub fn commit(&mut self) -> Result<()>
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());Sourcepub fn discard(&mut self) -> Result<()>
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);Sourcepub fn read_le<T: Primitive>(&self, offset: usize) -> Result<T>
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);Sourcepub fn read_be<T: Primitive>(&self, offset: usize) -> Result<T>
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);Sourcepub fn write_le<T: Primitive>(&self, offset: usize, value: T) -> Result<()>
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]);Sourcepub fn write_be<T: Primitive>(&self, offset: usize, value: T) -> Result<()>
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]);Sourcepub fn read_type<T: ReadFrom>(&self, offset: usize) -> Result<T>
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);Sourcepub fn write_type<T: WriteTo>(&self, offset: usize, value: &T) -> Result<()>
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]);Sourcepub fn cursor(&self) -> CowFileCursor<'_> ⓘ
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]);Sourcepub fn source_path(&self) -> Option<&Path>
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());Sourcepub fn fork(&self) -> Result<CowFile>
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]);Sourcepub fn to_vec(&self) -> Result<Vec<u8>>
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]);Sourcepub fn to_file(&self, path: impl AsRef<Path>) -> Result<()>
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();Sourcepub fn into_vec(self) -> Result<Vec<u8>>
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]);