1use alloc::borrow::ToOwned;
2use alloc::boxed::Box;
3use alloc::ffi::CString;
4use alloc::string::String;
5use alloc::sync::Arc;
6use core::mem::MaybeUninit;
7
8use async_lock::Mutex;
9use embedded_io::{ErrorType, Read, Write};
10use memory_addresses::VirtAddr;
11use uhyve_interface::GuestPhysAddr;
12use uhyve_interface::v2::Hypercall;
13use uhyve_interface::v2::parameters::{
14 CloseParams, FstatParams, GetdentParams, GetdentResult, LseekParams, OpenParams, ReadParams,
15 StatKind, StatParams, StatResult, UnlinkParams, WriteParams,
16};
17
18use crate::arch::mm::paging::virtual_to_physical;
19use crate::env::FdtStartInfo;
20use crate::errno::Errno;
21use crate::fd::{Fd, RawFd};
22use crate::fs::{
23 self, AccessPermission, FileAttr, NodeKind, ObjectInterface, OpenOption, SeekWhence, VfsNode,
24};
25use crate::syscalls::DirentFormat;
26use crate::uhyve::uhyve_hypercall;
27use crate::{env, io};
28
29fn fstat_hypercall(fd: i32) -> io::Result<FileAttr> {
30 let mut attr = FileAttr::default();
31 let mut fstat_params = FstatParams {
32 fd,
33 attr: GuestPhysAddr::new(
34 virtual_to_physical(VirtAddr::from_ptr((&raw mut attr).cast::<FileAttr>()))
35 .unwrap()
36 .as_u64(),
37 ),
38 ret: StatResult::None,
39 };
40 uhyve_hypercall(Hypercall::FileFstat(&mut fstat_params));
41 match fstat_params.ret {
42 StatResult::None => Err(Errno::Nosys),
43 StatResult::Success => Ok(attr),
44 StatResult::Error(errno) => Err(Errno::try_from(errno).unwrap()),
45 }
46}
47
48#[derive(Debug)]
49struct UhyveFileHandleInner(i32);
50
51impl UhyveFileHandleInner {
52 pub fn new(fd: RawFd) -> Self {
53 Self(fd)
54 }
55
56 fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
57 let mut lseek_params = LseekParams {
58 fd: self.0,
59 offset: offset.try_into().unwrap(),
60 whence: u8::from(whence).into(),
61 };
62 uhyve_hypercall(Hypercall::FileLseek(&mut lseek_params));
63 match lseek_params.offset {
66 offset if offset >= 0 => Ok(offset.try_into().unwrap()),
67 errno if errno < 0 => Err((errno as i32).abs().try_into().unwrap()),
68 _ => {
69 debug!("Uhyve lseek hypercall yielded a zero.");
70 Err(Errno::Inval)
71 }
72 }
73 }
74}
75
76impl ErrorType for UhyveFileHandleInner {
77 type Error = Errno;
78}
79
80impl Read for UhyveFileHandleInner {
81 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
82 let mut read_params = ReadParams {
83 fd: self.0,
84 buf: GuestPhysAddr::new(
85 virtual_to_physical(VirtAddr::from_ptr(buf.as_mut_ptr()))
86 .unwrap()
87 .as_u64(),
88 ),
89 len: buf.len().try_into().unwrap(),
90 ret: 0i64,
91 };
92 uhyve_hypercall(Hypercall::FileRead(&mut read_params));
93 match read_params.ret {
94 ret if ret >= 0 => Ok(ret.try_into().unwrap()),
95 _ => Err((read_params.ret as i32).abs().try_into().unwrap()),
96 }
97 }
98}
99
100impl Write for UhyveFileHandleInner {
101 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
102 let mut write_params = WriteParams {
103 fd: self.0,
104 buf: GuestPhysAddr::new(
105 virtual_to_physical(VirtAddr::from_ptr(buf.as_ptr()))
106 .unwrap()
107 .as_u64(),
108 ),
109 len: buf.len().try_into().unwrap(),
110 ret: 0i64,
111 };
112 uhyve_hypercall(Hypercall::FileWrite(&mut write_params));
114 match write_params.ret {
115 ret if ret > 0 || (ret == 0 && write_params.len == 0) => Ok(ret.try_into().unwrap()),
119 errno if errno < 0 => Err((errno as i32).abs().try_into().unwrap()),
120 _ => {
121 debug!("Uhyve write hypercall yielded a zero.");
122 Err(Errno::Inval)
123 }
124 }
125 }
126
127 fn flush(&mut self) -> Result<(), Self::Error> {
128 Ok(())
129 }
130}
131
132impl Drop for UhyveFileHandleInner {
133 fn drop(&mut self) {
134 let mut close_params = CloseParams { fd: self.0, ret: 0 };
135 uhyve_hypercall(Hypercall::FileClose(&mut close_params));
136 if close_params.ret != 0 {
137 let ret = close_params.ret; panic!("Can't close fd {} - return value {ret}", self.0);
139 }
140 }
141}
142
143pub struct UhyveFileHandle(Arc<Mutex<UhyveFileHandleInner>>);
144
145impl UhyveFileHandle {
146 pub fn new(fd: RawFd) -> Self {
147 Self(Arc::new(Mutex::new(UhyveFileHandleInner::new(fd))))
148 }
149}
150
151impl ObjectInterface for UhyveFileHandle {
152 async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
153 self.0.lock().await.read(buf)
154 }
155
156 async fn write(&self, buf: &[u8]) -> io::Result<usize> {
157 self.0.lock().await.write(buf)
158 }
159
160 async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
161 self.0.lock().await.lseek(offset, whence)
162 }
163
164 async fn fstat(&self) -> io::Result<FileAttr> {
165 fstat_hypercall(self.0.lock().await.0)
166 }
167}
168
169impl Clone for UhyveFileHandle {
170 fn clone(&self) -> Self {
171 Self(self.0.clone())
172 }
173}
174
175pub struct UhyveDirectoryHandle {
176 fd: i32,
178}
179
180impl UhyveDirectoryHandle {
181 pub const fn new(fd: i32) -> Self {
182 Self { fd }
183 }
184}
185
186impl Drop for UhyveDirectoryHandle {
187 fn drop(&mut self) {
188 let mut close_params = CloseParams {
189 fd: self.fd,
190 ret: 0,
191 };
192 uhyve_hypercall(Hypercall::FileClose(&mut close_params));
193 }
194}
195
196impl ObjectInterface for UhyveDirectoryHandle {
197 async fn getdents(
198 &self,
199 buf: &mut [MaybeUninit<u8>],
200 format: DirentFormat,
201 ) -> io::Result<usize> {
202 match format {
203 DirentFormat::PosixDent => unimplemented!("Uhyve has no POSIX dirent support (yet)"),
204 DirentFormat::Dirent64 => {
205 let mut read_dir_params = GetdentParams {
206 fd: self.fd,
207 buf: GuestPhysAddr::new(
208 virtual_to_physical(VirtAddr::from_ptr(buf.as_mut_ptr()))
209 .unwrap()
210 .as_u64(),
211 ),
212 len: buf.len().try_into().unwrap(),
213 ret: GetdentResult::None,
214 };
215 uhyve_hypercall(Hypercall::Getdents(&mut read_dir_params));
216 match read_dir_params.ret {
217 GetdentResult::None => Err(Errno::Nosys),
218 GetdentResult::Success(len) => Ok(len.try_into().unwrap()),
219 GetdentResult::EndOfDirectory => Ok(0),
220 GetdentResult::Error(errno) => Err(Errno::try_from(errno).unwrap()),
221 }
222 }
223 }
224 }
225
226 async fn fstat(&self) -> io::Result<FileAttr> {
227 fstat_hypercall(self.fd)
228 }
229}
230
231#[derive(Debug)]
232pub(crate) struct UhyveDirectory {
233 prefix: String,
238}
239
240impl UhyveDirectory {
241 pub const fn new(prefix: String) -> Self {
242 UhyveDirectory { prefix }
243 }
244
245 fn traversal_path(&self, path: &str) -> CString {
246 let prefix = self.prefix.as_str();
247 let prefix = prefix.strip_suffix("/").unwrap_or(prefix);
248 if path.is_empty() {
249 return CString::new(prefix).unwrap();
250 }
251 let path = [prefix, path].join("/");
252 CString::new(path).unwrap()
253 }
254
255 fn stat_hypercall(&self, path: &str, kind: StatKind) -> io::Result<FileAttr> {
256 let path = self.traversal_path(path);
257 let mut attr = FileAttr::default();
258 let mut stat_params = StatParams {
259 name: GuestPhysAddr::new(
260 virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
261 .unwrap()
262 .as_u64(),
263 ),
264 kind,
265 attr: GuestPhysAddr::new(
266 virtual_to_physical(VirtAddr::from_ptr((&raw mut attr).cast::<FileAttr>()))
267 .unwrap()
268 .as_u64(),
269 ),
270 ret: StatResult::None,
271 };
272 uhyve_hypercall(Hypercall::FileStat(&mut stat_params));
273 match stat_params.ret {
274 StatResult::None => Err(Errno::Nosys),
275 StatResult::Success => Ok(attr),
276 StatResult::Error(errno) => Err(Errno::try_from(errno).unwrap()),
277 }
278 }
279}
280
281impl VfsNode for UhyveDirectory {
282 fn get_kind(&self) -> NodeKind {
284 NodeKind::Directory
285 }
286
287 fn get_file_attributes(&self) -> io::Result<FileAttr> {
288 self.stat_hypercall("", StatKind::Stat)
289 }
290
291 fn traverse_stat(&self, path: &str) -> io::Result<FileAttr> {
292 self.stat_hypercall(path, StatKind::Stat)
293 }
294
295 fn traverse_lstat(&self, path: &str) -> io::Result<FileAttr> {
296 self.stat_hypercall(path, StatKind::LStat)
297 }
298
299 fn traverse_open(
300 &self,
301 path: &str,
302 opt: OpenOption,
303 mode: AccessPermission,
304 ) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
305 let path = self.traversal_path(path);
306
307 let mut open_params = OpenParams {
308 name: GuestPhysAddr::new(
309 virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
310 .unwrap()
311 .as_u64(),
312 ),
313 flags: opt.bits(),
314 mode: mode.bits() as i32,
315 ret: -1,
316 };
317 uhyve_hypercall(Hypercall::FileOpen(&mut open_params));
318 let ret = open_params.ret; match ret {
320 ret if ret >= 0 => {
322 let obj = if opt.contains(OpenOption::O_DIRECTORY) {
323 UhyveDirectoryHandle::new(ret).into()
324 } else {
325 UhyveFileHandle::new(ret).into()
326 };
327 Ok(Arc::new(async_lock::RwLock::new(obj)))
328 }
329 _ => Err(ret.abs().try_into().unwrap()),
330 }
331 }
332
333 fn traverse_unlink(&self, path: &str) -> io::Result<()> {
334 let path = self.traversal_path(path);
335
336 let mut unlink_params = UnlinkParams {
337 name: GuestPhysAddr::new(
338 virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
339 .unwrap()
340 .as_u64(),
341 ),
342 ret: -1,
343 };
344 uhyve_hypercall(Hypercall::FileUnlink(&mut unlink_params));
345 let ret = unlink_params.ret; match ret {
347 0 => Ok(()),
348 _ => Err(unlink_params.ret.abs().try_into().unwrap()),
349 }
350 }
351
352 fn traverse_rmdir(&self, _path: &str) -> io::Result<()> {
353 Err(Errno::Nosys)
354 }
355
356 fn traverse_mkdir(&self, _path: &str, _mode: AccessPermission) -> io::Result<()> {
357 Err(Errno::Nosys)
358 }
359
360 fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
362 let path = CString::new(self.prefix.clone()).unwrap();
363 let mut open_params = OpenParams {
364 name: GuestPhysAddr::new(
365 virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
366 .unwrap()
367 .as_u64(),
368 ),
369 flags: (OpenOption::O_RDONLY | OpenOption::O_DIRECTORY).bits(),
370 mode: 0,
371 ret: -1,
372 };
373 uhyve_hypercall(Hypercall::FileOpen(&mut open_params));
374 let fd = open_params.ret;
375 match fd {
376 fd if fd >= 0 => Ok(Arc::new(async_lock::RwLock::new(
377 UhyveDirectoryHandle::new(fd).into(),
378 ))),
379 _ => Err(fd.abs().try_into().unwrap()),
380 }
381 }
382}
383
384pub(crate) fn init() {
385 info!("Try to initialize uhyve filesystem");
386
387 let mount_str = env::start_info().fdt().and_then(|fdt| {
388 fdt.find_node("/uhyve,mounts")
389 .and_then(|node| node.property("mounts"))
390 .and_then(|property| property.as_str())
391 });
392
393 let Some(mount_str) = mount_str else {
394 let mount_point = hermit_var_or!("UHYVE_MOUNT", "/root").to_owned();
396 info!("Mounting uhyve filesystem at {mount_point}");
397 fs::FILESYSTEM
398 .get()
399 .unwrap()
400 .mount(
401 &mount_point,
402 Box::new(UhyveDirectory::new(mount_point.clone())),
403 )
404 .expect("Mount failed. Duplicate mount_point?");
405 return;
406 };
407
408 assert_ne!(mount_str.len(), 0, "Invalid /uhyve,mounts node in FDT");
409 for mount_point in mount_str.split('\0') {
410 info!("Mounting uhyve filesystem at {mount_point}");
411
412 let obj = Box::new(UhyveDirectory::new(mount_point.to_owned()));
413 let Err(errno) = fs::FILESYSTEM.get().unwrap().mount(mount_point, obj) else {
414 continue;
415 };
416
417 assert_eq!(errno, Errno::Badf);
418 debug!("Mounting of {mount_point} failed with {errno:?}. Creating missing parent folders");
419 let (parent_path, _file_name) = mount_point.rsplit_once('/').unwrap();
420 create_dir_recursive(parent_path, AccessPermission::S_IRWXU).unwrap();
421
422 let obj = Box::new(UhyveDirectory::new(mount_point.to_owned()));
423 fs::FILESYSTEM
424 .get()
425 .unwrap()
426 .mount(mount_point, obj)
427 .unwrap();
428 }
429}
430
431fn create_dir_recursive(path: &str, mode: AccessPermission) -> io::Result<()> {
433 trace!("create_dir_recursive: {path}");
434 fs::create_dir(path, mode).or_else(|errno| {
435 if errno != Errno::Badf {
436 return Err(errno);
437 }
438 let (parent_path, _file_name) = path.rsplit_once('/').unwrap();
439 create_dir_recursive(parent_path, mode)?;
440 fs::create_dir(path, mode)
441 })
442}