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