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