hermit/fs/
uhyve.rs

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