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