Skip to content

Warn about deadlocks caused by accessing ".vol" on certain requests in MacFUSE's #694

Description

@khanhtranngoccva

This bug may be encountered when a developer migrates or ports a passthrough (filter) filesystem from Linux (using procfs or /proc) to MacOS (using volfs or /.vol) using existing fuser code. So far, I have only tested the minimal lifecycle (1 mount and 1 unmount). The FUSE driver fires STATFS, GETATTR, LOOKUP and OPENDIR during the process, and opening a file from /.vol while handling the last 3 request causes a deadlock which can only be unblocked by a timeout.

This is a minimal demo of the quirk on MacFUSE.

use fuser::{AccessFlags, Config, FileAttr, Filesystem, INodeNo, MountOption, ReplyEmpty};
use rustix::fs::{FileType, Mode, OFlags};
use std::{
    io,
    os::fd::OwnedFd,
    path::Path,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

struct TestFilesystem {
    dir_fd: OwnedFd,
}

impl Filesystem for TestFilesystem {
    fn access(
        &self,
        _req: &fuser::Request,
        ino: fuser::INodeNo,
        mask: AccessFlags,
        reply: ReplyEmpty,
    ) {
        reply.ok();
    }

    fn getattr(
        &self,
        _req: &fuser::Request,
        ino: fuser::INodeNo,
        fh: Option<fuser::FileHandle>,
        reply: fuser::ReplyAttr,
    ) {
        let handler = || -> Result<FileAttr, io::Error> {
            let stat = rustix::fs::fstat(&self.dir_fd)?;
            let stable_path = Path::new("/.vol")
                .join(stat.st_dev.to_string())
                .join(stat.st_ino.to_string());
            // log::trace!("opening with stable path: {}", stable_path.display());
            // let reopen_fd = rustix::fs::open(&stable_path, OFlags::NONBLOCK, Mode::empty())?;
            // log::trace!("opened with stable path: {}", stable_path.display());
            let metadata = rustix::fs::fstat(&self.dir_fd)?;
            let transformed = FileAttr {
                ino: INodeNo(metadata.st_ino),
                size: metadata.st_size as u64,
                blocks: metadata.st_blocks as u64,
                atime: system_time_from_unix(metadata.st_atime, metadata.st_atime_nsec as u32),
                mtime: system_time_from_unix(metadata.st_mtime, metadata.st_mtime_nsec as u32),
                ctime: system_time_from_unix(metadata.st_ctime, metadata.st_ctime_nsec as u32),
                crtime: system_time_from_unix(
                    metadata.st_birthtime,
                    metadata.st_birthtime_nsec as u32,
                ),
                kind: rustix_file_type_to_file_type(&FileType::from_raw_mode(metadata.st_mode))
                    .ok_or_else(|| io::Error::other("unknown file type"))?,
                perm: Mode::from_raw_mode(metadata.st_mode).bits(),
                nlink: metadata.st_nlink as u32,
                uid: metadata.st_uid,
                gid: metadata.st_gid,
                rdev: metadata.st_rdev as u32,
                blksize: metadata.st_blksize as u32,
                flags: metadata.st_flags,
            };
            Ok(transformed)
        };
        let result = handler();
        match result {
            Ok(transformed) => reply.attr(&Duration::from_secs(0), &transformed),
            Err(e) => {
                log::error!("error: {}", e);
                reply.error(e.into());
            }
        }
    }
}

fn system_time_from_unix(sec: i64, nsec: u32) -> SystemTime {
    if sec >= 0 {
        UNIX_EPOCH + Duration::new(sec as u64, nsec)
    } else {
        UNIX_EPOCH - Duration::new((-sec) as u64, nsec)
    }
}

fn rustix_file_type_to_file_type(file_type: &FileType) -> Option<fuser::FileType> {
    if file_type.is_dir() {
        Some(fuser::FileType::Directory)
    } else if file_type.is_symlink() {
        Some(fuser::FileType::Symlink)
    } else if file_type.is_socket() {
        Some(fuser::FileType::Socket)
    } else if file_type.is_fifo() {
        Some(fuser::FileType::NamedPipe)
    } else if file_type.is_char_device() {
        Some(fuser::FileType::CharDevice)
    } else if file_type.is_block_device() {
        Some(fuser::FileType::BlockDevice)
    } else if file_type.is_file() {
        Some(fuser::FileType::RegularFile)
    } else {
        None
    }
}

fn main() {
    env_logger::init();
    let temp_dir = tempdir::TempDir::new("test").unwrap();
    let dir_fd_1 = rustix::fs::open(temp_dir.path(), OFlags::empty(), Mode::empty()).unwrap();
    let fs = TestFilesystem { dir_fd: dir_fd_1 };
    let mut config = Config::default();
    config
        .mount_options
        .push(MountOption::CUSTOM("daemon_timeout=5".to_string()));
    let session = fuser::spawn_mount(fs, temp_dir.path(), &fuser::Config::default()).unwrap();
    session.umount_and_join().unwrap();
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions