Skip to main content

reifydb_runtime/io/fs/
memory.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4#[cfg(any(test, feature = "testing"))]
5use std::mem;
6use std::{
7	collections::{BTreeMap, BTreeSet},
8	path::{Path, PathBuf},
9	sync::Arc,
10};
11
12use crate::{
13	io::fs::{
14		Create, Filesystem, FsError, Len, Mkdir, Open, OpenMut, Pread, Pwrite, ReadDir, Rename, Result,
15		SyncData, SyncDir, Truncate, Unlink,
16	},
17	sync::mutex::Mutex,
18};
19
20pub const SECTOR_BYTES: usize = 512;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum SectorState {
24	Unwritten,
25	Dirty,
26	Durable,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct SectorMask(Vec<bool>);
31
32impl SectorMask {
33	pub fn full(sectors: usize) -> Self {
34		Self(vec![true; sectors])
35	}
36
37	pub fn empty(sectors: usize) -> Self {
38		Self(vec![false; sectors])
39	}
40
41	pub fn set(&mut self, sector: usize) {
42		if sector < self.0.len() {
43			self.0[sector] = true;
44		}
45	}
46
47	pub fn clear(&mut self, sector: usize) {
48		if sector < self.0.len() {
49			self.0[sector] = false;
50		}
51	}
52
53	pub fn is_set(&self, sector: usize) -> bool {
54		self.0.get(sector).copied().unwrap_or(false)
55	}
56
57	pub fn sectors(&self) -> usize {
58		self.0.len()
59	}
60}
61
62pub(crate) fn sector_span(offset: u64, len: usize, sector_bytes: usize) -> usize {
63	if len == 0 {
64		return 0;
65	}
66	let first = offset as usize / sector_bytes;
67	let last = (offset as usize + len - 1) / sector_bytes;
68	last - first + 1
69}
70
71struct FileData {
72	bytes: Vec<u8>,
73	sectors: Vec<SectorState>,
74	shadow: BTreeMap<usize, (SectorState, Vec<u8>)>,
75}
76
77impl FileData {
78	fn new(len: usize, sector_bytes: usize) -> Self {
79		Self {
80			bytes: vec![0u8; len],
81			sectors: vec![SectorState::Unwritten; len.div_ceil(sector_bytes)],
82			shadow: BTreeMap::new(),
83		}
84	}
85
86	fn resize(&mut self, len: usize, sector_bytes: usize) {
87		self.bytes.resize(len, 0);
88		self.sectors.resize(len.div_ceil(sector_bytes), SectorState::Unwritten);
89		let sectors = self.sectors.len();
90		self.shadow.retain(|sector, _| *sector < sectors);
91	}
92
93	fn dirty(&mut self, sector: usize, sector_bytes: usize) {
94		if !self.shadow.contains_key(&sector) {
95			let lo = sector * sector_bytes;
96			let hi = (lo + sector_bytes).min(self.bytes.len());
97			self.shadow.insert(sector, (self.sectors[sector], self.bytes[lo..hi].to_vec()));
98		}
99		self.sectors[sector] = SectorState::Dirty;
100	}
101
102	fn sync(&mut self) {
103		for state in self.sectors.iter_mut() {
104			if *state == SectorState::Dirty {
105				*state = SectorState::Durable;
106			}
107		}
108		self.shadow.clear();
109	}
110
111	#[cfg(any(test, feature = "testing"))]
112	fn revert(&mut self, sector_bytes: usize) {
113		while let Some((sector, (state, bytes))) = self.shadow.pop_first() {
114			let lo = sector * sector_bytes;
115			if lo >= self.bytes.len() {
116				continue;
117			}
118			let hi = (lo + bytes.len()).min(self.bytes.len());
119			self.bytes[lo..hi].copy_from_slice(&bytes[..hi - lo]);
120			if sector < self.sectors.len() {
121				self.sectors[sector] = state;
122			}
123		}
124	}
125}
126
127pub(crate) struct MemoryFileState {
128	sector_bytes: usize,
129	data: Mutex<FileData>,
130}
131
132impl MemoryFileState {
133	fn new(len: usize, sector_bytes: usize) -> Self {
134		Self {
135			sector_bytes,
136			data: Mutex::new(FileData::new(len, sector_bytes)),
137		}
138	}
139
140	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
141		let data = self.data.lock();
142		let offset = offset as usize;
143		if offset >= data.bytes.len() {
144			return Ok(0);
145		}
146		let read = buf.len().min(data.bytes.len() - offset);
147		buf[..read].copy_from_slice(&data.bytes[offset..offset + read]);
148		Ok(read)
149	}
150
151	fn write_masked(&self, offset: u64, buf: &[u8], mask: &SectorMask) -> Result<usize> {
152		if buf.is_empty() {
153			return Ok(0);
154		}
155		let sector_bytes = self.sector_bytes;
156		let offset = offset as usize;
157		let end = offset + buf.len();
158		let mut data = self.data.lock();
159		if end > data.bytes.len() {
160			data.resize(end, sector_bytes);
161		}
162		let first = offset / sector_bytes;
163		let last = (end - 1) / sector_bytes;
164		for sector in first..=last {
165			if !mask.is_set(sector - first) {
166				continue;
167			}
168			let lo = (sector * sector_bytes).max(offset);
169			let hi = (sector * sector_bytes + sector_bytes).min(end);
170			data.dirty(sector, sector_bytes);
171			data.bytes[lo..hi].copy_from_slice(&buf[lo - offset..hi - offset]);
172		}
173		Ok(buf.len())
174	}
175
176	fn sync_data(&self) -> Result<()> {
177		self.data.lock().sync();
178		Ok(())
179	}
180
181	fn truncate(&self, len: u64) -> Result<()> {
182		self.data.lock().resize(len as usize, self.sector_bytes);
183		Ok(())
184	}
185
186	fn len(&self) -> Result<u64> {
187		Ok(self.data.lock().bytes.len() as u64)
188	}
189
190	#[cfg(any(test, feature = "testing"))]
191	fn revert(&self) {
192		self.data.lock().revert(self.sector_bytes);
193	}
194
195	#[cfg(any(test, feature = "testing"))]
196	fn sector_states(&self) -> Vec<SectorState> {
197		self.data.lock().sectors.clone()
198	}
199}
200
201struct State {
202	files: BTreeMap<PathBuf, Arc<MemoryFileState>>,
203	dirs: BTreeSet<PathBuf>,
204	dirty_dirs: BTreeSet<PathBuf>,
205	sector_bytes: usize,
206}
207
208impl Default for State {
209	fn default() -> Self {
210		Self {
211			files: BTreeMap::new(),
212			dirs: BTreeSet::from([PathBuf::from("/")]),
213			dirty_dirs: BTreeSet::new(),
214			sector_bytes: SECTOR_BYTES,
215		}
216	}
217}
218
219impl State {
220	fn exists(&self, path: &Path) -> bool {
221		self.files.contains_key(path) || self.dirs.contains(path)
222	}
223
224	fn parent_is_dir(&self, path: &Path) -> bool {
225		match path.parent() {
226			Some(parent) => self.dirs.contains(parent),
227			None => false,
228		}
229	}
230}
231
232#[derive(Default)]
233struct Inner {
234	state: Mutex<State>,
235}
236
237#[derive(Clone, Default)]
238pub struct MemoryFs(Arc<Inner>);
239
240impl MemoryFs {
241	pub fn new() -> Self {
242		Self::default()
243	}
244
245	pub fn with_sector_bytes(sector_bytes: usize) -> Self {
246		Self(Arc::new(Inner {
247			state: Mutex::new(State {
248				sector_bytes,
249				..State::default()
250			}),
251		}))
252	}
253
254	#[cfg(any(test, feature = "testing"))]
255	pub fn crash(&self) {
256		{
257			let mut state = self.0.state.lock();
258			let dirty: Vec<PathBuf> = mem::take(&mut state.dirty_dirs).into_iter().collect();
259			for path in dirty {
260				state.dirs.retain(|dir| !dir.starts_with(&path));
261				state.files.retain(|key, _| !key.starts_with(&path));
262			}
263		}
264		let files: Vec<Arc<MemoryFileState>> = self.0.state.lock().files.values().cloned().collect();
265		for file in files {
266			file.revert();
267		}
268	}
269
270	#[cfg(feature = "testing")]
271	pub(crate) fn detach(&self, path: &Path) -> Result<Arc<MemoryFileState>> {
272		self.0.state.lock().files.remove(path).ok_or_else(|| FsError::NotFound(path.to_path_buf()))
273	}
274
275	#[cfg(feature = "testing")]
276	pub(crate) fn attach(&self, path: &Path, state: Arc<MemoryFileState>) {
277		self.0.state.lock().files.insert(path.to_path_buf(), state);
278	}
279}
280
281pub struct MemoryFile {
282	path: PathBuf,
283	state: Arc<MemoryFileState>,
284}
285
286pub struct MemoryFileMut {
287	path: PathBuf,
288	state: Arc<MemoryFileState>,
289}
290
291impl MemoryFileMut {
292	pub fn write_masked(&self, offset: u64, buf: &[u8], mask: &SectorMask) -> Result<usize> {
293		self.state.write_masked(offset, buf, mask)
294	}
295
296	pub fn path(&self) -> &Path {
297		&self.path
298	}
299
300	pub fn sector_bytes(&self) -> usize {
301		self.state.sector_bytes
302	}
303
304	#[cfg(any(test, feature = "testing"))]
305	pub fn sector_states(&self) -> Vec<SectorState> {
306		self.state.sector_states()
307	}
308}
309
310impl MemoryFile {
311	pub fn path(&self) -> &Path {
312		&self.path
313	}
314
315	pub fn sector_bytes(&self) -> usize {
316		self.state.sector_bytes
317	}
318
319	#[cfg(any(test, feature = "testing"))]
320	pub fn sector_states(&self) -> Vec<SectorState> {
321		self.state.sector_states()
322	}
323}
324
325impl Filesystem for MemoryFs {
326	type File = MemoryFile;
327	type FileMut = MemoryFileMut;
328}
329
330impl Mkdir for MemoryFs {
331	fn mkdir(&self, path: &Path) -> Result<()> {
332		let mut state = self.0.state.lock();
333		if !state.parent_is_dir(path) {
334			return Err(FsError::NotFound(path.to_path_buf()));
335		}
336		if state.exists(path) {
337			return Err(FsError::AlreadyExists(path.to_path_buf()));
338		}
339		state.dirs.insert(path.to_path_buf());
340		state.dirty_dirs.insert(path.to_path_buf());
341		Ok(())
342	}
343}
344
345impl Create for MemoryFs {
346	fn create(&self, path: &Path, len: u64) -> Result<MemoryFileMut> {
347		let mut state = self.0.state.lock();
348		if !state.parent_is_dir(path) {
349			return Err(FsError::NotFound(path.to_path_buf()));
350		}
351		if state.exists(path) {
352			return Err(FsError::AlreadyExists(path.to_path_buf()));
353		}
354		let file = Arc::new(MemoryFileState::new(len as usize, state.sector_bytes));
355		state.files.insert(path.to_path_buf(), Arc::clone(&file));
356		Ok(MemoryFileMut {
357			path: path.to_path_buf(),
358			state: file,
359		})
360	}
361}
362
363impl Open for MemoryFs {
364	fn open(&self, path: &Path) -> Result<MemoryFile> {
365		let state = self.0.state.lock();
366		state.files
367			.get(path)
368			.map(|file| MemoryFile {
369				path: path.to_path_buf(),
370				state: Arc::clone(file),
371			})
372			.ok_or_else(|| FsError::NotFound(path.to_path_buf()))
373	}
374}
375
376impl OpenMut for MemoryFs {
377	fn open_mut(&self, path: &Path) -> Result<MemoryFileMut> {
378		let state = self.0.state.lock();
379		state.files
380			.get(path)
381			.map(|file| MemoryFileMut {
382				path: path.to_path_buf(),
383				state: Arc::clone(file),
384			})
385			.ok_or_else(|| FsError::NotFound(path.to_path_buf()))
386	}
387}
388
389impl ReadDir for MemoryFs {
390	fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>> {
391		let state = self.0.state.lock();
392		if !state.dirs.contains(path) {
393			if state.files.contains_key(path) {
394				return Err(FsError::NotADirectory(path.to_path_buf()));
395			}
396			return Err(FsError::NotFound(path.to_path_buf()));
397		}
398		let mut entries: Vec<PathBuf> = state
399			.files
400			.keys()
401			.chain(state.dirs.iter())
402			.filter(|entry| entry.parent() == Some(path))
403			.cloned()
404			.collect();
405		entries.sort();
406		Ok(entries)
407	}
408}
409
410impl Rename for MemoryFs {
411	fn rename(&self, from: &Path, to: &Path) -> Result<()> {
412		let mut state = self.0.state.lock();
413		let Some(file) = state.files.remove(from) else {
414			return Err(FsError::NotFound(from.to_path_buf()));
415		};
416		if !state.parent_is_dir(to) {
417			state.files.insert(from.to_path_buf(), file);
418			return Err(FsError::NotFound(to.to_path_buf()));
419		}
420		state.files.insert(to.to_path_buf(), file);
421		Ok(())
422	}
423}
424
425impl Unlink for MemoryFs {
426	fn unlink(&self, path: &Path) -> Result<()> {
427		let mut state = self.0.state.lock();
428		state.files.remove(path).map(|_| ()).ok_or_else(|| FsError::NotFound(path.to_path_buf()))
429	}
430}
431
432impl SyncDir for MemoryFs {
433	fn sync_dir(&self, path: &Path) -> Result<()> {
434		let mut state = self.0.state.lock();
435		if !state.dirs.contains(path) {
436			return Err(FsError::NotFound(path.to_path_buf()));
437		}
438		state.dirty_dirs.retain(|dirty| dirty.parent() != Some(path));
439		Ok(())
440	}
441}
442
443impl Pread for MemoryFile {
444	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
445		self.state.pread(offset, buf)
446	}
447}
448
449impl Len for MemoryFile {
450	fn len(&self) -> Result<u64> {
451		self.state.len()
452	}
453}
454
455impl Pread for MemoryFileMut {
456	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
457		self.state.pread(offset, buf)
458	}
459}
460
461impl Pwrite for MemoryFileMut {
462	fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize> {
463		let mask = SectorMask::full(sector_span(offset, buf.len(), self.state.sector_bytes));
464		self.state.write_masked(offset, buf, &mask)
465	}
466}
467
468impl SyncData for MemoryFileMut {
469	fn sync_data(&self) -> Result<()> {
470		self.state.sync_data()
471	}
472}
473
474impl Truncate for MemoryFileMut {
475	fn truncate(&self, len: u64) -> Result<()> {
476		self.state.truncate(len)
477	}
478}
479
480impl Len for MemoryFileMut {
481	fn len(&self) -> Result<u64> {
482		self.state.len()
483	}
484}
485
486#[cfg(test)]
487mod tests {
488	use super::*;
489
490	fn read_all(file: &MemoryFileMut) -> Vec<u8> {
491		let len = file.len().unwrap() as usize;
492		let mut buf = vec![0u8; len];
493		assert_eq!(file.pread(0, &mut buf).unwrap(), len);
494		buf
495	}
496
497	#[test]
498	fn create_leaves_every_sector_unwritten_and_zeroed() {
499		// a fresh file owns no durable and no dirty state, so a crash must leave it exactly as created.
500		let fs = MemoryFs::new();
501		let file = fs.create(Path::new("/a"), 1024).unwrap();
502		assert_eq!(file.sector_states(), vec![SectorState::Unwritten; 2]);
503		assert_eq!(read_all(&file), vec![0u8; 1024]);
504	}
505
506	#[test]
507	fn write_dirties_every_grazed_sector_whole() {
508		// a device cannot make half a sector durable, so a one-byte overlap must dirty the entire sector.
509		let fs = MemoryFs::new();
510		let file = fs.create(Path::new("/a"), 2048).unwrap();
511		assert_eq!(file.pwrite(500, &[7u8; 20]).unwrap(), 20);
512		assert_eq!(
513			file.sector_states(),
514			vec![SectorState::Dirty, SectorState::Dirty, SectorState::Unwritten, SectorState::Unwritten]
515		);
516	}
517
518	#[test]
519	fn sync_promotes_every_dirty_sector() {
520		// only a sync may turn dirty into durable; untouched sectors must stay unwritten.
521		let fs = MemoryFs::new();
522		let file = fs.create(Path::new("/a"), 2048).unwrap();
523		file.pwrite(0, &[1u8; 600]).unwrap();
524		file.sync_data().unwrap();
525		assert_eq!(
526			file.sector_states(),
527			vec![
528				SectorState::Durable,
529				SectorState::Durable,
530				SectorState::Unwritten,
531				SectorState::Unwritten
532			]
533		);
534	}
535
536	#[test]
537	fn write_masked_skips_unmasked_sectors_but_reports_full_length() {
538		// a torn write must tell the caller it succeeded while only some sectors reached the platter.
539		let fs = MemoryFs::new();
540		let file = fs.create(Path::new("/a"), 1536).unwrap();
541		let mut mask = SectorMask::empty(3);
542		mask.set(0);
543		mask.set(2);
544		assert_eq!(file.write_masked(0, &[0xABu8; 1536], &mask).unwrap(), 1536);
545		let bytes = read_all(&file);
546		assert!(bytes[0..512].iter().all(|byte| *byte == 0xAB));
547		assert!(bytes[512..1024].iter().all(|byte| *byte == 0));
548		assert!(bytes[1024..1536].iter().all(|byte| *byte == 0xAB));
549		assert_eq!(file.sector_states(), vec![SectorState::Dirty, SectorState::Unwritten, SectorState::Dirty]);
550	}
551
552	#[test]
553	fn mask_ignores_out_of_range_indices() {
554		// a mask shorter than the span reads as unset rather than panicking, so no fault plan aborts a run.
555		let mut mask = SectorMask::empty(2);
556		mask.set(5);
557		assert_eq!(mask.sectors(), 2);
558		assert!(!mask.is_set(5));
559		let mut full = SectorMask::full(2);
560		full.clear(1);
561		assert!(full.is_set(0));
562		assert!(!full.is_set(1));
563	}
564
565	#[test]
566	fn pread_round_trips_clamps_and_stops_at_the_end() {
567		// a short read at the end must report the count copied, and a read past the end is not an error.
568		let fs = MemoryFs::new();
569		let file = fs.create(Path::new("/a"), 10).unwrap();
570		file.pwrite(0, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).unwrap();
571		let mut buf = [0u8; 4];
572		assert_eq!(file.pread(2, &mut buf).unwrap(), 4);
573		assert_eq!(buf, [3, 4, 5, 6]);
574		let mut tail = [0u8; 16];
575		assert_eq!(file.pread(5, &mut tail).unwrap(), 5);
576		assert_eq!(&tail[..5], &[6, 7, 8, 9, 10]);
577		assert_eq!(file.pread(10, &mut tail).unwrap(), 0);
578		assert_eq!(file.pread(9999, &mut tail).unwrap(), 0);
579	}
580
581	#[test]
582	fn write_past_the_end_grows_bytes_and_sectors_together() {
583		// the sector vector must track the byte vector, or a crash would index a sector that does not exist.
584		let fs = MemoryFs::new();
585		let file = fs.create(Path::new("/a"), 0).unwrap();
586		assert!(file.sector_states().is_empty());
587		assert_eq!(file.pwrite(1000, &[9u8; 10]).unwrap(), 10);
588		assert_eq!(file.len().unwrap(), 1010);
589		assert_eq!(file.sector_states(), vec![SectorState::Unwritten, SectorState::Dirty]);
590		assert_eq!(fs.open(Path::new("/a")).unwrap().sector_states().len(), 2);
591	}
592
593	#[test]
594	fn truncate_shortens_bytes_and_sector_states_together() {
595		// a shrink must drop the sectors it removed, otherwise a crash would restore bytes past the new end.
596		let fs = MemoryFs::new();
597		let file = fs.create(Path::new("/a"), 2048).unwrap();
598		file.pwrite(0, &[3u8; 2048]).unwrap();
599		file.truncate(600).unwrap();
600		assert_eq!(file.len().unwrap(), 600);
601		assert_eq!(file.sector_states().len(), 2);
602		file.truncate(100).unwrap();
603		assert_eq!(file.sector_states().len(), 1);
604		assert_eq!(read_all(&file), vec![3u8; 100]);
605		file.truncate(1024).unwrap();
606		assert_eq!(file.sector_states(), vec![SectorState::Dirty, SectorState::Unwritten]);
607	}
608
609	#[test]
610	fn crash_reverts_dirty_sectors_to_their_prior_bytes_and_state() {
611		// an unsynced write must vanish exactly, leaving the sector in the state it held before it went dirty.
612		let fs = MemoryFs::new();
613		let file = fs.create(Path::new("/a"), 1024).unwrap();
614		file.pwrite(0, &[1u8; 512]).unwrap();
615		file.sync_data().unwrap();
616		file.pwrite(0, &[2u8; 512]).unwrap();
617		file.pwrite(512, &[2u8; 512]).unwrap();
618		fs.crash();
619		let bytes = read_all(&file);
620		assert!(bytes[0..512].iter().all(|byte| *byte == 1));
621		assert!(bytes[512..1024].iter().all(|byte| *byte == 0));
622		assert_eq!(file.sector_states(), vec![SectorState::Durable, SectorState::Unwritten]);
623	}
624
625	#[test]
626	fn crash_after_truncate_drops_shadow_beyond_the_new_end() {
627		// a shadow entry past the new end must not be replayed, or the crash would resurrect truncated bytes.
628		let fs = MemoryFs::new();
629		let file = fs.create(Path::new("/a"), 2048).unwrap();
630		file.pwrite(1536, &[8u8; 512]).unwrap();
631		file.truncate(512).unwrap();
632		fs.crash();
633		assert_eq!(file.len().unwrap(), 512);
634		assert_eq!(file.sector_states(), vec![SectorState::Unwritten]);
635	}
636
637	#[test]
638	fn sync_clears_the_shadow_so_a_later_crash_keeps_the_data() {
639		// once a sync covers a sector the bytes are durable, so a crash after it must change nothing.
640		let fs = MemoryFs::new();
641		let file = fs.create(Path::new("/a"), 512).unwrap();
642		file.pwrite(0, &[5u8; 512]).unwrap();
643		file.sync_data().unwrap();
644		fs.crash();
645		assert_eq!(read_all(&file), vec![5u8; 512]);
646		assert_eq!(file.sector_states(), vec![SectorState::Durable]);
647	}
648
649	#[test]
650	fn sector_size_is_configurable_and_consistent() {
651		// the sector size decides the blast radius of a torn write, so every handle must agree on it.
652		let fs = MemoryFs::with_sector_bytes(64);
653		let file = fs.create(Path::new("/a"), 100).unwrap();
654		assert_eq!(file.sector_bytes(), 64);
655		assert_eq!(file.sector_states().len(), 2);
656		file.pwrite(0, &[1u8; 100]).unwrap();
657		assert_eq!(file.sector_states(), vec![SectorState::Dirty, SectorState::Dirty]);
658
659		let wide = MemoryFs::new();
660		let file = wide.create(Path::new("/a"), 100).unwrap();
661		assert_eq!(file.sector_bytes(), SECTOR_BYTES);
662		file.pwrite(0, &[1u8; 100]).unwrap();
663		assert_eq!(file.sector_states(), vec![SectorState::Dirty]);
664	}
665
666	#[test]
667	fn mkdir_needs_a_parent_directory_and_rejects_an_existing_path() {
668		// a tree that accepts an orphan path lets a walk see files under a parent that never existed.
669		let fs = MemoryFs::new();
670		assert_eq!(fs.mkdir(Path::new("/a/b")), Err(FsError::NotFound(PathBuf::from("/a/b"))));
671		fs.mkdir(Path::new("/a")).unwrap();
672		assert_eq!(fs.mkdir(Path::new("/a")), Err(FsError::AlreadyExists(PathBuf::from("/a"))));
673		fs.create(Path::new("/a/f"), 0).unwrap();
674		assert_eq!(fs.mkdir(Path::new("/a/f")), Err(FsError::AlreadyExists(PathBuf::from("/a/f"))));
675	}
676
677	#[test]
678	fn create_needs_a_parent_directory_and_rejects_an_existing_path() {
679		let fs = MemoryFs::new();
680		assert!(fs.create(Path::new("/a/f"), 0).is_err());
681		fs.mkdir(Path::new("/a")).unwrap();
682		fs.create(Path::new("/a/f"), 0).unwrap();
683		assert_eq!(fs.create(Path::new("/a/f"), 0).err(), Some(FsError::AlreadyExists(PathBuf::from("/a/f"))));
684	}
685
686	#[test]
687	fn open_and_open_mut_report_a_missing_file() {
688		let fs = MemoryFs::new();
689		assert_eq!(fs.open(Path::new("/a")).err(), Some(FsError::NotFound(PathBuf::from("/a"))));
690		assert_eq!(fs.open_mut(Path::new("/a")).err(), Some(FsError::NotFound(PathBuf::from("/a"))));
691		fs.create(Path::new("/a"), 8).unwrap();
692		let file = fs.open(Path::new("/a")).unwrap();
693		assert_eq!(file.path(), Path::new("/a"));
694		assert_eq!(file.len().unwrap(), 8);
695		assert_eq!(file.sector_bytes(), SECTOR_BYTES);
696		assert_eq!(file.sector_states(), vec![SectorState::Unwritten]);
697	}
698
699	#[test]
700	fn open_shares_the_state_with_the_creating_handle() {
701		// two handles onto one path are one file, so a write through either must be visible through the other.
702		let fs = MemoryFs::new();
703		let writer = fs.create(Path::new("/a"), 512).unwrap();
704		writer.pwrite(0, &[4u8; 8]).unwrap();
705		let reader = fs.open(Path::new("/a")).unwrap();
706		let mut buf = [0u8; 8];
707		assert_eq!(reader.pread(0, &mut buf).unwrap(), 8);
708		assert_eq!(buf, [4u8; 8]);
709		assert_eq!(reader.sector_states(), vec![SectorState::Dirty]);
710	}
711
712	#[test]
713	fn read_dir_lists_direct_children_in_ascending_order() {
714		// the walk order feeds seed replay, so it must be sorted rather than whatever a hash gives back.
715		let fs = MemoryFs::new();
716		fs.mkdir(Path::new("/d")).unwrap();
717		fs.mkdir(Path::new("/d/sub")).unwrap();
718		fs.create(Path::new("/d/c"), 0).unwrap();
719		fs.create(Path::new("/d/a"), 0).unwrap();
720		fs.create(Path::new("/d/sub/deep"), 0).unwrap();
721		assert_eq!(
722			fs.read_dir(Path::new("/d")).unwrap(),
723			vec![PathBuf::from("/d/a"), PathBuf::from("/d/c"), PathBuf::from("/d/sub")]
724		);
725		assert_eq!(fs.read_dir(Path::new("/nope")).err(), Some(FsError::NotFound(PathBuf::from("/nope"))));
726		assert_eq!(fs.read_dir(Path::new("/d/a")).err(), Some(FsError::NotADirectory(PathBuf::from("/d/a"))));
727	}
728
729	#[test]
730	fn rename_moves_the_entry_and_keeps_the_contents() {
731		let fs = MemoryFs::new();
732		let file = fs.create(Path::new("/a"), 512).unwrap();
733		file.pwrite(0, &[6u8; 4]).unwrap();
734		fs.rename(Path::new("/a"), Path::new("/b")).unwrap();
735		assert_eq!(fs.open(Path::new("/a")).err(), Some(FsError::NotFound(PathBuf::from("/a"))));
736		let moved = fs.open(Path::new("/b")).unwrap();
737		let mut buf = [0u8; 4];
738		assert_eq!(moved.pread(0, &mut buf).unwrap(), 4);
739		assert_eq!(buf, [6u8; 4]);
740		assert_eq!(
741			fs.rename(Path::new("/a"), Path::new("/c")).err(),
742			Some(FsError::NotFound(PathBuf::from("/a")))
743		);
744	}
745
746	#[test]
747	fn rename_into_a_missing_directory_leaves_the_source_in_place() {
748		// a failed rename must not lose the file, or recovery would find neither name.
749		let fs = MemoryFs::new();
750		fs.create(Path::new("/a"), 0).unwrap();
751		assert_eq!(
752			fs.rename(Path::new("/a"), Path::new("/d/b")).err(),
753			Some(FsError::NotFound(PathBuf::from("/d/b")))
754		);
755		assert!(fs.open(Path::new("/a")).is_ok());
756	}
757
758	#[test]
759	fn unlink_removes_the_entry_or_reports_it_missing() {
760		let fs = MemoryFs::new();
761		fs.create(Path::new("/a"), 0).unwrap();
762		fs.unlink(Path::new("/a")).unwrap();
763		assert_eq!(fs.unlink(Path::new("/a")).err(), Some(FsError::NotFound(PathBuf::from("/a"))));
764	}
765
766	#[test]
767	fn sync_dir_checks_the_directory_exists() {
768		let fs = MemoryFs::new();
769		fs.sync_dir(Path::new("/")).unwrap();
770		assert_eq!(fs.sync_dir(Path::new("/d")).err(), Some(FsError::NotFound(PathBuf::from("/d"))));
771		fs.mkdir(Path::new("/d")).unwrap();
772		fs.sync_dir(Path::new("/d")).unwrap();
773	}
774
775	#[test]
776	fn a_cloned_handle_shares_one_filesystem() {
777		// the wasm build hands the same disk to several owners, so a clone must not fork the namespace.
778		let fs = MemoryFs::new();
779		let clone = fs.clone();
780		clone.mkdir(Path::new("/d")).unwrap();
781		assert_eq!(fs.read_dir(Path::new("/")).unwrap(), vec![PathBuf::from("/d")]);
782	}
783}