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