mmap_simple/lib.rs
1//! A simple API for treating a file basically as an infinite vector that can be written to at any
2//! point, appended to, read from and shrinken at will and in a very fast way.
3//!
4//! The file is memory-mapped with a libc call specifying basically an infinite memory size. But it
5//! doesn't consume that amount of memory. Should only be used on Linux and from a single caller/process.
6//! All write calls immediately call `sync_all` after them, which is not ideal, but maybe we'll improve
7//! later.
8//!
9//! # Example
10//!
11//! ```rust
12//! use std::path::Path;
13//! use mmap_simple::Mmap;
14//!
15//! fn main() -> Result<(), Box<dyn std::error::Error>> {
16//! let mut mmap = Mmap::new(Path::new("example.txt"))?;
17//! mmap.append(b"Hello, world!")?;
18//! mmap.overwrite(0, b"Goodbye")?;
19//! mmap.drop_from_tail(6)?;
20//! mmap.append(b", world!")?;
21//! Ok(())
22//! }
23//! ```
24
25use std::{fs, io, os::unix::prelude::AsRawFd, path};
26
27mod errors;
28
29use crate::errors::*;
30use crate::MmapError::*;
31
32/// A struct that represents a memory-mapped file.
33pub struct Mmap {
34 file: fs::File,
35 ptr: *mut u8,
36 pub size: u64,
37}
38
39impl Mmap {
40 /// Creates a new `Mmap` instance by opening the file at the given `path`.
41 ///
42 /// # Arguments
43 /// * `path` - The path to the file to be memory-mapped.
44 ///
45 /// # Returns
46 /// A `Result` containing the `Mmap` instance or a `MmapError` if the operation fails.
47 ///
48 /// # Errors
49 /// This function may return the following errors:
50 /// - `ErrFdNotAvail`: The file descriptor is not available for mapping.
51 /// - `ErrInvalidFd`: The file descriptor is invalid.
52 /// - `ErrUnaligned`: The mapping is not properly aligned.
53 /// - `ErrNoMapSupport`: The file system does not support memory mapping.
54 /// - `ErrNoMem`: There is not enough memory available to complete the operation.
55 /// - `ErrUnknown(code)`: An unknown error occurred with the given OS error code.
56 pub fn new(path: &path::Path) -> Result<Self, MmapError> {
57 let file = fs::OpenOptions::new()
58 .read(true)
59 .write(true)
60 .truncate(false)
61 .create(true)
62 .open(path)
63 .unwrap();
64
65 let size = file.metadata().unwrap().len();
66
67 unsafe {
68 let r = libc::mmap(
69 std::ptr::null::<*const u8>() as *mut libc::c_void,
70 1 << 40,
71 libc::PROT_READ | libc::PROT_WRITE,
72 libc::MAP_SHARED,
73 file.as_raw_fd(),
74 0,
75 );
76
77 if r == libc::MAP_FAILED {
78 Err(
79 match io::Error::last_os_error().raw_os_error().unwrap_or(-1) {
80 libc::EACCES => ErrFdNotAvail,
81 libc::EBADF => ErrInvalidFd,
82 libc::EINVAL => ErrUnaligned,
83 libc::ENODEV => ErrNoMapSupport,
84 libc::ENOMEM => ErrNoMem,
85 code => ErrUnknown(code as isize),
86 },
87 )
88 } else {
89 let ptr = r as *mut u8;
90 Ok(Mmap { ptr, file, size })
91 }
92 }
93 }
94
95 /// Appends the given data to the end of the memory-mapped file.
96 ///
97 /// # Arguments
98 /// * `data` - The data to be appended to the file.
99 ///
100 /// # Returns
101 /// A `Result` containing the unit type (`()`) or an `io::Error` if the operation fails.
102 pub fn append(&mut self, data: &[u8]) -> Result<(), io::Error> {
103 self.append_with(data.len(), |w| w.copy_from_slice(data))
104 }
105
106 /// Appends data to the end of the memory-mapped file using a custom writer function.
107 ///
108 /// # Arguments
109 /// * `len` - The length of the data to be appended.
110 /// * `writer` - A closure that writes the data to the file.
111 ///
112 /// # Returns
113 /// A `Result` containing the unit type (`()`) or an `io::Error` if the operation fails.
114 pub fn append_with<F>(&mut self, len: usize, writer: F) -> Result<(), io::Error>
115 where
116 F: FnOnce(&mut [u8]),
117 {
118 self.file.set_len(self.size + len as u64)?;
119 let slice = unsafe {
120 std::slice::from_raw_parts_mut(self.ptr.wrapping_offset(self.size as isize), len)
121 };
122 writer(slice);
123 self.size += len as u64;
124 self.file.sync_all()?;
125 Ok(())
126 }
127
128 /// Overwrites the data at the specified offset in the memory-mapped file.
129 ///
130 /// # Arguments
131 /// * `offset` - The offset in the file where the data should be overwritten.
132 /// * `data` - The data to be written.
133 ///
134 /// # Returns
135 /// A `Result` containing the unit type (`()`) or an `io::Error` if the operation fails.
136 pub fn overwrite(&self, offset: usize, data: &[u8]) -> Result<(), io::Error> {
137 self.overwrite_with(offset, data.len(), |w| w.copy_from_slice(data))
138 }
139
140 /// Overwrites data in the memory-mapped file using a custom writer function.
141 ///
142 /// # Arguments
143 /// * `offset` - The offset in the file where the data should be overwritten.
144 /// * `len` - The length of the data to be written.
145 /// * `writer` - A closure that writes the data to the file.
146 ///
147 /// # Returns
148 /// A `Result` containing the unit type (`()`) or an `io::Error` if the operation fails.
149 pub fn overwrite_with<F>(&self, offset: usize, len: usize, writer: F) -> Result<(), io::Error>
150 where
151 F: FnOnce(&mut [u8]),
152 {
153 if offset + len > self.size as usize {
154 return Err(io::Error::from(io::ErrorKind::UnexpectedEof));
155 }
156
157 let slice = unsafe {
158 std::slice::from_raw_parts_mut(self.ptr.wrapping_offset(offset as isize), len)
159 };
160 writer(slice);
161 self.file.sync_all()?;
162 Ok(())
163 }
164
165 /// Removes the specified amount of data from the end of the memory-mapped file by truncating the file.
166 ///
167 /// # Arguments
168 /// * `len` - The amount of data to be removed from the end of the file.
169 ///
170 /// # Returns
171 /// A `Result` containing the unit type (`()`) or an `io::Error` if the operation fails.
172 pub fn drop_from_tail(&mut self, len: usize) -> Result<(), io::Error> {
173 self.file.set_len(self.size - len as u64)?;
174 self.file.sync_all()?;
175 self.size -= len as u64;
176 Ok(())
177 }
178
179 /// Reads data from the memory-mapped file at the specified offset.
180 ///
181 /// # Arguments
182 /// * `offset` - The offset in the file where the data should be read from.
183 /// * `buf` - The buffer to store the read data.
184 ///
185 /// # Returns
186 /// A `Result` containing the number of bytes read or an `io::Error` if the operation fails.
187 pub fn read(&self, offset: usize, len: usize) -> Result<Vec<u8>, io::Error> {
188 let mut buf = vec![0u8; len];
189 self.read_with(offset, len, |b| buf.copy_from_slice(b))?;
190
191 Ok(buf)
192 }
193
194 /// Reads data from the memory-mapped file at the specified offset using a custom reader function.
195 ///
196 /// # Arguments
197 /// * `offset` - The offset in the file where the data should be read from.
198 /// * `len` - The length of the data to be read.
199 /// * `reader` - A closure that reads the data from the file.
200 ///
201 /// # Returns
202 /// A `Result` containing the number of bytes read or an `io::Error` if the operation fails.
203 pub fn read_with<F>(&self, offset: usize, len: usize, reader: F) -> Result<(), io::Error>
204 where
205 F: FnOnce(&[u8]),
206 {
207 if offset + len > self.size as usize {
208 return Err(io::Error::from(io::ErrorKind::UnexpectedEof));
209 }
210
211 let slice = unsafe {
212 std::slice::from_raw_parts_mut(self.ptr.wrapping_offset(offset as isize), len)
213 };
214 reader(slice);
215 Ok(())
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use crate::*;
222 use std::io::Read;
223
224 const TEST_FILE: &str = "/tmp/test-mmap";
225
226 fn setup_file() -> Mmap {
227 let path = path::Path::new(TEST_FILE);
228 fs::remove_file(path).unwrap();
229 Mmap::new(path).unwrap()
230 }
231
232 fn check_result(match_: &[u8]) {
233 let path = path::Path::new(TEST_FILE);
234 let mut file = fs::OpenOptions::new().read(true).open(path).unwrap();
235 let mut v = vec![0u8; match_.len()];
236 let buf = v.as_mut_slice();
237 file.read(buf).unwrap();
238 assert_eq!(buf, match_);
239 }
240
241 #[test]
242 #[serial_test::serial]
243 fn letters_loop() {
244 let mut mmap_file = setup_file();
245 let mut c = b'a';
246 loop {
247 mmap_file.append(&[c]).unwrap();
248 println!("appended {}", c);
249 if c == b'z' {
250 break;
251 }
252 c += 1;
253 }
254
255 drop(mmap_file);
256 check_result("abcdefghijklmnopqrstuvwxyz".as_bytes());
257 }
258
259 #[test]
260 #[serial_test::serial]
261 fn multiple_ops() {
262 let mut mmap_file = setup_file();
263
264 mmap_file.append("xxxxx".as_bytes()).unwrap();
265 let r = mmap_file.overwrite(2, "overflows".as_bytes());
266 assert!(r.is_err());
267 check_result("xxxxx".as_bytes());
268
269 mmap_file.append("yyyyy".as_bytes()).unwrap();
270 mmap_file.overwrite(3, "wwww".as_bytes()).unwrap();
271 check_result("xxxwwwwyyy".as_bytes());
272
273 mmap_file.drop_from_tail(4).unwrap();
274 check_result("xxxwww".as_bytes());
275
276 let read = mmap_file.read(1, 3).unwrap();
277 assert_eq!(read, "xxw".as_bytes());
278 }
279}