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;
10use core::ops::BitAnd;
11
12use async_trait::async_trait;
13use hermit_sync::{InterruptSpinMutex, OnceCell};
14use mem::MemDirectory;
15use num_enum::{IntoPrimitive, TryFromPrimitive};
16
17use crate::errno::Errno;
18use crate::executor::block_on;
19use crate::fd::{AccessPermission, ObjectInterface, OpenOption, insert_object, remove_object};
20use crate::io;
21use crate::io::Write;
22use crate::time::{SystemTime, timespec};
23
24static FILESYSTEM: OnceCell<Filesystem> = OnceCell::new();
25
26static WORKING_DIRECTORY: InterruptSpinMutex<Option<String>> = InterruptSpinMutex::new(None);
27
28static UMASK: InterruptSpinMutex<AccessPermission> =
29 InterruptSpinMutex::new(AccessPermission::from_bits_retain(0o777));
30
31#[derive(Debug, Clone)]
32pub struct DirectoryEntry {
33 pub name: String,
34}
35
36impl DirectoryEntry {
37 pub fn new(name: String) -> Self {
38 Self { name }
39 }
40}
41
42#[derive(Copy, Clone, Debug, PartialEq, Eq)]
44pub(crate) enum NodeKind {
45 File,
47 Directory,
49}
50
51pub(crate) trait VfsNode: core::fmt::Debug {
53 fn get_kind(&self) -> NodeKind;
55
56 fn get_file_attributes(&self) -> io::Result<FileAttr> {
58 Err(Errno::Nosys)
59 }
60
61 fn get_object(&self) -> io::Result<Arc<dyn ObjectInterface>> {
63 Err(Errno::Nosys)
64 }
65
66 fn traverse_mkdir(
68 &self,
69 _components: &mut Vec<&str>,
70 _mode: AccessPermission,
71 ) -> io::Result<()> {
72 Err(Errno::Nosys)
73 }
74
75 fn traverse_rmdir(&self, _components: &mut Vec<&str>) -> io::Result<()> {
77 Err(Errno::Nosys)
78 }
79
80 fn traverse_unlink(&self, _components: &mut Vec<&str>) -> io::Result<()> {
82 Err(Errno::Nosys)
83 }
84
85 fn traverse_readdir(&self, _components: &mut Vec<&str>) -> io::Result<Vec<DirectoryEntry>> {
87 Err(Errno::Nosys)
88 }
89
90 fn traverse_lstat(&self, _components: &mut Vec<&str>) -> io::Result<FileAttr> {
92 Err(Errno::Nosys)
93 }
94
95 fn traverse_stat(&self, _components: &mut Vec<&str>) -> io::Result<FileAttr> {
97 Err(Errno::Nosys)
98 }
99
100 fn traverse_mount(
102 &self,
103 _components: &mut Vec<&str>,
104 _obj: Box<dyn VfsNode + core::marker::Send + core::marker::Sync>,
105 ) -> io::Result<()> {
106 Err(Errno::Nosys)
107 }
108
109 fn traverse_open(
111 &self,
112 _components: &mut Vec<&str>,
113 _option: OpenOption,
114 _mode: AccessPermission,
115 ) -> io::Result<Arc<dyn ObjectInterface>> {
116 Err(Errno::Nosys)
117 }
118
119 fn traverse_create_file(
121 &self,
122 _components: &mut Vec<&str>,
123 _data: &'static [u8],
124 _mode: AccessPermission,
125 ) -> io::Result<()> {
126 Err(Errno::Nosys)
127 }
128}
129
130#[derive(Debug, Clone)]
131struct DirectoryReader(Vec<DirectoryEntry>);
132
133impl DirectoryReader {
134 pub fn new(data: Vec<DirectoryEntry>) -> Self {
135 Self(data)
136 }
137}
138
139#[async_trait]
140impl ObjectInterface for DirectoryReader {
141 async fn getdents(&self, _buf: &mut [core::mem::MaybeUninit<u8>]) -> io::Result<usize> {
142 let _ = &self.0; unimplemented!()
144 }
145}
146
147#[derive(Debug)]
148pub(crate) struct Filesystem {
149 root: MemDirectory,
150}
151
152impl Filesystem {
153 pub fn new() -> Self {
154 Self {
155 root: MemDirectory::new(AccessPermission::from_bits(0o777).unwrap()),
156 }
157 }
158
159 pub fn open(
161 &self,
162 path: &str,
163 opt: OpenOption,
164 mode: AccessPermission,
165 ) -> io::Result<Arc<dyn ObjectInterface>> {
166 debug!("Open file {path} with {opt:?}");
167 let mut components: Vec<&str> = path.split('/').collect();
168
169 components.reverse();
170 components.pop();
171
172 self.root.traverse_open(&mut components, opt, mode)
173 }
174
175 pub fn unlink(&self, path: &str) -> io::Result<()> {
177 debug!("Unlinking file {path}");
178 let mut components: Vec<&str> = path.split('/').collect();
179
180 components.reverse();
181 components.pop();
182
183 self.root.traverse_unlink(&mut components)
184 }
185
186 pub fn rmdir(&self, path: &str) -> io::Result<()> {
188 debug!("Removing directory {path}");
189 let mut components: Vec<&str> = path.split('/').collect();
190
191 components.reverse();
192 components.pop();
193
194 self.root.traverse_rmdir(&mut components)
195 }
196
197 pub fn mkdir(&self, path: &str, mode: AccessPermission) -> io::Result<()> {
199 debug!("Create directory {path}");
200 let mut components: Vec<&str> = path.split('/').collect();
201
202 components.reverse();
203 components.pop();
204
205 self.root.traverse_mkdir(&mut components, mode)
206 }
207
208 pub fn opendir(&self, path: &str) -> io::Result<Arc<dyn ObjectInterface>> {
209 debug!("Open directory {path}");
210 Ok(Arc::new(DirectoryReader::new(self.readdir(path)?)))
211 }
212
213 pub fn readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
215 if path.trim() == "/" {
216 let mut components: Vec<&str> = Vec::new();
217 self.root.traverse_readdir(&mut components)
218 } else {
219 let mut components: Vec<&str> = path.split('/').collect();
220
221 components.reverse();
222 components.pop();
223
224 self.root.traverse_readdir(&mut components)
225 }
226 }
227
228 pub fn stat(&self, path: &str) -> io::Result<FileAttr> {
230 debug!("Getting stats {path}");
231
232 let mut components: Vec<&str> = path.split('/').collect();
233 components.reverse();
234 components.pop();
235
236 self.root.traverse_stat(&mut components)
237 }
238
239 pub fn lstat(&self, path: &str) -> io::Result<FileAttr> {
241 debug!("Getting lstats {path}");
242
243 let mut components: Vec<&str> = path.split('/').collect();
244 components.reverse();
245 components.pop();
246
247 self.root.traverse_lstat(&mut components)
248 }
249
250 pub fn mount(
252 &self,
253 path: &str,
254 obj: Box<dyn VfsNode + core::marker::Send + core::marker::Sync>,
255 ) -> io::Result<()> {
256 debug!("Mounting {path}");
257
258 let mut components: Vec<&str> = path.split('/').collect();
259
260 components.reverse();
261 components.pop();
262
263 self.root.traverse_mount(&mut components, obj)
264 }
265
266 pub fn create_file(
268 &self,
269 path: &str,
270 data: &'static [u8],
271 mode: AccessPermission,
272 ) -> io::Result<()> {
273 debug!("Create read-only file {path}");
274
275 let mut components: Vec<&str> = path.split('/').collect();
276
277 components.reverse();
278 components.pop();
279
280 self.root.traverse_create_file(&mut components, data, mode)
281 }
282}
283
284#[repr(C)]
285#[derive(Debug, Default, Copy, Clone)]
286pub struct FileAttr {
287 pub st_dev: u64,
288 pub st_ino: u64,
289 pub st_nlink: u64,
290 pub st_mode: AccessPermission,
292 pub st_uid: u32,
294 pub st_gid: u32,
296 pub st_rdev: u64,
298 pub st_size: i64,
300 pub st_blksize: i64,
302 pub st_blocks: i64,
304 pub st_atim: timespec,
306 pub st_mtim: timespec,
308 pub st_ctim: timespec,
310}
311
312#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
313#[repr(u8)]
314pub enum FileType {
315 Unknown = 0, Fifo = 1, CharacterDevice = 2, Directory = 4, BlockDevice = 6, RegularFile = 8, SymbolicLink = 10, Socket = 12, Whiteout = 14, }
325
326#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
327#[repr(u8)]
328pub enum SeekWhence {
329 Set = 0,
330 Cur = 1,
331 End = 2,
332 Data = 3,
333 Hole = 4,
334}
335
336pub(crate) fn init() {
337 const VERSION: &str = env!("CARGO_PKG_VERSION");
338 const UTC_BUILT_TIME: &str = build_time::build_time_utc!();
339
340 FILESYSTEM.set(Filesystem::new()).unwrap();
341 FILESYSTEM
342 .get()
343 .unwrap()
344 .mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
345 .expect("Unable to create /tmp");
346 FILESYSTEM
347 .get()
348 .unwrap()
349 .mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
350 .expect("Unable to create /proc");
351
352 if let Ok(mut file) = File::create("/proc/version") {
353 if write!(file, "HermitOS version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
354 error!("Unable to write in /proc/version");
355 }
356 } else {
357 error!("Unable to create /proc/version");
358 }
359
360 let mut cwd = WORKING_DIRECTORY.lock();
361 *cwd = Some("/tmp".to_string());
362 drop(cwd);
363
364 #[cfg(all(feature = "fuse", feature = "pci"))]
365 fuse::init();
366 uhyve::init();
367}
368
369pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
370 with_relative_filename(name, |name| {
371 FILESYSTEM
372 .get()
373 .ok_or(Errno::Inval)?
374 .create_file(name, data, mode)
375 })
376}
377
378pub fn remove_dir(path: &str) -> io::Result<()> {
380 with_relative_filename(path, |path| {
381 FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
382 })
383}
384
385pub fn unlink(path: &str) -> io::Result<()> {
386 with_relative_filename(path, |path| {
387 FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
388 })
389}
390
391pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
393 let mask = *UMASK.lock();
394
395 with_relative_filename(path, |path| {
396 FILESYSTEM
397 .get()
398 .ok_or(Errno::Inval)?
399 .mkdir(path, mode.bitand(mask))
400 })
401}
402
403pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
405 debug!("Read directory {name}");
406
407 with_relative_filename(name, |name| {
408 FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
409 })
410}
411
412pub fn read_stat(name: &str) -> io::Result<FileAttr> {
413 with_relative_filename(name, |name| {
414 FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
415 })
416}
417
418pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
419 with_relative_filename(name, |name| {
420 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
421 })
422}
423
424fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
425where
426 F: FnOnce(&str) -> io::Result<T>,
427{
428 if name.starts_with("/") {
429 callback(name)
430 } else {
431 let cwd = WORKING_DIRECTORY.lock();
432 if let Some(cwd) = cwd.as_ref() {
433 let mut path = String::with_capacity(cwd.len() + name.len() + 1);
434 path.push_str(cwd);
435 path.push('/');
436 path.push_str(name);
437
438 callback(&path)
439 } else {
440 Err(Errno::Badf)
442 }
443 }
444}
445
446pub fn truncate(name: &str, size: usize) -> io::Result<()> {
447 with_relative_filename(name, |name| {
448 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
449 if let Ok(file) = fs.open(name, OpenOption::O_TRUNC, AccessPermission::empty()) {
450 block_on(file.truncate(size), None)
451 } else {
452 Err(Errno::Badf)
453 }
454 })
455}
456
457pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<FileDescriptor> {
458 let mask = *UMASK.lock();
462
463 with_relative_filename(name, |name| {
464 debug!("Open {name}, {flags:?}, {mode:?}");
465
466 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
467 let file = fs.open(name, flags, mode.bitand(mask))?;
468 let fd = insert_object(file)?;
469 Ok(fd)
470 })
471}
472
473pub fn get_cwd() -> io::Result<String> {
474 let cwd = WORKING_DIRECTORY.lock();
475 if let Some(cwd) = cwd.as_ref() {
476 Ok(cwd.clone())
477 } else {
478 Err(Errno::Noent)
479 }
480}
481
482pub fn set_cwd(cwd: &str) -> io::Result<()> {
483 let mut working_dir = WORKING_DIRECTORY.lock();
486 if cwd.starts_with("/") {
487 *working_dir = Some(cwd.to_string());
488 } else {
489 let Some(working_dir) = working_dir.as_mut() else {
490 return Err(Errno::Badf);
491 };
492 working_dir.push('/');
493 working_dir.push_str(cwd);
494 }
495
496 Ok(())
497}
498
499pub fn umask(new_mask: AccessPermission) -> AccessPermission {
500 let mut lock = UMASK.lock();
501 let old = *lock;
502 *lock = new_mask;
503 old
504}
505
506pub(crate) fn opendir(name: &str) -> io::Result<FileDescriptor> {
508 let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
509 insert_object(obj)
510}
511
512use crate::fd::{self, FileDescriptor};
513
514pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
515 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
516}
517
518#[allow(clippy::len_without_is_empty)]
519#[derive(Debug, Copy, Clone)]
520pub struct Metadata(FileAttr);
521
522impl Metadata {
523 pub fn len(&self) -> usize {
525 self.0.st_size.try_into().unwrap()
526 }
527
528 pub fn is_file(&self) -> bool {
530 self.0.st_mode.contains(AccessPermission::S_IFREG)
531 }
532
533 pub fn is_dir(&self) -> bool {
535 self.0.st_mode.contains(AccessPermission::S_IFDIR)
536 }
537
538 pub fn modified(&self) -> io::Result<SystemTime> {
540 Ok(SystemTime::from(self.0.st_mtim))
541 }
542
543 pub fn accessed(&self) -> io::Result<SystemTime> {
545 Ok(SystemTime::from(self.0.st_atim))
546 }
547}
548
549pub fn metadata(path: &str) -> io::Result<Metadata> {
551 Ok(Metadata(file_attributes(path)?))
552}
553
554#[derive(Debug)]
555pub struct File {
556 fd: FileDescriptor,
557 path: String,
558}
559
560impl File {
561 pub fn create(path: &str) -> io::Result<Self> {
567 let fd = open(
568 path,
569 OpenOption::O_CREAT | OpenOption::O_RDWR,
570 AccessPermission::from_bits(0o666).unwrap(),
571 )?;
572
573 Ok(File {
574 fd,
575 path: path.to_string(),
576 })
577 }
578
579 pub fn open(path: &str) -> io::Result<Self> {
581 let fd = open(
582 path,
583 OpenOption::O_RDWR,
584 AccessPermission::from_bits(0o666).unwrap(),
585 )?;
586
587 Ok(File {
588 fd,
589 path: path.to_string(),
590 })
591 }
592
593 pub fn metadata(&self) -> io::Result<Metadata> {
594 metadata(&self.path)
595 }
596}
597
598impl crate::io::Read for File {
599 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
600 let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
601 fd::read(self.fd, buf)
602 }
603}
604
605impl crate::io::Write for File {
606 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
607 fd::write(self.fd, buf)
608 }
609}
610
611impl Drop for File {
612 fn drop(&mut self) {
613 let _ = remove_object(self.fd);
614 }
615}