melvin_ob/imaging/
file_based_buffer.rs

1use core::slice;
2use std::{
3    ffi::c_void,
4    ops::{Deref, DerefMut},
5    os::fd::AsRawFd,
6    path::Path,
7    ptr::null_mut,
8};
9
10/// A buffer backed by a memory-mapped file.
11///
12/// This structure provides a way to read and write to a file-backed buffer as if it were in memory.
13/// The file is memory-mapped using `mmap`, allowing efficient access to potentially large files
14/// without loading them entirely into memory.
15///
16/// # Safety
17///
18/// This struct involves unsafe operations due to the use of `mmap` and raw pointers:
19/// - The pointer (`ptr`) must not be dereferenced outside the bounds of the memory-mapped region.
20/// - The file must remain open and valid for the lifetime of the memory-mapped buffer.
21/// - Modifications to the underlying file outside of this struct can lead to undefined behavior.
22///
23/// Incorrect use of this struct could result in:
24/// - Data races when the file or the mapped memory is accessed by multiple threads without proper synchronization.
25/// - `mmap` being called on memory that is still in use, leading to segmentation faults.
26///
27/// # Advantages
28///
29/// - Provides an efficient way to work with large files, minimizing memory usage by paging data on demand.
30/// - Offers memory-like access semantics for read and write operations, making it simpler to manipulate file data.
31/// - Eliminates the need for manually reading/writing files in chunks.
32pub(crate) struct FileBackedBuffer {
33    /// The file backing this buffer. Must remain valid for the lifetime of the buffer.
34    file: std::fs::File,
35    /// Pointer to the memory-mapped region. Unsafe to dereference if the region is unmapped
36    /// or access is performed outside the valid range.
37    ptr: *mut u8,
38    /// The length of the memory-mapped region in bytes. Used to ensure bounds are enforced.
39    length: usize,
40}
41
42impl FileBackedBuffer {
43    /// Opens or creates a memory-mapped file-backed buffer.
44    ///
45    /// # Arguments
46    ///
47    /// * `path` - The path to the file to be used for memory mapping.
48    /// * `length` - The size of the memory-mapped buffer in bytes.
49    ///
50    /// # Returns
51    ///
52    /// A `Result` containing the created `FileBackedBuffer` on success,
53    /// or an error message on failure.
54    #[allow(clippy::cast_possible_wrap)]
55    pub(crate) fn open<T: AsRef<Path>>(path: T, length: usize) -> Result<Self, &'static str> {
56        let file = std::fs::OpenOptions::new()
57            .create(true)
58            .write(true)
59            .read(true)
60            .truncate(false)
61            .open(path)
62            .unwrap();
63        let res = unsafe { libc::ftruncate(file.as_raw_fd(), length as i64) };
64        if res != 0 {
65            return Err("ftruncate failed");
66        }
67        let ptr = unsafe {
68            libc::mmap(
69                null_mut(),
70                length,
71                libc::PROT_READ | libc::PROT_WRITE,
72                libc::MAP_SHARED | libc::MAP_FILE,
73                file.as_raw_fd(),
74                0,
75            )
76        };
77        if ptr == libc::MAP_FAILED {
78            return Err("mmap failed");
79        }
80        Ok(FileBackedBuffer { file, length, ptr: ptr.cast::<u8>() })
81    }
82}
83
84impl Drop for FileBackedBuffer {
85    /// Cleans up the memory-mapped region when the [`FileBackedBuffer`] is dropped.
86    fn drop(&mut self) {
87        unsafe {
88            libc::munmap(self.ptr.cast::<c_void>(), self.length);
89        }
90    }
91}
92
93unsafe impl Send for FileBackedBuffer {}
94unsafe impl Sync for FileBackedBuffer {}
95
96impl Deref for FileBackedBuffer {
97    type Target = [u8];
98
99    /// Provides immutable access to the contents of the memory-mapped region.
100    fn deref(&self) -> &Self::Target { unsafe { slice::from_raw_parts(self.ptr, self.length) } }
101}
102
103impl DerefMut for FileBackedBuffer {
104    /// Provides mutable access to the contents of the memory-mapped region.
105    fn deref_mut(&mut self) -> &mut Self::Target {
106        unsafe { slice::from_raw_parts_mut(self.ptr, self.length) }
107    }
108}