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 FILESYSTEM.set(Filesystem::new()).unwrap();
308 FILESYSTEM
309 .get()
310 .unwrap()
311 .mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
312 .expect("Unable to create /tmp");
313 FILESYSTEM
314 .get()
315 .unwrap()
316 .mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
317 .expect("Unable to create /proc");
318
319 if let Ok(mut file) = File::create("/proc/version") {
320 if write!(file, "Hermit version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
321 error!("Unable to write in /proc/version");
322 }
323 } else {
324 error!("Unable to create /proc/version");
325 }
326
327 let mut cwd = WORKING_DIRECTORY.lock();
328 *cwd = Some("/tmp".to_owned());
329 drop(cwd);
330
331 #[cfg(feature = "virtio-fs")]
332 virtio_fs::init();
333 if crate::env::is_uhyve() {
334 uhyve::init();
335 }
336}
337
338pub fn create_file(name: &str, data: &'static [u8], mode: AccessPermission) -> io::Result<()> {
339 with_relative_filename(name, |name| {
340 FILESYSTEM
341 .get()
342 .ok_or(Errno::Inval)?
343 .create_file(name, data, mode)
344 })
345}
346
347pub fn remove_dir(path: &str) -> io::Result<()> {
349 with_relative_filename(path, |path| {
350 FILESYSTEM.get().ok_or(Errno::Inval)?.rmdir(path)
351 })
352}
353
354pub fn unlink(path: &str) -> io::Result<()> {
355 with_relative_filename(path, |path| {
356 FILESYSTEM.get().ok_or(Errno::Inval)?.unlink(path)
357 })
358}
359
360pub fn create_dir(path: &str, mode: AccessPermission) -> io::Result<()> {
362 let mask = *UMASK.lock();
363
364 with_relative_filename(path, |path| {
365 FILESYSTEM
366 .get()
367 .ok_or(Errno::Inval)?
368 .mkdir(path, mode.bitand(mask))
369 })
370}
371
372fn create_dir_recursive(path: &str, mode: AccessPermission) -> io::Result<()> {
374 trace!("create_dir_recursive: {path}");
375 create_dir(path, mode).or_else(|errno| {
376 if errno != Errno::Badf {
377 return Err(errno);
378 }
379 let (parent_path, _file_name) = path.rsplit_once('/').unwrap();
380 create_dir_recursive(parent_path, mode)?;
381 create_dir(path, mode)
382 })
383}
384
385pub fn readdir(name: &str) -> io::Result<Vec<DirectoryEntry>> {
387 debug!("Read directory {name}");
388
389 with_relative_filename(name, |name| {
390 FILESYSTEM.get().ok_or(Errno::Inval)?.readdir(name)
391 })
392}
393
394pub fn read_stat(name: &str) -> io::Result<FileAttr> {
395 with_relative_filename(name, |name| {
396 FILESYSTEM.get().ok_or(Errno::Inval)?.stat(name)
397 })
398}
399
400pub fn read_lstat(name: &str) -> io::Result<FileAttr> {
401 with_relative_filename(name, |name| {
402 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(name)
403 })
404}
405
406fn with_relative_filename<F, T>(name: &str, callback: F) -> io::Result<T>
407where
408 F: FnOnce(&str) -> io::Result<T>,
409{
410 if name.starts_with("/") {
411 return callback(name);
412 }
413
414 let cwd = WORKING_DIRECTORY.lock();
415
416 let Some(cwd) = cwd.as_ref() else {
417 return Err(Errno::Badf);
419 };
420
421 let mut path = String::with_capacity(cwd.len() + name.len() + 1);
422 path.push_str(cwd);
423 path.push('/');
424 path.push_str(name);
425
426 callback(&path)
427}
428
429pub fn truncate(name: &str, size: usize) -> io::Result<()> {
430 with_relative_filename(name, |name| {
431 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
432 let file = fs
433 .open(name, OpenOption::O_TRUNC, AccessPermission::empty())
434 .map_err(|_| Errno::Badf)?;
435
436 block_on(async { file.read().await.truncate(size).await }, None)
437 })
438}
439
440pub fn open(name: &str, flags: OpenOption, mode: AccessPermission) -> io::Result<RawFd> {
441 let mask = *UMASK.lock();
445
446 with_relative_filename(name, |name| {
447 debug!("Open {name}, {flags:?}, {mode:?}");
448
449 let fs = FILESYSTEM.get().ok_or(Errno::Inval)?;
450 let file = fs.open(name, flags, mode.bitand(mask))?;
451 let fd = insert_object(file)?;
452 Ok(fd)
453 })
454}
455
456pub fn get_cwd() -> io::Result<String> {
457 let cwd = WORKING_DIRECTORY.lock();
458 let cwd = cwd.as_ref().ok_or(Errno::Noent)?;
459 Ok(cwd.clone())
460}
461
462pub fn set_cwd(cwd: &str) -> io::Result<()> {
463 let mut working_dir = WORKING_DIRECTORY.lock();
466 if cwd.starts_with("/") {
467 *working_dir = Some(cwd.to_owned());
468 } else {
469 let working_dir = working_dir.as_mut().ok_or(Errno::Badf)?;
470 working_dir.push('/');
471 working_dir.push_str(cwd);
472 }
473
474 Ok(())
475}
476
477pub fn umask(new_mask: AccessPermission) -> AccessPermission {
478 let mut lock = UMASK.lock();
479 let old = *lock;
480 *lock = new_mask;
481 old
482}
483
484pub(crate) fn opendir(name: &str) -> io::Result<RawFd> {
486 let obj = FILESYSTEM.get().ok_or(Errno::Inval)?.opendir(name)?;
487 insert_object(obj)
488}
489
490use crate::fd::{self, RawFd};
491
492pub fn file_attributes(path: &str) -> io::Result<FileAttr> {
493 FILESYSTEM.get().ok_or(Errno::Inval)?.lstat(path)
494}
495
496#[allow(clippy::len_without_is_empty)]
497#[derive(Debug, Copy, Clone)]
498pub struct Metadata(FileAttr);
499
500impl Metadata {
501 pub fn len(&self) -> usize {
503 self.0.st_size.try_into().unwrap()
504 }
505
506 pub fn is_file(&self) -> bool {
508 self.0.st_mode.contains(AccessPermission::S_IFREG)
509 }
510
511 pub fn is_dir(&self) -> bool {
513 self.0.st_mode.contains(AccessPermission::S_IFDIR)
514 }
515
516 pub fn modified(&self) -> io::Result<SystemTime> {
518 Ok(SystemTime::from(self.0.st_mtim))
519 }
520
521 pub fn accessed(&self) -> io::Result<SystemTime> {
523 Ok(SystemTime::from(self.0.st_atim))
524 }
525}
526
527pub fn metadata(path: &str) -> io::Result<Metadata> {
529 Ok(Metadata(file_attributes(path)?))
530}
531
532#[derive(Debug)]
533pub struct File {
534 fd: RawFd,
535 path: String,
536}
537
538impl File {
539 fn create(path: &str) -> io::Result<File> {
543 let fd = open(
544 path,
545 OpenOption::O_CREAT | OpenOption::O_TRUNC | OpenOption::O_WRONLY,
546 AccessPermission::from_bits(0o666).unwrap(),
547 )?;
548
549 Ok(File {
550 fd,
551 path: path.to_owned(),
552 })
553 }
554
555 pub fn create_new(path: &str) -> io::Result<Self> {
561 let fd = open(
562 path,
563 OpenOption::O_CREAT | OpenOption::O_EXCL | OpenOption::O_RDWR,
564 AccessPermission::from_bits(0o666).unwrap(),
565 )?;
566
567 Ok(File {
568 fd,
569 path: path.to_owned(),
570 })
571 }
572
573 pub fn open(path: &str) -> io::Result<Self> {
575 let fd = open(
576 path,
577 OpenOption::O_RDWR,
578 AccessPermission::from_bits(0o666).unwrap(),
579 )?;
580
581 Ok(File {
582 fd,
583 path: path.to_owned(),
584 })
585 }
586
587 pub fn metadata(&self) -> io::Result<Metadata> {
588 metadata(&self.path)
589 }
590}
591
592impl embedded_io::ErrorType for File {
593 type Error = crate::errno::Errno;
594}
595
596impl Read for File {
597 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
598 let buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
599 fd::read(self.fd, buf)
600 }
601}
602
603impl Write for File {
604 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
605 fd::write(self.fd, buf)
606 }
607
608 fn flush(&mut self) -> Result<(), Self::Error> {
609 Ok(())
610 }
611}
612
613impl Drop for File {
614 fn drop(&mut self) {
615 let _ = remove_object(self.fd);
616 }
617}