hermit/fs/
mod.rs

1#[cfg(all(feature = "fuse", feature = "pci"))]
2pub(crate) mod fuse;
3mod mem;
4mod uhyve;
5
6use alloc::boxed::Box;
7use alloc::string::{String, ToString};
8use alloc::sync::Arc;
9use alloc::vec::Vec;
10use core::ops::BitAnd;
11
12use async_trait::async_trait;
13use embedded_io::{Read, Write};
14use hermit_sync::{InterruptSpinMutex, OnceCell};
15use mem::MemDirectory;
16use num_enum::{IntoPrimitive, TryFromPrimitive};
17
18use crate::errno::Errno;
19use crate::executor::block_on;
20use crate::fd::{AccessPermission, ObjectInterface, OpenOption, insert_object, remove_object};
21use crate::io;
22use crate::time::{SystemTime, timespec};
23
24static FILESYSTEM: OnceCell<Filesystem> = OnceCell::new();
25
26static WORKING_DIRECTORY: InterruptSpinMutex<Option<String>> = InterruptSpinMutex::new(None);
27
28static UMASK: InterruptSpinMutex<AccessPermission> =
29	InterruptSpinMutex::new(AccessPermission::from_bits_retain(0o777));
30
31#[derive(Debug, Clone)]
32pub struct DirectoryEntry {
33	pub name: String,
34}
35
36impl DirectoryEntry {
37	pub fn new(name: String) -> Self {
38		Self { name }
39	}
40}
41
42/// Type of the VNode
43#[derive(Copy, Clone, Debug, PartialEq, Eq)]
44pub(crate) enum NodeKind {
45	/// Node represent a file
46	File,
47	/// Node represent a directory
48	Directory,
49}
50
51/// VfsNode represents an internal node of the ramdisk.
52pub(crate) trait VfsNode: core::fmt::Debug {
53	/// Determines the current node type
54	fn get_kind(&self) -> NodeKind;
55
56	/// determines the current file attribute
57	fn get_file_attributes(&self) -> io::Result<FileAttr> {
58		Err(Errno::Nosys)
59	}
60
61	/// Determine the syscall interface
62	fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
63		Err(Errno::Nosys)
64	}
65
66	/// Helper function to create a new directory node
67	fn traverse_mkdir(
68		&self,
69		_components: &mut Vec<&str>,
70		_mode: AccessPermission,
71	) -> io::Result<()> {
72		Err(Errno::Nosys)
73	}
74
75	/// Helper function to delete a directory node
76	fn traverse_rmdir(&self, _components: &mut Vec<&str>) -> io::Result<()> {
77		Err(Errno::Nosys)
78	}
79
80	/// Helper function to remove the specified file
81	fn traverse_unlink(&self, _components: &mut Vec<&str>) -> io::Result<()> {
82		Err(Errno::Nosys)
83	}
84
85	/// Helper function to open a directory
86	fn traverse_readdir(&self, _components: &mut Vec<&str>) -> io::Result<Vec<DirectoryEntry>> {
87		Err(Errno::Nosys)
88	}
89
90	/// Helper function to get file status
91	fn traverse_lstat(&self, _components: &mut Vec<&str>) -> io::Result<FileAttr> {
92		Err(Errno::Nosys)
93	}
94
95	/// Helper function to get file status
96	fn traverse_stat(&self, _components: &mut Vec<&str>) -> io::Result<FileAttr> {
97		Err(Errno::Nosys)
98	}
99
100	/// Helper function to mount a file system
101	fn traverse_mount(
102		&self,
103		_components: &mut Vec<&str>,
104		_obj: Box<dyn VfsNode + core::marker::Send + core::marker::Sync>,
105	) -> io::Result<()> {
106		Err(Errno::Nosys)
107	}
108
109	/// Helper function to open a file
110	fn traverse_open(
111		&self,
112		_components: &mut Vec<&str>,
113		_option: OpenOption,
114		_mode: AccessPermission,
115	) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
116		Err(Errno::Nosys)
117	}
118
119	/// Helper function to create a read-only file
120	fn traverse_create_file(
121		&self,
122		_components: &mut Vec<&str>,
123		_data: &'static [u8],
124		_mode: AccessPermission,
125	) -> io::Result<()> {
126		Err(Errno::Nosys)
127	}
128}
129
130#[derive(Debug, Clone)]
131struct DirectoryReader(Vec<DirectoryEntry>);
132
133impl DirectoryReader {
134	pub fn new(data: Vec<DirectoryEntry>) -> Self {
135		Self(data)
136	}
137}
138
139#[async_trait]
140impl ObjectInterface for DirectoryReader {
141	async fn getdents(&self, _buf: &mut [core::mem::MaybeUninit<u8>]) -> io::Result<usize> {
142		let _ = &self.0; // Dummy statement to avoid warning for the moment
143		unimplemented!()
144	}
145}
146
147#[derive(Debug)]
148pub(crate) struct Filesystem {
149	root: MemDirectory,
150}
151
152impl Filesystem {
153	pub fn new() -> Self {
154		Self {
155			root: MemDirectory::new(AccessPermission::from_bits(0o777).unwrap()),
156		}
157	}
158
159	/// Tries to open file at given path.
160	pub fn open(
161		&self,
162		path: &str,
163		opt: OpenOption,
164		mode: AccessPermission,
165	) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
166		debug!("Open file {path} with {opt:?}");
167		let mut components: Vec<&str> = path.split('/').collect();
168
169		components.reverse();
170		components.pop();
171
172		self.root.traverse_open(&mut components, opt, mode)
173	}
174
175	/// Unlinks a file given by path
176	pub fn unlink(&self, path: &str) -> io::Result<()> {
177		debug!("Unlinking file {path}");
178		let mut components: Vec<&str> = path.split('/').collect();
179
180		components.reverse();
181		components.pop();
182
183		self.root.traverse_unlink(&mut components)
184	}
185
186	/// Remove directory given by path
187	pub fn rmdir(&self, path: &str) -> io::Result<()> {
188		debug!("Removing directory {path}");
189		let mut components: Vec<&str> = path.split('/').collect();
190
191		components.reverse();
192		components.pop();
193
194		self.root.traverse_rmdir(&mut components)
195	}
196
197	/// Create directory given by path
198	pub fn mkdir(&self, path: &str, mode: AccessPermission) -> io::Result<()> {
199		debug!("Create directory {path}");
200		let mut components: Vec<&str> = path.split('/').collect();
201
202		components.reverse();
203		components.pop();
204
205		self.root.traverse_mkdir(&mut components, mode)
206	}
207
208	pub fn opendir(&self, path: &str) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
209		debug!("Open directory {path}");
210		Ok(Arc::new(async_lock::RwLock::new(DirectoryReader::new(
211			self.readdir(path)?,
212		))))
213	}
214
215	/// List given directory
216	pub fn readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
217		if path.trim() == "/" {
218			let mut components: Vec<&str> = Vec::new();
219			self.root.traverse_readdir(&mut components)
220		} else {
221			let mut components: Vec<&str> = path.split('/').collect();
222
223			components.reverse();
224			components.pop();
225
226			self.root.traverse_readdir(&mut components)
227		}
228	}
229
230	/// stat
231	pub fn stat(&self, path: &str) -> io::Result<FileAttr> {
232		debug!("Getting stats {path}");
233
234		let mut components: Vec<&str> = path.split('/').collect();
235		components.reverse();
236		components.pop();
237
238		self.root.traverse_stat(&mut components)
239	}
240
241	/// lstat
242	pub fn lstat(&self, path: &str) -> io::Result<FileAttr> {
243		debug!("Getting lstats {path}");
244
245		let mut components: Vec<&str> = path.split('/').collect();
246		components.reverse();
247		components.pop();
248
249		self.root.traverse_lstat(&mut components)
250	}
251
252	/// Create new backing-fs at mountpoint mntpath
253	pub fn mount(
254		&self,
255		path: &str,
256		obj: Box<dyn VfsNode + core::marker::Send + core::marker::Sync>,
257	) -> io::Result<()> {
258		debug!("Mounting {path}");
259
260		let mut components: Vec<&str> = path.split('/').collect();
261
262		components.reverse();
263		components.pop();
264
265		self.root.traverse_mount(&mut components, obj)
266	}
267
268	/// Create read-only file
269	pub fn create_file(
270		&self,
271		path: &str,
272		data: &'static [u8],
273		mode: AccessPermission,
274	) -> io::Result<()> {
275		debug!("Create read-only file {path}");
276
277		let mut components: Vec<&str> = path.split('/').collect();
278
279		components.reverse();
280		components.pop();
281
282		self.root.traverse_create_file(&mut components, data, mode)
283	}
284}
285
286#[repr(C)]
287#[derive(Debug, Default, Copy, Clone)]
288pub struct FileAttr {
289	pub st_dev: u64,
290	pub st_ino: u64,
291	pub st_nlink: u64,
292	/// access permissions
293	pub st_mode: AccessPermission,
294	/// user id
295	pub st_uid: u32,
296	/// group id
297	pub st_gid: u32,
298	/// device id
299	pub st_rdev: u64,
300	/// size in bytes
301	pub st_size: i64,
302	/// block size
303	pub st_blksize: i64,
304	/// size in blocks
305	pub st_blocks: i64,
306	/// time of last access
307	pub st_atim: timespec,
308	/// time of last modification
309	pub st_mtim: timespec,
310	/// time of last status change
311	pub st_ctim: timespec,
312}
313
314#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
315#[repr(u8)]
316pub enum FileType {
317	Unknown = 0,         // DT_UNKNOWN
318	Fifo = 1,            // DT_FIFO
319	CharacterDevice = 2, // DT_CHR
320	Directory = 4,       // DT_DIR
321	BlockDevice = 6,     // DT_BLK
322	RegularFile = 8,     // DT_REG
323	SymbolicLink = 10,   // DT_LNK
324	Socket = 12,         // DT_SOCK
325	Whiteout = 14,       // DT_WHT
326}
327
328#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
329#[repr(u8)]
330pub enum SeekWhence {
331	Set = 0,
332	Cur = 1,
333	End = 2,
334	Data = 3,
335	Hole = 4,
336}
337
338pub(crate) fn init() {
339	const VERSION: &str = env!("CARGO_PKG_VERSION");
340	const UTC_BUILT_TIME: &str = build_time::build_time_utc!();
341
342	FILESYSTEM.set(Filesystem::new()).unwrap();
343	FILESYSTEM
344		.get()
345		.unwrap()
346		.mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
347		.expect("Unable to create /tmp");
348	FILESYSTEM
349		.get()
350		.unwrap()
351		.mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
352		.expect("Unable to create /proc");
353
354	if let Ok(mut file) = File::create("/proc/version") {
355		if write!(file, "HermitOS version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
356			error!("Unable to write in /proc/version");
357		}
358	} else {
359		error!("Unable to create /proc/version");
360	}
361
362	let mut cwd = WORKING_DIRECTORY.lock();
363	*cwd = Some("/tmp".to_string());
364	drop(cwd);
365
366	#[cfg(all(feature = "fuse", feature = "pci"))]
367	fuse::init();
368	if crate::env::is_uhyve() {
369		uhyve::init();
370	}
371}
372
373pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
374	with_relative_filename(name, |name| {
375		FILESYSTEM
376			.get()
377			.ok_or(Errno::Inval)?
378			.create_file(name, data, mode)
379	})
380}
381
382/// Removes an empty directory.
383pub fn remove_dir(path: &str) -> io::Result<()> {
384	with_relative_filename(path, |path| {
385		FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
386	})
387}
388
389pub fn unlink(path: &str) -> io::Result<()> {
390	with_relative_filename(path, |path| {
391		FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
392	})
393}
394
395/// Creates a new, empty directory at the provided path
396pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
397	let mask = *UMASK.lock();
398
399	with_relative_filename(path, |path| {
400		FILESYSTEM
401			.get()
402			.ok_or(Errno::Inval)?
403			.mkdir(path, mode.bitand(mask))
404	})
405}
406
407/// Creates a directory and creates all missing parent directories as well.
408fn create_dir_recursive(path: &str, mode: AccessPermission) -> io::Result<()> {
409	trace!("create_dir_recursive: {path}");
410	create_dir(path, mode).or_else(|errno| {
411		if errno != Errno::Badf {
412			return Err(errno);
413		}
414		let (parent_path, _file_name) = path.rsplit_once('/').unwrap();
415		create_dir_recursive(parent_path, mode)?;
416		create_dir(path, mode)
417	})
418}
419
420/// Returns an vector with all the entries within a directory.
421pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
422	debug!("Read directory {name}");
423
424	with_relative_filename(name, |name| {
425		FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
426	})
427}
428
429pub fn read_stat(name: &str) -> io::Result<FileAttr> {
430	with_relative_filename(name, |name| {
431		FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
432	})
433}
434
435pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
436	with_relative_filename(name, |name| {
437		FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
438	})
439}
440
441fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
442where
443	F: FnOnce(&str) -> io::Result<T>,
444{
445	if name.starts_with("/") {
446		callback(name)
447	} else {
448		let cwd = WORKING_DIRECTORY.lock();
449		if let Some(cwd) = cwd.as_ref() {
450			let mut path = String::with_capacity(cwd.len() + name.len() + 1);
451			path.push_str(cwd);
452			path.push('/');
453			path.push_str(name);
454
455			callback(&path)
456		} else {
457			// Relative path with no CWD, this is weird/impossible
458			Err(Errno::Badf)
459		}
460	}
461}
462
463pub fn truncate(name: &str, size: usize) -> io::Result<()> {
464	with_relative_filename(name, |name| {
465		let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
466		if let Ok(file) = fs.open(name, OpenOption::O_TRUNC, AccessPermission::empty()) {
467			block_on(async { file.read().await.truncate(size).await }, None)
468		} else {
469			Err(Errno::Badf)
470		}
471	})
472}
473
474pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<FileDescriptor> {
475	// mode is 0x777 (0b0111_0111_0111), when flags | O_CREAT, else 0
476	// flags is bitmask of O_DEC_* defined above.
477	// (taken from rust stdlib/sys hermit target )
478	let mask = *UMASK.lock();
479
480	with_relative_filename(name, |name| {
481		debug!("Open {name}, {flags:?}, {mode:?}");
482
483		let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
484		let file = fs.open(name, flags, mode.bitand(mask))?;
485		let fd = insert_object(file)?;
486		Ok(fd)
487	})
488}
489
490pub fn get_cwd() -> io::Result<String> {
491	let cwd = WORKING_DIRECTORY.lock();
492	if let Some(cwd) = cwd.as_ref() {
493		Ok(cwd.clone())
494	} else {
495		Err(Errno::Noent)
496	}
497}
498
499pub fn set_cwd(cwd: &str) -> io::Result<()> {
500	// TODO: check that the directory exists and that permission flags are correct
501
502	let mut working_dir = WORKING_DIRECTORY.lock();
503	if cwd.starts_with("/") {
504		*working_dir = Some(cwd.to_string());
505	} else {
506		let Some(working_dir) = working_dir.as_mut() else {
507			return Err(Errno::Badf);
508		};
509		working_dir.push('/');
510		working_dir.push_str(cwd);
511	}
512
513	Ok(())
514}
515
516pub fn umask(new_mask: AccessPermission) -> AccessPermission {
517	let mut lock = UMASK.lock();
518	let old = *lock;
519	*lock = new_mask;
520	old
521}
522
523/// Open a directory to read the directory entries
524pub(crate) fn opendir(name: &str) -> io::Result<FileDescriptor> {
525	let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
526	insert_object(obj)
527}
528
529use crate::fd::{self, FileDescriptor};
530
531pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
532	FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
533}
534
535#[allow(clippy::len_without_is_empty)]
536#[derive(Debug, Copy, Clone)]
537pub struct Metadata(FileAttr);
538
539impl Metadata {
540	/// Returns the size of the file, in bytes
541	pub fn len(&self) -> usize {
542		self.0.st_size.try_into().unwrap()
543	}
544
545	/// Returns true if this metadata is for a file.
546	pub fn is_file(&self) -> bool {
547		self.0.st_mode.contains(AccessPermission::S_IFREG)
548	}
549
550	/// Returns true if this metadata is for a directory.
551	pub fn is_dir(&self) -> bool {
552		self.0.st_mode.contains(AccessPermission::S_IFDIR)
553	}
554
555	/// Returns the last modification time listed in this metadata.
556	pub fn modified(&self) -> io::Result<SystemTime> {
557		Ok(SystemTime::from(self.0.st_mtim))
558	}
559
560	/// Returns the last modification time listed in this metadata.
561	pub fn accessed(&self) -> io::Result<SystemTime> {
562		Ok(SystemTime::from(self.0.st_atim))
563	}
564}
565
566/// Given a path, query the file system to get information about a file, directory, etc.
567pub fn metadata(path: &str) -> io::Result<Metadata> {
568	Ok(Metadata(file_attributes(path)?))
569}
570
571#[derive(Debug)]
572pub struct File {
573	fd: FileDescriptor,
574	path: String,
575}
576
577impl File {
578	/// Creates a new file in read-write mode; error if the file exists.
579	///
580	/// This function will create a file if it does not exist, or return
581	/// an error if it does. This way, if the call succeeds, the file
582	/// returned is guaranteed to be new.
583	pub fn create(path: &str) -> io::Result<Self> {
584		let fd = open(
585			path,
586			OpenOption::O_CREAT | OpenOption::O_RDWR,
587			AccessPermission::from_bits(0o666).unwrap(),
588		)?;
589
590		Ok(File {
591			fd,
592			path: path.to_string(),
593		})
594	}
595
596	/// Attempts to open a file in read-write mode.
597	pub fn open(path: &str) -> io::Result<Self> {
598		let fd = open(
599			path,
600			OpenOption::O_RDWR,
601			AccessPermission::from_bits(0o666).unwrap(),
602		)?;
603
604		Ok(File {
605			fd,
606			path: path.to_string(),
607		})
608	}
609
610	pub fn metadata(&self) -> io::Result<Metadata> {
611		metadata(&self.path)
612	}
613}
614
615impl embedded_io::ErrorType for File {
616	type Error = crate::errno::Errno;
617}
618
619impl Read for File {
620	fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
621		let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
622		fd::read(self.fd, buf)
623	}
624}
625
626impl Write for File {
627	fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
628		fd::write(self.fd, buf)
629	}
630
631	fn flush(&mut self) -> Result<(), Self::Error> {
632		Ok(())
633	}
634}
635
636impl Drop for File {
637	fn drop(&mut self) {
638		let _ = remove_object(self.fd);
639	}
640}