hermit/fs/
mod.rs

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