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 embedded_io::{Read, Write};
14use hermit_sync::{InterruptSpinMutex, OnceCell};
15use mem::MemDirectory;
16use num_enum::{IntoPrimitive, TryFromPrimitive};
17
18use crate::errno::Errno;
19use crate::executor::block_on;
20use crate::fd::{AccessPermission, ObjectInterface, OpenOption, insert_object, remove_object};
21use crate::io;
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<async_lock::RwLock<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<async_lock::RwLock<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<async_lock::RwLock<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<async_lock::RwLock<dyn ObjectInterface>>> {
209 debug!("Open directory {path}");
210 Ok(Arc::new(async_lock::RwLock::new(DirectoryReader::new(
211 self.readdir(path)?,
212 ))))
213 }
214
215 pub fn readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
217 if path.trim() == "/" {
218 let mut components: Vec<&str> = Vec::new();
219 self.root.traverse_readdir(&mut components)
220 } else {
221 let mut components: Vec<&str> = path.split('/').collect();
222
223 components.reverse();
224 components.pop();
225
226 self.root.traverse_readdir(&mut components)
227 }
228 }
229
230 pub fn stat(&self, path: &str) -> io::Result<FileAttr> {
232 debug!("Getting stats {path}");
233
234 let mut components: Vec<&str> = path.split('/').collect();
235 components.reverse();
236 components.pop();
237
238 self.root.traverse_stat(&mut components)
239 }
240
241 pub fn lstat(&self, path: &str) -> io::Result<FileAttr> {
243 debug!("Getting lstats {path}");
244
245 let mut components: Vec<&str> = path.split('/').collect();
246 components.reverse();
247 components.pop();
248
249 self.root.traverse_lstat(&mut components)
250 }
251
252 pub fn mount(
254 &self,
255 path: &str,
256 obj: Box<dyn VfsNode + core::marker::Send + core::marker::Sync>,
257 ) -> io::Result<()> {
258 debug!("Mounting {path}");
259
260 let mut components: Vec<&str> = path.split('/').collect();
261
262 components.reverse();
263 components.pop();
264
265 self.root.traverse_mount(&mut components, obj)
266 }
267
268 pub fn create_file(
270 &self,
271 path: &str,
272 data: &'static [u8],
273 mode: AccessPermission,
274 ) -> io::Result<()> {
275 debug!("Create read-only file {path}");
276
277 let mut components: Vec<&str> = path.split('/').collect();
278
279 components.reverse();
280 components.pop();
281
282 self.root.traverse_create_file(&mut components, data, mode)
283 }
284}
285
286#[repr(C)]
287#[derive(Debug, Default, Copy, Clone)]
288pub struct FileAttr {
289 pub st_dev: u64,
290 pub st_ino: u64,
291 pub st_nlink: u64,
292 pub st_mode: AccessPermission,
294 pub st_uid: u32,
296 pub st_gid: u32,
298 pub st_rdev: u64,
300 pub st_size: i64,
302 pub st_blksize: i64,
304 pub st_blocks: i64,
306 pub st_atim: timespec,
308 pub st_mtim: timespec,
310 pub st_ctim: timespec,
312}
313
314#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
315#[repr(u8)]
316pub enum FileType {
317 Unknown = 0, Fifo = 1, CharacterDevice = 2, Directory = 4, BlockDevice = 6, RegularFile = 8, SymbolicLink = 10, Socket = 12, Whiteout = 14, }
327
328#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
329#[repr(u8)]
330pub enum SeekWhence {
331 Set = 0,
332 Cur = 1,
333 End = 2,
334 Data = 3,
335 Hole = 4,
336}
337
338pub(crate) fn init() {
339 const VERSION: &str = env!("CARGO_PKG_VERSION");
340 const UTC_BUILT_TIME: &str = build_time::build_time_utc!();
341
342 FILESYSTEM.set(Filesystem::new()).unwrap();
343 FILESYSTEM
344 .get()
345 .unwrap()
346 .mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
347 .expect("Unable to create /tmp");
348 FILESYSTEM
349 .get()
350 .unwrap()
351 .mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
352 .expect("Unable to create /proc");
353
354 if let Ok(mut file) = File::create("/proc/version") {
355 if write!(file, "HermitOS version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
356 error!("Unable to write in /proc/version");
357 }
358 } else {
359 error!("Unable to create /proc/version");
360 }
361
362 let mut cwd = WORKING_DIRECTORY.lock();
363 *cwd = Some("/tmp".to_string());
364 drop(cwd);
365
366 #[cfg(all(feature = "fuse", feature = "pci"))]
367 fuse::init();
368 uhyve::init();
369}
370
371pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
372 with_relative_filename(name, |name| {
373 FILESYSTEM
374 .get()
375 .ok_or(Errno::Inval)?
376 .create_file(name, data, mode)
377 })
378}
379
380pub fn remove_dir(path: &str) -> io::Result<()> {
382 with_relative_filename(path, |path| {
383 FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
384 })
385}
386
387pub fn unlink(path: &str) -> io::Result<()> {
388 with_relative_filename(path, |path| {
389 FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
390 })
391}
392
393pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
395 let mask = *UMASK.lock();
396
397 with_relative_filename(path, |path| {
398 FILESYSTEM
399 .get()
400 .ok_or(Errno::Inval)?
401 .mkdir(path, mode.bitand(mask))
402 })
403}
404
405pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
407 debug!("Read directory {name}");
408
409 with_relative_filename(name, |name| {
410 FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
411 })
412}
413
414pub fn read_stat(name: &str) -> io::Result<FileAttr> {
415 with_relative_filename(name, |name| {
416 FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
417 })
418}
419
420pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
421 with_relative_filename(name, |name| {
422 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
423 })
424}
425
426fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
427where
428 F: FnOnce(&str) -> io::Result<T>,
429{
430 if name.starts_with("/") {
431 callback(name)
432 } else {
433 let cwd = WORKING_DIRECTORY.lock();
434 if let Some(cwd) = cwd.as_ref() {
435 let mut path = String::with_capacity(cwd.len() + name.len() + 1);
436 path.push_str(cwd);
437 path.push('/');
438 path.push_str(name);
439
440 callback(&path)
441 } else {
442 Err(Errno::Badf)
444 }
445 }
446}
447
448pub fn truncate(name: &str, size: usize) -> io::Result<()> {
449 with_relative_filename(name, |name| {
450 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
451 if let Ok(file) = fs.open(name, OpenOption::O_TRUNC, AccessPermission::empty()) {
452 block_on(async { file.read().await.truncate(size).await }, None)
453 } else {
454 Err(Errno::Badf)
455 }
456 })
457}
458
459pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<FileDescriptor> {
460 let mask = *UMASK.lock();
464
465 with_relative_filename(name, |name| {
466 debug!("Open {name}, {flags:?}, {mode:?}");
467
468 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
469 let file = fs.open(name, flags, mode.bitand(mask))?;
470 let fd = insert_object(file)?;
471 Ok(fd)
472 })
473}
474
475pub fn get_cwd() -> io::Result<String> {
476 let cwd = WORKING_DIRECTORY.lock();
477 if let Some(cwd) = cwd.as_ref() {
478 Ok(cwd.clone())
479 } else {
480 Err(Errno::Noent)
481 }
482}
483
484pub fn set_cwd(cwd: &str) -> io::Result<()> {
485 let mut working_dir = WORKING_DIRECTORY.lock();
488 if cwd.starts_with("/") {
489 *working_dir = Some(cwd.to_string());
490 } else {
491 let Some(working_dir) = working_dir.as_mut() else {
492 return Err(Errno::Badf);
493 };
494 working_dir.push('/');
495 working_dir.push_str(cwd);
496 }
497
498 Ok(())
499}
500
501pub fn umask(new_mask: AccessPermission) -> AccessPermission {
502 let mut lock = UMASK.lock();
503 let old = *lock;
504 *lock = new_mask;
505 old
506}
507
508pub(crate) fn opendir(name: &str) -> io::Result<FileDescriptor> {
510 let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
511 insert_object(obj)
512}
513
514use crate::fd::{self, FileDescriptor};
515
516pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
517 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
518}
519
520#[allow(clippy::len_without_is_empty)]
521#[derive(Debug, Copy, Clone)]
522pub struct Metadata(FileAttr);
523
524impl Metadata {
525 pub fn len(&self) -> usize {
527 self.0.st_size.try_into().unwrap()
528 }
529
530 pub fn is_file(&self) -> bool {
532 self.0.st_mode.contains(AccessPermission::S_IFREG)
533 }
534
535 pub fn is_dir(&self) -> bool {
537 self.0.st_mode.contains(AccessPermission::S_IFDIR)
538 }
539
540 pub fn modified(&self) -> io::Result<SystemTime> {
542 Ok(SystemTime::from(self.0.st_mtim))
543 }
544
545 pub fn accessed(&self) -> io::Result<SystemTime> {
547 Ok(SystemTime::from(self.0.st_atim))
548 }
549}
550
551pub fn metadata(path: &str) -> io::Result<Metadata> {
553 Ok(Metadata(file_attributes(path)?))
554}
555
556#[derive(Debug)]
557pub struct File {
558 fd: FileDescriptor,
559 path: String,
560}
561
562impl File {
563 pub fn create(path: &str) -> io::Result<Self> {
569 let fd = open(
570 path,
571 OpenOption::O_CREAT | OpenOption::O_RDWR,
572 AccessPermission::from_bits(0o666).unwrap(),
573 )?;
574
575 Ok(File {
576 fd,
577 path: path.to_string(),
578 })
579 }
580
581 pub fn open(path: &str) -> io::Result<Self> {
583 let fd = open(
584 path,
585 OpenOption::O_RDWR,
586 AccessPermission::from_bits(0o666).unwrap(),
587 )?;
588
589 Ok(File {
590 fd,
591 path: path.to_string(),
592 })
593 }
594
595 pub fn metadata(&self) -> io::Result<Metadata> {
596 metadata(&self.path)
597 }
598}
599
600impl embedded_io::ErrorType for File {
601 type Error = crate::errno::Errno;
602}
603
604impl Read for File {
605 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
606 let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
607 fd::read(self.fd, buf)
608 }
609}
610
611impl Write for File {
612 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
613 fd::write(self.fd, buf)
614 }
615
616 fn flush(&mut self) -> Result<(), Self::Error> {
617 Ok(())
618 }
619}
620
621impl Drop for File {
622 fn drop(&mut self) {
623 let _ = remove_object(self.fd);
624 }
625}