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