hermit/fs/
uhyve.rs

1use alloc::borrow::ToOwned;
2use alloc::boxed::Box;
3use alloc::ffi::CString;
4use alloc::string::{String, ToString};
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
101#[derive(Debug)]
102struct UhyveFileHandle(pub Arc<Mutex<UhyveFileHandleInner>>);
103
104impl UhyveFileHandle {
105	pub fn new(fd: i32) -> Self {
106		Self(Arc::new(Mutex::new(UhyveFileHandleInner::new(fd))))
107	}
108}
109
110#[async_trait]
111impl ObjectInterface for UhyveFileHandle {
112	async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
113		self.0.lock().await.read(buf)
114	}
115
116	async fn write(&self, buf: &[u8]) -> io::Result<usize> {
117		self.0.lock().await.write(buf)
118	}
119
120	async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
121		self.0.lock().await.lseek(offset, whence)
122	}
123}
124
125impl Clone for UhyveFileHandle {
126	fn clone(&self) -> Self {
127		Self(self.0.clone())
128	}
129}
130
131#[derive(Debug)]
132pub(crate) struct UhyveDirectory {
133	prefix: Option<String>,
134}
135
136impl UhyveDirectory {
137	pub const fn new(prefix: Option<String>) -> Self {
138		UhyveDirectory { prefix }
139	}
140
141	fn traversal_path(&self, components: &[&str]) -> CString {
142		let prefix_deref = self.prefix.as_deref();
143		let components_with_prefix = prefix_deref.iter().chain(components.iter().rev());
144		// Unlike src/fs/fuse.rs, we skip the first element here so as to not prepend / before /root
145		let path: String = components_with_prefix
146			.flat_map(|component| ["/", component])
147			.skip(1)
148			.collect();
149		if path.is_empty() {
150			CString::new("/").unwrap()
151		} else {
152			CString::new(path).unwrap()
153		}
154	}
155}
156
157impl VfsNode for UhyveDirectory {
158	/// Returns the node type
159	fn get_kind(&self) -> NodeKind {
160		NodeKind::Directory
161	}
162
163	fn traverse_stat(&self, _components: &mut Vec<&str>) -> io::Result<FileAttr> {
164		Err(Errno::Nosys)
165	}
166
167	fn traverse_lstat(&self, _components: &mut Vec<&str>) -> io::Result<FileAttr> {
168		Err(Errno::Nosys)
169	}
170
171	fn traverse_open(
172		&self,
173		components: &mut Vec<&str>,
174		opt: OpenOption,
175		mode: AccessPermission,
176	) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
177		let path = self.traversal_path(components);
178
179		let mut open_params = OpenParams {
180			name: GuestPhysAddr::new(
181				paging::virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
182					.unwrap()
183					.as_u64(),
184			),
185			flags: opt.bits(),
186			mode: mode.bits() as i32,
187			ret: -1,
188		};
189		uhyve_hypercall(Hypercall::FileOpen(&mut open_params));
190
191		if open_params.ret > 0 {
192			Ok(Arc::new(async_lock::RwLock::new(UhyveFileHandle::new(
193				open_params.ret,
194			))))
195		} else {
196			Err(Errno::Io)
197		}
198	}
199
200	fn traverse_unlink(&self, components: &mut Vec<&str>) -> io::Result<()> {
201		let path = self.traversal_path(components);
202
203		let mut unlink_params = UnlinkParams {
204			name: GuestPhysAddr::new(
205				paging::virtual_to_physical(VirtAddr::from_ptr(path.as_ptr()))
206					.unwrap()
207					.as_u64(),
208			),
209			ret: -1,
210		};
211		uhyve_hypercall(Hypercall::FileUnlink(&mut unlink_params));
212
213		if unlink_params.ret == 0 {
214			Ok(())
215		} else {
216			Err(Errno::Io)
217		}
218	}
219
220	fn traverse_rmdir(&self, _components: &mut Vec<&str>) -> io::Result<()> {
221		Err(Errno::Nosys)
222	}
223
224	fn traverse_mkdir(
225		&self,
226		_components: &mut Vec<&str>,
227		_mode: AccessPermission,
228	) -> io::Result<()> {
229		Err(Errno::Nosys)
230	}
231}
232
233pub(crate) fn init() {
234	info!("Try to initialize uhyve filesystem");
235	let mount_str = fdt().and_then(|fdt| {
236		fdt.find_node("/uhyve,mounts")
237			.and_then(|node| node.property("mounts"))
238			.and_then(|property| property.as_str())
239	});
240	if let Some(mount_str) = mount_str {
241		assert_ne!(mount_str.len(), 0, "Invalid /uhyve,mounts node in FDT");
242		for mount_point in mount_str.split('\0') {
243			info!("Mounting uhyve filesystem at {mount_point}");
244
245			if let Err(errno) = fs::FILESYSTEM.get().unwrap().mount(
246				mount_point,
247				Box::new(UhyveDirectory::new(Some(mount_point.to_owned()))),
248			) {
249				assert_eq!(errno, Errno::Badf);
250				debug!(
251					"Mounting of {mount_point} failed with {errno:?}. Creating missing parent folders"
252				);
253				let (parent_path, _file_name) = mount_point.rsplit_once('/').unwrap();
254				create_dir_recursive(parent_path, AccessPermission::S_IRWXU).unwrap();
255
256				fs::FILESYSTEM
257					.get()
258					.unwrap()
259					.mount(
260						mount_point,
261						Box::new(UhyveDirectory::new(Some(mount_point.to_owned()))),
262					)
263					.unwrap();
264			}
265		}
266	} else {
267		// No FDT -> Uhyve legacy mounting (to /root)
268		let mount_point = hermit_var_or!("UHYVE_MOUNT", "/root").to_string();
269		info!("Mounting uhyve filesystem at {mount_point}");
270		fs::FILESYSTEM
271			.get()
272			.unwrap()
273			.mount(
274				&mount_point,
275				Box::new(UhyveDirectory::new(Some(mount_point.clone()))),
276			)
277			.expect("Mount failed. Duplicate mount_point?");
278	}
279}