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, Rename, Result, SyncData,
13 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 SyncDir for HostFs {
113 fn sync_dir(&self, path: &Path) -> Result<()> {
114 File::open(path).and_then(|dir| dir.sync_all()).map_err(|err| FsError::from_io(path, err))
115 }
116}
117
118impl Pread for HostFile {
119 fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
120 self.file.read_at(buf, offset).map_err(|err| FsError::from_io(&self.path, err))
121 }
122}
123
124impl Len for HostFile {
125 fn len(&self) -> Result<u64> {
126 self.file.metadata().map(|meta| meta.len()).map_err(|err| FsError::from_io(&self.path, err))
127 }
128}
129
130impl Pread for HostFileMut {
131 fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
132 self.file.read_at(buf, offset).map_err(|err| FsError::from_io(&self.path, err))
133 }
134}
135
136impl Pwrite for HostFileMut {
137 fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize> {
138 self.file.write_at(buf, offset).map_err(|err| FsError::from_io(&self.path, err))
139 }
140}
141
142impl SyncData for HostFileMut {
143 fn sync_data(&self) -> Result<()> {
144 self.file.sync_data().map_err(|err| FsError::from_io(&self.path, err))
145 }
146}
147
148impl Truncate for HostFileMut {
149 fn truncate(&self, len: u64) -> Result<()> {
150 self.file.set_len(len).map_err(|err| FsError::from_io(&self.path, err))
151 }
152}
153
154impl Len for HostFileMut {
155 fn len(&self) -> Result<u64> {
156 self.file.metadata().map(|meta| meta.len()).map_err(|err| FsError::from_io(&self.path, err))
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use std::{
163 env, process,
164 sync::atomic::{AtomicU64, Ordering},
165 };
166
167 use super::*;
168
169 static COUNTER: AtomicU64 = AtomicU64::new(0);
170
171 struct Scratch {
172 root: PathBuf,
173 }
174
175 impl Scratch {
176 fn new() -> Self {
177 let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
178 let root = env::temp_dir().join(format!("reifydb-host-fs-{}-{}", process::id(), seq));
179 fs::create_dir_all(&root).unwrap();
180 Self {
181 root,
182 }
183 }
184
185 fn path(&self, name: &str) -> PathBuf {
186 self.root.join(name)
187 }
188 }
189
190 impl Drop for Scratch {
191 fn drop(&mut self) {
192 let _ = fs::remove_dir_all(&self.root);
193 }
194 }
195
196 #[test]
197 fn test_create_preallocates_length() {
198 let scratch = Scratch::new();
200 let fs = HostFs::new();
201 let file = fs.create(&scratch.path("seg"), 4096).unwrap();
202 assert_eq!(file.len().unwrap(), 4096);
203 }
204
205 #[test]
206 fn test_pwrite_pread_roundtrip_at_offsets() {
207 let scratch = Scratch::new();
209 let fs = HostFs::new();
210 let file = fs.create(&scratch.path("seg"), 4096).unwrap();
211
212 for offset in [0u64, 512, 700] {
213 let payload = [offset as u8; 64];
214 assert_eq!(file.pwrite(offset, &payload).unwrap(), 64);
215 let mut buf = [0u8; 64];
216 assert_eq!(file.pread(offset, &mut buf).unwrap(), 64);
217 assert_eq!(buf, payload);
218 }
219
220 assert_eq!(file.len().unwrap(), 4096);
221 }
222
223 #[test]
224 fn test_pread_past_end_is_short_not_error() {
225 let scratch = Scratch::new();
227 let fs = HostFs::new();
228 let file = fs.create(&scratch.path("seg"), 16).unwrap();
229
230 let mut buf = [0u8; 32];
231 assert_eq!(file.pread(8, &mut buf).unwrap(), 8);
232 assert_eq!(file.pread(16, &mut buf).unwrap(), 0);
233 assert_eq!(file.pread(4096, &mut buf).unwrap(), 0);
234 }
235
236 #[test]
237 fn test_sync_data_succeeds() {
238 let scratch = Scratch::new();
240 let fs = HostFs::new();
241 let file = fs.create(&scratch.path("seg"), 64).unwrap();
242 file.pwrite(0, b"durable").unwrap();
243 file.sync_data().unwrap();
244 }
245
246 #[test]
247 fn test_truncate_shortens_file() {
248 let scratch = Scratch::new();
250 let fs = HostFs::new();
251 let file = fs.create(&scratch.path("seg"), 4096).unwrap();
252 file.truncate(1000).unwrap();
253 assert_eq!(file.len().unwrap(), 1000);
254 }
255
256 #[test]
257 fn test_rename_moves_file_and_old_path_is_gone() {
258 let scratch = Scratch::new();
260 let fs = HostFs::new();
261 let from = scratch.path("tmp");
262 let to = scratch.path("final");
263
264 fs.create(&from, 32).unwrap();
265 fs.rename(&from, &to).unwrap();
266
267 assert_eq!(fs.open(&to).unwrap().len().unwrap(), 32);
268 assert_eq!(fs.open(&from).unwrap_err(), FsError::NotFound(from.clone()));
269 }
270
271 #[test]
272 fn test_unlink_removes_file() {
273 let scratch = Scratch::new();
275 let fs = HostFs::new();
276 let path = scratch.path("seg");
277
278 fs.create(&path, 32).unwrap();
279 fs.unlink(&path).unwrap();
280
281 assert_eq!(fs.open(&path).unwrap_err(), FsError::NotFound(path.clone()));
282 }
283
284 #[test]
285 fn test_read_dir_returns_sorted_entries() {
286 let scratch = Scratch::new();
289 let fs = HostFs::new();
290
291 for name in ["c", "a", "b"] {
292 fs.create(&scratch.path(name), 0).unwrap();
293 }
294
295 let entries = fs.read_dir(&scratch.root).unwrap();
296 assert_eq!(entries, vec![scratch.path("a"), scratch.path("b"), scratch.path("c")]);
297 }
298
299 #[test]
300 fn test_mkdir_creates_and_rejects_existing() {
301 let scratch = Scratch::new();
303 let fs = HostFs::new();
304 let path = scratch.path("store");
305
306 fs.mkdir(&path).unwrap();
307 assert!(path.is_dir());
308 assert_eq!(fs.mkdir(&path).unwrap_err(), FsError::AlreadyExists(path.clone()));
309 }
310
311 #[test]
312 fn test_open_missing_path_is_not_found() {
313 let scratch = Scratch::new();
315 let fs = HostFs::new();
316 let path = scratch.path("absent");
317
318 assert_eq!(fs.open(&path).unwrap_err(), FsError::NotFound(path.clone()));
319 assert_eq!(fs.open_mut(&path).unwrap_err(), FsError::NotFound(path.clone()));
320 }
321
322 #[test]
323 fn test_create_existing_path_is_already_exists() {
324 let scratch = Scratch::new();
326 let fs = HostFs::new();
327 let path = scratch.path("seg");
328
329 fs.create(&path, 128).unwrap();
330 assert_eq!(fs.create(&path, 128).unwrap_err(), FsError::AlreadyExists(path.clone()));
331 assert_eq!(fs.open(&path).unwrap().len().unwrap(), 128);
332 }
333
334 #[test]
335 fn test_open_reads_and_open_mut_writes_same_file() {
336 let scratch = Scratch::new();
339 let fs = HostFs::new();
340 let path = scratch.path("seg");
341
342 fs.create(&path, 256).unwrap();
343
344 let writer = fs.open_mut(&path).unwrap();
345 assert_eq!(writer.pwrite(64, b"payload").unwrap(), 7);
346 writer.sync_data().unwrap();
347
348 let reader = fs.open(&path).unwrap();
349 let mut buf = [0u8; 7];
350 assert_eq!(reader.pread(64, &mut buf).unwrap(), 7);
351 assert_eq!(&buf, b"payload");
352 assert_eq!(reader.len().unwrap(), 256);
353 }
354}