1use alloc::borrow::ToOwned;
2use alloc::boxed::Box;
3use alloc::ffi::CString;
4use alloc::string::String;
5use alloc::sync::Arc;
6
7use async_lock::Mutex;
8use embedded_io::{ErrorType, Read, Write};
9use memory_addresses::VirtAddr;
10use uhyve_interface::GuestPhysAddr;
11use uhyve_interface::v2::Hypercall;
12use uhyve_interface::v2::parameters::{
13 CloseParams, LseekParams, OpenParams, ReadParams, UnlinkParams, WriteParams,
14};
15
16use crate::arch::mm::paging;
17use crate::env::fdt;
18use crate::errno::Errno;
19use crate::fd::Fd;
20use crate::fs::{
21 self, AccessPermission, FileAttr, NodeKind, ObjectInterface, OpenOption, SeekWhence, VfsNode,
22};
23use crate::io;
24use crate::uhyve::uhyve_hypercall;
25
26#[derive(Debug)]
27struct UhyveFileHandleInner(i32);
28
29impl UhyveFileHandleInner {
30 pub fn new(fd: i32) -> Self {
31 Self(fd)
32 }
33
34 fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
35 let mut lseek_params = LseekParams {
36 fd: self.0,
37 offset: offset.try_into().unwrap(),
38 whence: u8::from(whence).into(),
39 };
40 uhyve_hypercall(Hypercall::FileLseek(&mut lseek_params));
41 match lseek_params.offset {
44 offset if offset >= 0 => Ok(offset.try_into().unwrap()),
45 errno if errno < 0 => Err((errno as i32).abs().try_into().unwrap()),
46 _ => {
47 debug!("Uhyve lseek hypercall yielded a zero.");
48 Err(Errno::Inval)
49 }
50 }
51 }
52}
53
54impl ErrorType for UhyveFileHandleInner {
55 type Error = Errno;
56}
57
58impl Read for UhyveFileHandleInner {
59 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
60 let mut read_params = ReadParams {
61 fd: self.0,
62 buf: GuestPhysAddr::new(
63 paging::virtual_to_physical(VirtAddr::from_ptr(buf.as_mut_ptr()))
64 .unwrap()
65 .as_u64(),
66 ),
67 len: buf.len().try_into().unwrap(),
68 ret: 0i64,
69 };
70 uhyve_hypercall(Hypercall::FileRead(&mut read_params));
71 match read_params.ret {
72 ret if ret >= 0 => Ok(ret.try_into().unwrap()),
73 _ => Err((read_params.ret as i32).abs().try_into().unwrap()),
74 }
75 }
76}
77
78impl Write for UhyveFileHandleInner {
79 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
80 let mut write_params = WriteParams {
81 fd: self.0,
82 buf: GuestPhysAddr::new(
83 paging::virtual_to_physical(VirtAddr::from_ptr(buf.as_ptr()))
84 .unwrap()
85 .as_u64(),
86 ),
87 len: buf.len().try_into().unwrap(),
88 ret: 0i64,
89 };
90 uhyve_hypercall(Hypercall::FileWrite(&mut write_params));
92 match write_params.ret {
93 ret if ret > 0 || (ret == 0 && write_params.len == 0) => Ok(ret.try_into().unwrap()),
97 errno if errno < 0 => Err((errno as i32).abs().try_into().unwrap()),
98 _ => {
99 debug!("Uhyve write hypercall yielded a zero.");
100 Err(Errno::Inval)
101 }
102 }
103 }
104
105 fn flush(&mut self) -> Result<(), Self::Error> {
106 Ok(())
107 }
108}
109
110impl Drop for UhyveFileHandleInner {
111 fn drop(&mut self) {
112 let mut close_params = CloseParams { fd: self.0, ret: 0 };
113 uhyve_hypercall(Hypercall::FileClose(&mut close_params));
114 if close_params.ret != 0 {
115 let ret = close_params.ret; panic!("Can't close fd {} - return value {ret}", self.0);
117 }
118 }
119}
120
121pub struct UhyveFileHandle(Arc<Mutex<UhyveFileHandleInner>>);
122
123impl UhyveFileHandle {
124 pub fn new(fd: i32) -> Self {
125 Self(Arc::new(Mutex::new(UhyveFileHandleInner::new(fd))))
126 }
127}
128
129impl ObjectInterface for UhyveFileHandle {
130 async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
131 self.0.lock().await.read(buf)
132 }
133
134 async fn write(&self, buf: &[u8]) -> io::Result<usize> {
135 self.0.lock().await.write(buf)
136 }
137
138 async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
139 self.0.lock().await.lseek(offset, whence)
140 }
141}
142
143impl Clone for UhyveFileHandle {
144 fn clone(&self) -> Self {
145 Self(self.0.clone())
146 }
147}
148
149#[derive(Debug)]
150pub(crate) struct UhyveDirectory {
151 prefix: String,
156}
157
158impl UhyveDirectory {
159 pub const fn new(prefix: String) -> Self {
160 UhyveDirectory { prefix }
161 }
162
163 fn traversal_path(&self, path: &str) -> CString {
164 let prefix = self.prefix.as_str();
165 let prefix = prefix.strip_suffix("/").unwrap_or(prefix);
166 let path = [prefix, path].join("/");
167 CString::new(path).unwrap()
168 }
169}
170
171impl VfsNode for UhyveDirectory {
172 fn get_kind(&self) -> NodeKind {
174 NodeKind::Directory
175 }
176
177 fn traverse_stat(&self, _path: &str) -> io::Result<FileAttr> {
178 Err(Errno::Nosys)
179 }
180
181 fn traverse_lstat(&self, _path: &str) -> io::Result<FileAttr> {
182 Err(Errno::Nosys)
183 }
184
185 fn traverse_open(
186 &self,
187 path: &str,
188 opt: OpenOption,
189 mode: AccessPermission,
190 ) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
191 let path = self.traversal_path(path);
192
193 let mut open_params = OpenParams {
194 name: GuestPhysAddr::new(
195 paging::virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
196 .unwrap()
197 .as_u64(),
198 ),
199 flags: opt.bits(),
200 mode: mode.bits() as i32,
201 ret: -1,
202 };
203 uhyve_hypercall(Hypercall::FileOpen(&mut open_params));
204 let ret = open_params.ret; match ret {
206 ret if ret >= 0 => Ok(Arc::new(async_lock::RwLock::new(
208 UhyveFileHandle::new(ret).into(),
209 ))),
210 _ => Err(ret.abs().try_into().unwrap()),
211 }
212 }
213
214 fn traverse_unlink(&self, path: &str) -> io::Result<()> {
215 let path = self.traversal_path(path);
216
217 let mut unlink_params = UnlinkParams {
218 name: GuestPhysAddr::new(
219 paging::virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
220 .unwrap()
221 .as_u64(),
222 ),
223 ret: -1,
224 };
225 uhyve_hypercall(Hypercall::FileUnlink(&mut unlink_params));
226 let ret = unlink_params.ret; match ret {
228 0 => Ok(()),
229 _ => Err(unlink_params.ret.abs().try_into().unwrap()),
230 }
231 }
232
233 fn traverse_rmdir(&self, _path: &str) -> io::Result<()> {
234 Err(Errno::Nosys)
235 }
236
237 fn traverse_mkdir(&self, _path: &str, _mode: AccessPermission) -> io::Result<()> {
238 Err(Errno::Nosys)
239 }
240}
241
242pub(crate) fn init() {
243 info!("Try to initialize uhyve filesystem");
244
245 let mount_str = fdt().and_then(|fdt| {
246 fdt.find_node("/uhyve,mounts")
247 .and_then(|node| node.property("mounts"))
248 .and_then(|property| property.as_str())
249 });
250
251 let Some(mount_str) = mount_str else {
252 let mount_point = hermit_var_or!("UHYVE_MOUNT", "/root").to_owned();
254 info!("Mounting uhyve filesystem at {mount_point}");
255 fs::FILESYSTEM
256 .get()
257 .unwrap()
258 .mount(
259 &mount_point,
260 Box::new(UhyveDirectory::new(mount_point.clone())),
261 )
262 .expect("Mount failed. Duplicate mount_point?");
263 return;
264 };
265
266 assert_ne!(mount_str.len(), 0, "Invalid /uhyve,mounts node in FDT");
267 for mount_point in mount_str.split('\0') {
268 info!("Mounting uhyve filesystem at {mount_point}");
269
270 let obj = Box::new(UhyveDirectory::new(mount_point.to_owned()));
271 let Err(errno) = fs::FILESYSTEM.get().unwrap().mount(mount_point, obj) else {
272 continue;
273 };
274
275 assert_eq!(errno, Errno::Badf);
276 debug!("Mounting of {mount_point} failed with {errno:?}. Creating missing parent folders");
277 let (parent_path, _file_name) = mount_point.rsplit_once('/').unwrap();
278 create_dir_recursive(parent_path, AccessPermission::S_IRWXU).unwrap();
279
280 let obj = Box::new(UhyveDirectory::new(mount_point.to_owned()));
281 fs::FILESYSTEM
282 .get()
283 .unwrap()
284 .mount(mount_point, obj)
285 .unwrap();
286 }
287}
288
289fn create_dir_recursive(path: &str, mode: AccessPermission) -> io::Result<()> {
291 trace!("create_dir_recursive: {path}");
292 fs::create_dir(path, mode).or_else(|errno| {
293 if errno != Errno::Badf {
294 return Err(errno);
295 }
296 let (parent_path, _file_name) = path.rsplit_once('/').unwrap();
297 create_dir_recursive(parent_path, mode)?;
298 fs::create_dir(path, mode)
299 })
300}