1#![allow(clippy::unused_async)]
5
6use std::convert::AsRef;
7#[cfg(feature = "abi-7-18")]
8use std::ffi::CString;
9use std::ffi::OsStr;
10use std::fmt::Debug;
11use std::fs::File;
12use std::io::{IoSlice, Write};
13use std::os::unix::ffi::OsStrExt;
14use std::time::Duration;
15use std::{mem, slice};
16
17use clippy_utilities::{Cast, OverflowArithmetic};
18use nix::errno::Errno;
19use nix::sys::stat::SFlag;
20use tracing::debug;
21
22use super::abi_marker;
23use super::protocol::{
24 FuseAttr, FuseAttrOut, FuseBMapOut, FuseDirEnt, FuseEntryOut, FuseFileLock, FuseGetXAttrOut,
25 FuseInitOut, FuseKStatFs, FuseLockOut, FuseOpenOut, FuseOutHeader, FuseStatFsOut, FuseWriteOut,
26};
27#[cfg(feature = "abi-7-18")]
28use super::protocol::{FuseNotifyCode::FUSE_NOTIFY_DELETE, FuseNotifyDeleteOut};
29
30pub trait AsIoSliceList {
32 fn as_io_slice_list(&self) -> Vec<IoSlice<'_>>;
34 fn len(&self) -> usize;
36 fn is_empty(&self) -> bool;
38}
39
40impl<T> AsIoSliceList for Vec<T>
43where
44 T: AsIoSlice,
45{
46 fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
47 self.iter().map(T::as_io_slice).collect()
48 }
49
50 fn len(&self) -> usize {
51 self.iter().map(T::len).sum()
52 }
53
54 fn is_empty(&self) -> bool {
55 self.is_empty()
56 }
57}
58
59pub trait CouldBeAsIoSliceList {}
62
63impl<T> AsIoSliceList for T
64where
65 T: AsIoSlice + CouldBeAsIoSliceList,
66{
67 fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
68 vec![self.as_io_slice()]
69 }
70
71 fn len(&self) -> usize {
72 self.len()
73 }
74
75 fn is_empty(&self) -> bool {
76 self.is_empty()
77 }
78}
79
80impl AsIoSliceList for Vec<u8> {
82 fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
83 vec![self.as_io_slice()]
84 }
85
86 fn len(&self) -> usize {
87 self.len()
88 }
89
90 fn is_empty(&self) -> bool {
91 self.is_empty()
92 }
93}
94
95impl AsIoSliceList for () {
97 fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
98 vec![]
99 }
100
101 fn len(&self) -> usize {
102 0
103 }
104
105 fn is_empty(&self) -> bool {
106 true
107 }
108}
109
110impl<U, V> AsIoSliceList for (U, V)
111where
112 U: AsIoSlice,
113 V: AsIoSlice,
114{
115 fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
116 vec![self.0.as_io_slice(), self.1.as_io_slice()]
117 }
118
119 fn len(&self) -> usize {
120 AsIoSlice::len(&self.0).overflow_add(AsIoSlice::len(&self.1))
121 }
122
123 fn is_empty(&self) -> bool {
124 self.0.is_empty() && self.1.is_empty()
125 }
126}
127
128pub trait AsIoSlice {
130 fn as_io_slice(&self) -> IoSlice<'_>;
132 fn can_convert(&self) -> bool;
135 fn len(&self) -> usize;
137 fn is_empty(&self) -> bool;
139}
140
141impl AsIoSlice for Vec<u8> {
142 fn as_io_slice(&self) -> IoSlice<'_> {
143 IoSlice::new(self.as_slice())
144 }
145
146 fn can_convert(&self) -> bool {
147 true
148 }
149
150 fn len(&self) -> usize {
151 self.len()
152 }
153
154 fn is_empty(&self) -> bool {
155 self.is_empty()
156 }
157}
158
159#[derive(Debug)]
161struct ReplyRaw<'a> {
162 unique: u64,
164 file: &'a mut File,
166}
167
168impl<'a> ReplyRaw<'a> {
169 fn new(unique: u64, file: &'a mut File) -> Self {
171 Self { unique, file }
172 }
173
174 #[allow(clippy::needless_pass_by_value)]
176 fn send_raw_message(
177 self,
178 error: i32,
179 data: impl AsIoSliceList + Send + Sync + 'static,
180 ) -> nix::Result<usize> {
181 let header_len = mem::size_of::<FuseOutHeader>();
182
183 let header = FuseOutHeader {
184 len: (header_len.overflow_add(data.len())).cast(),
185 error,
186 unique: self.unique,
187 };
188
189 let header_bytes = abi_marker::as_abi_bytes(&header);
190
191 let (single, mut vecs);
192 let io_slices: &[IoSlice] = if data.len() > 0 {
193 vecs = data.as_io_slice_list();
194 vecs.insert(0, IoSlice::new(header_bytes));
195 vecs.as_slice()
196 } else {
197 single = [IoSlice::new(header_bytes)];
198 &single
199 };
200
201 let wsize = self
202 .file
203 .write_vectored(io_slices)
204 .map_err(|e| Errno::try_from(e).unwrap_or(Errno::EIO))?;
205
206 debug!("sent {} bytes to fuse device successfully", wsize);
207 Ok(wsize)
208 }
209
210 async fn send(self, data: impl AsIoSliceList + Send + Sync + 'static) -> nix::Result<usize> {
212 self.send_raw_message(0_i32, data)
213 }
214
215 async fn send_error_code(self, error_code: Errno) -> nix::Result<usize> {
217 self.send_raw_message(
219 crate::util::convert_nix_errno_to_cint(error_code).overflow_neg(),
220 (),
221 )
222 }
223
224 #[allow(clippy::wildcard_enum_match_arm)]
225 async fn send_error(self, err: AsyncFusexError) -> nix::Result<usize> {
227 match err {
228 AsyncFusexError::InternalErr { source, context } => {
229 let error_code = if let Some(nix_err) =
230 source.root_cause().downcast_ref::<nix::Error>()
231 {
232 if *nix_err == nix::errno::Errno::UnknownErrno {
233 panic!(
234 "should not send nix::errno::Errno::UnknownErrno to FUSE kernel, \
235 the error is: {} ,context is : {:?}",
236 crate::util::format_anyhow_error(&source),
237 context,
238 );
239 } else {
240 nix_err
241 }
242 } else {
243 panic!(
244 "should not send non-nix error to FUSE kernel, the error is: {},context is : {:?}",
245 crate::util::format_anyhow_error(&source),context,
246 );
247 };
248 self.send_error_code(*error_code).await
249 }
250 err => {
251 panic!("should not send non-internal error to FUSE kernel ,the error is : {err}",);
252 }
253 }
254 }
255}
256
257macro_rules! impl_fuse_reply_new_for{
259 {$($t:tt,)+} => {
260 $(impl<'a> $t<'a> {
261 #[must_use]
263 pub fn new(unique: u64, file: &'a mut File) -> Self {
264 Self {
265 reply: ReplyRaw::new(unique, file),
266 }
267 }
268 })+
269 }
270}
271
272impl_fuse_reply_new_for! {
273 ReplyAttr,
274 ReplyBMap,
275 ReplyCreate,
276 ReplyData,
277 ReplyEmpty,
278 ReplyEntry,
279 ReplyInit,
280 ReplyLock,
281 ReplyOpen,
282 ReplyStatFs,
283 ReplyWrite,
284 ReplyXAttr,
285}
286
287use crate::error::AsyncFusexError;
288use crate::fs_util::StatFsParam;
289
290macro_rules! impl_fuse_reply_error_for{
292 {$($t:tt,)+} => {
293 $(impl $t<'_> {
294 #[allow(dead_code)]
295 pub async fn error(self, err: AsyncFusexError) -> nix::Result<usize> {
297 self.reply.send_error(err).await
298 }
299
300 #[allow(dead_code)]
301 pub async fn error_code(self, error_code: Errno) -> nix::Result<usize> {
303 self.reply.send_error_code(error_code).await
304 }
305 })+
306 }
307}
308
309impl_fuse_reply_error_for! {
310 ReplyAttr,
311 ReplyBMap,
312 ReplyCreate,
313 ReplyData,
314 ReplyDirectory,
315 ReplyEmpty,
316 ReplyEntry,
317 ReplyInit,
318 ReplyLock,
319 ReplyOpen,
320 ReplyStatFs,
321 ReplyWrite,
322 ReplyXAttr,
323}
324
325macro_rules! impl_as_ioslice_for {
327 {$($t:ty,)+} => {
328 $(impl AsIoSlice for $t {
329 #[allow(dead_code)]
330 fn as_io_slice(&self) -> IoSlice<'_> {
331 IoSlice::new(abi_marker::as_abi_bytes(self))
332 }
333
334 #[allow(dead_code)]
335 fn can_convert(&self) -> bool {
336 true
337 }
338
339 #[allow(dead_code)]
340 fn len(&self) -> usize {
341 mem::size_of::<Self>()
342 }
343
344 #[allow(dead_code)]
345 fn is_empty(&self) -> bool {
346 AsIoSlice::len(self) == 0
347 }
348 }
349
350 impl CouldBeAsIoSliceList for $t {}
351 )+
352 }
353}
354
355impl_as_ioslice_for! {
356 FuseAttrOut,
357 FuseBMapOut,
358 FuseEntryOut,
359 FuseInitOut,
360 FuseLockOut,
361 FuseOpenOut,
362 FuseStatFsOut,
363 FuseWriteOut,
364 FuseGetXAttrOut,
365}
366#[cfg(feature = "abi-7-18")]
367impl_as_ioslice_for! {
368 FuseNotifyDeleteOut,
369}
370
371#[derive(Debug)]
373pub struct ReplyInit<'a> {
374 reply: ReplyRaw<'a>,
376}
377
378impl ReplyInit<'_> {
379 pub async fn init(self, resp: FuseInitOut) -> nix::Result<usize> {
381 self.reply.send(resp).await
382 }
383}
384
385#[derive(Debug)]
387pub struct ReplyEmpty<'a> {
388 reply: ReplyRaw<'a>,
390}
391
392impl ReplyEmpty<'_> {
393 pub async fn ok(self) -> nix::Result<usize> {
395 self.reply.send(()).await
396 }
397}
398
399#[derive(Debug)]
401pub struct ReplyData<'a> {
402 reply: ReplyRaw<'a>,
404}
405
406impl ReplyData<'_> {
407 pub async fn data(
409 self,
410 bytes: impl AsIoSliceList + Send + Sync + 'static,
411 ) -> nix::Result<usize> {
412 self.reply.send(bytes).await
413 }
414}
415
416#[derive(Debug)]
418pub struct ReplyEntry<'a> {
419 reply: ReplyRaw<'a>,
421}
422
423impl ReplyEntry<'_> {
424 pub async fn entry(self, ttl: Duration, attr: FuseAttr, generation: u64) -> nix::Result<usize> {
426 self.reply
427 .send(FuseEntryOut {
428 nodeid: attr.ino,
429 generation,
430 entry_valid: ttl.as_secs(),
431 attr_valid: ttl.as_secs(),
432 entry_valid_nsec: ttl.subsec_nanos(),
433 attr_valid_nsec: ttl.subsec_nanos(),
434 attr,
435 })
436 .await
437 }
438}
439
440#[derive(Debug)]
442pub struct ReplyAttr<'a> {
443 reply: ReplyRaw<'a>,
445}
446
447impl ReplyAttr<'_> {
448 pub async fn attr(self, ttl: Duration, attr: FuseAttr) -> nix::Result<usize> {
450 self.reply
451 .send(FuseAttrOut {
452 attr_valid: ttl.as_secs(),
453 attr_valid_nsec: ttl.subsec_nanos(),
454 dummy: 0,
455 attr,
456 })
457 .await
458 }
459}
460
461#[derive(Debug)]
463pub struct ReplyOpen<'a> {
464 reply: ReplyRaw<'a>,
466}
467
468impl ReplyOpen<'_> {
469 pub async fn opened(self, fh: u64, flags: u32) -> nix::Result<usize> {
471 self.reply
472 .send(FuseOpenOut {
473 fh,
474 open_flags: flags,
475 padding: 0,
476 })
477 .await
478 }
479}
480
481#[derive(Debug)]
483pub struct ReplyWrite<'a> {
484 reply: ReplyRaw<'a>,
486}
487
488impl ReplyWrite<'_> {
489 pub async fn written(self, size: u32) -> nix::Result<usize> {
491 self.reply.send(FuseWriteOut { size, padding: 0 }).await
492 }
493}
494
495#[derive(Debug)]
497pub struct ReplyStatFs<'a> {
498 reply: ReplyRaw<'a>,
500}
501
502impl ReplyStatFs<'_> {
503 #[allow(dead_code)]
505 pub async fn statfs(self, param: StatFsParam) -> nix::Result<usize> {
506 self.reply
507 .send(FuseStatFsOut {
508 st: FuseKStatFs {
509 blocks: param.blocks,
510 bfree: param.bfree,
511 bavail: param.bavail,
512 files: param.files,
513 ffree: param.f_free,
514 bsize: param.bsize,
515 namelen: param.namelen,
516 frsize: param.frsize,
517 padding: 0,
518 spare: [0; 6],
519 },
520 })
521 .await
522 }
523}
524
525#[derive(Debug)]
527pub struct ReplyCreate<'a> {
528 reply: ReplyRaw<'a>,
530}
531
532impl ReplyCreate<'_> {
533 #[allow(dead_code)]
535 pub async fn created(
536 self,
537 ttl: &Duration,
538 attr: FuseAttr,
539 generation: u64,
540 fh: u64,
541 flags: u32,
542 ) -> nix::Result<usize> {
543 self.reply
544 .send((
545 FuseEntryOut {
546 nodeid: attr.ino,
547 generation,
548 entry_valid: ttl.as_secs(),
549 attr_valid: ttl.as_secs(),
550 entry_valid_nsec: ttl.subsec_nanos(),
551 attr_valid_nsec: ttl.subsec_nanos(),
552 attr,
553 },
554 FuseOpenOut {
555 fh,
556 open_flags: flags,
557 padding: 0,
558 },
559 ))
560 .await
561 }
562}
563
564#[derive(Debug)]
566pub struct ReplyLock<'a> {
567 reply: ReplyRaw<'a>,
569}
570
571impl ReplyLock<'_> {
572 #[allow(dead_code)]
574 pub async fn locked(self, start: u64, end: u64, typ: u32, pid: u32) -> nix::Result<usize> {
575 self.reply
576 .send(FuseLockOut {
577 lk: FuseFileLock {
578 start,
579 end,
580 typ,
581 pid,
582 },
583 })
584 .await
585 }
586}
587
588#[derive(Debug)]
590pub struct ReplyBMap<'a> {
591 reply: ReplyRaw<'a>,
593}
594
595impl ReplyBMap<'_> {
596 #[allow(dead_code)]
598 pub async fn bmap(self, block: u64) -> nix::Result<usize> {
599 self.reply.send(FuseBMapOut { block }).await
600 }
601}
602
603#[derive(Debug)]
605pub struct ReplyDirectory<'a> {
606 reply: ReplyRaw<'a>,
608 data: Vec<u8>,
610}
611
612impl<'a> ReplyDirectory<'a> {
613 #[must_use]
615 pub fn new(unique: u64, file: &'a mut File, size: usize) -> Self {
616 Self {
617 reply: ReplyRaw::new(unique, file),
618 data: Vec::with_capacity(size),
619 }
620 }
621
622 pub fn add<T: AsRef<OsStr>>(&mut self, ino: u64, offset: i64, kind: SFlag, name: T) -> bool {
627 let name_bytes = name.as_ref().as_bytes();
628 let dirent = FuseDirEnt {
629 ino,
630 off: offset.cast(),
631 namelen: name_bytes.len().cast(),
632 typ: crate::util::mode_from_kind_and_perm(kind, 0).overflow_shr(12),
633 };
634 let entlen = dirent.size_with_name();
635
636 let entsize = crate::util::round_up(entlen, mem::size_of::<u64>()); let padlen = entsize.overflow_sub(entlen);
642 if self.data.len().overflow_add(entsize) > self.data.capacity() {
643 return true;
644 }
645
646 let dirent_bytes = unsafe { fuse_dir_ent_in_raw(&dirent) };
654 self.data.extend_from_slice(dirent_bytes);
656
657 self.data.extend_from_slice(name_bytes);
659
660 self.data.extend(std::iter::repeat(0).take(padlen));
662
663 false
664 }
665
666 pub async fn ok(self) -> nix::Result<usize> {
668 self.reply.send(self.data).await
669 }
670}
671
672unsafe fn fuse_dir_ent_in_raw(from: &FuseDirEnt) -> &[u8] {
683 let base: *const u8 = <*const FuseDirEnt>::cast(from);
684 unsafe {
685 let bytes = slice::from_raw_parts(base, mem::size_of::<FuseDirEnt>());
686 bytes
687 }
688}
689
690#[derive(Debug)]
692pub struct ReplyXAttr<'a> {
693 reply: ReplyRaw<'a>,
695}
696
697impl ReplyXAttr<'_> {
698 #[allow(dead_code)]
700 pub async fn size(self, size: u32) -> nix::Result<usize> {
701 self.reply.send(FuseGetXAttrOut { size, padding: 0 }).await
702 }
703
704 #[allow(dead_code)]
706 pub async fn data(self, bytes: FuseGetXAttrOut) -> nix::Result<usize> {
707 self.reply.send(bytes).await
708 }
709}
710
711#[cfg(feature = "abi-7-18")]
712impl AsIoSlice for CString {
713 fn as_io_slice(&self) -> IoSlice<'_> {
714 IoSlice::new(self.as_bytes_with_nul())
715 }
716
717 fn can_convert(&self) -> bool {
718 true
719 }
720
721 fn len(&self) -> usize {
722 self.as_bytes_with_nul().len()
723 }
724
725 fn is_empty(&self) -> bool {
727 false
728 }
729}
730#[cfg(feature = "abi-7-18")]
732#[derive(Debug)]
733pub struct FuseDeleteNotification<'a> {
734 reply: ReplyRaw<'a>,
736}
737
738#[cfg(feature = "abi-7-18")]
739impl<'a> FuseDeleteNotification<'a> {
740 #[must_use]
742 pub fn new(file: &'a mut File) -> Self {
743 Self {
744 reply: ReplyRaw::new(0, file),
745 }
746 }
747
748 pub async fn notify(self, parent: u64, child: u64, name: String) -> nix::Result<usize> {
750 let notify_delete = FuseNotifyDeleteOut {
751 parent,
752 child,
753 namelen: name.len().cast(),
754 padding: 0,
755 };
756 let file_name = CString::new(name.clone())
757 .unwrap_or_else(|e| panic!("failed to create CString for {name}, error is {e:?}"));
758 #[allow(clippy::as_conversions)] self.reply
760 .send_raw_message(FUSE_NOTIFY_DELETE as i32, (notify_delete, file_name))
761 }
762}
763
764#[cfg(test)]
765mod test {
766 use std::fs::File;
767 use std::os::unix::io::FromRawFd;
768 use std::time::Duration;
769
770 use aligned_utils::bytes::AlignedBytes;
771 use anyhow::Context;
772 use nix::fcntl::{self, OFlag};
773 use nix::sys::stat::Mode;
774 use nix::unistd;
775 use tokio::io::{AsyncReadExt, AsyncSeekExt};
776
777 use super::super::de::Deserializer;
778 use super::super::protocol::{FuseAttr, FuseAttrOut, FuseOutHeader};
779 use super::ReplyAttr;
780
781 #[test]
782 fn test_slice() {
783 let s = [1_i32, 2_i32, 3_i32, 4_i32, 5_i32, 6_i32];
784 let v = s.to_owned();
785 println!("{v:?}");
786 let v1 = s.to_vec();
787 println!("{v1:?}");
788
789 let s1 = [1_i32, 2_i32, 3_i32];
790 let s2 = [4_i32, 5_i32, 6_i32];
791 let s3 = [7_i32, 8_i32, 9_i32];
792 let l1 = [&s1];
793 let l2 = [&s2, &s3];
794 let mut v1 = l1.to_vec();
795 v1.extend(&l2);
796
797 println!("{l1:?}");
798 println!("{v1:?}");
799 }
800 #[tokio::test(flavor = "multi_thread")]
801 async fn test_reply_output() -> anyhow::Result<()> {
802 let file_name = "fuse_reply.log";
803 let fd = tokio::task::spawn_blocking(move || {
804 fcntl::open(
805 file_name,
806 OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_RDWR,
807 Mode::all(),
808 )
809 })
810 .await??;
811 tokio::task::spawn_blocking(move || unistd::unlink(file_name)).await??;
812
813 let ino = 64;
814 let size = 64;
815 let blocks = 64;
816 let a_time = 64;
817 let m_time = 64;
818 let c_time = 64;
819 let a_timensec = 32;
820 let m_timensec = 32;
821 let c_timensec = 32;
822 let mode = 32;
823 let nlink = 32;
824 let uid = 32;
825 let g_id = 32;
826 let rdev = 32;
827 #[cfg(feature = "abi-7-9")]
828 let blksize = 32;
829 #[cfg(feature = "abi-7-9")]
830 let padding = 32;
831 let attr = FuseAttr {
832 ino,
833 size,
834 blocks,
835 atime: a_time,
836 mtime: m_time,
837 ctime: c_time,
838 atimensec: a_timensec,
839 mtimensec: m_timensec,
840 ctimensec: c_timensec,
841 mode,
842 nlink,
843 uid,
844 gid: g_id,
845 rdev,
846 #[cfg(feature = "abi-7-9")]
847 blksize,
848 #[cfg(feature = "abi-7-9")]
849 padding,
850 };
851
852 let unique = 12345;
853 let mut file = unsafe { File::from_raw_fd(fd) };
855 let reply_attr = ReplyAttr::new(unique, &mut file);
856 reply_attr.attr(Duration::from_secs(1), attr).await?;
857
858 let mut file =
859 tokio::task::spawn_blocking(move || unsafe { tokio::fs::File::from_raw_fd(fd) })
860 .await?;
861 file.seek(std::io::SeekFrom::Start(0)).await?;
862 let mut bytes = Vec::new();
863 file.read_to_end(&mut bytes).await?;
864
865 let mut aligned_bytes = AlignedBytes::new_zeroed(bytes.len(), 4096);
866 aligned_bytes.copy_from_slice(&bytes);
867
868 let mut de = Deserializer::new(&aligned_bytes);
869 let _foh: &FuseOutHeader = de.fetch_ref().context("failed to fetch FuseOutHeader")?;
870 let fao: &FuseAttrOut = de.fetch_ref().context("failed to fetch FuseAttrOut")?;
871
872 debug_assert_eq!(fao.attr.ino, ino);
873 debug_assert_eq!(fao.attr.size, size);
874 debug_assert_eq!(fao.attr.blocks, blocks);
875 debug_assert_eq!(fao.attr.mtime, m_time);
876 debug_assert_eq!(fao.attr.atime, a_time);
877 debug_assert_eq!(fao.attr.ctime, c_time);
878 Ok(())
879 }
880}