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