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