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}