1use std::fs::{File as StdFile, OpenOptions};
14use std::path::{Path, PathBuf};
15
16use rudb_common::{Error, Result};
17
18use crate::{File, Filesystem, OpenMode};
19
20#[derive(Debug, Default, Clone, Copy)]
22pub struct RealFilesystem;
23
24impl RealFilesystem {
25 #[must_use]
27 pub fn new() -> Self {
28 Self
29 }
30}
31
32impl Filesystem for RealFilesystem {
33 fn open(&self, path: &Path, mode: OpenMode) -> Result<Box<dyn File>> {
34 let mut options = OpenOptions::new();
35 match mode {
36 OpenMode::Read => {
37 options.read(true);
38 }
39 OpenMode::ReadWrite => {
40 options.read(true).write(true);
41 }
42 OpenMode::Create => {
43 options.read(true).write(true).create(true);
44 }
45 OpenMode::CreateNew => {
46 options.read(true).write(true).create_new(true);
47 }
48 }
49 let file = options
50 .open(path)
51 .map_err(|e| Error::io(format!("could not open {}: {e}", path.display())))?;
52 Ok(Box::new(RealFile { file, writable: mode.writable() }))
53 }
54
55 fn exists(&self, path: &Path) -> bool {
56 path.exists()
57 }
58
59 fn is_dir(&self, path: &Path) -> bool {
60 path.is_dir()
61 }
62
63 fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>> {
64 let entries = std::fs::read_dir(path)
65 .map_err(|e| Error::io(format!("could not read {}: {e}", path.display())))?;
66 let mut found = Vec::new();
67 for entry in entries {
68 let entry =
69 entry.map_err(|e| Error::io(format!("could not read {}: {e}", path.display())))?;
70 found.push(entry.path());
71 }
72 Ok(found)
73 }
74
75 fn remove(&self, path: &Path) -> Result<()> {
76 std::fs::remove_file(path)
77 .map_err(|e| Error::io(format!("could not remove {}: {e}", path.display())))
78 }
79
80 fn rename(&self, from: &Path, to: &Path) -> Result<()> {
81 std::fs::rename(from, to).map_err(|e| {
82 Error::io(format!("could not rename {} to {}: {e}", from.display(), to.display()))
83 })
84 }
85
86 fn create_dir_all(&self, path: &Path) -> Result<()> {
87 std::fs::create_dir_all(path)
88 .map_err(|e| Error::io(format!("could not create {}: {e}", path.display())))
89 }
90
91 fn sync_dir(&self, path: &Path) -> Result<()> {
92 #[cfg(windows)]
97 {
98 let _ = path;
99 Ok(())
100 }
101 #[cfg(not(windows))]
102 {
103 let dir = StdFile::open(path)
104 .map_err(|e| Error::io(format!("could not open {}: {e}", path.display())))?;
105 dir.sync_all().map_err(|e| Error::io(format!("could not sync {}: {e}", path.display())))
106 }
107 }
108}
109
110#[derive(Debug)]
112struct RealFile {
113 file: StdFile,
114 writable: bool,
115}
116
117impl File for RealFile {
118 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
119 #[cfg(unix)]
120 let result = std::os::unix::fs::FileExt::read_at(&self.file, buf, offset);
121 #[cfg(windows)]
122 let result = std::os::windows::fs::FileExt::seek_read(&self.file, buf, offset);
123 result.map_err(|e| Error::io(format!("read at {offset} failed: {e}")))
124 }
125
126 fn write_at(&self, offset: u64, data: &[u8]) -> Result<()> {
127 if !self.writable {
128 return Err(Error::io("this file was opened for reading"));
129 }
130 let mut written = 0usize;
131 while written < data.len() {
132 let at = offset + written as u64;
133 let chunk = &data[written..];
134 #[cfg(unix)]
135 let result = std::os::unix::fs::FileExt::write_at(&self.file, chunk, at);
136 #[cfg(windows)]
137 let result = std::os::windows::fs::FileExt::seek_write(&self.file, chunk, at);
138 let n = result.map_err(|e| Error::io(format!("write at {at} failed: {e}")))?;
139 if n == 0 {
140 return Err(Error::io(format!("write at {at} wrote nothing")));
141 }
142 written += n;
143 }
144 Ok(())
145 }
146
147 fn sync(&self) -> Result<()> {
148 self.file.sync_all().map_err(|e| Error::io(format!("sync failed: {e}")))
151 }
152
153 fn truncate(&self, len: u64) -> Result<()> {
154 if !self.writable {
155 return Err(Error::io("this file was opened for reading"));
156 }
157 self.file.set_len(len).map_err(|e| Error::io(format!("truncate to {len} failed: {e}")))
158 }
159
160 fn len(&self) -> Result<u64> {
161 Ok(self.file.metadata().map_err(|e| Error::io(format!("stat failed: {e}")))?.len())
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::RealFilesystem;
168 use crate::scratch::TempDir;
169 use crate::submit::Request;
170 use crate::{Filesystem, OpenMode};
171
172 #[test]
173 fn a_file_reads_back_what_was_written_at_the_offset_it_was_written_to() {
174 let dir = TempDir::new("roundtrip");
175 let fs = RealFilesystem::new();
176 let path = dir.join("data");
177 let file = fs.open(&path, OpenMode::CreateNew).unwrap();
178 file.write_at(0, b"hello").unwrap();
179 file.write_at(16, b"world").unwrap();
180 file.sync().unwrap();
181
182 let mut buf = [0u8; 5];
183 file.read_exact_at(16, &mut buf).unwrap();
184 assert_eq!(&buf, b"world");
185 assert_eq!(file.len().unwrap(), 21);
186 let mut gap = [0xffu8; 11];
189 file.read_exact_at(5, &mut gap).unwrap();
190 assert_eq!(gap, [0u8; 11]);
191 }
192
193 #[test]
194 fn create_new_refuses_to_open_a_file_that_is_already_there() {
195 let dir = TempDir::new("createnew");
198 let fs = RealFilesystem::new();
199 let path = dir.join("db");
200 fs.open(&path, OpenMode::CreateNew).unwrap();
201 assert!(fs.open(&path, OpenMode::CreateNew).is_err());
202 assert!(fs.open(&path, OpenMode::Create).is_ok());
203 }
204
205 #[test]
206 fn a_read_only_handle_refuses_to_write() {
207 let dir = TempDir::new("readonly");
208 let fs = RealFilesystem::new();
209 let path = dir.join("data");
210 fs.open(&path, OpenMode::CreateNew).unwrap().write_at(0, b"x").unwrap();
211 let file = fs.open(&path, OpenMode::Read).unwrap();
212 assert!(file.write_at(0, b"y").is_err());
213 assert!(file.truncate(0).is_err());
214 }
215
216 #[test]
217 fn a_short_read_at_the_end_is_a_short_read_and_an_exact_read_is_an_error() {
218 let dir = TempDir::new("shortread");
219 let fs = RealFilesystem::new();
220 let path = dir.join("data");
221 let file = fs.open(&path, OpenMode::CreateNew).unwrap();
222 file.write_at(0, b"abc").unwrap();
223 let mut buf = [0u8; 8];
224 assert_eq!(file.read_at(0, &mut buf).unwrap(), 3);
225 assert!(file.read_exact_at(0, &mut buf).is_err());
226 }
227
228 #[test]
229 fn truncate_cuts_and_extends() {
230 let dir = TempDir::new("truncate");
231 let fs = RealFilesystem::new();
232 let path = dir.join("data");
233 let file = fs.open(&path, OpenMode::CreateNew).unwrap();
234 file.write_at(0, b"abcdefgh").unwrap();
235 file.truncate(3).unwrap();
236 assert_eq!(file.len().unwrap(), 3);
237 file.truncate(6).unwrap();
238 let mut buf = [0xffu8; 6];
239 file.read_exact_at(0, &mut buf).unwrap();
240 assert_eq!(&buf, b"abc\0\0\0");
241 }
242
243 #[test]
244 fn a_batch_of_reads_comes_back_answering_the_requests_it_was_given() {
245 let dir = TempDir::new("submit");
249 let fs = RealFilesystem::new();
250 let path = dir.join("data");
251 let file = fs.open(&path, OpenMode::CreateNew).unwrap();
252 file.write_at(0, b"abcdefghijklmnop").unwrap();
253 file.sync().unwrap();
254
255 let responses = file
256 .submit(vec![Request::new(8, 4), Request::new(0, 4), Request::new(12, 8)])
257 .wait()
258 .unwrap();
259 assert_eq!(responses.len(), 3);
260 assert_eq!(responses[0].bytes(), b"ijkl");
261 assert_eq!(responses[1].bytes(), b"abcd");
262 assert!(responses[2].is_short());
264 assert_eq!(responses[2].bytes(), b"mnop");
265 }
266
267 #[test]
268 fn rename_replaces_and_remove_removes() {
269 let dir = TempDir::new("rename");
270 let fs = RealFilesystem::new();
271 let from = dir.join("new");
272 let to = dir.join("live");
273 fs.open(&to, OpenMode::CreateNew).unwrap().write_at(0, b"old").unwrap();
274 fs.open(&from, OpenMode::CreateNew).unwrap().write_at(0, b"new").unwrap();
275 fs.rename(&from, &to).unwrap();
276 assert!(!fs.exists(&from));
277
278 let mut buf = [0u8; 3];
279 fs.open(&to, OpenMode::Read).unwrap().read_exact_at(0, &mut buf).unwrap();
280 assert_eq!(&buf, b"new");
281
282 fs.remove(&to).unwrap();
283 assert!(!fs.exists(&to));
284 }
285}