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