Skip to main content

hermit/fs/
virtio_fs.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;
7use core::marker::PhantomData;
8use core::mem::MaybeUninit;
9use core::sync::atomic::{AtomicU64, Ordering};
10use core::task::Poll;
11use core::{future, mem, ptr, slice};
12
13use align_address::Align;
14use async_lock::Mutex;
15use embedded_io::{ErrorType, Read, Write};
16use fuse_abi::linux::*;
17
18#[cfg(not(feature = "pci"))]
19use crate::arch::kernel::mmio::get_filesystem_driver;
20#[cfg(feature = "pci")]
21use crate::drivers::pci::get_filesystem_driver;
22use crate::drivers::virtio::virtqueue::error::VirtqError;
23use crate::errno::Errno;
24use crate::executor::block_on;
25use crate::fd::{Fd, PollEvent};
26use crate::fs::virtio_fs::ops::SetAttrValidFields;
27use crate::fs::{
28	self, AccessPermission, DirectoryEntry, FileAttr, NodeKind, ObjectInterface, OpenOption,
29	SeekWhence, VfsNode,
30};
31use crate::mm::device_alloc::DeviceAlloc;
32use crate::syscalls::Dirent64;
33use crate::time::{time_t, timespec};
34use crate::{arch, io};
35
36// response out layout eg @ https://github.com/zargony/fuse-rs/blob/bf6d1cf03f3277e35b580f3c7b9999255d72ecf3/src/ll/request.rs#L44
37// op in/out sizes/layout: https://github.com/hanwen/go-fuse/blob/204b45dba899dfa147235c255908236d5fde2d32/fuse/opcode.go#L439
38// possible responses for command: qemu/tools/virtiofsd/fuse_lowlevel.h
39
40const MAX_READ_LEN: usize = 1024 * 64;
41const MAX_WRITE_LEN: usize = 1024 * 64;
42
43const U64_SIZE: usize = size_of::<u64>();
44
45const S_IFLNK: u32 = 0o120_000;
46const S_IFMT: u32 = 0o170_000;
47
48pub(crate) trait VirtioFsInterface {
49	fn send_command<O: ops::Op + 'static>(
50		&mut self,
51		cmd: Cmd<O>,
52		rsp_payload_len: u32,
53	) -> Result<Rsp<O>, VirtioFsError>
54	where
55		<O as ops::Op>::InStruct: Send,
56		<O as ops::Op>::OutStruct: Send;
57
58	fn get_mount_point(&self) -> String;
59}
60
61pub(crate) mod ops {
62	#![allow(clippy::type_complexity)]
63	use alloc::boxed::Box;
64	use alloc::ffi::CString;
65	use core::fmt;
66
67	use fuse_abi::linux::*;
68
69	use super::Cmd;
70	use crate::fd::PollEvent;
71	use crate::fs::{FileAttr, SeekWhence};
72
73	#[repr(C)]
74	#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
75	pub(crate) struct CreateOut {
76		pub entry: fuse_entry_out,
77		pub open: fuse_open_out,
78	}
79
80	pub(crate) trait Op {
81		const OP_CODE: fuse_opcode;
82
83		type InStruct: fmt::Debug;
84		type InPayload: ?Sized;
85		type OutStruct: fmt::Debug;
86		type OutPayload: ?Sized;
87	}
88
89	#[derive(Debug)]
90	pub(crate) struct Init;
91
92	impl Op for Init {
93		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_INIT;
94		type InStruct = fuse_init_in;
95		type InPayload = ();
96		type OutStruct = fuse_init_out;
97		type OutPayload = ();
98	}
99
100	impl Init {
101		pub(crate) fn create() -> (Cmd<Self>, u32) {
102			let cmd = Cmd::new(
103				FUSE_ROOT_ID,
104				fuse_init_in {
105					major: 7,
106					minor: 31,
107					..Default::default()
108				},
109			);
110			(cmd, 0)
111		}
112	}
113
114	#[derive(Debug)]
115	pub(crate) struct Create;
116
117	impl Op for Create {
118		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_CREATE;
119		type InStruct = fuse_create_in;
120		type InPayload = CString;
121		type OutStruct = CreateOut;
122		type OutPayload = ();
123	}
124
125	impl Create {
126		#[allow(clippy::self_named_constructors)]
127		pub(crate) fn create(path: CString, flags: u32, mode: u32) -> (Cmd<Self>, u32) {
128			let cmd = Cmd::with_cstring(
129				FUSE_ROOT_ID,
130				fuse_create_in {
131					flags,
132					mode,
133					..Default::default()
134				},
135				path,
136			);
137			(cmd, 0)
138		}
139	}
140
141	#[derive(Debug)]
142	pub(crate) struct Open;
143
144	impl Op for Open {
145		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_OPEN;
146		type InStruct = fuse_open_in;
147		type InPayload = ();
148		type OutStruct = fuse_open_out;
149		type OutPayload = ();
150	}
151
152	impl Open {
153		pub(crate) fn create(nid: u64, flags: u32) -> (Cmd<Self>, u32) {
154			let cmd = Cmd::new(
155				nid,
156				fuse_open_in {
157					flags,
158					..Default::default()
159				},
160			);
161			(cmd, 0)
162		}
163	}
164
165	#[derive(Debug)]
166	pub(crate) struct Write;
167
168	impl Op for Write {
169		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_WRITE;
170		type InStruct = fuse_write_in;
171		type InPayload = [u8];
172		type OutStruct = fuse_write_out;
173		type OutPayload = ();
174	}
175
176	impl Write {
177		pub(crate) fn create(nid: u64, fh: u64, buf: Box<[u8]>, offset: u64) -> (Cmd<Self>, u32) {
178			let cmd = Cmd::with_boxed_slice(
179				nid,
180				fuse_write_in {
181					fh,
182					offset,
183					size: buf.len().try_into().unwrap(),
184					..Default::default()
185				},
186				buf,
187			);
188			(cmd, 0)
189		}
190	}
191
192	#[derive(Debug)]
193	pub(crate) struct Read;
194
195	impl Op for Read {
196		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_READ;
197		type InStruct = fuse_read_in;
198		type InPayload = ();
199		type OutStruct = ();
200		type OutPayload = [u8];
201	}
202
203	impl Read {
204		pub(crate) fn create(nid: u64, fh: u64, size: u32, offset: u64) -> (Cmd<Self>, u32) {
205			let cmd = Cmd::new(
206				nid,
207				fuse_read_in {
208					fh,
209					offset,
210					size,
211					..Default::default()
212				},
213			);
214			(cmd, size)
215		}
216	}
217
218	#[derive(Debug)]
219	pub(crate) struct Lseek;
220
221	impl Op for Lseek {
222		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_LSEEK;
223		type InStruct = fuse_lseek_in;
224		type InPayload = ();
225		type OutStruct = fuse_lseek_out;
226		type OutPayload = ();
227	}
228
229	impl Lseek {
230		pub(crate) fn create(
231			nid: u64,
232			fh: u64,
233			offset: isize,
234			whence: SeekWhence,
235		) -> (Cmd<Self>, u32) {
236			let cmd = Cmd::new(
237				nid,
238				fuse_lseek_in {
239					fh,
240					offset: i64::try_from(offset).unwrap() as u64,
241					whence: u8::from(whence).into(),
242					..Default::default()
243				},
244			);
245			(cmd, 0)
246		}
247	}
248
249	#[derive(Debug)]
250	pub(crate) struct Getattr;
251
252	impl Op for Getattr {
253		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_GETATTR;
254		type InStruct = fuse_getattr_in;
255		type InPayload = ();
256		type OutStruct = fuse_attr_out;
257		type OutPayload = ();
258	}
259
260	impl Getattr {
261		pub(crate) fn create(nid: u64, fh: u64, getattr_flags: u32) -> (Cmd<Self>, u32) {
262			let cmd = Cmd::new(
263				nid,
264				fuse_getattr_in {
265					getattr_flags,
266					fh,
267					..Default::default()
268				},
269			);
270			(cmd, 0)
271		}
272	}
273
274	#[derive(Debug)]
275	pub(crate) struct Setattr;
276
277	impl Op for Setattr {
278		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_SETATTR;
279		type InStruct = fuse_setattr_in;
280		type InPayload = ();
281		type OutStruct = fuse_attr_out;
282		type OutPayload = ();
283	}
284
285	bitflags! {
286		#[derive(Debug, Copy, Clone, Default)]
287		pub struct SetAttrValidFields: u32 {
288			const FATTR_MODE = FATTR_MODE;
289			const FATTR_UID = FATTR_UID;
290			const FATTR_GID = FATTR_GID;
291			const FATTR_SIZE = FATTR_SIZE;
292			const FATTR_ATIME = FATTR_ATIME;
293			const FATTR_MTIME = FATTR_MTIME;
294			const FATTR_FH = FATTR_FH;
295			const FATTR_ATIME_NOW = FATTR_ATIME_NOW;
296			const FATTR_MTIME_NOW = FATTR_MTIME_NOW;
297			const FATTR_LOCKOWNER = FATTR_LOCKOWNER;
298			const FATTR_CTIME = FATTR_CTIME;
299			const FATTR_KILL_SUIDGID = FATTR_KILL_SUIDGID;
300		}
301	}
302
303	impl Setattr {
304		pub(crate) fn create(
305			nid: u64,
306			fh: u64,
307			attr: FileAttr,
308			valid_attr: SetAttrValidFields,
309		) -> (Cmd<Self>, u32) {
310			let cmd = Cmd::new(
311				nid,
312				fuse_setattr_in {
313					valid: valid_attr
314						.difference(
315							// Remove unsupported attributes
316							SetAttrValidFields::FATTR_LOCKOWNER,
317						)
318						.bits(),
319					padding: 0,
320					fh,
321
322					// FUSE attributes mapping: https://github.com/libfuse/libfuse/blob/fc1c8da0cf8a18d222cb1feed0057ba44ea4d18f/lib/fuse_lowlevel.c#L105
323					size: attr.st_size as u64,
324					atime: attr.st_atim.tv_sec as u64,
325					atimensec: attr.st_atim.tv_nsec as u32,
326					mtime: attr.st_ctim.tv_sec as u64,
327					mtimensec: attr.st_ctim.tv_nsec as u32,
328					ctime: attr.st_ctim.tv_sec as u64,
329					ctimensec: attr.st_ctim.tv_nsec as u32,
330					mode: attr.st_mode.bits(),
331					unused4: 0,
332					uid: attr.st_uid,
333					gid: attr.st_gid,
334					unused5: 0,
335
336					lock_owner: 0, // unsupported
337				},
338			);
339
340			(cmd, 0)
341		}
342	}
343
344	#[derive(Debug)]
345	pub(crate) struct Readlink;
346
347	impl Op for Readlink {
348		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_READLINK;
349		type InStruct = ();
350		type InPayload = ();
351		type OutStruct = ();
352		type OutPayload = [u8];
353	}
354
355	impl Readlink {
356		pub(crate) fn create(nid: u64, size: u32) -> (Cmd<Self>, u32) {
357			let cmd = Cmd::new(nid, ());
358			(cmd, size)
359		}
360	}
361
362	#[derive(Debug)]
363	pub(crate) struct Release;
364
365	impl Op for Release {
366		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_RELEASE;
367		type InStruct = fuse_release_in;
368		type InPayload = ();
369		type OutStruct = ();
370		type OutPayload = ();
371	}
372
373	impl Release {
374		pub(crate) fn create(nid: u64, fh: u64) -> (Cmd<Self>, u32) {
375			let cmd = Cmd::new(
376				nid,
377				fuse_release_in {
378					fh,
379					..Default::default()
380				},
381			);
382			(cmd, 0)
383		}
384	}
385
386	#[derive(Debug)]
387	pub(crate) struct Poll;
388
389	impl Op for Poll {
390		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_POLL;
391		type InStruct = fuse_poll_in;
392		type InPayload = ();
393		type OutStruct = fuse_poll_out;
394		type OutPayload = ();
395	}
396
397	impl Poll {
398		pub(crate) fn create(nid: u64, fh: u64, kh: u64, event: PollEvent) -> (Cmd<Self>, u32) {
399			let cmd = Cmd::new(
400				nid,
401				fuse_poll_in {
402					fh,
403					kh,
404					events: event.bits() as u32,
405					..Default::default()
406				},
407			);
408			(cmd, 0)
409		}
410	}
411
412	#[derive(Debug)]
413	pub(crate) struct Mkdir;
414
415	impl Op for Mkdir {
416		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_MKDIR;
417		type InStruct = fuse_mkdir_in;
418		type InPayload = CString;
419		type OutStruct = fuse_entry_out;
420		type OutPayload = ();
421	}
422
423	impl Mkdir {
424		pub(crate) fn create(path: CString, mode: u32) -> (Cmd<Self>, u32) {
425			let cmd = Cmd::with_cstring(
426				FUSE_ROOT_ID,
427				fuse_mkdir_in {
428					mode,
429					..Default::default()
430				},
431				path,
432			);
433			(cmd, 0)
434		}
435	}
436
437	#[derive(Debug)]
438	pub(crate) struct Unlink;
439
440	impl Op for Unlink {
441		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_UNLINK;
442		type InStruct = ();
443		type InPayload = CString;
444		type OutStruct = ();
445		type OutPayload = ();
446	}
447
448	impl Unlink {
449		pub(crate) fn create(name: CString) -> (Cmd<Self>, u32) {
450			let cmd = Cmd::with_cstring(FUSE_ROOT_ID, (), name);
451			(cmd, 0)
452		}
453	}
454
455	#[derive(Debug)]
456	pub(crate) struct Rmdir;
457
458	impl Op for Rmdir {
459		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_RMDIR;
460		type InStruct = ();
461		type InPayload = CString;
462		type OutStruct = ();
463		type OutPayload = ();
464	}
465
466	impl Rmdir {
467		pub(crate) fn create(name: CString) -> (Cmd<Self>, u32) {
468			let cmd = Cmd::with_cstring(FUSE_ROOT_ID, (), name);
469			(cmd, 0)
470		}
471	}
472
473	#[derive(Debug)]
474	pub(crate) struct Lookup;
475
476	impl Op for Lookup {
477		const OP_CODE: fuse_opcode = fuse_opcode::FUSE_LOOKUP;
478		type InStruct = ();
479		type InPayload = CString;
480		type OutStruct = fuse_entry_out;
481		type OutPayload = ();
482	}
483
484	impl Lookup {
485		pub(crate) fn create(name: CString) -> (Cmd<Self>, u32) {
486			let cmd = Cmd::with_cstring(FUSE_ROOT_ID, (), name);
487			(cmd, 0)
488		}
489	}
490}
491
492impl From<fuse_attr> for FileAttr {
493	fn from(attr: fuse_attr) -> FileAttr {
494		FileAttr {
495			st_ino: attr.ino,
496			st_nlink: attr.nlink.into(),
497			st_mode: AccessPermission::from_bits_retain(attr.mode),
498			st_uid: attr.uid,
499			st_gid: attr.gid,
500			st_rdev: attr.rdev.into(),
501			st_size: attr.size.try_into().unwrap(),
502			st_blksize: attr.blksize.into(),
503			st_blocks: attr.blocks.try_into().unwrap(),
504			st_atim: timespec {
505				tv_sec: attr.atime as time_t,
506				tv_nsec: attr.atimensec as i32,
507			},
508			st_mtim: timespec {
509				tv_sec: attr.mtime as time_t,
510				tv_nsec: attr.mtimensec as i32,
511			},
512			st_ctim: timespec {
513				tv_sec: attr.ctime as time_t,
514				tv_nsec: attr.ctimensec as i32,
515			},
516			..Default::default()
517		}
518	}
519}
520
521#[repr(C)]
522#[derive(Debug)]
523pub(crate) struct CmdHeader<O: ops::Op> {
524	pub in_header: fuse_in_header,
525	op_header: O::InStruct,
526}
527
528impl<O: ops::Op> CmdHeader<O>
529where
530	O: ops::Op<InPayload = ()>,
531{
532	fn new(nodeid: u64, op_header: O::InStruct) -> Self {
533		Self::with_payload_size(nodeid, op_header, 0)
534	}
535}
536
537impl<O: ops::Op> CmdHeader<O> {
538	fn with_payload_size(nodeid: u64, op_header: O::InStruct, len: usize) -> CmdHeader<O> {
539		CmdHeader {
540			in_header: fuse_in_header {
541				// The length we need the provide in the header is not the same as the size of the struct because of padding, so we need to calculate it manually.
542				len: (size_of::<fuse_in_header>() + size_of::<O::InStruct>() + len)
543					.try_into()
544					.expect("The command is too large"),
545				opcode: O::OP_CODE.into(),
546				nodeid,
547				unique: 1,
548				..Default::default()
549			},
550			op_header,
551		}
552	}
553}
554
555pub(crate) struct Cmd<O: ops::Op> {
556	pub headers: Box<CmdHeader<O>, DeviceAlloc>,
557	pub payload: Option<Vec<u8, DeviceAlloc>>,
558}
559
560impl<O: ops::Op> Cmd<O>
561where
562	O: ops::Op<InPayload = ()>,
563{
564	fn new(nodeid: u64, op_header: O::InStruct) -> Self {
565		Self {
566			headers: Box::new_in(CmdHeader::new(nodeid, op_header), DeviceAlloc),
567			payload: None,
568		}
569	}
570}
571
572impl<O: ops::Op> Cmd<O>
573where
574	O: ops::Op<InPayload = CString>,
575{
576	fn with_cstring(nodeid: u64, op_header: O::InStruct, cstring: CString) -> Self {
577		let cstring_bytes = cstring.into_bytes_with_nul().to_vec_in(DeviceAlloc);
578		Self {
579			headers: Box::new_in(
580				CmdHeader::with_payload_size(nodeid, op_header, cstring_bytes.len()),
581				DeviceAlloc,
582			),
583			payload: Some(cstring_bytes),
584		}
585	}
586}
587
588impl<O: ops::Op> Cmd<O>
589where
590	O: ops::Op<InPayload = [u8]>,
591{
592	fn with_boxed_slice(nodeid: u64, op_header: O::InStruct, slice: Box<[u8]>) -> Self {
593		let mut device_slice = Vec::with_capacity_in(slice.len(), DeviceAlloc);
594		device_slice.extend_from_slice(&slice);
595		Self {
596			headers: Box::new_in(
597				CmdHeader::with_payload_size(nodeid, op_header, slice.len()),
598				DeviceAlloc,
599			),
600			payload: Some(device_slice),
601		}
602	}
603}
604
605#[repr(C)]
606#[derive(Debug)]
607// The generic H parameter allows us to handle RspHeaders with their op_header
608// potenitally uninitialized. After checking for the error code in the out_header,
609// the object can be transmuted to one with an initialized op_header.
610pub(crate) struct RspHeader<O: ops::Op, H = <O as ops::Op>::OutStruct> {
611	pub out_header: fuse_out_header,
612	op_header: H,
613	_phantom: PhantomData<O::OutStruct>,
614}
615
616#[derive(Debug)]
617pub(crate) struct Rsp<O: ops::Op> {
618	pub headers: Box<RspHeader<O>, DeviceAlloc>,
619	pub payload: Option<Vec<u8, DeviceAlloc>>,
620}
621
622#[derive(Debug)]
623pub(crate) enum VirtioFsError {
624	VirtqError(VirtqError),
625	IOError(Errno),
626}
627
628impl From<VirtqError> for VirtioFsError {
629	fn from(value: VirtqError) -> Self {
630		Self::VirtqError(value)
631	}
632}
633
634impl From<VirtioFsError> for Errno {
635	fn from(value: VirtioFsError) -> Self {
636		match value {
637			VirtioFsError::VirtqError(virtq_error) => virtq_error.into(),
638			VirtioFsError::IOError(io_error) => io_error,
639		}
640	}
641}
642
643fn lookup(name: CString) -> Option<u64> {
644	let (cmd, rsp_payload_len) = ops::Lookup::create(name);
645	let rsp = get_filesystem_driver()
646		.unwrap()
647		.lock()
648		.send_command(cmd, rsp_payload_len)
649		.ok()?;
650	Some(rsp.headers.op_header.nodeid)
651}
652
653fn readlink(nid: u64) -> io::Result<String> {
654	let len = MAX_READ_LEN as u32;
655	let (cmd, rsp_payload_len) = ops::Readlink::create(nid, len);
656	let rsp = get_filesystem_driver()
657		.unwrap()
658		.lock()
659		.send_command(cmd, rsp_payload_len)?;
660	let len: usize = if rsp.headers.out_header.len as usize - size_of::<fuse_out_header>()
661		>= usize::try_from(len).unwrap()
662	{
663		len.try_into().unwrap()
664	} else {
665		(rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>()
666	};
667
668	Ok(String::from_utf8(rsp.payload.unwrap()[..len].to_vec()).unwrap())
669}
670
671#[derive(Debug)]
672struct VirtioFsFileHandleInner {
673	fuse_nid: Option<u64>,
674	fuse_fh: Option<u64>,
675	offset: usize,
676}
677
678impl VirtioFsFileHandleInner {
679	pub fn new() -> Self {
680		Self {
681			fuse_nid: None,
682			fuse_fh: None,
683			offset: 0,
684		}
685	}
686
687	async fn poll(&self, events: PollEvent) -> io::Result<PollEvent> {
688		static KH: AtomicU64 = AtomicU64::new(0);
689		let kh = KH.fetch_add(1, Ordering::SeqCst);
690
691		future::poll_fn(|cx| {
692			let Some(nid) = self.fuse_nid else {
693				return Poll::Ready(Ok(PollEvent::POLLERR));
694			};
695
696			let Some(fh) = self.fuse_fh else {
697				return Poll::Ready(Ok(PollEvent::POLLERR));
698			};
699
700			let (cmd, rsp_payload_len) = ops::Poll::create(nid, fh, kh, events);
701			let rsp = get_filesystem_driver()
702				.ok_or(Errno::Nosys)?
703				.lock()
704				.send_command(cmd, rsp_payload_len)?;
705
706			if rsp.headers.out_header.error < 0 {
707				return Poll::Ready(Err(Errno::Io));
708			}
709
710			let revents =
711				PollEvent::from_bits(i16::try_from(rsp.headers.op_header.revents).unwrap())
712					.unwrap();
713			if !revents.intersects(events)
714				&& !revents
715					.intersects(PollEvent::POLLERR | PollEvent::POLLNVAL | PollEvent::POLLHUP)
716			{
717				// the current implementation use polling to wait for an event
718				// consequently, we have to wakeup the waker, if the the event doesn't arrive
719				cx.waker().wake_by_ref();
720			}
721			Poll::Ready(Ok(revents))
722		})
723		.await
724	}
725
726	fn lseek(&mut self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
727		debug!("virtio-fs lseek: offset: {offset}, whence: {whence:?}");
728
729		// Seek on FUSE file systems seems to be a little odd: All reads are referenced from the
730		// beginning of the file, thus we have to track the offset ourself. Also, a read doesn't
731		// move the read pointer on the remote side, so we can't get the current position using
732		// remote lseek when referencing from `Cur` and we have to use the internally tracked
733		// position instead.
734		match whence {
735			SeekWhence::End | SeekWhence::Data | SeekWhence::Hole => {
736				let nid = self.fuse_nid.ok_or(Errno::Io)?;
737				let fh = self.fuse_fh.ok_or(Errno::Io)?;
738
739				let (cmd, rsp_payload_len) = ops::Lseek::create(nid, fh, offset, whence);
740				let rsp = get_filesystem_driver()
741					.ok_or(Errno::Nosys)?
742					.lock()
743					.send_command(cmd, rsp_payload_len)?;
744
745				if rsp.headers.out_header.error < 0 {
746					return Err(Errno::Io);
747				}
748
749				let rsp_offset = rsp.headers.op_header.offset;
750				self.offset = rsp.headers.op_header.offset.try_into().unwrap();
751
752				Ok(rsp_offset.try_into().unwrap())
753			}
754			SeekWhence::Set => {
755				self.offset = offset.try_into().map_err(|_e| Errno::Inval)?;
756				Ok(self.offset as isize)
757			}
758			SeekWhence::Cur => {
759				self.offset = (self.offset as isize + offset)
760					.try_into()
761					.map_err(|_e| Errno::Inval)?;
762				Ok(self.offset as isize)
763			}
764		}
765	}
766
767	fn fstat(&mut self) -> io::Result<FileAttr> {
768		debug!("virtio-fs getattr");
769
770		let nid = self.fuse_nid.ok_or(Errno::Io)?;
771		let fh = self.fuse_fh.ok_or(Errno::Io)?;
772
773		let (cmd, rsp_payload_len) = ops::Getattr::create(nid, fh, FUSE_GETATTR_FH);
774		let rsp = get_filesystem_driver()
775			.ok_or(Errno::Nosys)?
776			.lock()
777			.send_command(cmd, rsp_payload_len)?;
778
779		if rsp.headers.out_header.error < 0 {
780			return Err(Errno::Io);
781		}
782
783		Ok(rsp.headers.op_header.attr.into())
784	}
785
786	fn set_attr(&mut self, attr: FileAttr, valid: SetAttrValidFields) -> io::Result<FileAttr> {
787		debug!("virtio-fs setattr");
788
789		let nid = self.fuse_nid.ok_or(Errno::Io)?;
790		let fh = self.fuse_fh.ok_or(Errno::Io)?;
791
792		let (cmd, rsp_payload_len) = ops::Setattr::create(nid, fh, attr, valid);
793		let rsp = get_filesystem_driver()
794			.ok_or(Errno::Nosys)?
795			.lock()
796			.send_command(cmd, rsp_payload_len)?;
797
798		if rsp.headers.out_header.error < 0 {
799			return Err(Errno::Io);
800		}
801
802		Ok(rsp.headers.op_header.attr.into())
803	}
804}
805
806impl ErrorType for VirtioFsFileHandleInner {
807	type Error = Errno;
808}
809
810impl Read for VirtioFsFileHandleInner {
811	fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
812		let mut len = buf.len();
813		if len > MAX_READ_LEN {
814			debug!("Reading longer than max_read_len: {len}");
815			len = MAX_READ_LEN;
816		}
817
818		let nid = self.fuse_nid.ok_or(Errno::Io)?;
819		let fh = self.fuse_fh.ok_or(Errno::Io)?;
820
821		let (cmd, rsp_payload_len) =
822			ops::Read::create(nid, fh, len.try_into().unwrap(), self.offset as u64);
823		let rsp = get_filesystem_driver()
824			.ok_or(Errno::Nosys)?
825			.lock()
826			.send_command(cmd, rsp_payload_len)?;
827		let len: usize =
828			if (rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>() >= len {
829				len
830			} else {
831				(rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>()
832			};
833		self.offset += len;
834
835		buf[..len].copy_from_slice(&rsp.payload.unwrap()[..len]);
836
837		Ok(len)
838	}
839}
840
841impl Write for VirtioFsFileHandleInner {
842	fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
843		debug!("virtio-fs write!");
844		let mut truncated_len = buf.len();
845		if truncated_len > MAX_WRITE_LEN {
846			debug!(
847				"Writing longer than max_write_len: {} > {}",
848				buf.len(),
849				MAX_WRITE_LEN
850			);
851			truncated_len = MAX_WRITE_LEN;
852		}
853
854		let nid = self.fuse_nid.ok_or(Errno::Io)?;
855		let fh = self.fuse_fh.ok_or(Errno::Io)?;
856
857		let truncated_buf = Box::<[u8]>::from(&buf[..truncated_len]);
858		let (cmd, rsp_payload_len) = ops::Write::create(nid, fh, truncated_buf, self.offset as u64);
859		let rsp = get_filesystem_driver()
860			.ok_or(Errno::Nosys)?
861			.lock()
862			.send_command(cmd, rsp_payload_len)?;
863
864		if rsp.headers.out_header.error < 0 {
865			return Err(Errno::Io);
866		}
867
868		let rsp_size = rsp.headers.op_header.size;
869		let rsp_len: usize = if rsp_size > u32::try_from(truncated_len).unwrap() {
870			truncated_len
871		} else {
872			rsp_size.try_into().unwrap()
873		};
874		self.offset += rsp_len;
875		Ok(rsp_len)
876	}
877
878	fn flush(&mut self) -> Result<(), Self::Error> {
879		Ok(())
880	}
881}
882
883impl Drop for VirtioFsFileHandleInner {
884	fn drop(&mut self) {
885		let Some(fuse_nid) = self.fuse_nid else {
886			return;
887		};
888
889		let Some(fuse_fh) = self.fuse_fh else {
890			return;
891		};
892
893		let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
894		get_filesystem_driver()
895			.unwrap()
896			.lock()
897			.send_command(cmd, rsp_payload_len)
898			.unwrap();
899	}
900}
901
902pub struct VirtioFsFileHandle(Arc<Mutex<VirtioFsFileHandleInner>>);
903
904impl VirtioFsFileHandle {
905	pub fn new() -> Self {
906		Self(Arc::new(Mutex::new(VirtioFsFileHandleInner::new())))
907	}
908}
909
910impl ObjectInterface for VirtioFsFileHandle {
911	async fn poll(&self, event: PollEvent) -> io::Result<PollEvent> {
912		self.0.lock().await.poll(event).await
913	}
914
915	async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
916		self.0.lock().await.read(buf)
917	}
918
919	async fn write(&self, buf: &[u8]) -> io::Result<usize> {
920		self.0.lock().await.write(buf)
921	}
922
923	async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
924		self.0.lock().await.lseek(offset, whence)
925	}
926
927	async fn fstat(&self) -> io::Result<FileAttr> {
928		self.0.lock().await.fstat()
929	}
930
931	async fn truncate(&self, size: usize) -> io::Result<()> {
932		let attr = FileAttr {
933			st_size: size.try_into().unwrap(),
934			..FileAttr::default()
935		};
936
937		self.0
938			.lock()
939			.await
940			.set_attr(attr, SetAttrValidFields::FATTR_SIZE)
941			.map(|_| ())
942	}
943
944	async fn chmod(&self, access_permission: AccessPermission) -> io::Result<()> {
945		let attr = FileAttr {
946			st_mode: access_permission,
947			..FileAttr::default()
948		};
949
950		self.0
951			.lock()
952			.await
953			.set_attr(attr, SetAttrValidFields::FATTR_MODE)
954			.map(|_| ())
955	}
956}
957
958impl Clone for VirtioFsFileHandle {
959	fn clone(&self) -> Self {
960		warn!("VirtioFsFileHandle: clone not tested");
961		Self(self.0.clone())
962	}
963}
964
965pub struct VirtioFsDirectoryHandle {
966	name: String,
967	read_position: Mutex<usize>,
968}
969
970impl VirtioFsDirectoryHandle {
971	pub fn new(name: String) -> Self {
972		Self {
973			name,
974			read_position: Mutex::new(0),
975		}
976	}
977}
978
979impl ObjectInterface for VirtioFsDirectoryHandle {
980	async fn getdents(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
981		let path = if self.name.is_empty() {
982			CString::new("/").unwrap()
983		} else {
984			let path = ["/", &self.name].join("");
985			CString::new(path).unwrap()
986		};
987
988		debug!("virtio-fs opendir: {path:#?}");
989
990		let fuse_nid = lookup(path.clone()).ok_or(Errno::Noent)?;
991
992		// Opendir
993		// Flag 0x10000 for O_DIRECTORY might not be necessary
994		let (mut cmd, rsp_payload_len) = ops::Open::create(fuse_nid, 0x10000);
995		cmd.headers.in_header.opcode = fuse_opcode::FUSE_OPENDIR as u32;
996		let rsp = get_filesystem_driver()
997			.ok_or(Errno::Nosys)?
998			.lock()
999			.send_command(cmd, rsp_payload_len)?;
1000		let fuse_fh = rsp.headers.op_header.fh;
1001
1002		debug!("virtio-fs readdir: {path:#?}");
1003
1004		// Linux seems to allocate a single page to store the dirfile
1005		let len = MAX_READ_LEN as u32;
1006		let rsp_offset: &mut usize = &mut *self.read_position.lock().await;
1007		let mut buf_offset: usize = 0;
1008
1009		// read content of the directory
1010		let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
1011		cmd.headers.in_header.opcode = fuse_opcode::FUSE_READDIR as u32;
1012		let rsp = get_filesystem_driver()
1013			.ok_or(Errno::Nosys)?
1014			.lock()
1015			.send_command(cmd, rsp_payload_len)?;
1016
1017		let len = usize::min(
1018			MAX_READ_LEN,
1019			rsp.headers.out_header.len as usize - size_of::<fuse_out_header>(),
1020		);
1021
1022		if len <= size_of::<fuse_dirent>() {
1023			debug!("virtio-fs no new dirs");
1024			return Err(Errno::Noent);
1025		}
1026
1027		let mut ret = 0;
1028
1029		while (rsp.headers.out_header.len as usize) - *rsp_offset > size_of::<fuse_dirent>() {
1030			let dirent = unsafe {
1031				&*rsp
1032					.payload
1033					.as_ref()
1034					.unwrap()
1035					.as_ptr()
1036					.byte_add(*rsp_offset)
1037					.cast::<fuse_dirent>()
1038			};
1039
1040			let dirent_len = mem::offset_of!(Dirent64, d_name) + dirent.namelen as usize + 1;
1041			let next_dirent = (buf_offset + dirent_len).align_up(align_of::<Dirent64>());
1042
1043			if next_dirent > buf.len() {
1044				// target buffer full -> we return the nr. of bytes written (like linux does)
1045				break;
1046			}
1047
1048			// could be replaced with slice_as_ptr once maybe_uninit_slice is stabilized.
1049			let target_dirent = buf[buf_offset].as_mut_ptr().cast::<Dirent64>();
1050			unsafe {
1051				target_dirent.write(Dirent64 {
1052					d_ino: dirent.ino,
1053					d_off: 0,
1054					d_reclen: (dirent_len.align_up(align_of::<Dirent64>()))
1055						.try_into()
1056						.unwrap(),
1057					d_type: (dirent.type_ as u8).try_into().unwrap(),
1058					d_name: PhantomData {},
1059				});
1060				let nameptr = ptr::from_mut(&mut (*(target_dirent)).d_name).cast::<u8>();
1061				nameptr.copy_from_nonoverlapping(
1062					dirent.name.as_ptr().cast::<u8>(),
1063					dirent.namelen as usize,
1064				);
1065				nameptr.add(dirent.namelen as usize).write(0); // zero termination
1066			}
1067
1068			*rsp_offset += size_of::<fuse_dirent>() + dirent.namelen as usize;
1069			// Align to dirent struct
1070			*rsp_offset = ((*rsp_offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
1071			buf_offset = next_dirent;
1072			ret = buf_offset;
1073		}
1074
1075		let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
1076		get_filesystem_driver()
1077			.unwrap()
1078			.lock()
1079			.send_command(cmd, rsp_payload_len)?;
1080
1081		Ok(ret)
1082	}
1083
1084	/// lseek for a directory entry is the equivalent for seekdir on linux. But on Hermit this is
1085	/// logically the same operation, so we can just use the same fn in the backend.
1086	/// Any other offset than 0 is not supported. (Mostly because it doesn't make any sense, as
1087	/// userspace applications have no way of knowing valid offsets)
1088	async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
1089		if whence != SeekWhence::Set && offset != 0 {
1090			error!("Invalid offset for directory lseek ({offset})");
1091			return Err(Errno::Inval);
1092		}
1093		*self.read_position.lock().await = offset as usize;
1094		Ok(offset)
1095	}
1096}
1097
1098#[derive(Debug)]
1099pub(crate) struct VirtioFsDirectory {
1100	/// The external path of this directory.
1101	///
1102	/// Before talking to virtio-fs, the relative path inside this directory is
1103	/// adjoined with this prefix.
1104	prefix: String,
1105	attr: FileAttr,
1106}
1107
1108impl VirtioFsDirectory {
1109	pub fn new(prefix: String) -> Self {
1110		let microseconds = arch::kernel::systemtime::now_micros();
1111		let t = timespec::from_usec(microseconds as i64);
1112
1113		VirtioFsDirectory {
1114			prefix,
1115			attr: FileAttr {
1116				st_mode: AccessPermission::from_bits(0o777).unwrap() | AccessPermission::S_IFDIR,
1117				st_atim: t,
1118				st_mtim: t,
1119				st_ctim: t,
1120				..Default::default()
1121			},
1122		}
1123	}
1124
1125	fn traversal_path(&self, path: &str) -> CString {
1126		let prefix = self.prefix.as_str();
1127		let prefix = prefix.strip_suffix("/").unwrap_or(prefix);
1128		let path = [prefix, path].join("/");
1129		CString::new(path).unwrap()
1130	}
1131}
1132
1133impl VfsNode for VirtioFsDirectory {
1134	/// Returns the node type
1135	fn get_kind(&self) -> NodeKind {
1136		NodeKind::Directory
1137	}
1138
1139	fn get_file_attributes(&self) -> io::Result<FileAttr> {
1140		Ok(self.attr)
1141	}
1142
1143	fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
1144		Ok(Arc::new(async_lock::RwLock::new(
1145			VirtioFsDirectoryHandle::new(self.prefix.clone()).into(),
1146		)))
1147	}
1148
1149	fn traverse_readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
1150		let path = self.traversal_path(path);
1151
1152		debug!("virtio-fs opendir: {path:#?}");
1153
1154		let fuse_nid = lookup(path.clone()).ok_or(Errno::Noent)?;
1155
1156		// Opendir
1157		// Flag 0x10000 for O_DIRECTORY might not be necessary
1158		let (mut cmd, rsp_payload_len) = ops::Open::create(fuse_nid, 0x10000);
1159		cmd.headers.in_header.opcode = fuse_opcode::FUSE_OPENDIR as u32;
1160		let rsp = get_filesystem_driver()
1161			.ok_or(Errno::Nosys)?
1162			.lock()
1163			.send_command(cmd, rsp_payload_len)?;
1164		let fuse_fh = rsp.headers.op_header.fh;
1165
1166		debug!("virtio-fs readdir: {path:#?}");
1167
1168		// Linux seems to allocate a single page to store the dirfile
1169		let len = MAX_READ_LEN as u32;
1170		let mut offset: usize = 0;
1171
1172		// read content of the directory
1173		let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
1174		cmd.headers.in_header.opcode = fuse_opcode::FUSE_READDIR as u32;
1175		let rsp = get_filesystem_driver()
1176			.ok_or(Errno::Nosys)?
1177			.lock()
1178			.send_command(cmd, rsp_payload_len)?;
1179
1180		let len: usize = if rsp.headers.out_header.len as usize - size_of::<fuse_out_header>()
1181			>= usize::try_from(len).unwrap()
1182		{
1183			len.try_into().unwrap()
1184		} else {
1185			(rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>()
1186		};
1187
1188		if len <= size_of::<fuse_dirent>() {
1189			debug!("virtio-fs no new dirs");
1190			return Err(Errno::Noent);
1191		}
1192
1193		let mut entries: Vec<DirectoryEntry> = Vec::new();
1194		while (rsp.headers.out_header.len as usize) - offset > size_of::<fuse_dirent>() {
1195			let dirent = unsafe {
1196				&*rsp
1197					.payload
1198					.as_ref()
1199					.unwrap()
1200					.as_ptr()
1201					.byte_add(offset)
1202					.cast::<fuse_dirent>()
1203			};
1204
1205			offset += size_of::<fuse_dirent>() + dirent.namelen as usize;
1206			// Align to dirent struct
1207			offset = ((offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
1208
1209			let name: &'static [u8] = unsafe {
1210				slice::from_raw_parts(
1211					dirent.name.as_ptr().cast(),
1212					dirent.namelen.try_into().unwrap(),
1213				)
1214			};
1215			entries.push(DirectoryEntry::new(unsafe {
1216				core::str::from_utf8_unchecked(name).to_owned()
1217			}));
1218		}
1219
1220		let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
1221		get_filesystem_driver()
1222			.unwrap()
1223			.lock()
1224			.send_command(cmd, rsp_payload_len)?;
1225
1226		Ok(entries)
1227	}
1228
1229	fn traverse_stat(&self, path: &str) -> io::Result<FileAttr> {
1230		let path = self.traversal_path(path);
1231
1232		debug!("virtio-fs stat: {path:#?}");
1233
1234		// Is there a better way to implement this?
1235		let (cmd, rsp_payload_len) = ops::Lookup::create(path);
1236		let rsp = get_filesystem_driver()
1237			.unwrap()
1238			.lock()
1239			.send_command(cmd, rsp_payload_len)?;
1240
1241		if rsp.headers.out_header.error != 0 {
1242			return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap());
1243		}
1244
1245		let entry_out = rsp.headers.op_header;
1246		let attr = entry_out.attr;
1247
1248		if attr.mode & S_IFMT != S_IFLNK {
1249			return Ok(FileAttr::from(attr));
1250		}
1251
1252		let path = readlink(entry_out.nodeid)?;
1253		self.traverse_stat(&path)
1254	}
1255
1256	fn traverse_lstat(&self, path: &str) -> io::Result<FileAttr> {
1257		let path = self.traversal_path(path);
1258
1259		debug!("virtio-fs lstat: {path:#?}");
1260
1261		let (cmd, rsp_payload_len) = ops::Lookup::create(path);
1262		let rsp = get_filesystem_driver()
1263			.unwrap()
1264			.lock()
1265			.send_command(cmd, rsp_payload_len)?;
1266		Ok(FileAttr::from(rsp.headers.op_header.attr))
1267	}
1268
1269	fn traverse_open(
1270		&self,
1271		path: &str,
1272		opt: OpenOption,
1273		mode: AccessPermission,
1274	) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
1275		let path = self.traversal_path(path);
1276
1277		debug!("virtio-fs open: {path:#?}, {opt:?} {mode:?}");
1278
1279		if opt.contains(OpenOption::O_DIRECTORY) {
1280			if opt.contains(OpenOption::O_CREAT) {
1281				// See https://lwn.net/Articles/926782/
1282				warn!("O_DIRECTORY and O_CREAT are together invalid as open options.");
1283				return Err(Errno::Inval);
1284			}
1285
1286			let (cmd, rsp_payload_len) = ops::Lookup::create(path.clone());
1287			let rsp = get_filesystem_driver()
1288				.unwrap()
1289				.lock()
1290				.send_command(cmd, rsp_payload_len)?;
1291
1292			let attr = FileAttr::from(rsp.headers.op_header.attr);
1293			if !attr.st_mode.contains(AccessPermission::S_IFDIR) {
1294				return Err(Errno::Notdir);
1295			}
1296
1297			let mut path = path.into_string().unwrap();
1298			path.remove(0);
1299			return Ok(Arc::new(async_lock::RwLock::new(
1300				VirtioFsDirectoryHandle::new(path).into(),
1301			)));
1302		}
1303
1304		let file = VirtioFsFileHandle::new();
1305
1306		// 1.FUSE_INIT to create session
1307		// Already done
1308		let mut file_guard = block_on(async { Ok(file.0.lock().await) }, None)?;
1309
1310		// Differentiate between opening and creating new file, since FUSE does not support O_CREAT on open.
1311		if opt.contains(OpenOption::O_CREAT) {
1312			// Create file (opens implicitly, returns results from both lookup and open calls)
1313			let (cmd, rsp_payload_len) =
1314				ops::Create::create(path, opt.bits().try_into().unwrap(), mode.bits());
1315			let rsp = get_filesystem_driver()
1316				.ok_or(Errno::Nosys)?
1317				.lock()
1318				.send_command(cmd, rsp_payload_len)?;
1319
1320			let inner = rsp.headers.op_header;
1321			file_guard.fuse_nid = Some(inner.entry.nodeid);
1322			file_guard.fuse_fh = Some(inner.open.fh);
1323		} else {
1324			// 2.FUSE_LOOKUP(FUSE_ROOT_ID, “foo”) -> nodeid
1325			file_guard.fuse_nid = lookup(path);
1326
1327			if file_guard.fuse_nid.is_none() {
1328				warn!("virtio-fs lookup seems to have failed!");
1329				return Err(Errno::Noent);
1330			}
1331
1332			// 3.FUSE_OPEN(nodeid, O_RDONLY) -> fh
1333			let (cmd, rsp_payload_len) =
1334				ops::Open::create(file_guard.fuse_nid.unwrap(), opt.bits().try_into().unwrap());
1335			let rsp = get_filesystem_driver()
1336				.ok_or(Errno::Nosys)?
1337				.lock()
1338				.send_command(cmd, rsp_payload_len)?;
1339			file_guard.fuse_fh = Some(rsp.headers.op_header.fh);
1340		}
1341
1342		drop(file_guard);
1343
1344		Ok(Arc::new(async_lock::RwLock::new(file.into())))
1345	}
1346
1347	fn traverse_unlink(&self, path: &str) -> io::Result<()> {
1348		let path = self.traversal_path(path);
1349
1350		let (cmd, rsp_payload_len) = ops::Unlink::create(path);
1351		let rsp = get_filesystem_driver()
1352			.ok_or(Errno::Nosys)?
1353			.lock()
1354			.send_command(cmd, rsp_payload_len)?;
1355		trace!("unlink answer {rsp:?}");
1356
1357		Ok(())
1358	}
1359
1360	fn traverse_rmdir(&self, path: &str) -> io::Result<()> {
1361		let path = self.traversal_path(path);
1362
1363		let (cmd, rsp_payload_len) = ops::Rmdir::create(path);
1364		let rsp = get_filesystem_driver()
1365			.ok_or(Errno::Nosys)?
1366			.lock()
1367			.send_command(cmd, rsp_payload_len)?;
1368		trace!("rmdir answer {rsp:?}");
1369
1370		Ok(())
1371	}
1372
1373	fn traverse_mkdir(&self, path: &str, mode: AccessPermission) -> io::Result<()> {
1374		let path = self.traversal_path(path);
1375		let (cmd, rsp_payload_len) = ops::Mkdir::create(path, mode.bits());
1376
1377		let rsp = get_filesystem_driver()
1378			.ok_or(Errno::Nosys)?
1379			.lock()
1380			.send_command(cmd, rsp_payload_len)?;
1381		if rsp.headers.out_header.error != 0 {
1382			return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap());
1383		}
1384
1385		Ok(())
1386	}
1387}
1388
1389pub(crate) fn init() {
1390	debug!("Try to initialize virtio-fs filesystem");
1391
1392	let Some(driver) = get_filesystem_driver() else {
1393		return;
1394	};
1395
1396	let (cmd, rsp_payload_len) = ops::Init::create();
1397	let rsp = driver.lock().send_command(cmd, rsp_payload_len).unwrap();
1398	trace!("virtio-fs init answer: {rsp:?}");
1399
1400	let mount_point = driver.lock().get_mount_point();
1401	if mount_point != "/" {
1402		let mount_point = if mount_point.starts_with('/') {
1403			mount_point
1404		} else {
1405			["/", &mount_point].join("")
1406		};
1407
1408		info!("Mounting virtio-fs at {mount_point}");
1409		fs::FILESYSTEM
1410			.get()
1411			.unwrap()
1412			.mount(
1413				mount_point.as_str(),
1414				Box::new(VirtioFsDirectory::new("/".to_owned())),
1415			)
1416			.expect("Mount failed. Invalid mount_point?");
1417		return;
1418	}
1419
1420	let fuse_nid = lookup(c"/".to_owned()).unwrap();
1421	// Opendir
1422	// Flag 0x10000 for O_DIRECTORY might not be necessary
1423	let (mut cmd, rsp_payload_len) = ops::Open::create(fuse_nid, 0x10000);
1424	cmd.headers.in_header.opcode = fuse_opcode::FUSE_OPENDIR as u32;
1425	let rsp = get_filesystem_driver()
1426		.unwrap()
1427		.lock()
1428		.send_command(cmd, rsp_payload_len)
1429		.unwrap();
1430	let fuse_fh = rsp.headers.op_header.fh;
1431
1432	// Linux seems to allocate a single page to store the dirfile
1433	let len = MAX_READ_LEN as u32;
1434	let mut offset: usize = 0;
1435
1436	// read content of the directory
1437	let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
1438	cmd.headers.in_header.opcode = fuse_opcode::FUSE_READDIR as u32;
1439	let rsp = get_filesystem_driver()
1440		.unwrap()
1441		.lock()
1442		.send_command(cmd, rsp_payload_len)
1443		.unwrap();
1444
1445	let len: usize = if rsp.headers.out_header.len as usize - size_of::<fuse_out_header>()
1446		>= usize::try_from(len).unwrap()
1447	{
1448		len.try_into().unwrap()
1449	} else {
1450		(rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>()
1451	};
1452
1453	assert!(len > size_of::<fuse_dirent>(), "virtio-fs no new dirs");
1454
1455	let mut entries: Vec<String> = Vec::new();
1456	while (rsp.headers.out_header.len as usize) - offset > size_of::<fuse_dirent>() {
1457		let dirent = unsafe {
1458			&*rsp
1459				.payload
1460				.as_ref()
1461				.unwrap()
1462				.as_ptr()
1463				.byte_add(offset)
1464				.cast::<fuse_dirent>()
1465		};
1466
1467		offset += size_of::<fuse_dirent>() + dirent.namelen as usize;
1468		// Align to dirent struct
1469		offset = ((offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
1470
1471		let name: &'static [u8] = unsafe {
1472			slice::from_raw_parts(
1473				dirent.name.as_ptr().cast(),
1474				dirent.namelen.try_into().unwrap(),
1475			)
1476		};
1477		entries.push(unsafe { core::str::from_utf8_unchecked(name).to_owned() });
1478	}
1479
1480	let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
1481	get_filesystem_driver()
1482		.unwrap()
1483		.lock()
1484		.send_command(cmd, rsp_payload_len)
1485		.unwrap();
1486
1487	// remove predefined directories
1488	entries.retain(|x| x != ".");
1489	entries.retain(|x| x != "..");
1490	entries.retain(|x| x != "tmp");
1491	entries.retain(|x| x != "proc");
1492	warn!(
1493		"virtio-fs don't mount the host directories 'tmp' and 'proc' into the guest file system!"
1494	);
1495
1496	for entry in entries {
1497		let i_cstr = CString::new(entry.as_str()).unwrap();
1498		let (cmd, rsp_payload_len) = ops::Lookup::create(i_cstr);
1499		let rsp = get_filesystem_driver()
1500			.unwrap()
1501			.lock()
1502			.send_command(cmd, rsp_payload_len)
1503			.unwrap();
1504
1505		let attr = FileAttr::from(rsp.headers.op_header.attr);
1506		if attr.st_mode.contains(AccessPermission::S_IFDIR) {
1507			let path = ["/", &entry].join("");
1508			info!("virtio-fs mount {entry} to {path}");
1509			fs::FILESYSTEM
1510				.get()
1511				.unwrap()
1512				.mount(&path, Box::new(VirtioFsDirectory::new(path.clone())))
1513				.expect("Mount failed. Invalid mount_point?");
1514		} else {
1515			warn!("virtio-fs don't mount {entry}. It isn't a directory!");
1516		}
1517	}
1518}