Skip to main content

hermit/fs/
mod.rs

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