vector_buffers/variants/disk_v2/
io.rs

1use std::{io, path::Path};
2
3use tokio::{
4    fs::OpenOptions,
5    io::{AsyncRead, AsyncWrite},
6};
7
8#[cfg(unix)]
9const FILE_MODE_OWNER_RW_GROUP_RO: u32 = 0o640;
10
11/// File metadata.
12pub struct Metadata {
13    pub(crate) len: u64,
14}
15
16impl Metadata {
17    /// Gets the length of the file, in bytes.
18    pub fn len(&self) -> u64 {
19        self.len
20    }
21}
22
23/// Generalized interface for opening and deleting files from a filesystem.
24pub trait Filesystem: Send + Sync {
25    type File: AsyncFile;
26    type MemoryMap: ReadableMemoryMap;
27    type MutableMemoryMap: WritableMemoryMap;
28
29    /// Opens a file for writing, creating it if it does not exist.
30    ///
31    /// This opens the file in "append" mode, such that the starting position in the file will be
32    /// set to the end of the file: the file will not be truncated.  Additionally, the file is
33    /// readable.
34    ///
35    /// # Errors
36    ///
37    /// If an I/O error occurred when attempting to open the file for writing, an error variant will
38    /// be returned describing the underlying error.
39    async fn open_file_writable(&self, path: &Path) -> io::Result<Self::File>;
40
41    /// Opens a file for writing, creating it if it does not already exist, but atomically.
42    ///
43    /// This opens the file in "append" mode, such that the starting position in the file will be
44    /// set to the end of the file: the file will not be truncated.  Additionally, the file is
45    /// readable.
46    ///
47    /// # Errors
48    ///
49    /// If the file already existed, then an error will be returned with an `ErrorKind` of `AlreadyExists`.
50    ///
51    /// If a general I/O error occurred when attempting to open the file for writing, an error variant will
52    /// be returned describing the underlying error.
53    async fn open_file_writable_atomic(&self, path: &Path) -> io::Result<Self::File>;
54
55    /// Opens a file for reading, creating it if it does not exist.
56    ///
57    /// Files will be opened at the logical end position.
58    ///
59    /// # Errors
60    ///
61    /// If an I/O error occurred when attempting to open the file for reading, an error variant will
62    /// be returned describing the underlying error.
63    async fn open_file_readable(&self, path: &Path) -> io::Result<Self::File>;
64
65    /// Opens a file as a readable memory-mapped region.
66    ///
67    /// # Errors
68    ///
69    /// If an I/O error occurred when attempting to open the file for reading, or attempting to
70    /// memory map the file, an error variant will be returned describing the underlying error.
71    async fn open_mmap_readable(&self, path: &Path) -> io::Result<Self::MemoryMap>;
72
73    /// Opens a file as a writable memory-mapped region.
74    ///
75    /// # Errors
76    ///
77    /// If an I/O error occurred when attempting to open the file for reading, or attempting to
78    /// memory map the file, an error variant will be returned describing the underlying error.
79    async fn open_mmap_writable(&self, path: &Path) -> io::Result<Self::MutableMemoryMap>;
80
81    /// Deletes a file.
82    ///
83    /// # Errors
84    ///
85    /// If an I/O error occurred when attempting to delete the file, an error variant will be
86    /// returned describing the underlying error.
87    async fn delete_file(&self, path: &Path) -> io::Result<()>;
88}
89
90pub trait AsyncFile: AsyncRead + AsyncWrite + Send + Sync {
91    /// Queries metadata about the underlying file.
92    ///
93    /// # Errors
94    ///
95    /// If an I/O error occurred when attempting to get the metadata for the file, an error variant
96    /// will be returned describing the underlying error.
97    async fn metadata(&self) -> io::Result<Metadata>;
98
99    /// Attempts to synchronize all OS-internal data, and metadata, to disk.
100    ///
101    /// This function will attempt to ensure that all in-memory data reaches the filesystem before returning.
102    ///
103    /// This can be used to handle errors that would otherwise only be caught when the File is closed. Dropping a file will ignore errors in synchronizing this in-memory data.
104    ///
105    /// # Errors
106    /// If an I/O error occurred when attempting to synchronize the file data and metadata to disk,
107    /// an error variant will be returned describing the underlying error.
108    async fn sync_all(&self) -> io::Result<()>;
109}
110
111pub trait ReadableMemoryMap: AsRef<[u8]> + Send + Sync {}
112
113pub trait WritableMemoryMap: ReadableMemoryMap {
114    /// Flushes outstanding memory map modifications to disk.
115    ///
116    /// When this method returns with a non-error result, all outstanding changes to a file-backed
117    /// memory map are guaranteed to be durably stored. The file’s metadata (including last
118    /// modification timestamp) may not be updated.
119    fn flush(&self) -> io::Result<()>;
120}
121
122/// A normal filesystem used for production operations.
123///
124/// Uses Tokio's `File` for asynchronous file reading/writing, and `memmap2` for memory-mapped files.
125#[derive(Clone, Debug)]
126pub struct ProductionFilesystem;
127
128impl Filesystem for ProductionFilesystem {
129    type File = tokio::fs::File;
130    type MemoryMap = memmap2::Mmap;
131    type MutableMemoryMap = memmap2::MmapMut;
132
133    async fn open_file_writable(&self, path: &Path) -> io::Result<Self::File> {
134        create_writable_file_options(false)
135            .append(true)
136            .open(path)
137            .await
138    }
139
140    async fn open_file_writable_atomic(&self, path: &Path) -> io::Result<Self::File> {
141        create_writable_file_options(true)
142            .append(true)
143            .open(path)
144            .await
145    }
146
147    async fn open_file_readable(&self, path: &Path) -> io::Result<Self::File> {
148        open_readable_file_options().open(path).await
149    }
150
151    async fn open_mmap_readable(&self, path: &Path) -> io::Result<Self::MemoryMap> {
152        let file = open_readable_file_options().open(path).await?;
153        let std_file = file.into_std().await;
154        unsafe { memmap2::Mmap::map(&std_file) }
155    }
156
157    async fn open_mmap_writable(&self, path: &Path) -> io::Result<Self::MutableMemoryMap> {
158        let file = open_writable_file_options().open(path).await?;
159
160        let std_file = file.into_std().await;
161        unsafe { memmap2::MmapMut::map_mut(&std_file) }
162    }
163
164    async fn delete_file(&self, path: &Path) -> io::Result<()> {
165        tokio::fs::remove_file(path).await
166    }
167}
168
169/// Builds a set of `OpenOptions` for opening a file as readable/writable.
170fn open_writable_file_options() -> OpenOptions {
171    let mut open_options = OpenOptions::new();
172    open_options.read(true).write(true);
173
174    #[cfg(unix)]
175    {
176        open_options.mode(FILE_MODE_OWNER_RW_GROUP_RO);
177    }
178
179    open_options
180}
181
182/// Builds a set of `OpenOptions` for opening a file as readable/writable, creating it if it does
183/// not already exist.
184///
185/// When `create_atomic` is set to `true`, this ensures that the operation only succeeds if the
186/// subsequent call to `open` is able to create the file, ensuring that another process did not
187/// create it before us. Otherwise, the normal create mode is configured, which creates the file if
188/// it does not exist but does not throw an error if it already did.
189///
190/// On Unix platforms, file permissions will be set so that only the owning user of the file can
191/// write to it, the owning group can read it, and the file is inaccessible otherwise.
192fn create_writable_file_options(create_atomic: bool) -> OpenOptions {
193    let mut open_options = open_writable_file_options();
194
195    #[cfg(unix)]
196    {
197        open_options.mode(FILE_MODE_OWNER_RW_GROUP_RO);
198    }
199
200    if create_atomic {
201        open_options.create_new(true);
202    } else {
203        open_options.create(true);
204    }
205
206    open_options
207}
208
209/// Builds a set of `OpenOptions` for opening a file as readable.
210fn open_readable_file_options() -> OpenOptions {
211    let mut open_options = OpenOptions::new();
212    open_options.read(true);
213    open_options
214}
215
216impl AsyncFile for tokio::fs::File {
217    async fn metadata(&self) -> io::Result<Metadata> {
218        let metadata = self.metadata().await?;
219        Ok(Metadata {
220            len: metadata.len(),
221        })
222    }
223
224    async fn sync_all(&self) -> io::Result<()> {
225        self.sync_all().await
226    }
227}
228
229impl ReadableMemoryMap for memmap2::Mmap {}
230
231impl ReadableMemoryMap for memmap2::MmapMut {}
232
233impl WritableMemoryMap for memmap2::MmapMut {
234    fn flush(&self) -> io::Result<()> {
235        self.flush()
236    }
237}