1pub(crate) mod dev_directory;
2pub(crate) mod mem;
3#[cfg(feature = "uhyve")]
4pub(crate) mod uhyve;
5#[cfg(feature = "virtio-fs")]
6pub(crate) mod virtio_fs;
7
8use alloc::borrow::ToOwned;
9use alloc::boxed::Box;
10use alloc::string::String;
11use alloc::sync::Arc;
12use alloc::vec::Vec;
13use core::fmt;
14use core::mem::MaybeUninit;
15use core::ops::BitAnd;
16
17use embedded_io::{Read, Write};
18use hermit_sync::{InterruptSpinMutex, OnceCell};
19use mem::MemDirectory;
20use num_enum::{IntoPrimitive, TryFromPrimitive};
21
22use crate::errno::Errno;
23use crate::executor::block_on;
24use crate::fd::{AccessPermission, Fd, ObjectInterface, OpenOption, insert_object, remove_object};
25use crate::io;
26use crate::syscalls::DirentFormat;
27use crate::time::{SystemTime, timespec};
28
29static FILESYSTEM: OnceCell<Filesystem> = OnceCell::new();
30
31static WORKING_DIRECTORY: InterruptSpinMutex<Option<String>> = InterruptSpinMutex::new(None);
32
33static UMASK: InterruptSpinMutex<AccessPermission> =
34 InterruptSpinMutex::new(AccessPermission::from_bits_retain(0o777));
35
36#[derive(Debug, Clone)]
37pub struct DirectoryEntry {
38 pub name: String,
39}
40
41impl DirectoryEntry {
42 pub fn new(name: String) -> Self {
43 Self { name }
44 }
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Eq)]
49pub(crate) enum NodeKind {
50 File,
52 Directory,
54}
55
56pub(crate) trait VfsNode: Send + Sync + fmt::Debug {
58 fn get_kind(&self) -> NodeKind;
60
61 fn get_file_attributes(&self) -> io::Result<FileAttr> {
63 Err(Errno::Nosys)
64 }
65
66 fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
68 Err(Errno::Nosys)
69 }
70
71 fn traverse_mkdir(&self, _path: &str, _mode: AccessPermission) -> io::Result<()> {
73 Err(Errno::Nosys)
74 }
75
76 fn traverse_rmdir(&self, _path: &str) -> io::Result<()> {
78 Err(Errno::Nosys)
79 }
80
81 fn traverse_unlink(&self, _path: &str) -> io::Result<()> {
83 Err(Errno::Nosys)
84 }
85
86 fn traverse_readdir(&self, _path: &str) -> io::Result<Vec<DirectoryEntry>> {
88 Err(Errno::Nosys)
89 }
90
91 fn traverse_lstat(&self, _path: &str) -> io::Result<FileAttr> {
93 Err(Errno::Nosys)
94 }
95
96 fn traverse_stat(&self, _path: &str) -> io::Result<FileAttr> {
98 Err(Errno::Nosys)
99 }
100
101 fn traverse_mount(&self, _path: &str, _obj: Box<dyn VfsNode>) -> io::Result<()> {
103 Err(Errno::Nosys)
104 }
105
106 fn traverse_open(
108 &self,
109 _path: &str,
110 _option: OpenOption,
111 _mode: AccessPermission,
112 ) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
113 Err(Errno::Nosys)
114 }
115
116 fn traverse_create_file(
118 &self,
119 _path: &str,
120 _data: &'static [u8],
121 _mode: AccessPermission,
122 ) -> io::Result<()> {
123 Err(Errno::Nosys)
124 }
125}
126
127pub(crate) struct DirectoryReader {
128 entries: Vec<DirectoryEntry>,
129 read_idx: async_lock::Mutex<usize>,
130}
131
132impl DirectoryReader {
133 pub fn new(entries: Vec<DirectoryEntry>) -> Self {
134 Self {
135 entries,
136 read_idx: async_lock::Mutex::new(0),
137 }
138 }
139}
140
141impl ObjectInterface for DirectoryReader {
142 async fn getdents(
143 &self,
144 buf: &mut [MaybeUninit<u8>],
145 format: DirentFormat,
146 ) -> io::Result<usize> {
147 let mut buf_offset: usize = 0;
148 let mut read_idx = self.read_idx.lock().await;
149 for entry in self.entries.iter().skip(*read_idx) {
150 let Some(next_dirent) =
151 format.write_entry(buf, buf_offset, 1, FileType::Unknown, entry.name.as_bytes())
152 else {
153 if buf_offset == 0 {
154 return Err(Errno::Inval);
156 }
157 break;
159 };
160
161 *read_idx += 1;
162 buf_offset = next_dirent;
163 }
164 Ok(buf_offset)
165 }
166
167 async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
168 if whence != SeekWhence::Set && offset != 0 {
169 error!("Invalid offset for directory lseek ({offset})");
170 return Err(Errno::Inval);
171 }
172 *self.read_idx.lock().await = offset as usize;
173 Ok(offset)
174 }
175}
176
177#[derive(Debug)]
178pub(crate) struct Filesystem {
179 root: MemDirectory,
180}
181
182impl Filesystem {
183 pub fn new() -> Self {
184 Self {
185 root: MemDirectory::new(AccessPermission::from_bits(0o777).unwrap()),
186 }
187 }
188
189 pub fn open(
191 &self,
192 path: &str,
193 opt: OpenOption,
194 mode: AccessPermission,
195 ) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
196 debug!("Open file {path} with {opt:?}");
197
198 let path = path.strip_prefix('/').unwrap_or(path);
199
200 self.root.traverse_open(path, opt, mode)
201 }
202
203 pub fn unlink(&self, path: &str) -> io::Result<()> {
205 debug!("Unlinking file {path}");
206
207 let path = path.strip_prefix('/').unwrap_or(path);
208
209 self.root.traverse_unlink(path)
210 }
211
212 pub fn rmdir(&self, path: &str) -> io::Result<()> {
214 debug!("Removing directory {path}");
215
216 let path = path.strip_prefix('/').unwrap_or(path);
217
218 self.root.traverse_rmdir(path)
219 }
220
221 pub fn mkdir(&self, path: &str, mode: AccessPermission) -> io::Result<()> {
223 debug!("Create directory {path}");
224
225 let path = path.strip_prefix('/').unwrap_or(path);
226
227 self.root.traverse_mkdir(path, mode)
228 }
229
230 pub fn opendir(&self, path: &str) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
231 debug!("Open directory {path}");
232 Ok(Arc::new(async_lock::RwLock::new(
233 DirectoryReader::new(self.readdir(path)?).into(),
234 )))
235 }
236
237 pub fn readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
239 debug!("Readdir {path}");
240
241 let path = path.strip_prefix('/').unwrap_or(path);
242
243 self.root.traverse_readdir(path)
244 }
245
246 pub fn stat(&self, path: &str) -> io::Result<FileAttr> {
248 debug!("Getting stats {path}");
249
250 let path = path.strip_prefix('/').unwrap_or(path);
251
252 self.root.traverse_stat(path)
253 }
254
255 pub fn lstat(&self, path: &str) -> io::Result<FileAttr> {
257 debug!("Getting lstats {path}");
258
259 let path = path.strip_prefix('/').unwrap_or(path);
260
261 self.root.traverse_lstat(path)
262 }
263
264 pub fn mount(&self, path: &str, obj: Box<dyn VfsNode>) -> io::Result<()> {
266 debug!("Mounting {path}");
267
268 let path = path.strip_prefix('/').unwrap_or(path);
269
270 self.root.traverse_mount(path, obj)
271 }
272
273 pub fn create_file(
275 &self,
276 path: &str,
277 data: &'static [u8],
278 mode: AccessPermission,
279 ) -> io::Result<()> {
280 debug!("Create read-only file {path}");
281
282 let path = path.strip_prefix('/').unwrap_or(path);
283
284 self.root.traverse_create_file(path, data, mode)
285 }
286}
287
288#[repr(C)]
289#[derive(Debug, Default, Copy, Clone)]
290pub struct FileAttr {
291 pub st_dev: u64,
292 pub st_ino: u64,
293 pub st_nlink: u64,
294 pub st_mode: AccessPermission,
296 pub st_uid: u32,
298 pub st_gid: u32,
300 pub st_rdev: u64,
302 pub st_size: i64,
304 pub st_blksize: i64,
306 pub st_blocks: i64,
308 pub st_atim: timespec,
310 pub st_mtim: timespec,
312 pub st_ctim: timespec,
314}
315
316#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
317#[repr(u8)]
318pub enum FileType {
319 Unknown = 0, Fifo = 1, CharacterDevice = 2, Directory = 4, BlockDevice = 6, RegularFile = 8, SymbolicLink = 10, Socket = 12, Whiteout = 14, }
329
330#[derive(TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Clone, Copy, Debug)]
331#[repr(u8)]
332pub enum SeekWhence {
333 Set = 0,
334 Cur = 1,
335 End = 2,
336 Data = 3,
337 Hole = 4,
338}
339
340pub(crate) fn init() {
341 const VERSION: &str = env!("CARGO_PKG_VERSION");
342 const UTC_BUILT_TIME: &str = build_time::build_time_utc!();
343
344 let root_filesystem = Filesystem::new();
345
346 root_filesystem
347 .mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
348 .expect("Unable to create /tmp");
349 root_filesystem
350 .mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
351 .expect("Unable to create /proc");
352
353 FILESYSTEM.set(root_filesystem).unwrap();
354
355 if let Ok(mut file) = File::create("/proc/version") {
356 if write!(file, "Hermit version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
357 error!("Unable to write in /proc/version");
358 }
359 } else {
360 error!("Unable to create /proc/version");
361 }
362
363 *WORKING_DIRECTORY.lock() = Some("/tmp".to_owned());
364
365 #[cfg(feature = "virtio-fs")]
366 virtio_fs::init();
367
368 #[cfg(feature = "uhyve")]
369 use crate::env::UhyveStartInfo;
370
371 #[cfg(feature = "uhyve")]
372 if crate::env::start_info().is_uhyve() {
373 uhyve::init();
374 }
375
376 dev_directory::init();
377}
378
379pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
380 with_relative_filename(name, |name| {
381 FILESYSTEM
382 .get()
383 .ok_or(Errno::Inval)?
384 .create_file(name, data, mode)
385 })
386}
387
388pub fn remove_dir(path: &str) -> io::Result<()> {
390 with_relative_filename(path, |path| {
391 FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
392 })
393}
394
395pub fn unlink(path: &str) -> io::Result<()> {
396 with_relative_filename(path, |path| {
397 FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
398 })
399}
400
401pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
403 let mask = *UMASK.lock();
404
405 with_relative_filename(path, |path| {
406 FILESYSTEM
407 .get()
408 .ok_or(Errno::Inval)?
409 .mkdir(path, mode.bitand(mask))
410 })
411}
412
413pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
415 debug!("Read directory {name}");
416
417 with_relative_filename(name, |name| {
418 FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
419 })
420}
421
422pub fn read_stat(name: &str) -> io::Result<FileAttr> {
423 with_relative_filename(name, |name| {
424 FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
425 })
426}
427
428pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
429 with_relative_filename(name, |name| {
430 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
431 })
432}
433
434fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
435where
436 F: FnOnce(&str) -> io::Result<T>,
437{
438 if name.starts_with("/") {
439 return callback(name);
440 }
441
442 let cwd = WORKING_DIRECTORY.lock();
443
444 let Some(cwd) = cwd.as_ref() else {
445 return Err(Errno::Badf);
447 };
448
449 let mut path = String::with_capacity(cwd.len() + name.len() + 1);
450 path.push_str(cwd);
451 path.push('/');
452 path.push_str(name);
453
454 callback(&path)
455}
456
457pub fn truncate(name: &str, size: usize) -> io::Result<()> {
458 with_relative_filename(name, |name| {
459 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
460 let file = fs
461 .open(name, OpenOption::O_TRUNC, AccessPermission::empty())
462 .map_err(|_| Errno::Badf)?;
463
464 block_on(async { file.read().await.truncate(size).await }, None)
465 })
466}
467
468pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<RawFd> {
469 let mask = *UMASK.lock();
473
474 with_relative_filename(name, |name| {
475 debug!("Open {name}, {flags:?}, {mode:?}");
476
477 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
478 let file = fs.open(name, flags, mode.bitand(mask))?;
479 let fd = insert_object(file)?;
480 Ok(fd)
481 })
482}
483
484pub fn get_cwd() -> io::Result<String> {
485 let cwd = WORKING_DIRECTORY.lock();
486 let cwd = cwd.as_ref().ok_or(Errno::Noent)?;
487 Ok(cwd.clone())
488}
489
490pub fn set_cwd(cwd: &str) -> io::Result<()> {
491 let mut working_dir = WORKING_DIRECTORY.lock();
494 if cwd.starts_with("/") {
495 *working_dir = Some(cwd.to_owned());
496 } else {
497 let working_dir = working_dir.as_mut().ok_or(Errno::Badf)?;
498 working_dir.push('/');
499 working_dir.push_str(cwd);
500 }
501
502 Ok(())
503}
504
505pub fn umask(new_mask: AccessPermission) -> AccessPermission {
506 let mut lock = UMASK.lock();
507 let old = *lock;
508 *lock = new_mask;
509 old
510}
511
512pub(crate) fn opendir(name: &str) -> io::Result<RawFd> {
514 let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
515 insert_object(obj)
516}
517
518use crate::fd::{self, RawFd};
519
520pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
521 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
522}
523
524#[allow(clippy::len_without_is_empty)]
525#[derive(Debug, Copy, Clone)]
526pub struct Metadata(FileAttr);
527
528impl Metadata {
529 pub fn len(&self) -> usize {
531 self.0.st_size.try_into().unwrap()
532 }
533
534 pub fn is_file(&self) -> bool {
536 self.0.st_mode.contains(AccessPermission::S_IFREG)
537 }
538
539 pub fn is_dir(&self) -> bool {
541 self.0.st_mode.contains(AccessPermission::S_IFDIR)
542 }
543
544 pub fn modified(&self) -> io::Result<SystemTime> {
546 Ok(SystemTime::from(self.0.st_mtim))
547 }
548
549 pub fn accessed(&self) -> io::Result<SystemTime> {
551 Ok(SystemTime::from(self.0.st_atim))
552 }
553}
554
555pub fn metadata(path: &str) -> io::Result<Metadata> {
557 Ok(Metadata(file_attributes(path)?))
558}
559
560#[derive(Debug)]
561pub struct File {
562 fd: RawFd,
563 path: String,
564}
565
566impl File {
567 fn create(path: &str) -> io::Result<File> {
571 let fd = open(
572 path,
573 OpenOption::O_CREAT | OpenOption::O_TRUNC | OpenOption::O_WRONLY,
574 AccessPermission::from_bits(0o666).unwrap(),
575 )?;
576
577 Ok(File {
578 fd,
579 path: path.to_owned(),
580 })
581 }
582
583 pub fn create_new(path: &str) -> io::Result<Self> {
589 let fd = open(
590 path,
591 OpenOption::O_CREAT | OpenOption::O_EXCL | OpenOption::O_RDWR,
592 AccessPermission::from_bits(0o666).unwrap(),
593 )?;
594
595 Ok(File {
596 fd,
597 path: path.to_owned(),
598 })
599 }
600
601 pub fn open(path: &str) -> io::Result<Self> {
603 let fd = open(
604 path,
605 OpenOption::O_RDWR,
606 AccessPermission::from_bits(0o666).unwrap(),
607 )?;
608
609 Ok(File {
610 fd,
611 path: path.to_owned(),
612 })
613 }
614
615 pub fn metadata(&self) -> io::Result<Metadata> {
616 metadata(&self.path)
617 }
618}
619
620impl embedded_io::ErrorType for File {
621 type Error = Errno;
622}
623
624impl Read for File {
625 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
626 fd::read(self.fd, buf)
627 }
628}
629
630impl Write for File {
631 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
632 fd::write(self.fd, buf)
633 }
634
635 fn flush(&mut self) -> Result<(), Self::Error> {
636 Ok(())
637 }
638}
639
640impl Drop for File {
641 fn drop(&mut self) {
642 if let Err(err) = remove_object(self.fd) {
643 error!("File::drop failed: {err}");
644 }
645 }
646}