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