1use log::{debug, warn};
2use os_pipe::PipeWriter;
3use std::ffi::CString;
4use std::ffi::OsStr;
5use std::ffi::OsString;
6use std::fs;
7use std::fs::OpenOptions;
8use std::io::Write;
9use std::os::raw::c_int;
10use std::os::unix::ffi::OsStrExt;
11use std::os::unix::ffi::OsStringExt;
12use std::os::unix::fs::FileTypeExt;
13use std::path::{Path, PathBuf};
14use std::thread;
15
16use fuser::{
17 FileAttr, FileType, Filesystem, KernelConfig, ReplyData, ReplyEntry, ReplyOpen, Request,
18 TimeOrNow,
19};
20use nix::errno::Errno;
21use nix::fcntl::OFlag;
22use std::time::{Duration, SystemTime};
23
24use crate::format::{DirEnt, Inode, InodeMode, Result, WireFormatError};
25
26use super::puzzlefs::{file_read, PuzzleFS};
27
28pub enum PipeDescriptor {
29 UnnamedPipe(PipeWriter),
30 NamedPipe(PathBuf),
31}
32
33pub struct Fuse {
34 pfs: PuzzleFS,
35 sender: Option<std::sync::mpsc::Sender<()>>,
36 init_notify: Option<PipeDescriptor>,
37 }
40
41fn mode_to_fuse_type(inode: &Inode) -> Result<FileType> {
42 Ok(match inode.mode {
43 InodeMode::File { .. } => FileType::RegularFile,
44 InodeMode::Dir { .. } => FileType::Directory,
45 InodeMode::Fifo { .. } => FileType::NamedPipe,
46 InodeMode::Chr { .. } => FileType::CharDevice,
47 InodeMode::Blk { .. } => FileType::BlockDevice,
48 InodeMode::Lnk { .. } => FileType::Symlink,
49 InodeMode::Sock { .. } => FileType::Socket,
50 _ => return Err(WireFormatError::from_errno(Errno::EINVAL)),
51 })
52}
53
54impl Fuse {
55 pub fn new(
56 pfs: PuzzleFS,
57 sender: Option<std::sync::mpsc::Sender<()>>,
58 init_notify: Option<PipeDescriptor>,
59 ) -> Fuse {
60 Fuse {
61 pfs,
62 sender,
63 init_notify,
64 }
65 }
66
67 fn _lookup(&mut self, parent: u64, name: &OsStr) -> Result<FileAttr> {
68 let dir = self.pfs.find_inode(parent)?;
69 let ino = dir.dir_lookup(name.as_bytes())?;
70 self._getattr(ino)
71 }
72
73 fn _getattr(&mut self, ino: u64) -> Result<FileAttr> {
74 let ic = self.pfs.find_inode(ino)?;
75 let kind = mode_to_fuse_type(&ic)?;
76 let len = ic.file_len().unwrap_or(0);
77 Ok(FileAttr {
78 ino: ic.ino,
79 size: len,
80 blocks: 0,
81 atime: SystemTime::UNIX_EPOCH,
82 mtime: SystemTime::UNIX_EPOCH,
83 ctime: SystemTime::UNIX_EPOCH,
84 crtime: SystemTime::UNIX_EPOCH,
85 kind,
86 perm: ic.permissions,
87 nlink: 0,
88 uid: ic.uid,
89 gid: ic.gid,
90 rdev: 0,
91 blksize: 0,
92 flags: 0,
93 })
94 }
95
96 fn _open(&self, flags_i: i32, reply: ReplyOpen) {
97 let allowed_flags = OFlag::O_RDONLY
98 | OFlag::O_PATH
99 | OFlag::O_NONBLOCK
100 | OFlag::O_DIRECTORY
101 | OFlag::O_NOFOLLOW
102 | OFlag::O_NOATIME;
103 let flags = OFlag::from_bits_truncate(flags_i);
104 if !allowed_flags.contains(flags) {
105 warn!("invalid flags {flags:?}, only allowed {allowed_flags:?}");
106 reply.error(Errno::EROFS as i32)
107 } else {
108 reply.opened(0, flags_i.try_into().unwrap());
110 }
111 }
112
113 fn _read(&mut self, ino: u64, offset: u64, size: u32) -> Result<Vec<u8>> {
114 let inode = self.pfs.find_inode(ino)?;
115 let mut buf = vec![0_u8; size as usize];
116 let read = file_read(
117 &self.pfs.oci,
118 &inode,
119 offset as usize,
120 &mut buf,
121 &self.pfs.verity_data,
122 )?;
123 buf.truncate(read);
124 Ok(buf)
125 }
126
127 fn _readdir(&mut self, ino: u64, offset: i64, reply: &mut fuser::ReplyDirectory) -> Result<()> {
128 let inode = self.pfs.find_inode(ino)?;
129 let entries = inode.dir_entries()?;
130 for (index, DirEnt { name, ino: ino_r }) in entries.iter().enumerate().skip(offset as usize)
131 {
132 let ino = *ino_r;
133 let inode = self.pfs.find_inode(ino)?;
134 let kind = mode_to_fuse_type(&inode)?;
135
136 if reply.add(ino, (index + 1) as i64, kind, OsStr::from_bytes(name)) {
138 break;
139 }
140 }
141
142 Ok(())
143 }
144
145 fn _readlink(&mut self, ino: u64) -> Result<OsString> {
146 let inode = self.pfs.find_inode(ino)?;
147 let error = WireFormatError::from_errno(Errno::EINVAL);
148 let kind = mode_to_fuse_type(&inode)?;
149 match kind {
150 FileType::Symlink => inode
151 .additional
152 .and_then(|add| add.symlink_target.map(OsString::from_vec))
153 .ok_or(error),
154 _ => Err(error),
155 }
156 }
157
158 fn _listxattr(&mut self, ino: u64) -> Result<Vec<u8>> {
159 let inode = self.pfs.find_inode(ino)?;
160 let xattr_list = inode
161 .additional
162 .map(|add| {
163 add.xattrs
164 .iter()
165 .flat_map(|x| {
166 CString::new(x.key.as_slice())
167 .expect("xattr is a valid string")
168 .as_bytes_with_nul()
169 .to_vec()
170 })
171 .collect::<Vec<u8>>()
172 })
173 .unwrap_or_else(Vec::<u8>::new);
174
175 Ok(xattr_list)
176 }
177
178 fn _getxattr(&mut self, ino: u64, name: &OsStr) -> Result<Vec<u8>> {
179 let inode = self.pfs.find_inode(ino)?;
180 inode
181 .additional
182 .and_then(|add| {
183 add.xattrs
184 .into_iter()
185 .find(|elem| elem.key == name.as_bytes())
186 })
187 .map(|xattr| xattr.val)
188 .ok_or_else(|| WireFormatError::from_errno(Errno::ENODATA))
189 }
190}
191
192impl Drop for Fuse {
193 fn drop(&mut self) {
194 if let Some(sender) = &self.sender {
198 sender.send(()).unwrap();
199 }
200 }
201}
202
203impl Filesystem for Fuse {
204 fn init(
205 &mut self,
206 _req: &Request<'_>,
207 _config: &mut KernelConfig,
208 ) -> std::result::Result<(), c_int> {
209 if let Some(init_notify) = self.init_notify.take() {
210 match init_notify {
211 PipeDescriptor::UnnamedPipe(mut pipe_writer) => {
212 if let Err(e) = pipe_writer.write_all(b"s") {
213 warn!("unsuccessful send! {e}");
214 }
215 }
216 PipeDescriptor::NamedPipe(named_pipe) => {
217 thread::spawn(move || {
220 let md = fs::metadata(&named_pipe);
221 match md {
222 Err(e) => {
223 warn!("cannot get file metadata, {e}");
224 return;
225 }
226 Ok(md) => {
227 if !md.file_type().is_fifo() {
228 warn!(
229 "the provided file {} is not a fifo!",
230 named_pipe.display()
231 );
232 return;
233 }
234 }
235 }
236 let file = OpenOptions::new().write(true).open(&named_pipe);
237 match file {
238 Ok(mut file) => {
239 if let Err(e) = file.write_all(b"s") {
240 warn!("cannot write to pipe {}, {e}", named_pipe.display());
241 }
242 }
243 Err(e) => {
244 warn!("cannot open pipe {}, {e}", named_pipe.display());
245 }
246 }
247 });
248 }
249 }
250 }
251 Ok(())
252 }
253
254 fn destroy(&mut self) {}
255 fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {}
256
257 fn setattr(
259 &mut self,
260 _req: &Request<'_>,
261 _ino: u64,
262 _mode: Option<u32>,
263 _uid: Option<u32>,
264 _gid: Option<u32>,
265 _size: Option<u64>,
266 _atime: Option<TimeOrNow>,
267 _mtime: Option<TimeOrNow>,
268 _ctime: Option<SystemTime>,
269 _fh: Option<u64>,
270 _crtime: Option<SystemTime>,
271 _chgtime: Option<SystemTime>,
272 _bkuptime: Option<SystemTime>,
273 _flags: Option<u32>,
274 reply: fuser::ReplyAttr,
275 ) {
276 debug!("setattr not supported!");
277 reply.error(Errno::EROFS as i32)
278 }
279
280 fn mknod(
281 &mut self,
282 _req: &Request<'_>,
283 _parent: u64,
284 _name: &OsStr,
285 _mode: u32,
286 _umask: u32,
287 _rdev: u32,
288 reply: ReplyEntry,
289 ) {
290 debug!("mknod not supported!");
291 reply.error(Errno::EROFS as i32)
292 }
293
294 fn mkdir(
295 &mut self,
296 _req: &Request<'_>,
297 _parent: u64,
298 _name: &OsStr,
299 _mode: u32,
300 _umask: u32,
301 reply: ReplyEntry,
302 ) {
303 debug!("mkdir not supported!");
304 reply.error(Errno::EROFS as i32)
305 }
306
307 fn unlink(
308 &mut self,
309 _req: &Request<'_>,
310 _parent: u64,
311 _name: &OsStr,
312 reply: fuser::ReplyEmpty,
313 ) {
314 debug!("unlink not supported!");
315 reply.error(Errno::EROFS as i32)
316 }
317
318 fn rmdir(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: fuser::ReplyEmpty) {
319 debug!("rmdir not supported!");
320 reply.error(Errno::EROFS as i32)
321 }
322
323 fn symlink(
324 &mut self,
325 _req: &Request<'_>,
326 _parent: u64,
327 _name: &OsStr,
328 _link: &Path,
329 reply: ReplyEntry,
330 ) {
331 debug!("symlink not supported!");
332 reply.error(Errno::EROFS as i32)
333 }
334
335 fn rename(
336 &mut self,
337 _req: &Request<'_>,
338 _parent: u64,
339 _name: &OsStr,
340 _newparent: u64,
341 _newname: &OsStr,
342 _flags: u32,
343 reply: fuser::ReplyEmpty,
344 ) {
345 debug!("rename not supported!");
346 reply.error(Errno::EROFS as i32)
347 }
348
349 fn link(
350 &mut self,
351 _req: &Request<'_>,
352 _ino: u64,
353 _newparent: u64,
354 _newname: &OsStr,
355 reply: ReplyEntry,
356 ) {
357 debug!("link not supported!");
358 reply.error(Errno::EROFS as i32)
359 }
360
361 fn write(
362 &mut self,
363 _req: &Request<'_>,
364 _ino: u64,
365 _fh: u64,
366 _offset: i64,
367 _data: &[u8],
368 _write_flags: u32,
369 _flags: i32,
370 _lock_owner: Option<u64>,
371 reply: fuser::ReplyWrite,
372 ) {
373 debug!("write not supported!");
374 reply.error(Errno::EROFS as i32)
375 }
376
377 fn flush(
378 &mut self,
379 _req: &Request<'_>,
380 _ino: u64,
381 _fh: u64,
382 _lock_owner: u64,
383 reply: fuser::ReplyEmpty,
384 ) {
385 debug!("flush not supported!");
386 reply.error(Errno::ENOSYS as i32)
387 }
388
389 fn fsync(
390 &mut self,
391 _req: &Request<'_>,
392 _ino: u64,
393 _fh: u64,
394 _datasync: bool,
395 reply: fuser::ReplyEmpty,
396 ) {
397 debug!("fsync not supported!");
398 reply.error(Errno::EROFS as i32)
399 }
400
401 fn fsyncdir(
402 &mut self,
403 _req: &Request<'_>,
404 _ino: u64,
405 _fh: u64,
406 _datasync: bool,
407 reply: fuser::ReplyEmpty,
408 ) {
409 debug!("fsyncdir not supported!");
410 reply.error(Errno::EROFS as i32)
411 }
412
413 fn setxattr(
414 &mut self,
415 _req: &Request<'_>,
416 _ino: u64,
417 _name: &OsStr,
418 _value: &[u8],
419 _flags: i32,
420 _position: u32,
421 reply: fuser::ReplyEmpty,
422 ) {
423 reply.error(Errno::EROFS as i32)
424 }
425
426 fn removexattr(
427 &mut self,
428 _req: &Request<'_>,
429 _ino: u64,
430 _name: &OsStr,
431 reply: fuser::ReplyEmpty,
432 ) {
433 debug!("removexattr not supported!");
434 reply.error(Errno::EROFS as i32)
435 }
436
437 fn create(
438 &mut self,
439 _req: &Request<'_>,
440 _parent: u64,
441 _name: &OsStr,
442 _mode: u32,
443 _umask: u32,
444 _flags: i32,
445 reply: fuser::ReplyCreate,
446 ) {
447 debug!("create not supported!");
448 reply.error(Errno::EROFS as i32)
449 }
450
451 fn getlk(
452 &mut self,
453 _req: &Request<'_>,
454 _ino: u64,
455 _fh: u64,
456 _lock_owner: u64,
457 _start: u64,
458 _end: u64,
459 _typ: i32,
460 _pid: u32,
461 reply: fuser::ReplyLock,
462 ) {
463 debug!("getlk not supported!");
464 reply.error(Errno::EROFS as i32)
465 }
466
467 fn setlk(
468 &mut self,
469 _req: &Request<'_>,
470 _ino: u64,
471 _fh: u64,
472 _lock_owner: u64,
473 _start: u64,
474 _end: u64,
475 _typ: i32,
476 _pid: u32,
477 _sleep: bool,
478 reply: fuser::ReplyEmpty,
479 ) {
480 debug!("setlk not supported!");
481 reply.error(Errno::EROFS as i32)
482 }
483
484 fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
485 match self._lookup(parent, name) {
486 Ok(attr) => {
487 let ttl = Duration::new(u64::MAX, 0);
489 let generation = 0;
490 reply.entry(&ttl, &attr, generation)
491 }
492 Err(e) => {
493 debug!("cannot lookup parent: {parent}, name {name:?} {e}!");
494 reply.error(e.to_errno());
495 }
496 }
497 }
498
499 fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: fuser::ReplyAttr) {
500 match self._getattr(ino) {
501 Ok(attr) => {
502 let ttl = Duration::new(u64::MAX, 0);
504 reply.attr(&ttl, &attr)
505 }
506 Err(e) => {
507 debug!("cannot getattr for ino {ino} {e}!");
508 reply.error(e.to_errno())
509 }
510 }
511 }
512
513 fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyData) {
514 match self._readlink(ino) {
515 Ok(symlink) => reply.data(symlink.as_bytes()),
516 Err(e) => {
517 debug!("cannot readlink ino: {ino} {e}!");
518 reply.error(e.to_errno())
519 }
520 }
521 }
522
523 fn open(&mut self, _req: &Request<'_>, _ino: u64, flags: i32, reply: ReplyOpen) {
524 self._open(flags, reply)
525 }
526
527 fn read(
528 &mut self,
529 _req: &Request<'_>,
530 ino: u64,
531 _fh: u64,
532 offset: i64,
533 size: u32,
534 _flags: i32,
535 _lock_owner: Option<u64>,
536 reply: ReplyData,
537 ) {
538 let uoffset: u64 = offset.try_into().unwrap();
540 match self._read(ino, uoffset, size) {
541 Ok(data) => reply.data(data.as_slice()),
542 Err(e) => {
543 debug!("cannot read ino {ino}, offset: {uoffset} {e}!");
544 reply.error(e.to_errno())
545 }
546 }
547 }
548
549 fn release(
550 &mut self,
551 _req: &Request<'_>,
552 _ino: u64,
553 _fh: u64,
554 _flags: i32,
555 _lock_owner: Option<u64>,
556 _flush: bool,
557 reply: fuser::ReplyEmpty,
558 ) {
559 reply.ok()
561 }
562
563 fn opendir(&mut self, _req: &Request<'_>, _ino: u64, flags: i32, reply: ReplyOpen) {
564 self._open(flags, reply)
565 }
566
567 fn readdir(
568 &mut self,
569 _req: &Request<'_>,
570 ino: u64,
571 _fh: u64,
572 offset: i64,
573 mut reply: fuser::ReplyDirectory,
574 ) {
575 match self._readdir(ino, offset, &mut reply) {
576 Ok(_) => reply.ok(),
577 Err(e) => {
578 debug!("cannot readdir ino: {ino}, offset {offset} {e}!");
579 reply.error(e.to_errno())
580 }
581 }
582 }
583
584 fn releasedir(
585 &mut self,
586 _req: &Request<'_>,
587 _ino: u64,
588 _fh: u64,
589 _flags: i32,
590 reply: fuser::ReplyEmpty,
591 ) {
592 reply.ok()
594 }
595
596 fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: fuser::ReplyStatfs) {
597 reply.statfs(
598 0, 0, 0, 0, 0, 0, 256, 0, )
607 }
608
609 fn getxattr(
610 &mut self,
611 _req: &Request<'_>,
612 ino: u64,
613 name: &OsStr,
614 size: u32,
615 reply: fuser::ReplyXattr,
616 ) {
617 match self._getxattr(ino, name) {
618 Ok(xattr) => {
619 let xattr_len: u32 = xattr
620 .len()
621 .try_into()
622 .expect("xattrs should not exceed u32");
623 if size == 0 {
624 reply.size(xattr_len)
625 } else if xattr_len <= size {
626 reply.data(&xattr)
627 } else {
628 reply.error(Errno::ERANGE as i32)
629 }
630 }
631 Err(e) => {
632 debug!("cannot getxattr, ino: {ino}, name {name:?} {e}!");
633 reply.error(e.to_errno())
634 }
635 }
636 }
637
638 fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32, reply: fuser::ReplyXattr) {
639 match self._listxattr(ino) {
640 Ok(xattr) => {
641 let xattr_len: u32 = xattr
642 .len()
643 .try_into()
644 .expect("xattrs should not exceed u32");
645 if size == 0 {
646 reply.size(xattr_len)
647 } else if xattr_len <= size {
648 reply.data(&xattr)
649 } else {
650 reply.error(Errno::ERANGE as i32)
651 }
652 }
653 Err(e) => {
654 debug!("cannot listxattr, ino {ino}, size {size} {e}!");
655 reply.error(e.to_errno())
656 }
657 }
658 }
659
660 fn access(&mut self, _req: &Request<'_>, _ino: u64, _mask: i32, reply: fuser::ReplyEmpty) {
661 reply.ok()
662 }
663
664 fn bmap(
665 &mut self,
666 _req: &Request<'_>,
667 _ino: u64,
668 _blocksize: u32,
669 _idx: u64,
670 reply: fuser::ReplyBmap,
671 ) {
672 reply.error(Errno::ENOLCK as i32)
673 }
674}
675
676#[cfg(test)]
677mod tests {
678 use std::fs;
679 use std::io;
680 use std::path::Path;
681
682 use sha2::{Digest, Sha256};
683 use tempfile::tempdir;
684
685 use crate::builder::build_test_fs;
686 use crate::oci::Image;
687
688 #[test]
689 fn test_fuse() {
690 let dir = tempdir().unwrap();
691 let image = Image::new(dir.path()).unwrap();
692 build_test_fs(Path::new("src/builder/test/test-1"), &image, "test").unwrap();
693 let mountpoint = tempdir().unwrap();
694 let _bg = crate::reader::spawn_mount::<&str>(
695 image,
696 "test",
697 Path::new(mountpoint.path()),
698 &[],
699 None,
700 None,
701 None,
702 )
703 .unwrap();
704 let ents = fs::read_dir(mountpoint.path())
705 .unwrap()
706 .collect::<io::Result<Vec<fs::DirEntry>>>()
707 .unwrap();
708 assert_eq!(ents.len(), 1);
709 assert_eq!(
710 ents[0].path().strip_prefix(mountpoint.path()).unwrap(),
711 Path::new("SekienAkashita.jpg")
712 );
713
714 let mut hasher = Sha256::new();
715 let mut f = fs::File::open(ents[0].path()).unwrap();
716 io::copy(&mut f, &mut hasher).unwrap();
717 let digest = hasher.finalize();
718 const FILE_DIGEST: &str =
719 "d9e749d9367fc908876749d6502eb212fee88c9a94892fb07da5ef3ba8bc39ed";
720 assert_eq!(hex::encode(digest), FILE_DIGEST);
721 }
722}