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