Skip to main content

hermit/fs/
uhyve.rs

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		// TODO: Although we can generally assume that what Uhyve delivers should be
42		// correct for now, it might make sense to build in checks (or at least debug_assert's)
43		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		// fd refers to a regular file
91		uhyve_hypercall(Hypercall::FileWrite(&mut write_params));
92		match write_params.ret {
93			// Assumption: fd is a regular file, a zero is only valid if the len
94			// (aka. "count") is also zero. Otherwise, however, we assume that something
95			// is wrong in Hermit<>Uhyve communication.
96			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; // circumvent packed field access
116			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	/// The external path of this directory.
152	///
153	/// Before talking to virtio-fs, the relative path inside this directory is
154	/// adjoined with this prefix.
155	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	/// Returns the node type
173	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; // circumvent packed field access
205		match ret {
206			// Assumption: Uhyve will never return a standard stream.
207			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; // circumvent packed field access
227		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		// No FDT -> Uhyve legacy mounting (to /root)
253		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
289/// Creates a directory and creates all missing parent directories as well.
290fn 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}