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}