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