Skip to main content

hermit/fs/
mod.rs

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