Skip to main content

hexz_store/local/
file.rs

1//! Local file storage backend using position-independent I/O (`pread`).
2
3use bytes::{Bytes, BytesMut};
4use hexz_common::Result;
5use hexz_core::store::StorageBackend;
6use std::fs::File;
7
8#[cfg(unix)]
9fn read_exact_at(file: &File, buffer: &mut [u8], offset: u64) -> std::io::Result<()> {
10    use std::os::unix::fs::FileExt;
11    file.read_exact_at(buffer, offset)
12}
13
14#[cfg(windows)]
15fn read_exact_at(file: &File, buffer: &mut [u8], mut offset: u64) -> std::io::Result<()> {
16    use std::os::windows::fs::FileExt;
17    let mut pos = 0;
18    while pos < buffer.len() {
19        let n = file.seek_read(&mut buffer[pos..], offset)?;
20        if n == 0 {
21            return Err(std::io::Error::new(
22                std::io::ErrorKind::UnexpectedEof,
23                "unexpected eof",
24            ));
25        }
26        pos += n;
27        offset += n as u64;
28    }
29    Ok(())
30}
31
32/// Storage backend backed by a local file using `pread(2)` for lock-free concurrent reads.
33#[derive(Debug)]
34pub struct FileBackend {
35    inner: File,
36    size: u64,
37}
38
39impl FileBackend {
40    /// Opens the file at `path` read-only and caches its size.
41    pub fn new(path: &std::path::Path) -> Result<Self> {
42        let file = File::open(path)?;
43        let metadata = file.metadata()?;
44        Ok(Self {
45            inner: file,
46            size: metadata.len(),
47        })
48    }
49}
50
51impl StorageBackend for FileBackend {
52    fn read_exact(&self, offset: u64, len: usize) -> Result<Bytes> {
53        let mut buffer = BytesMut::with_capacity(len);
54        // SAFETY: read_exact_at initialises all bytes before we read them.
55        unsafe { buffer.set_len(len) }
56        match read_exact_at(&self.inner, &mut buffer, offset) {
57            Ok(()) => Ok(buffer.freeze()),
58            Err(e) => Err(hexz_common::Error::Io(e)),
59        }
60    }
61
62    fn len(&self) -> u64 {
63        self.size
64    }
65}