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