Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Modernize (continued) #126

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ const HELLO_DIR_ATTR: FileAttr = FileAttr {
atime: UNIX_EPOCH, // 1970-01-01 00:00:00
mtime: UNIX_EPOCH,
ctime: UNIX_EPOCH,
#[cfg(target_os = "macos")]
crtime: UNIX_EPOCH,
kind: FileType::Directory,
ftype: FileType::Directory,
perm: 0o755,
nlink: 2,
uid: 501,
gid: 20,
rdev: 0,
#[cfg(target_os = "macos")]
flags: 0,
};

Expand All @@ -32,13 +34,15 @@ const HELLO_TXT_ATTR: FileAttr = FileAttr {
atime: UNIX_EPOCH, // 1970-01-01 00:00:00
mtime: UNIX_EPOCH,
ctime: UNIX_EPOCH,
#[cfg(target_os = "macos")]
crtime: UNIX_EPOCH,
kind: FileType::RegularFile,
ftype: FileType::RegularFile,
perm: 0o644,
nlink: 1,
uid: 501,
gid: 20,
rdev: 0,
#[cfg(target_os = "macos")]
flags: 0,
};

Expand Down
183 changes: 33 additions & 150 deletions src/channel.rs
Original file line number Diff line number Diff line change
@@ -1,174 +1,57 @@
//! FUSE kernel driver communication
//!
//! Raw communication channel to the FUSE kernel driver.
//!
//! TODO: This module is meant to go away soon in favor of `lowlevel::Channel`.

use std::io;
use std::ffi::{CString, CStr, OsStr};
use std::os::unix::ffi::OsStrExt;
use std::path::{PathBuf, Path};
use fuse_sys::{fuse_args, fuse_mount_compat25};
use libc::{self, c_int, c_void, size_t};
use log::error;

use crate::reply::ReplySender;

/// Helper function to provide options as a fuse_args struct
/// (which contains an argc count and an argv pointer)
fn with_fuse_args<T, F: FnOnce(&fuse_args) -> T>(options: &[&OsStr], f: F) -> T {
let mut args = vec![CString::new("fuse-rs").unwrap()];
args.extend(options.iter().map(|s| CString::new(s.as_bytes()).unwrap()));
let argptrs: Vec<_> = args.iter().map(|s| s.as_ptr()).collect();
f(&fuse_args { argc: argptrs.len() as i32, argv: argptrs.as_ptr(), allocated: 0 })
}

/// A raw communication channel to the FUSE kernel driver
#[derive(Debug)]
pub struct Channel {
mountpoint: PathBuf,
fd: c_int,
}

impl Channel {
/// Create a new communication channel to the kernel driver by mounting the
/// given path. The kernel driver will delegate filesystem operations of
/// the given path to the channel. If the channel is dropped, the path is
/// unmounted.
pub fn new(mountpoint: &Path, options: &[&OsStr]) -> io::Result<Channel> {
let mountpoint = mountpoint.canonicalize()?;
with_fuse_args(options, |args| {
let mnt = CString::new(mountpoint.as_os_str().as_bytes())?;
let fd = unsafe { fuse_mount_compat25(mnt.as_ptr(), args) };
if fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(Channel { mountpoint: mountpoint, fd: fd })
}
})
}
use std::os::unix::io::{AsRawFd, RawFd};

/// Return path of the mounted filesystem
pub fn mountpoint(&self) -> &Path {
&self.mountpoint
}

