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