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 hermit_sync::{InterruptSpinMutex, OnceCell};
14use mem::MemDirectory;
15use num_enum::{IntoPrimitive, TryFromPrimitive};
16
17use crate::errno::Errno;
18use crate::executor::block_on;
19use crate::fd::{AccessPermission, ObjectInterface, OpenOption, insert_object, remove_object};
20use crate::io;
21use crate::io::Write;
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<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<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<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<dyn ObjectInterface>> {
209		debug!("Open directory {path}");
210		Ok(Arc::new(DirectoryReader::new(self.readdir(path)?)))
211	}
212
213	/// List given directory
214	pub fn readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
215		if path.trim() == "/" {
216			let mut components: Vec<&str> = Vec::new();
217			self.root.traverse_readdir(&mut components)
218		} else {
219			let mut components: Vec<&str> = path.split('/').collect();
220
221			components.reverse();
222			components.pop();
223
224			self.root.traverse_readdir(&mut components)
225		}
226	}
227
228	/// stat
229	pub fn stat(&self, path: &str) -> io::Result<FileAttr> {
230		debug!("Getting stats {path}");
231
232		let mut components: Vec<&str> = path.split('/').collect();
233		components.reverse();
234		components.pop();
235
236		self.root.traverse_stat(&mut components)
237	}
238
239	/// lstat
240	pub fn lstat(&self, path: &str) -> io::Result<FileAttr> {
241		debug!("Getting lstats {path}");
242
243		let mut components: Vec<&str> = path.split('/').collect();
244		components.reverse();
245		components.pop();
246
247		self.root.traverse_lstat(&mut components)
248	}
249
250	/// Create new backing-fs at mountpoint mntpath
251	pub fn mount(
252		&self,
253		path: &str,
254		obj: Box<dyn VfsNode + core::marker::Send + core::marker::Sync>,
255	) -> io::Result<()> {
256		debug!("Mounting {path}");
257
258		let mut components: Vec<&str> = path.split('/').collect();
259
260		components.reverse();
261		components.pop();
262
263		self.root.traverse_mount(&mut components, obj)
264	}
265
266	/// Create read-only file
267	pub fn create_file(
268		&self,
269		path: &str,
270		data: &'static [u8],
271		mode: AccessPermission,
272	) -> io::Result<()> {
273		debug!("Create read-only file {path}");
274
275		let mut components: Vec<&str> = path.split('/').collect();
276
277		components.reverse();
278		components.pop();
279
280		self.root.traverse_create_file(&mut components, data, mode)
281	}
282}
283
284#[repr(C)]
285#[derive(Debug, Default, Copy, Clone)]
286pub struct FileAttr {
287	pub st_dev: u64,
288	pub st_ino: u64,
289	pub st_nlink: u64,
290	/// access permissions
291	pub st_mode: AccessPermission,
292	/// user id
293	pub st_uid: u32,
294	/// group id
295	pub st_gid: u32,
296	/// device id
297	pub st_rdev: u64,
298	/// size in bytes
299	pub st_size: i64,
300	/// block size
301	pub st_blksize: i64,
302	/// size in blocks
303	pub st_blocks: i64,
304	/// time of last access
305	pub st_atim: timespec,
306	/// time of last modification
307	pub st_mtim: timespec,
308	/// time of last status change
309	pub st_ctim: timespec,
310}
311
312#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
313#[repr(u8)]
314pub enum FileType {
315	Unknown = 0,         // DT_UNKNOWN
316	Fifo = 1,            // DT_FIFO
317	CharacterDevice = 2, // DT_CHR
318	Directory = 4,       // DT_DIR
319	BlockDevice = 6,     // DT_BLK
320	RegularFile = 8,     // DT_REG
321	SymbolicLink = 10,   // DT_LNK
322	Socket = 12,         // DT_SOCK
323	Whiteout = 14,       // DT_WHT
324}
325
326#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
327#[repr(u8)]
328pub enum SeekWhence {
329	Set = 0,
330	Cur = 1,
331	End = 2,
332	Data = 3,
333	Hole = 4,
334}
335
336pub(crate) fn init() {
337	const VERSION: &str = env!("CARGO_PKG_VERSION");
338	const UTC_BUILT_TIME: &str = build_time::build_time_utc!();
339
340	FILESYSTEM.set(Filesystem::new()).unwrap();
341	FILESYSTEM
342		.get()
343		.unwrap()
344		.mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
345		.expect("Unable to create /tmp");
346	FILESYSTEM
347		.get()
348		.unwrap()
349		.mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
350		.expect("Unable to create /proc");
351
352	if let Ok(mut file) = File::create("/proc/version") {
353		if write!(file, "HermitOS version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
354			error!("Unable to write in /proc/version");
355		}
356	} else {
357		error!("Unable to create /proc/version");
358	}
359
360	let mut cwd = WORKING_DIRECTORY.lock();
361	*cwd = Some("/tmp".to_string());
362	drop(cwd);
363
364	#[cfg(all(feature = "fuse", feature = "pci"))]
365	fuse::init();
366	uhyve::init();
367}
368
369pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
370	with_relative_filename(name, |name| {
371		FILESYSTEM
372			.get()
373			.ok_or(Errno::Inval)?
374			.create_file(name, data, mode)
375	})
376}
377
378/// Removes an empty directory.
379pub fn remove_dir(path: &str) -> io::Result<()> {
380	with_relative_filename(path, |path| {
381		FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
382	})
383}
384
385pub fn unlink(path: &str) -> io::Result<()> {
386	with_relative_filename(path, |path| {
387		FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
388	})
389}
390
391/// Creates a new, empty directory at the provided path
392pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
393	let mask = *UMASK.lock();
394
395	with_relative_filename(path, |path| {
396		FILESYSTEM
397			.get()
398			.ok_or(Errno::Inval)?
399			.mkdir(path, mode.bitand(mask))
400	})
401}
402
403/// Returns an vector with all the entries within a directory.
404pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
405	debug!("Read directory {name}");
406
407	with_relative_filename(name, |name| {
408		FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
409	})
410}
411
412pub fn read_stat(name: &str) -> io::Result<FileAttr> {
413	with_relative_filename(name, |name| {
414		FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
415	})
416}
417
418pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
419	with_relative_filename(name, |name| {
420		FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
421	})
422}
423
424fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
425where
426	F: FnOnce(&str) -> io::Result<T>,
427{
428	if name.starts_with("/") {
429		callback(name)
430	} else {
431		let cwd = WORKING_DIRECTORY.lock();
432		if let Some(cwd) = cwd.as_ref() {
433			let mut path = String::with_capacity(cwd.len() + name.len() + 1);
434			path.push_str(cwd);
435			path.push('/');
436			path.push_str(name);
437
438			callback(&path)
439		} else {
440			// Relative path with no CWD, this is weird/impossible
441			Err(Errno::Badf)
442		}
443	}
444}
445
446pub fn truncate(name: &str, size: usize) -> io::Result<()> {
447	with_relative_filename(name, |name| {
448		let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
449		if let Ok(file) = fs.open(name, OpenOption::O_TRUNC, AccessPermission::empty()) {
450			block_on(file.truncate(size), None)
451		} else {
452			Err(Errno::Badf)
453		}
454	})
455}
456
457pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<FileDescriptor> {
458	// mode is 0x777 (0b0111_0111_0111), when flags | O_CREAT, else 0
459	// flags is bitmask of O_DEC_* defined above.
460	// (taken from rust stdlib/sys hermit target )
461	let mask = *UMASK.lock();
462
463	with_relative_filename(name, |name| {
464		debug!("Open {name}, {flags:?}, {mode:?}");
465
466		let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
467		let file = fs.open(name, flags, mode.bitand(mask))?;
468		let fd = insert_object(file)?;
469		Ok(fd)
470	})
471}
472
473pub fn get_cwd() -> io::Result<String> {
474	let cwd = WORKING_DIRECTORY.lock();
475	if let Some(cwd) = cwd.as_ref() {
476		Ok(cwd.clone())
477	} else {
478		Err(Errno::Noent)
479	}
480}
481
482pub fn set_cwd(cwd: &str) -> io::Result<()> {
483	// TODO: check that the directory exists and that permission flags are correct
484
485	let mut working_dir = WORKING_DIRECTORY.lock();
486	if cwd.starts_with("/") {
487		*working_dir = Some(cwd.to_string());
488	} else {
489		let Some(working_dir) = working_dir.as_mut() else {
490			return Err(Errno::Badf);
491		};
492		working_dir.push('/');
493		working_dir.push_str(cwd);
494	}
495
496	Ok(())
497}
498
499pub fn umask(new_mask: AccessPermission) -> AccessPermission {
500	let mut lock = UMASK.lock();
501	let old = *lock;
502	*lock = new_mask;
503	old
504}
505
506/// Open a directory to read the directory entries
507pub(crate) fn opendir(name: &str) -> io::Result<FileDescriptor> {
508	let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
509	insert_object(obj)
510}
511
512use crate::fd::{self, FileDescriptor};
513
514pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
515	FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
516}
517
518#[allow(clippy::len_without_is_empty)]
519#[derive(Debug, Copy, Clone)]
520pub struct Metadata(FileAttr);
521
522impl Metadata {
523	/// Returns the size of the file, in bytes
524	pub fn len(&self) -> usize {
525		self.0.st_size.try_into().unwrap()
526	}
527
528	/// Returns true if this metadata is for a file.
529	pub fn is_file(&self) -> bool {
530		self.0.st_mode.contains(AccessPermission::S_IFREG)
531	}
532
533	/// Returns true if this metadata is for a directory.
534	pub fn is_dir(&self) -> bool {
535		self.0.st_mode.contains(AccessPermission::S_IFDIR)
536	}
537
538	/// Returns the last modification time listed in this metadata.
539	pub fn modified(&self) -> io::Result<SystemTime> {
540		Ok(SystemTime::from(self.0.st_mtim))
541	}
542
543	/// Returns the last modification time listed in this metadata.
544	pub fn accessed(&self) -> io::Result<SystemTime> {
545		Ok(SystemTime::from(self.0.st_atim))
546	}
547}
548
549/// Given a path, query the file system to get information about a file, directory, etc.
550pub fn metadata(path: &str) -> io::Result<Metadata> {
551	Ok(Metadata(file_attributes(path)?))
552}
553
554#[derive(Debug)]
555pub struct File {
556	fd: FileDescriptor,
557	path: String,
558}
559
560impl File {
561	/// Creates a new file in read-write mode; error if the file exists.
562	///
563	/// This function will create a file if it does not exist, or return
564	/// an error if it does. This way, if the call succeeds, the file
565	/// returned is guaranteed to be new.
566	pub fn create(path: &str) -> io::Result<Self> {
567		let fd = open(
568			path,
569			OpenOption::O_CREAT | OpenOption::O_RDWR,
570			AccessPermission::from_bits(0o666).unwrap(),
571		)?;
572
573		Ok(File {
574			fd,
575			path: path.to_string(),
576		})
577	}
578
579	/// Attempts to open a file in read-write mode.
580	pub fn open(path: &str) -> io::Result<Self> {
581		let fd = open(
582			path,
583			OpenOption::O_RDWR,
584			AccessPermission::from_bits(0o666).unwrap(),
585		)?;
586
587		Ok(File {
588			fd,
589			path: path.to_string(),
590		})
591	}
592
593	pub fn metadata(&self) -> io::Result<Metadata> {
594		metadata(&self.path)
595	}
596}
597
598impl crate::io::Read for File {
599	fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
600		let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
601		fd::read(self.fd, buf)
602	}
603}
604
605impl crate::io::Write for File {
606	fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
607		fd::write(self.fd, buf)
608	}
609}
610
611impl Drop for File {
612	fn drop(&mut self) {
613		let _ = remove_object(self.fd);
614	}
615}