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