1use std::{
5 fs,
6 fs::{File, OpenOptions},
7 os::unix::fs::FileExt,
8 path::{Path, PathBuf},
9};
10
11use crate::io::fs::{
12 Create, Filesystem, FsError, Len, Mkdir, Open, OpenMut, Pread, Pwrite, ReadDir, RemoveDir, Rename, Result,
13 SyncData, SyncDir, Truncate, Unlink,
14};
15
16#[derive(Debug, Clone, Default)]
17pub struct HostFs;
18
19impl HostFs {
20 pub fn new() -> Self {
21 Self
22 }
23}
24
25#[derive(Debug)]
26pub struct HostFile {
27 path: PathBuf,
28 file: File,
29}
30
31#[derive(Debug)]
32pub struct HostFileMut {
33 path: PathBuf,
34 file: File,
35}
36
37impl Filesystem for HostFs {
38 type File = HostFile;
39 type FileMut = HostFileMut;
40}
41
42impl Mkdir for HostFs {
43 fn mkdir(&self, path: &Path) -> Result<()> {
44 fs::create_dir(path).map_err(|err| FsError::from_io(path, err))
45 }
46}
47
48impl Create for HostFs {
49 fn create(&self, path: &Path, len: u64) -> Result<HostFileMut> {
50 let file = OpenOptions::new()
51 .create_new(true)
52 .read(true)
53 .write(true)
54 .open(path)
55 .map_err(|err| FsError::from_io(path, err))?;
56 file.set_len(len).map_err(|err| FsError::from_io(path, err))?;
57 Ok(HostFileMut {
58 path: path.to_path_buf(),
59 file,
60 })
61 }
62}
63
64impl Open for HostFs {
65 fn open(&self, path: &Path) -> Result<HostFile> {
66 let file = OpenOptions::new().read(true).open(path).map_err(|err| FsError::from_io(path, err))?;
67 Ok(HostFile {
68 path: path.to_path_buf(),
69 file,
70 })
71 }
72}
73
74impl OpenMut for HostFs {
75 fn open_mut(&self, path: &Path) -> Result<HostFileMut> {
76 let file = OpenOptions::new()
77 .read(true)
78 .write(true)
79 .open(path)
80 .map_err(|err| FsError::from_io(path, err))?;
81 Ok(HostFileMut {
82 path: path.to_path_buf(),
83 file,
84 })
85 }
86}
87
88impl ReadDir for HostFs {
89 fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>> {
90 let mut entries = Vec::new();
91 for entry in fs::read_dir(path).map_err(|err| FsError::from_io(path, err))? {
92 let entry = entry.map_err(|err| FsError::from_io(path, err))?;
93 entries.push(entry.path());
94 }
95 entries.sort();
96 Ok(entries)
97 }
98}
99
100impl Rename for HostFs {
101 fn rename(&self, from: &Path, to: &Path) -> Result<()> {
102 fs::rename(from, to).map_err(|err| FsError::from_io(from, err))
103 }
104}
105
106impl Unlink for HostFs {
107 fn unlink(&self, path: &Path) -> Result<()> {
108 fs::remove_file(path).map_err(|err| FsError::from_io(path, err))
109 }
110}
111
112impl RemoveDir for HostFs {
113 fn remove_dir(&self, path: &Path) -> Result<()> {
114 fs::remove_dir_all(path).map_err(|err| FsError::from_io(path, err))
115 }
116}
117
118impl SyncDir for HostFs {
119 fn sync_dir(&self, path: &Path) -> Result<()> {
120 File::open(path).and_then(|dir| dir.sync_all()).map_err(|err| FsError::from_io(path, err))
121 }
122}
123
124impl Pread for HostFile {
125 fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
126 self.file.read_at(buf, offset).map_err(|err| FsError::from_io(&self.path, err))
127 }
128}
129
130impl Len for HostFile {
131 fn len(&self) -> Result<u64> {
132 self.file.metadata().map(|meta| meta.len()).map_err(|err| FsError::from_io(&self.path, err))
133 }
134}
135
136impl Pread for HostFileMut {
137 fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
138 self.file.read_at(buf, offset).map_err(|err| FsError::from_io(&self.path, err))
139 }
140}
141
142impl Pwrite for HostFileMut {
143 fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize> {
144 self.file.write_at(buf, offset).map_err(|err| FsError::from_io(&self.path, err))
145 }
146}
147
148impl SyncData for HostFileMut {
149 fn sync_data(&self) -> Result<()> {
150 self.file.sync_data().map_err(|err| FsError::from_io(&self.path, err))
151 }
152}
153
154impl Truncate for HostFileMut {
155 fn truncate(&self, len: u64) -> Result<()> {
156 self.file.set_len(len).map_err(|err| FsError::from_io(&self.path, err))
157 }
158}
159
160impl Len for HostFileMut {
161 fn len(&self) -> Result<u64> {
162 self.file.metadata().map(|meta| meta.len()).map_err(|err| FsError::from_io(&self.path, err))
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use std::{
169 env, process,
170 sync::atomic::{AtomicU64, Ordering},
171 };
172
173 use super::*;
174
175 static COUNTER: AtomicU64 = AtomicU64::new(0);
176
177 struct Scratch {
178 root: PathBuf,
179 }
180
181 impl Scratch {
182 fn new() -> Self {
183 let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
184 let root = env::temp_dir().join(format!("reifydb-host-fs-{}-{}", process::id(), seq));
185 fs::create_dir_all(&root).unwrap();
186 Self {
187 root,
188 }
189 }
190
191 fn path(&self, name: &str) -> PathBuf {
192 self.root.join(name)
193 }
194 }
195
196 impl Drop for Scratch {
197 fn drop(&mut self) {
198 let _ = fs::remove_dir_all(&self.root);
199 }
200 }
201
202 #[test]
203 fn test_create_preallocates_length() {
204 let scratch = Scratch::new();
206 let fs = HostFs::new();
207 let file = fs.create(&scratch.path("seg"), 4096).unwrap();
208 assert_eq!(file.len().unwrap(), 4096);
209 }
210
211 #[test]
212 fn test_pwrite_pread_roundtrip_at_offsets() {
213 let scratch = Scratch::new();
215 let fs = HostFs::new();
216 let file = fs.create(&scratch.path("seg"), 4096).unwrap();
217
218 for offset in [0u64, 512, 700] {
219 let payload = [offset as u8; 64];
220 assert_eq!(file.pwrite(offset, &payload).unwrap(), 64);
221 let mut buf = [0u8; 64];
222 assert_eq!(file.pread(offset, &mut buf).unwrap(), 64);
223 assert_eq!(buf, payload);
224 }
225
226 assert_eq!(file.len().unwrap(), 4096);
227 }
228
229 #[test]
230 fn test_pread_past_end_is_short_not_error() {
231 let scratch = Scratch::new();
233 let fs = HostFs::new();
234 let file = fs.create(&scratch.path("seg"), 16).unwrap();
235
236 let mut buf = [0u8; 32];
237 assert_eq!(file.pread(8, &mut buf).unwrap(), 8);
238 assert_eq!(file.pread(16, &mut buf).unwrap(), 0);
239 assert_eq!(file.pread(4096, &mut buf).unwrap(), 0);
240 }
241
242 #[test]
243 fn test_sync_data_succeeds() {
244 let scratch = Scratch::new();
246 let fs = HostFs::new();
247 let file = fs.create(&scratch.path("seg"), 64).unwrap();
248 file.pwrite(0, b"durable").unwrap();
249 file.sync_data().unwrap();
250 }
251
252 #[test]
253 fn test_truncate_shortens_file() {
254 let scratch = Scratch::new();
256 let fs = HostFs::new();
257 let file = fs.create(&scratch.path("seg"), 4096).unwrap();
258 file.truncate(1000).unwrap();
259 assert_eq!(file.len().unwrap(), 1000);
260 }
261
262 #[test]
263 fn test_rename_moves_file_and_old_path_is_gone() {
264 let scratch = Scratch::new();
266 let fs = HostFs::new();
267 let from = scratch.path("tmp");
268 let to = scratch.path("final");
269
270 fs.create(&from, 32).unwrap();
271 fs.rename(&from, &to).unwrap();
272
273 assert_eq!(fs.open(&to).unwrap().len().unwrap(), 32);
274 assert_eq!(fs.open(&from).unwrap_err(), FsError::NotFound(from.clone()));
275 }
276
277 #[test]
278 fn test_unlink_removes_file() {
279 let scratch = Scratch::new();
281 let fs = HostFs::new();
282 let path = scratch.path("seg");
283
284 fs.create(&path, 32).unwrap();
285 fs.unlink(&path).unwrap();
286
287 assert_eq!(fs.open(&path).unwrap_err(), FsError::NotFound(path.clone()));
288 }
289
290 #[test]
291 fn test_read_dir_returns_sorted_entries() {
292 let scratch = Scratch::new();
295 let fs = HostFs::new();
296
297 for name in ["c", "a", "b"] {
298 fs.create(&scratch.path(name), 0).unwrap();
299 }
300
301 let entries = fs.read_dir(&scratch.root).unwrap();
302 assert_eq!(entries, vec![scratch.path("a"), scratch.path("b"), scratch.path("c")]);
303 }
304
305 #[test]
306 fn test_mkdir_creates_and_rejects_existing() {
307 let scratch = Scratch::new();
309 let fs = HostFs::new();
310 let path = scratch.path("store");
311
312 fs.mkdir(&path).unwrap();
313 assert!(path.is_dir());
314 assert_eq!(fs.mkdir(&path).unwrap_err(), FsError::AlreadyExists(path.clone()));
315 }
316
317 #[test]
318 fn test_open_missing_path_is_not_found() {
319 let scratch = Scratch::new();
321 let fs = HostFs::new();
322 let path = scratch.path("absent");
323
324 assert_eq!(fs.open(&path).unwrap_err(), FsError::NotFound(path.clone()));
325 assert_eq!(fs.open_mut(&path).unwrap_err(), FsError::NotFound(path.clone()));
326 }
327
328 #[test]
329 fn test_create_existing_path_is_already_exists() {
330 let scratch = Scratch::new();
332 let fs = HostFs::new();
333 let path = scratch.path("seg");
334
335 fs.create(&path, 128).unwrap();
336 assert_eq!(fs.create(&path, 128).unwrap_err(), FsError::AlreadyExists(path.clone()));
337 assert_eq!(fs.open(&path).unwrap().len().unwrap(), 128);
338 }
339
340 #[test]
341 fn test_open_reads_and_open_mut_writes_same_file() {
342 let scratch = Scratch::new();
345 let fs = HostFs::new();
346 let path = scratch.path("seg");
347
348 fs.create(&path, 256).unwrap();
349
350 let writer = fs.open_mut(&path).unwrap();
351 assert_eq!(writer.pwrite(64, b"payload").unwrap(), 7);
352 writer.sync_data().unwrap();
353
354 let reader = fs.open(&path).unwrap();
355 let mut buf = [0u8; 7];
356 assert_eq!(reader.pread(64, &mut buf).unwrap(), 7);
357 assert_eq!(&buf, b"payload");
358 assert_eq!(reader.len().unwrap(), 256);
359 }
360}