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::parameters::{
11	CloseParams, LseekParams, OpenParams, ReadParams, UnlinkParams, WriteParams,
12};
13use uhyve_interface::{GuestPhysAddr, GuestVirtAddr, Hypercall};
14
15use crate::arch::mm::paging;
16use crate::env::fdt;
17use crate::errno::Errno;
18use crate::fd::Fd;
19use crate::fs::{
20	self, AccessPermission, FileAttr, NodeKind, ObjectInterface, OpenOption, SeekWhence, VfsNode,
21	create_dir_recursive,
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,
38			whence: u8::from(whence).into(),
39		};
40		uhyve_hypercall(Hypercall::FileLseek(&mut lseek_params));
41
42		if lseek_params.offset < 0 {
43			return Err(Errno::Inval);
44		}
45
46		Ok(lseek_params.offset)
47	}
48}
49
50impl ErrorType for UhyveFileHandleInner {
51	type Error = Errno;
52}
53
54impl Read for UhyveFileHandleInner {
55	fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
56		let mut read_params = ReadParams {
57			fd: self.0,
58			buf: GuestVirtAddr::from_ptr(buf.as_mut_ptr()),
59			len: buf.len(),
60			ret: 0,
61		};
62		uhyve_hypercall(Hypercall::FileRead(&mut read_params));
63
64		if read_params.ret < 0 {
65			return Err(Errno::Io);
66		}
67
68		Ok(read_params.ret.try_into().unwrap())
69	}
70}
71
72impl Write for UhyveFileHandleInner {
73	fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
74		let write_params = WriteParams {
75			fd: self.0,
76			buf: GuestVirtAddr::from_ptr(buf.as_ptr()),
77			len: buf.len(),
78		};
79		uhyve_hypercall(Hypercall::FileWrite(&write_params));
80
81		Ok(write_params.len)
82	}
83
84	fn flush(&mut self) -> Result<(), Self::Error> {
85		Ok(())
86	}
87}
88
89impl Drop for UhyveFileHandleInner {
90	fn drop(&mut self) {
91		let mut close_params = CloseParams { fd: self.0, ret: 0 };
92		uhyve_hypercall(Hypercall::FileClose(&mut close_params));
93		if close_params.ret != 0 {
94			let ret = close_params.ret; // circumvent packed field access
95			panic!("Can't close fd {} - return value {ret}", self.0);
96		}
97	}
98}
99
100pub struct UhyveFileHandle(Arc<Mutex<UhyveFileHandleInner>>);
101
102impl UhyveFileHandle {
103	pub fn new(fd: i32) -> Self {
104		Self(Arc::new(Mutex::new(UhyveFileHandleInner::new(fd))))
105	}
106}
107
108impl ObjectInterface for UhyveFileHandle {
109	async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
110		self.0.lock().await.read(buf)
111	}
112
113	async fn write(&self, buf: &[u8]) -> io::Result<usize> {
114		self.0.lock().await.write(buf)
115	}
116
117	async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
118		self.0.lock().await.lseek(offset, whence)
119	}
120}
121
122impl Clone for UhyveFileHandle {
123	fn clone(&self) -> Self {
124		Self(self.0.clone())
125	}
126}
127
128#[derive(Debug)]
129pub(crate) struct UhyveDirectory {
130	/// The external path of this directory.
131	///
132	/// Before talking to virtio-fs, the relative path inside this directory is
133	/// adjoined with this prefix.
134	prefix: String,
135}
136
137impl UhyveDirectory {
138	pub const fn new(prefix: String) -> Self {
139		UhyveDirectory { prefix }
140	}
141
142	fn traversal_path(&self, path: &str) -> CString {
143		let prefix = self.prefix.as_str();
144		let prefix = prefix.strip_suffix("/").unwrap_or(prefix);
145		let path = [prefix, path].join("/");
146		CString::new(path).unwrap()
147	}
148}
149
150impl VfsNode for UhyveDirectory {
151	/// Returns the node type
152	fn get_kind(&self) -> NodeKind {
153		NodeKind::Directory
154	}
155
156	fn traverse_stat(&self, _path: &str) -> io::Result<FileAttr> {
157		Err(Errno::Nosys)
158	}
159
160	fn traverse_lstat(&self, _path: &str) -> io::Result<FileAttr> {
161		Err(Errno::Nosys)
162	}
163
164	fn traverse_open(
165		&self,
166		path: &str,
167		opt: OpenOption,
168		mode: AccessPermission,
169	) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
170		let path = self.traversal_path(path);
171
172		let mut open_params = OpenParams {
173			name: GuestPhysAddr::new(
174				paging::virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
175					.unwrap()
176					.as_u64(),
177			),
178			flags: opt.bits(),
179			mode: mode.bits() as i32,
180			ret: -1,
181		};
182		uhyve_hypercall(Hypercall::FileOpen(&mut open_params));
183
184		if open_params.ret <= 0 {
185			return Err(Errno::Io);
186		}
187
188		Ok(Arc::new(async_lock::RwLock::new(
189			UhyveFileHandle::new(open_params.ret).into(),
190		)))
191	}
192
193	fn traverse_unlink(&self, path: &str) -> io::Result<()> {
194		let path = self.traversal_path(path);
195
196		let mut unlink_params = UnlinkParams {
197			name: GuestPhysAddr::new(
198				paging::virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
199					.unwrap()
200					.as_u64(),
201			),
202			ret: -1,
203		};
204		uhyve_hypercall(Hypercall::FileUnlink(&mut unlink_params));
205
206		if unlink_params.ret != 0 {
207			return Err(Errno::Io);
208		}
209
210		Ok(())
211	}
212
213	fn traverse_rmdir(&self, _path: &str) -> io::Result<()> {
214		Err(Errno::Nosys)
215	}
216
217	fn traverse_mkdir(&self, _path: &str, _mode: AccessPermission) -> io::Result<()> {
218		Err(Errno::Nosys)
219	}
220}
221
222pub(crate) fn init() {
223	info!("Try to initialize uhyve filesystem");
224
225	let mount_str = fdt().and_then(|fdt| {
226		fdt.find_node("/uhyve,mounts")
227			.and_then(|node| node.property("mounts"))
228			.and_then(|property| property.as_str())
229	});
230
231	let Some(mount_str) = mount_str else {
232		// No FDT -> Uhyve legacy mounting (to /root)
233		let mount_point = hermit_var_or!("UHYVE_MOUNT", "/root").to_owned();
234		info!("Mounting uhyve filesystem at {mount_point}");
235		fs::FILESYSTEM
236			.get()
237			.unwrap()
238			.mount(
239				&mount_point,
240				Box::new(UhyveDirectory::new(mount_point.clone())),
241			)
242			.expect("Mount failed. Duplicate mount_point?");
243		return;
244	};
245
246	assert_ne!(mount_str.len(), 0, "Invalid /uhyve,mounts node in FDT");
247	for mount_point in mount_str.split('\0') {
248		info!("Mounting uhyve filesystem at {mount_point}");
249
250		let obj = Box::new(UhyveDirectory::new(mount_point.to_owned()));
251		let Err(errno) = fs::FILESYSTEM.get().unwrap().mount(mount_point, obj) else {
252			continue;
253		};
254
255		assert_eq!(errno, Errno::Badf);
256		debug!("Mounting of {mount_point} failed with {errno:?}. Creating missing parent folders");
257		let (parent_path, _file_name) = mount_point.rsplit_once('/').unwrap();
258		create_dir_recursive(parent_path, AccessPermission::S_IRWXU).unwrap();
259
260		let obj = Box::new(UhyveDirectory::new(mount_point.to_owned()));
261		fs::FILESYSTEM
262			.get()
263			.unwrap()
264			.mount(mount_point, obj)
265			.unwrap();
266	}
267}