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}