/// Receives data up to the capacity of the given buffer (can block).
pub fn receive(&self, buffer: &mut Vec<u8>) -> io::Result<()> {
let rc = unsafe { libc::read(self.fd, buffer.as_ptr() as *mut c_void, buffer.capacity() as size_t) };
if rc < 0 {
Err(io::Error::last_os_error())
} else {
unsafe { buffer.set_len(rc as usize); }
Ok(())
macro_rules! try_io {
($x:expr) => {
match $x {
rc if rc < 0 => return Err(io::Error::last_os_error()),
rc => rc,
}
}

/// Returns a sender object for this channel. The sender object can be
/// used to send to the channel. Multiple sender objects can be used
/// and they can safely be sent to other threads.
pub fn sender(&self) -> ChannelSender {
// Since write/writev syscalls are threadsafe, we can simply create
// a sender by using the same fd and use it in other threads. Only
// the channel closes the fd when dropped. If any sender is used after
// dropping the channel, it'll return an EBADF error.
ChannelSender { fd: self.fd }
}
};
}

impl Drop for Channel {
fn drop(&mut self) {
// TODO: send ioctl FUSEDEVIOCSETDAEMONDEAD on macOS before closing the fd
// Close the communication channel to the kernel driver
// (closing it before unnmount prevents sync unmount deadlock)
unsafe { libc::close(self.fd); }
// Unmount this channel's mount point
let _ = unmount(&self.mountpoint);
}
}

#[derive(Clone, Copy, Debug)]
pub struct ChannelSender {
fd: c_int,
fd: RawFd,
}

impl ChannelSender {
/// Send all data in the slice of slice of bytes in a single write (can block).
pub fn send(&self, buffer: &[&[u8]]) -> io::Result<()> {
let iovecs: Vec<_> = buffer.iter().map(|d| {
libc::iovec { iov_base: d.as_ptr() as *mut c_void, iov_len: d.len() as size_t }
}).collect();
let rc = unsafe { libc::writev(self.fd, iovecs.as_ptr(), iovecs.len() as c_int) };
if rc < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}

impl ReplySender for ChannelSender {
fn send(&self, data: &[&[u8]]) {
if let Err(err) = ChannelSender::send(self, data) {
error!("Failed to send FUSE reply: {}", err);
}
}
}

/// Unmount an arbitrary mount point
pub fn unmount(mountpoint: &Path) -> io::Result<()> {
// fuse_unmount_compat22 unfortunately doesn't return a status. Additionally,
// it attempts to call realpath, which in turn calls into the filesystem. So
// if the filesystem returns an error, the unmount does not take place, with
// no indication of the error available to the caller. So we call unmount
// directly, which is what osxfuse does anyway, since we already converted
// to the real path when we first mounted.

#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly",
target_os = "openbsd", target_os = "bitrig", target_os = "netbsd"))]
#[inline]
fn libc_umount(mnt: &CStr) -> c_int {
unsafe { libc::unmount(mnt.as_ptr(), 0) }
impl io::Write for ChannelSender {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(try_io!(unsafe {
libc::write(
self.fd,
buf.as_ptr() as *const libc::c_void,
buf.len() as libc::size_t,
)
}) as usize)
}

#[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly",
target_os = "openbsd", target_os = "bitrig", target_os = "netbsd")))]
#[inline]
fn libc_umount(mnt: &CStr) -> c_int {
use fuse_sys::fuse_unmount_compat22;
use std::io::ErrorKind::PermissionDenied;

let rc = unsafe { libc::umount(mnt.as_ptr()) };
if rc < 0 && io::Error::last_os_error().kind() == PermissionDenied {
// Linux always returns EPERM for non-root users. We have to let the
// library go through the setuid-root "fusermount -u" to unmount.
unsafe { fuse_unmount_compat22(mnt.as_ptr()); }
0
} else {
rc
}
fn flush(&mut self) -> io::Result<()> {
try_io!(unsafe { libc::fsync(self.fd) });
Ok(())
}

let mnt = CString::new(mountpoint.as_os_str().as_bytes())?;
let rc = libc_umount(&mnt);
if rc < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
Ok(try_io!(unsafe {
libc::writev(
self.fd,
bufs.as_ptr() as *const libc::iovec,
bufs.len() as libc::c_int,
)
}) as usize)
}
}


