Skip to main content

hermit/fs/
mod.rs

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