Skip to main content

reifydb_runtime/io/fs/
memory.rs

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