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				if ret == 0 {
1045					// Buffer too small to hold even one entry
1046					return Err(Errno::Inval);
1047				}
1048				// Buffer full -> return bytes written so far; caller retries from rsp_offset
1049				break;
1050			}
1051
1052			// could be replaced with slice_as_ptr once maybe_uninit_slice is stabilized.
1053			let target_dirent = buf[buf_offset].as_mut_ptr().cast::<Dirent64>();
1054			unsafe {
1055				target_dirent.write(Dirent64 {
1056					d_ino: dirent.ino,
1057					d_off: 0,
1058					d_reclen: (dirent_len.align_up(align_of::<Dirent64>()))
1059						.try_into()
1060						.unwrap(),
1061					d_type: (dirent.type_ as u8).try_into().unwrap(),
1062					d_name: PhantomData {},
1063				});
1064				let nameptr = ptr::from_mut(&mut (*(target_dirent)).d_name).cast::<u8>();
1065				nameptr.copy_from_nonoverlapping(
1066					dirent.name.as_ptr().cast::<u8>(),
1067					dirent.namelen as usize,
1068				);
1069				nameptr.add(dirent.namelen as usize).write(0); // zero termination
1070			}
1071
1072			*rsp_offset += size_of::<fuse_dirent>() + dirent.namelen as usize;
1073			// Align to dirent struct
1074			*rsp_offset = ((*rsp_offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
1075			buf_offset = next_dirent;
1076			ret = buf_offset;
1077		}
1078
1079		let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
1080		get_filesystem_driver()
1081			.unwrap()
1082			.lock()
1083			.send_command(cmd, rsp_payload_len)?;
1084
1085		Ok(ret)
1086	}
1087
1088	/// lseek for a directory entry is the equivalent for seekdir on linux. But on Hermit this is
1089	/// logically the same operation, so we can just use the same fn in the backend.
1090	/// Any other offset than 0 is not supported. (Mostly because it doesn't make any sense, as
1091	/// userspace applications have no way of knowing valid offsets)
1092	async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
1093		if whence != SeekWhence::Set && offset != 0 {
1094			error!("Invalid offset for directory lseek ({offset})");
1095			return Err(Errno::Inval);
1096		}
1097		*self.read_position.lock().await = offset as usize;
1098		Ok(offset)
1099	}
1100}
1101
1102#[derive(Debug)]
1103pub(crate) struct VirtioFsDirectory {
1104	/// The external path of this directory.
1105	///
1106	/// Before talking to virtio-fs, the relative path inside this directory is
1107	/// adjoined with this prefix.
1108	prefix: String,
1109	attr: FileAttr,
1110}
1111
1112impl VirtioFsDirectory {
1113	pub fn new(prefix: String) -> Self {
1114		let microseconds = arch::kernel::systemtime::now_micros();
1115		let t = timespec::from_usec(microseconds as i64);
1116
1117		VirtioFsDirectory {
1118			prefix,
1119			attr: FileAttr {
1120				st_mode: AccessPermission::from_bits(0o777).unwrap() | AccessPermission::S_IFDIR,
1121				st_atim: t,
1122				st_mtim: t,
1123				st_ctim: t,
1124				..Default::default()
1125			},
1126		}
1127	}
1128
1129	fn traversal_path(&self, path: &str) -> CString {
1130		let prefix = self.prefix.as_str();
1131		let prefix = prefix.strip_suffix("/").unwrap_or(prefix);
1132		let path = [prefix, path].join("/");
1133		CString::new(path).unwrap()
1134	}
1135}
1136
1137impl VfsNode for VirtioFsDirectory {
1138	/// Returns the node type
1139	fn get_kind(&self) -> NodeKind {
1140		NodeKind::Directory
1141	}
1142
1143	fn get_file_attributes(&self) -> io::Result<FileAttr> {
1144		Ok(self.attr)
1145	}
1146
1147	fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
1148		Ok(Arc::new(async_lock::RwLock::new(
1149			VirtioFsDirectoryHandle::new(self.prefix.clone()).into(),
1150		)))
1151	}
1152
1153	fn traverse_readdir(&self, path: &str) -> io::Result<Vec<DirectoryEntry>> {
1154		let path = self.traversal_path(path);
1155
1156		debug!("virtio-fs opendir: {path:#?}");
1157
1158		let fuse_nid = lookup(path.clone()).ok_or(Errno::Noent)?;
1159
1160		// Opendir
1161		// Flag 0x10000 for O_DIRECTORY might not be necessary
1162		let (mut cmd, rsp_payload_len) = ops::Open::create(fuse_nid, 0x10000);
1163		cmd.headers.in_header.opcode = fuse_opcode::FUSE_OPENDIR as u32;
1164		let rsp = get_filesystem_driver()
1165			.ok_or(Errno::Nosys)?
1166			.lock()
1167			.send_command(cmd, rsp_payload_len)?;
1168		let fuse_fh = rsp.headers.op_header.fh;
1169
1170		debug!("virtio-fs readdir: {path:#?}");
1171
1172		// Linux seems to allocate a single page to store the dirfile
1173		let len = MAX_READ_LEN as u32;
1174		let mut offset: usize = 0;
1175
1176		// read content of the directory
1177		let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
1178		cmd.headers.in_header.opcode = fuse_opcode::FUSE_READDIR as u32;
1179		let rsp = get_filesystem_driver()
1180			.ok_or(Errno::Nosys)?
1181			.lock()
1182			.send_command(cmd, rsp_payload_len)?;
1183
1184		let len: usize = if rsp.headers.out_header.len as usize - size_of::<fuse_out_header>()
1185			>= usize::try_from(len).unwrap()
1186		{
1187			len.try_into().unwrap()
1188		} else {
1189			(rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>()
1190		};
1191
1192		if len <= size_of::<fuse_dirent>() {
1193			debug!("virtio-fs no new dirs");
1194			return Err(Errno::Noent);
1195		}
1196
1197		let mut entries: Vec<DirectoryEntry> = Vec::new();
1198		while (rsp.headers.out_header.len as usize) - offset > size_of::<fuse_dirent>() {
1199			let dirent = unsafe {
1200				&*rsp
1201					.payload
1202					.as_ref()
1203					.unwrap()
1204					.as_ptr()
1205					.byte_add(offset)
1206					.cast::<fuse_dirent>()
1207			};
1208
1209			offset += size_of::<fuse_dirent>() + dirent.namelen as usize;
1210			// Align to dirent struct
1211			offset = ((offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
1212
1213			let name: &'static [u8] = unsafe {
1214				slice::from_raw_parts(
1215					dirent.name.as_ptr().cast(),
1216					dirent.namelen.try_into().unwrap(),
1217				)
1218			};
1219			entries.push(DirectoryEntry::new(unsafe {
1220				core::str::from_utf8_unchecked(name).to_owned()
1221			}));
1222		}
1223
1224		let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
1225		get_filesystem_driver()
1226			.unwrap()
1227			.lock()
1228			.send_command(cmd, rsp_payload_len)?;
1229
1230		Ok(entries)
1231	}
1232
1233	fn traverse_stat(&self, path: &str) -> io::Result<FileAttr> {
1234		let path = self.traversal_path(path);
1235
1236		debug!("virtio-fs stat: {path:#?}");
1237
1238		// Is there a better way to implement this?
1239		let (cmd, rsp_payload_len) = ops::Lookup::create(path);
1240		let rsp = get_filesystem_driver()
1241			.unwrap()
1242			.lock()
1243			.send_command(cmd, rsp_payload_len)?;
1244
1245		if rsp.headers.out_header.error != 0 {
1246			return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap());
1247		}
1248
1249		let entry_out = rsp.headers.op_header;
1250		let attr = entry_out.attr;
1251
1252		if attr.mode & S_IFMT != S_IFLNK {
1253			return Ok(FileAttr::from(attr));
1254		}
1255
1256		let path = readlink(entry_out.nodeid)?;
1257		self.traverse_stat(&path)
1258	}
1259
1260	fn traverse_lstat(&self, path: &str) -> io::Result<FileAttr> {
1261		let path = self.traversal_path(path);
1262
1263		debug!("virtio-fs lstat: {path:#?}");
1264
1265		let (cmd, rsp_payload_len) = ops::Lookup::create(path);
1266		let rsp = get_filesystem_driver()
1267			.unwrap()
1268			.lock()
1269			.send_command(cmd, rsp_payload_len)?;
1270		Ok(FileAttr::from(rsp.headers.op_header.attr))
1271	}
1272
1273	fn traverse_open(
1274		&self,
1275		path: &str,
1276		opt: OpenOption,
1277		mode: AccessPermission,
1278	) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
1279		let path = self.traversal_path(path);
1280
1281		debug!("virtio-fs open: {path:#?}, {opt:?} {mode:?}");
1282
1283		if opt.contains(OpenOption::O_DIRECTORY) {
1284			if opt.contains(OpenOption::O_CREAT) {
1285				// See https://lwn.net/Articles/926782/
1286				warn!("O_DIRECTORY and O_CREAT are together invalid as open options.");
1287				return Err(Errno::Inval);
1288			}
1289
1290			let (cmd, rsp_payload_len) = ops::Lookup::create(path.clone());
1291			let rsp = get_filesystem_driver()
1292				.unwrap()
1293				.lock()
1294				.send_command(cmd, rsp_payload_len)?;
1295
1296			let attr = FileAttr::from(rsp.headers.op_header.attr);
1297			if !attr.st_mode.contains(AccessPermission::S_IFDIR) {
1298				return Err(Errno::Notdir);
1299			}
1300
1301			let mut path = path.into_string().unwrap();
1302			path.remove(0);
1303			return Ok(Arc::new(async_lock::RwLock::new(
1304				VirtioFsDirectoryHandle::new(path).into(),
1305			)));
1306		}
1307
1308		let file = VirtioFsFileHandle::new();
1309
1310		// 1.FUSE_INIT to create session
1311		// Already done
1312		let mut file_guard = block_on(async { Ok(file.0.lock().await) }, None)?;
1313
1314		// Differentiate between opening and creating new file, since FUSE does not support O_CREAT on open.
1315		if opt.contains(OpenOption::O_CREAT) {
1316			// Create file (opens implicitly, returns results from both lookup and open calls)
1317			let (cmd, rsp_payload_len) =
1318				ops::Create::create(path, opt.bits().try_into().unwrap(), mode.bits());
1319			let rsp = get_filesystem_driver()
1320				.ok_or(Errno::Nosys)?
1321				.lock()
1322				.send_command(cmd, rsp_payload_len)?;
1323
1324			let inner = rsp.headers.op_header;
1325			file_guard.fuse_nid = Some(inner.entry.nodeid);
1326			file_guard.fuse_fh = Some(inner.open.fh);
1327		} else {
1328			// 2.FUSE_LOOKUP(FUSE_ROOT_ID, “foo”) -> nodeid
1329			file_guard.fuse_nid = lookup(path);
1330
1331			if file_guard.fuse_nid.is_none() {
1332				warn!("virtio-fs lookup seems to have failed!");
1333				return Err(Errno::Noent);
1334			}
1335
1336			// 3.FUSE_OPEN(nodeid, O_RDONLY) -> fh
1337			let (cmd, rsp_payload_len) =
1338				ops::Open::create(file_guard.fuse_nid.unwrap(), opt.bits().try_into().unwrap());
1339			let rsp = get_filesystem_driver()
1340				.ok_or(Errno::Nosys)?
1341				.lock()
1342				.send_command(cmd, rsp_payload_len)?;
1343			file_guard.fuse_fh = Some(rsp.headers.op_header.fh);
1344		}
1345
1346		drop(file_guard);
1347
1348		Ok(Arc::new(async_lock::RwLock::new(file.into())))
1349	}
1350
1351	fn traverse_unlink(&self, path: &str) -> io::Result<()> {
1352		let path = self.traversal_path(path);
1353
1354		let (cmd, rsp_payload_len) = ops::Unlink::create(path);
1355		let rsp = get_filesystem_driver()
1356			.ok_or(Errno::Nosys)?
1357			.lock()
1358			.send_command(cmd, rsp_payload_len)?;
1359		trace!("unlink answer {rsp:?}");
1360
1361		Ok(())
1362	}
1363
1364	fn traverse_rmdir(&self, path: &str) -> io::Result<()> {
1365		let path = self.traversal_path(path);
1366
1367		let (cmd, rsp_payload_len) = ops::Rmdir::create(path);
1368		let rsp = get_filesystem_driver()
1369			.ok_or(Errno::Nosys)?
1370			.lock()
1371			.send_command(cmd, rsp_payload_len)?;
1372		trace!("rmdir answer {rsp:?}");
1373
1374		Ok(())
1375	}
1376
1377	fn traverse_mkdir(&self, path: &str, mode: AccessPermission) -> io::Result<()> {
1378		let path = self.traversal_path(path);
1379		let (cmd, rsp_payload_len) = ops::Mkdir::create(path, mode.bits());
1380
1381		let rsp = get_filesystem_driver()
1382			.ok_or(Errno::Nosys)?
1383			.lock()
1384			.send_command(cmd, rsp_payload_len)?;
1385		if rsp.headers.out_header.error != 0 {
1386			return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap());
1387		}
1388
1389		Ok(())
1390	}
1391}
1392
1393pub(crate) fn init() {
1394	debug!("Try to initialize virtio-fs filesystem");
1395
1396	let Some(driver) = get_filesystem_driver() else {
1397		return;
1398	};
1399
1400	let (cmd, rsp_payload_len) = ops::Init::create();
1401	let rsp = driver.lock().send_command(cmd, rsp_payload_len).unwrap();
1402	trace!("virtio-fs init answer: {rsp:?}");
1403
1404	let mount_point = driver.lock().get_mount_point();
1405	if mount_point != "/" {
1406		let mount_point = if mount_point.starts_with('/') {
1407			mount_point
1408		} else {
1409			["/", &mount_point].join("")
1410		};
1411
1412		info!("Mounting virtio-fs at {mount_point}");
1413		fs::FILESYSTEM
1414			.get()
1415			.unwrap()
1416			.mount(
1417				mount_point.as_str(),
1418				Box::new(VirtioFsDirectory::new("/".to_owned())),
1419			)
1420			.expect("Mount failed. Invalid mount_point?");
1421		return;
1422	}
1423
1424	let fuse_nid = lookup(c"/".to_owned()).unwrap();
1425	// Opendir
1426	// Flag 0x10000 for O_DIRECTORY might not be necessary
1427	let (mut cmd, rsp_payload_len) = ops::Open::create(fuse_nid, 0x10000);
1428	cmd.headers.in_header.opcode = fuse_opcode::FUSE_OPENDIR as u32;
1429	let rsp = get_filesystem_driver()
1430		.unwrap()
1431		.lock()
1432		.send_command(cmd, rsp_payload_len)
1433		.unwrap();
1434	let fuse_fh = rsp.headers.op_header.fh;
1435
1436	// Linux seems to allocate a single page to store the dirfile
1437	let len = MAX_READ_LEN as u32;
1438	let mut offset: usize = 0;
1439
1440	// read content of the directory
1441	let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
1442	cmd.headers.in_header.opcode = fuse_opcode::FUSE_READDIR as u32;
1443	let rsp = get_filesystem_driver()
1444		.unwrap()
1445		.lock()
1446		.send_command(cmd, rsp_payload_len)
1447		.unwrap();
1448
1449	let len: usize = if rsp.headers.out_header.len as usize - size_of::<fuse_out_header>()
1450		>= usize::try_from(len).unwrap()
1451	{
1452		len.try_into().unwrap()
1453	} else {
1454		(rsp.headers.out_header.len as usize) - size_of::<fuse_out_header>()
1455	};
1456
1457	assert!(len > size_of::<fuse_dirent>(), "virtio-fs no new dirs");
1458
1459	let mut entries: Vec<String> = Vec::new();
1460	while (rsp.headers.out_header.len as usize) - offset > size_of::<fuse_dirent>() {
1461		let dirent = unsafe {
1462			&*rsp
1463				.payload
1464				.as_ref()
1465				.unwrap()
1466				.as_ptr()
1467				.byte_add(offset)
1468				.cast::<fuse_dirent>()
1469		};
1470
1471		offset += size_of::<fuse_dirent>() + dirent.namelen as usize;
1472		// Align to dirent struct
1473		offset = ((offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
1474
1475		let name: &'static [u8] = unsafe {
1476			slice::from_raw_parts(
1477				dirent.name.as_ptr().cast(),
1478				dirent.namelen.try_into().unwrap(),
1479			)
1480		};
1481		entries.push(unsafe { core::str::from_utf8_unchecked(name).to_owned() });
1482	}
1483
1484	let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
1485	get_filesystem_driver()
1486		.unwrap()
1487		.lock()
1488		.send_command(cmd, rsp_payload_len)
1489		.unwrap();
1490
1491	// remove predefined directories
1492	entries.retain(|x| x != ".");
1493	entries.retain(|x| x != "..");
1494	entries.retain(|x| x != "tmp");
1495	entries.retain(|x| x != "proc");
1496	warn!(
1497		"virtio-fs don't mount the host directories 'tmp' and 'proc' into the guest file system!"
1498	);
1499
1500	for entry in entries {
1501		let i_cstr = CString::new(entry.as_str()).unwrap();
1502		let (cmd, rsp_payload_len) = ops::Lookup::create(i_cstr);
1503		let rsp = get_filesystem_driver()
1504			.unwrap()
1505			.lock()
1506			.send_command(cmd, rsp_payload_len)
1507			.unwrap();
1508
1509		let attr = FileAttr::from(rsp.headers.op_header.attr);
1510		if attr.st_mode.contains(AccessPermission::S_IFDIR) {
1511			let path = ["/", &entry].join("");
1512			info!("virtio-fs mount {entry} to {path}");
1513			fs::FILESYSTEM
1514				.get()
1515				.unwrap()
1516				.mount(&path, Box::new(VirtioFsDirectory::new(path.clone())))
1517				.expect("Mount failed. Invalid mount_point?");
1518		} else {
1519			warn!("virtio-fs don't mount {entry}. It isn't a directory!");
1520		}
1521	}
1522}