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	FILESYSTEM.set(Filesystem::new()).unwrap();
308	FILESYSTEM
309		.get()
310		.unwrap()
311		.mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
312		.expect("Unable to create /tmp");
313	FILESYSTEM
314		.get()
315		.unwrap()
316		.mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
317		.expect("Unable to create /proc");
318
319	if let Ok(mut file) = File::create("/proc/version") {
320		if write!(file, "Hermit version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
321			error!("Unable to write in /proc/version");
322		}
323	} else {
324		error!("Unable to create /proc/version");
325	}
326
327	let mut cwd = WORKING_DIRECTORY.lock();
328	*cwd = Some("/tmp".to_owned());
329	drop(cwd);
330
331	#[cfg(feature = "virtio-fs")]
332	virtio_fs::init();
333	if crate::env::is_uhyve() {
334		uhyve::init();
335	}
336}
337
338pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
339	with_relative_filename(name, |name| {
340		FILESYSTEM
341			.get()
342			.ok_or(Errno::Inval)?
343			.create_file(name, data, mode)
344	})
345}
346
347/// Removes an empty directory.
348pub fn remove_dir(path: &str) -> io::Result<()> {
349	with_relative_filename(path, |path| {
350		FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
351	})
352}
353
354pub fn unlink(path: &str) -> io::Result<()> {
355	with_relative_filename(path, |path| {
356		FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
357	})
358}
359
360/// Creates a new, empty directory at the provided path
361pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
362	let mask = *UMASK.lock();
363
364	with_relative_filename(path, |path| {
365		FILESYSTEM
366			.get()
367			.ok_or(Errno::Inval)?
368			.mkdir(path, mode.bitand(mask))
369	})
370}
371
372/// Creates a directory and creates all missing parent directories as well.
373fn create_dir_recursive(path: &str, mode: AccessPermission) -> io::Result<()> {
374	trace!("create_dir_recursive: {path}");
375	create_dir(path, mode).or_else(|errno| {
376		if errno != Errno::Badf {
377			return Err(errno);
378		}
379		let (parent_path, _file_name) = path.rsplit_once('/').unwrap();
380		create_dir_recursive(parent_path, mode)?;
381		create_dir(path, mode)
382	})
383}
384
385/// Returns an vector with all the entries within a directory.
386pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
387	debug!("Read directory {name}");
388
389	with_relative_filename(name, |name| {
390		FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
391	})
392}
393
394pub fn read_stat(name: &str) -> io::Result<FileAttr> {
395	with_relative_filename(name, |name| {
396		FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
397	})
398}
399
400pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
401	with_relative_filename(name, |name| {
402		FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
403	})
404}
405
406fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
407where
408	F: FnOnce(&str) -> io::Result<T>,
409{
410	if name.starts_with("/") {
411		return callback(name);
412	}
413
414	let cwd = WORKING_DIRECTORY.lock();
415
416	let Some(cwd) = cwd.as_ref() else {
417		// Relative path with no CWD, this is weird/impossible
418		return Err(Errno::Badf);
419	};
420
421	let mut path = String::with_capacity(cwd.len() + name.len() + 1);
422	path.push_str(cwd);
423	path.push('/');
424	path.push_str(name);
425
426	callback(&path)
427}
428
429pub fn truncate(name: &str, size: usize) -> io::Result<()> {
430	with_relative_filename(name, |name| {
431		let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
432		let file = fs
433			.open(name, OpenOption::O_TRUNC, AccessPermission::empty())
434			.map_err(|_| Errno::Badf)?;
435
436		block_on(async { file.read().await.truncate(size).await }, None)
437	})
438}
439
440pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<RawFd> {
441	// mode is 0x777 (0b0111_0111_0111), when flags | O_CREAT, else 0
442	// flags is bitmask of O_DEC_* defined above.
443	// (taken from rust stdlib/sys hermit target )
444	let mask = *UMASK.lock();
445
446	with_relative_filename(name, |name| {
447		debug!("Open {name}, {flags:?}, {mode:?}");
448
449		let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
450		let file = fs.open(name, flags, mode.bitand(mask))?;
451		let fd = insert_object(file)?;
452		Ok(fd)
453	})
454}
455
456pub fn get_cwd() -> io::Result<String> {
457	let cwd = WORKING_DIRECTORY.lock();
458	let cwd = cwd.as_ref().ok_or(Errno::Noent)?;
459	Ok(cwd.clone())
460}
461
462pub fn set_cwd(cwd: &str) -> io::Result<()> {
463	// TODO: check that the directory exists and that permission flags are correct
464
465	let mut working_dir = WORKING_DIRECTORY.lock();
466	if cwd.starts_with("/") {
467		*working_dir = Some(cwd.to_owned());
468	} else {
469		let working_dir = working_dir.as_mut().ok_or(Errno::Badf)?;
470		working_dir.push('/');
471		working_dir.push_str(cwd);
472	}
473
474	Ok(())
475}
476
477pub fn umask(new_mask: AccessPermission) -> AccessPermission {
478	let mut lock = UMASK.lock();
479	let old = *lock;
480	*lock = new_mask;
481	old
482}
483
484/// Open a directory to read the directory entries
485pub(crate) fn opendir(name: &str) -> io::Result<RawFd> {
486	let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
487	insert_object(obj)
488}
489
490use crate::fd::{self, RawFd};
491
492pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
493	FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
494}
495
496#[allow(clippy::len_without_is_empty)]
497#[derive(Debug, Copy, Clone)]
498pub struct Metadata(FileAttr);
499
500impl Metadata {
501	/// Returns the size of the file, in bytes
502	pub fn len(&self) -> usize {
503		self.0.st_size.try_into().unwrap()
504	}
505
506	/// Returns true if this metadata is for a file.
507	pub fn is_file(&self) -> bool {
508		self.0.st_mode.contains(AccessPermission::S_IFREG)
509	}
510
511	/// Returns true if this metadata is for a directory.
512	pub fn is_dir(&self) -> bool {
513		self.0.st_mode.contains(AccessPermission::S_IFDIR)
514	}
515
516	/// Returns the last modification time listed in this metadata.
517	pub fn modified(&self) -> io::Result<SystemTime> {
518		Ok(SystemTime::from(self.0.st_mtim))
519	}
520
521	/// Returns the last modification time listed in this metadata.
522	pub fn accessed(&self) -> io::Result<SystemTime> {
523		Ok(SystemTime::from(self.0.st_atim))
524	}
525}
526
527/// Given a path, query the file system to get information about a file, directory, etc.
528pub fn metadata(path: &str) -> io::Result<Metadata> {
529	Ok(Metadata(file_attributes(path)?))
530}
531
532#[derive(Debug)]
533pub struct File {
534	fd: RawFd,
535	path: String,
536}
537
538impl File {
539	/// Opens a file in write-only mode.
540	///
541	/// This function will create a file if it does not exist, and will truncate it if it does.
542	fn create(path: &str) -> io::Result<File> {
543		let fd = open(
544			path,
545			OpenOption::O_CREAT | OpenOption::O_TRUNC | OpenOption::O_WRONLY,
546			AccessPermission::from_bits(0o666).unwrap(),
547		)?;
548
549		Ok(File {
550			fd,
551			path: path.to_owned(),
552		})
553	}
554
555	/// Creates a new file in read-write mode; error if the file exists.
556	///
557	/// This function will create a file if it does not exist, or return
558	/// an error if it does. This way, if the call succeeds, the file
559	/// returned is guaranteed to be new.
560	pub fn create_new(path: &str) -> io::Result<Self> {
561		let fd = open(
562			path,
563			OpenOption::O_CREAT | OpenOption::O_EXCL | OpenOption::O_RDWR,
564			AccessPermission::from_bits(0o666).unwrap(),
565		)?;
566
567		Ok(File {
568			fd,
569			path: path.to_owned(),
570		})
571	}
572
573	/// Attempts to open a file in read-write mode.
574	pub fn open(path: &str) -> io::Result<Self> {
575		let fd = open(
576			path,
577			OpenOption::O_RDWR,
578			AccessPermission::from_bits(0o666).unwrap(),
579		)?;
580
581		Ok(File {
582			fd,
583			path: path.to_owned(),
584		})
585	}
586
587	pub fn metadata(&self) -> io::Result<Metadata> {
588		metadata(&self.path)
589	}
590}
591
592impl embedded_io::ErrorType for File {
593	type Error = crate::errno::Errno;
594}
595
596impl Read for File {
597	fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
598		let buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
599		fd::read(self.fd, buf)
600	}
601}
602
603impl Write for File {
604	fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
605		fd::write(self.fd, buf)
606	}
607
608	fn flush(&mut self) -> Result<(), Self::Error> {
609		Ok(())
610	}
611}
612
613impl Drop for File {
614	fn drop(&mut self) {
615		let _ = remove_object(self.fd);
616	}
617}