#[cfg(test)]
mod test {
use super::with_fuse_args;
use std::ffi::{CStr, OsStr};

#[test]
fn fuse_args() {
with_fuse_args(&[OsStr::new("foo"), OsStr::new("bar")], |args| {
assert_eq!(args.argc, 3);
assert_eq!(unsafe { CStr::from_ptr(*args.argv.offset(0)).to_bytes() }, b"fuse-rs");
assert_eq!(unsafe { CStr::from_ptr(*args.argv.offset(1)).to_bytes() }, b"foo");
assert_eq!(unsafe { CStr::from_ptr(*args.argv.offset(2)).to_bytes() }, b"bar");
});
impl ChannelSender {
pub fn new<T: AsRawFd>(fd: &T) -> Self {
Self { fd: fd.as_raw_fd() }
}
}
54 changes: 2 additions & 52 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use libc::{c_int, ENOSYS};

pub use fuse_abi::FUSE_ROOT_ID;
pub use fuse_abi::consts;
pub use lowlevel::{FileAttr, FileAttrTryFromError, FileType, FileTypeTryFromError};
pub use reply::{Reply, ReplyEmpty, ReplyData, ReplyEntry, ReplyAttr, ReplyOpen};
pub use reply::{ReplyWrite, ReplyStatfs, ReplyCreate, ReplyLock, ReplyBmap, ReplyDirectory};
pub use reply::ReplyXattr;
Expand All @@ -24,62 +25,11 @@ pub use request::Request;
pub use session::{Session, BackgroundSession};

mod channel;
mod ll;
mod reply;
mod request;
mod session;

/// File types
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum FileType {
/// Named pipe (S_IFIFO)
NamedPipe,
/// Character device (S_IFCHR)
CharDevice,
/// Block device (S_IFBLK)
BlockDevice,
/// Directory (S_IFDIR)
Directory,
/// Regular file (S_IFREG)
RegularFile,
/// Symbolic link (S_IFLNK)
Symlink,
/// Unix domain socket (S_IFSOCK)
Socket,
}

/// File attributes
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FileAttr {
/// Inode number
pub ino: u64,
/// Size in bytes
pub size: u64,
/// Size in blocks
pub blocks: u64,
/// Time of last access
pub atime: SystemTime,
/// Time of last modification
pub mtime: SystemTime,
/// Time of last change
pub ctime: SystemTime,
/// Time of creation (macOS only)
pub crtime: SystemTime,
/// Kind of file (directory, file, pipe, etc)
pub kind: FileType,
/// Permissions
pub perm: u16,
/// Number of hard links
pub nlink: u32,
/// User id
pub uid: u32,
/// Group id
pub gid: u32,
/// Rdev
pub rdev: u32,
/// Flags (macOS only, see chflags(2))
pub flags: u32,
}
pub mod lowlevel;

/// Filesystem trait.
///
Expand Down
6 changes: 0 additions & 6 deletions src/ll/mod.rs

This file was deleted.

6 changes: 3 additions & 3 deletions src/ll/argument.rs → src/lowlevel/argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ mod tests {
let arg: &TestArgument = unsafe { it.fetch().unwrap() };
assert_eq!(arg.p1, 0x66);
assert_eq!(arg.p2, 0x6f);
assert_eq!(arg.p3, 0x006f);
assert_eq!(u16::from_le(arg.p3), 0x006f);
let arg: &TestArgument = unsafe { it.fetch().unwrap() };
assert_eq!(arg.p1, 0x62);
assert_eq!(arg.p2, 0x61);
assert_eq!(arg.p3, 0x0072);
assert_eq!(u16::from_le(arg.p3), 0x0072);
assert_eq!(it.len(), 2);
}

Expand All @@ -116,7 +116,7 @@ mod tests {
let arg: &TestArgument = unsafe { it.fetch().unwrap() };
assert_eq!(arg.p1, 0x66);
assert_eq!(arg.p2, 0x6f);
assert_eq!(arg.p3, 0x006f);
assert_eq!(u16::from_le(arg.p3), 0x006f);
let arg = unsafe { it.fetch_str().unwrap() };
assert_eq!(arg, "bar");
let arg = it.fetch_all();
Expand Down
Loading