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