diff --git a/Cargo.toml b/Cargo.toml index 17dd0437..abb86f51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,9 @@ name = "notify_inval_inode" [[example]] name = "ioctl" +[[example]] +name = "ioctl_client" + [[example]] name = "passthrough" required-features = ["abi-7-40"] diff --git a/README.md b/README.md index 1e051d0c..24c40403 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ of continuing development. In particular adding features from ABIs after 7.19 A working FUSE filesystem consists of three parts: -1. The **kernel driver** that registers as a filesystem and forwards operations into a communication channel to a userspace process that handles them. -1. The **userspace library** (libfuse) that helps the userspace process to establish and run communication with the kernel driver. -1. The **userspace implementation** that actually processes the filesystem operations. +1. The **kernel driver** (part of the operating system) that registers as a filesystem and forwards operations into a communication channel to a userspace process that handles them. +1. The **userspace library** (e.g., `fuser` and/or `libfuse`) that helps the userspace process to establish and run communication with the kernel driver. +1. The **userspace implementation** (your code here) that actually processes the filesystem operations. The kernel driver is provided by the FUSE project, the userspace implementation needs to be provided by the developer. FUSE-Rust provides a replacement for the libfuse userspace library between these two. This way, a developer can fully take advantage of the Rust type interface and runtime features when building a FUSE filesystem in Rust. @@ -61,10 +61,11 @@ sudo apt-get install libfuse-dev pkg-config sudo yum install fuse-devel pkgconfig ``` -### macOS (untested) +### macOS Install [FUSE for macOS], which can be obtained from their website or installed using the Homebrew or Nix package managers. macOS version 10.9 or later is required. If you are using a Mac with Apple Silicon, you must also [enable support for third party kernel extensions][enable kext]. +Note: Testing on macOS is only done infrequently. If you experience difficulties, please create an issue. #### To install using Homebrew @@ -107,22 +108,45 @@ fuser = "0.15" To create a new filesystem, implement the trait `fuser::Filesystem`. See the [documentation] for details or the `examples` directory for some basic examples. +### Feature Gates + +The crate uses feature gates to manage optional functionality and dependencies. Some key features include: +* **`abi-7-x`**: A set of features to select the FUSE protocol version. Recommended to select the highest version. +* **`libfuse`**: Use libfuse bindings for some very low-level operations. An older alternative to the newer Rust-native implementations. +* **`serializable`**: Enable conversion between `fuser` data structures and raw bytes, for saving to disk or transmission over a network. + ## To Do Most features of libfuse up to 3.10.3 are implemented. Feel free to contribute. See the [list of issues][issues] on GitHub and search the source files for comments containing "`TODO`" or "`FIXME`" to see what's still missing. ## Compatibility -Developed and tested on Linux. Tested under [Linux][FUSE for Linux] and [FreeBSD][FUSE for FreeBSD] using stable [Rust] (see CI for details). +Developed and automatically tested on Linux. Tested under [Linux][FUSE for Linux] and [FreeBSD][FUSE for FreeBSD] using stable [Rust] (see CI for details). Infrequently, manually tested for MacFUSE on MacOS. ## License Licensed under [MIT License](LICENSE.md), except for those files in `examples/` that explicitly contain a different license. -## Contribution +## Contributing Fork, hack, submit pull request. Make sure to make it useful for the target audience, keep the project's philosophy and Rust coding standards in mind. For larger or essential changes, you may want to open an issue for discussion first. Also remember to update the [Changelog] if your changes are relevant to the users. +### Concepts + +A brief overview of Fuser concepts for new contributors. + +* **`Session`**: The core struct which saves configuration options. Its provides methods to start and end event handling loops. +* **`Request`** and **`Reply`**: These structures represents one FUSE operation initiated by the Kernel. The Request methods handle unpacks this message, and directs it to the filesystem. The Reply methods packege the response and pass it back to the kernel. +* **`Notification`**: This structure represents a message for the Kernel initiated by the User application (i.e., not in response to a `Request`). +* **`Filesystem`**: User application code. + +### Subdirectories + +A bried overview of repository organization for new contributors. + +* **`src/mnt/`**: Code for establishing communication with the fuse device, which is called mounting. +* **`src/ll/`**: The low-level FUSE message interface. This module contains the raw FUSE ABI definitions and is responsible for the translating between Rust-based data structures and byte-based fuse kernel messages. It is not recommended for applications to use this code directly. + [Rust]: https://rust-lang.org [Homebrew]: https://brew.sh [Changelog]: https://keepachangelog.com/en/1.0.0/ @@ -134,4 +158,4 @@ Fork, hack, submit pull request. Make sure to make it useful for the target audi [FUSE for Linux]: https://github.com/libfuse/libfuse/ [FUSE for macOS]: https://macfuse.github.io [enable kext]: https://github.com/macfuse/macfuse/wiki/Getting-Started#enabling-support-for-third-party-kernel-extensions-apple-silicon-macs -[FUSE for FreeBSD]: https://wiki.freebsd.org/FUSEFS +[FUSE for FreeBSD]: https://wiki.freebsd.org/FUSEFS \ No newline at end of file diff --git a/build.rs b/build.rs index c6930cdc..994374b1 100644 --- a/build.rs +++ b/build.rs @@ -18,7 +18,7 @@ fn main() { if pkg_config::Config::new() .atleast_version("2.6.0") .probe("fuse") // for macFUSE 4.x - .map_err(|e| eprintln!("{}", e)) + .map_err(|e| eprintln!("{e}")) .is_ok() { println!("cargo:rustc-cfg=fuser_mount_impl=\"libfuse2\""); @@ -27,7 +27,7 @@ fn main() { pkg_config::Config::new() .atleast_version("2.6.0") .probe("osxfuse") // for osxfuse 3.x - .map_err(|e| eprintln!("{}", e)) + .map_err(|e| eprintln!("{e}")) .unwrap(); println!("cargo:rustc-cfg=fuser_mount_impl=\"libfuse2\""); } diff --git a/examples/hello.rs b/examples/hello.rs index 19bc60af..89018178 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -66,6 +66,8 @@ impl Filesystem for HelloFS { } } + // cast is safe because max read < max usize + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] fn read( &mut self, _req: &Request, @@ -84,6 +86,12 @@ impl Filesystem for HelloFS { } } + // cast is safe because the sign of an offset has no meaning + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap + )] fn readdir( &mut self, _req: &Request, diff --git a/examples/ioctl.rs b/examples/ioctl.rs index 14a844a3..fb70ab58 100644 --- a/examples/ioctl.rs +++ b/examples/ioctl.rs @@ -1,6 +1,6 @@ // This example requires fuse 7.11 or later. Run with: // -// cargo run --example ioctl --features abi-7-11 /tmp/foobar +// cargo run --example ioctl DIR use clap::{Arg, ArgAction, Command, crate_version}; use fuser::{ @@ -14,6 +14,9 @@ use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second +const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::()); +const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::()); + struct FiocFS { content: Vec, root_attr: FileAttr, @@ -86,6 +89,8 @@ impl Filesystem for FiocFS { } } + // Max read < max i64 + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn read( &mut self, _req: &Request, @@ -98,12 +103,18 @@ impl Filesystem for FiocFS { reply: ReplyData, ) { if ino == 2 { - reply.data(&self.content[offset as usize..]) + reply.data(&self.content[offset as usize..]); } else { reply.error(ENOENT); } } + // the sign of an offset has no meaning + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap + )] fn readdir( &mut self, _req: &Request, @@ -148,9 +159,6 @@ impl Filesystem for FiocFS { return; } - const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::()); - const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::()); - match cmd.into() { FIOC_GET_SIZE => { let size_bytes = self.content.len().to_ne_bytes(); @@ -162,7 +170,7 @@ impl Filesystem for FiocFS { reply.ioctl(0, &[]); } _ => { - debug!("unknown ioctl: {}", cmd); + debug!("unknown ioctl: {cmd}"); reply.error(EINVAL); } } diff --git a/examples/ioctl_client.rs b/examples/ioctl_client.rs new file mode 100644 index 00000000..8293d4c8 --- /dev/null +++ b/examples/ioctl_client.rs @@ -0,0 +1,151 @@ +// A small client to exercise the ioctl example filesystem. +// +// Usage: +// 1) In one shell, mount the example: +// cargo run --example ioctl DIR +// 2) In another shell, run this client to test setting/getting size: +// cargo run --example ioctl_client DIR +// +// Ioctl actions: +// - GET size +// - SET size to 4096 +// - GET size (expect 4096) +// - SET size to 0 +// - GET size (expect 0) + +#![allow(clippy::cast_possible_truncation)] + +use clap::{Arg, Command, crate_version}; +use std::fs::OpenOptions; +use std::io::{self, Write}; +use std::os::fd::AsRawFd; +use std::path::PathBuf; + +// Generate wrappers matching examples/ioctl.rs +nix::ioctl_read!(ioctl_get_size, 'E', 0, usize); +nix::ioctl_write_ptr!(ioctl_set_size, 'E', 1, usize); + +// Helper to GET size +fn get_size(fd: i32) -> nix::Result { + let mut out: usize = 0; + unsafe { + ioctl_get_size(fd, &raw mut out)?; + } + Ok(out) +} + +// Helper to SET size +fn set_size(fd: i32, sz: usize) -> nix::Result<()> { + let mut val = sz; + unsafe { + ioctl_set_size(fd, &raw mut val)?; + } + Ok(()) +} + +fn main() -> io::Result<()> { + let matches = Command::new("ioctl_client") + .version(crate_version!()) + .author("Richard Lawrence") + .arg( + Arg::new("IOCTL_MOUNT_POINT") + .required(true) + .index(1) + .help("Test an example Ioctl filesystem mounted at the given path"), + ) + .get_matches(); + env_logger::init(); + let mountpoint = matches.get_one::("MOUNT_POINT").unwrap(); + let dir = PathBuf::from(mountpoint); + assert!( + dir.is_dir(), + "MOUNT_POINT does not look like a valid directory" + ); + // Open the example file in the current directory mount + let f = OpenOptions::new() + .read(true) + .write(true) + .open(dir.join("fioc")) + .expect("File `fioc` not opened. (Are you sure Ioctl is mounted correctly?)"); + let fd = f.as_raw_fd(); + + // 1) Read current size + match get_size(fd) { + Ok(sz) => println!("Initial size: {sz} bytes"), + Err(e) => { + eprintln!("FIOC_GET_SIZE failed: {e}"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("ioctl get failed: {e}"), + )); + } + } + + // 2) Set size to 4096 + if let Err(e) = set_size(fd, 4096) { + eprintln!("FIOC_SET_SIZE(4096) failed: {e}"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("ioctl set failed: {e}"), + )); + } + println!("Set size to 4096 bytes"); + + // 3) Get size and expect 4096 + match get_size(fd) { + Ok(sz) => { + println!("After set(4096), size: {sz} bytes"); + if sz != 4096 { + eprintln!("Unexpected size after set(4096): got {sz}, expected 4096"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("size mismatch after set(4096): {sz}"), + )); + } + } + Err(e) => { + eprintln!("FIOC_GET_SIZE failed after set(4096): {e}"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("ioctl get failed: {e}"), + )); + } + } + + // 4) Set size to 0 + if let Err(e) = set_size(fd, 0) { + eprintln!("FIOC_SET_SIZE(0) failed: {e}"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("ioctl set failed: {e}"), + )); + } + println!("Set size to 0 bytes"); + + // 5) Get size and expect 0 + match get_size(fd) { + Ok(sz) => { + println!("After set(0), size: {sz} bytes"); + if sz != 0 { + eprintln!("Unexpected size after set(0): got {sz}, expected 0"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("size mismatch after set(0): {sz}"), + )); + } + } + Err(e) => { + eprintln!("FIOC_GET_SIZE failed after set(0): {e}"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("ioctl get failed: {e}"), + )); + } + } + + // Write a newline to stdout to flush buffered output (looks better in some environments) + io::stdout().flush().ok(); + + println!("ioctl_client completed successfully."); + Ok(()) +} diff --git a/examples/notify_inval_entry.rs b/examples/notify_inval_entry.rs index e671225f..518fe1b5 100644 --- a/examples/notify_inval_entry.rs +++ b/examples/notify_inval_entry.rs @@ -129,7 +129,7 @@ fn now_filename() -> String { format!("Time_is_{}", d.as_secs()) } -#[derive(Parser)] +#[derive(Parser, Debug)] struct Options { /// Mount demo filesystem at given path mount_point: String, @@ -174,7 +174,7 @@ fn main() { if opts.only_expire { // fuser::notify_expire_entry(_SOME_HANDLE_, FUSE_ROOT_ID, &oldname); } else if let Err(e) = notifier.inval_entry(FUSE_ROOT_ID, oldname.as_ref()) { - eprintln!("Warning: failed to invalidate entry '{}': {}", oldname, e); + eprintln!("Warning: failed to invalidate entry '{oldname}': {e}"); } } thread::sleep(Duration::from_secs_f32(opts.update_interval)); diff --git a/examples/notify_inval_inode.rs b/examples/notify_inval_inode.rs index a1f68048..5d5d119b 100644 --- a/examples/notify_inval_inode.rs +++ b/examples/notify_inval_inode.rs @@ -126,7 +126,7 @@ impl Filesystem for ClockFS<'_> { } else if flags & libc::O_ACCMODE != libc::O_RDONLY { reply.error(EACCES); } else if ino != Self::FILE_INO { - eprintln!("Got open for nonexistent inode {}", ino); + eprintln!("Got open for nonexistent inode {ino}"); reply.error(ENOENT); } else { reply.opened(ino, consts::FOPEN_KEEP_CACHE); @@ -156,7 +156,7 @@ impl Filesystem for ClockFS<'_> { reply.error(EINVAL); return; }; - let Ok(end) = (offset + size as i64).min(dlen).try_into() else { + let Ok(end) = (offset + i64::from(size)).min(dlen).try_into() else { reply.error(EINVAL); return; }; @@ -172,7 +172,7 @@ fn now_string() -> String { format!("The current time is {}\n", d.as_secs()) } -#[derive(Parser)] +#[derive(Parser, Debug)] struct Options { /// Mount demo filesystem at given path mount_point: String, @@ -185,7 +185,7 @@ struct Options { #[clap(short, long)] no_notify: bool, - /// Use notify_store() instead of notify_inval_inode() + /// Use `notify_store()` instead of `notify_inval_inode()` #[clap(short = 's', long)] notify_store: bool, } @@ -213,12 +213,12 @@ fn main() { if let Err(e) = notifier.store(ClockFS::FILE_INO, 0, fdata.lock().unwrap().as_bytes()) { - eprintln!("Warning: failed to update kernel cache: {}", e); + eprintln!("Warning: failed to update kernel cache: {e}"); } } else if let Err(e) = notifier.inval_inode(ClockFS::FILE_INO, 0, olddata.len().try_into().unwrap()) { - eprintln!("Warning: failed to invalidate inode: {}", e); + eprintln!("Warning: failed to invalidate inode: {e}"); } } thread::sleep(Duration::from_secs_f32(opts.update_interval)); diff --git a/examples/passthrough.rs b/examples/passthrough.rs index eec90502..81b53429 100644 --- a/examples/passthrough.rs +++ b/examples/passthrough.rs @@ -1,6 +1,6 @@ // This example requires fuse 7.40 or later. Run with: // -// cargo run --example passthrough --features abi-7-40 /tmp/foobar +// cargo run --example passthrough --features abi-7-40 DIR use clap::{Arg, ArgAction, Command, crate_version}; use fuser::{ @@ -16,20 +16,20 @@ use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second -/// BackingCache is an example of how a filesystem might manage BackingId objects for fd -/// passthrough. The idea is to avoid creating more than one BackingId object per file at a time. +/// `BackingCache` is an example of how a filesystem might manage `BackingId` objects for fd +/// passthrough. The idea is to avoid creating more than one `BackingId` object per file at a time. /// -/// We do this by keeping a weak "by inode" hash table mapping inode numbers to BackingId. If a -/// BackingId already exists, we use it. Otherwise, we create it. This is not enough to keep the -/// BackingId alive, though. For each Filesystem::open() request we allocate a fresh 'fh' -/// (monotonically increasing u64, next_fh, never recycled) and use that to keep a *strong* -/// reference on the BackingId for that open. We drop it from the table on Filesystem::release(), -/// which means the BackingId will be dropped in the kernel when the last user of it closes. +/// We do this by keeping a weak "by inode" hash table mapping inode numbers to `BackingId`. If a +/// `BackingId` already exists, we use it. Otherwise, we create it. This is not enough to keep the +/// `BackingId` alive, though. For each `Filesystem::open()` request we allocate a fresh 'fh' +/// (monotonically increasing u64, `next_fh`, never recycled) and use that to keep a *strong* +/// reference on the `BackingId` for that open. We drop it from the table on `Filesystem::release()`, +/// which means the `BackingId` will be dropped in the kernel when the last user of it closes. /// /// In this way, if a request to open a file comes in and the file is already open, we'll reuse the -/// BackingId, but as soon as all references are closed, the BackingId will be dropped. +/// `BackingId`, but as soon as all references are closed, the `BackingId` will be dropped. /// -/// It's left as an exercise to the reader to implement an active cleanup of the by_inode table, if +/// It's left as an exercise to the reader to implement an active cleanup of the `by_inode` table, if /// desired, but our little example filesystem only contains one file. :) #[derive(Debug, Default)] struct BackingCache { @@ -44,9 +44,9 @@ impl BackingCache { self.next_fh } - /// Gets the existing BackingId for `ino` if it exists, or calls `callback` to create it. + /// Gets the existing `BackingId` for `ino` if it exists, or calls `callback` to create it. /// - /// Returns a unique file handle and the BackingID (possibly shared, possibly new). The + /// Returns a unique file handle and the `BackingID` (possibly shared, possibly new). The /// returned file handle should be `put()` when you're done with it. fn get_or( &mut self, @@ -70,7 +70,7 @@ impl BackingCache { } /// Releases a file handle previously obtained from `get_or()`. If this was a last user of a - /// particular BackingId then it will be dropped. + /// particular `BackingId` then it will be dropped. fn put(&mut self, fh: u64) { eprintln!("Put fh {fh}"); match self.by_handle.remove(&fh) { @@ -112,7 +112,7 @@ impl PassthroughFs { let passthrough_file_attr = FileAttr { ino: 2, - size: 123456, + size: 123_456, blocks: 1, atime: UNIX_EPOCH, // 1970-01-01 00:00:00 mtime: UNIX_EPOCH, @@ -131,7 +131,7 @@ impl PassthroughFs { Self { root_attr, passthrough_file_attr, - backing_cache: Default::default(), + backing_cache: BackingCache::default(), } } } @@ -172,7 +172,7 @@ impl Filesystem for PassthroughFs { let (fh, id) = self .backing_cache .get_or(ino, || { - let file = File::open("/etc/os-release")?; + let file = File::open("/etc/profile")?; reply.open_backing(file) }) .unwrap(); @@ -195,6 +195,12 @@ impl Filesystem for PassthroughFs { reply.ok(); } + // cast is safe because the sign of an offset has no meaning + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap + )] fn readdir( &mut self, _req: &Request, diff --git a/examples/poll.rs b/examples/poll.rs index e4214ccc..aebdf5b5 100644 --- a/examples/poll.rs +++ b/examples/poll.rs @@ -306,9 +306,9 @@ fn producer(data: &Mutex, notifier: &fuser::Notifier) { if d.bytecnt[tidx] != MAXBYTES { d.bytecnt[tidx] += 1; if d.notify_mask & (1 << t) != 0 { - println!("NOTIFY {:X}", t); + println!("NOTIFY {t:X}"); if let Err(e) = notifier.poll(d.poll_handles[tidx]) { - eprintln!("poll notification failed: {}", e); + eprintln!("poll notification failed: {e}"); } d.notify_mask &= !(1 << t); } diff --git a/examples/poll_client.rs b/examples/poll_client.rs index d4beb4f6..89eb2037 100644 --- a/examples/poll_client.rs +++ b/examples/poll_client.rs @@ -25,7 +25,7 @@ fn make_nonblock(fd: RawFd) { fn main() -> std::io::Result<()> { let mut files = Vec::with_capacity(NUMFILES); for c in "0123456789ABCDEF".chars() { - let name = format!("{}", c); + let name = format!("{c}"); let f = std::fs::File::open(name)?; make_nonblock(f.as_raw_fd()); files.push(f); @@ -46,10 +46,10 @@ fn main() -> std::io::Result<()> { print!("_: "); continue; } - print!("{:X}:", i); + print!("{i:X}:"); let fd = pfd.as_fd().as_raw_fd(); let nbytes = nix::unistd::read(fd, readbuf.as_mut_slice())?; - print!("{:02} ", nbytes); + print!("{nbytes:02} "); } println!(); } diff --git a/examples/simple.rs b/examples/simple.rs index dc6cf52d..6cb5d45e 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,22 +1,26 @@ #![allow(clippy::needless_return)] #![allow(clippy::unnecessary_cast)] // libc::S_* are u16 or u32 depending on the platform +#![allow(clippy::cast_possible_truncation)] // u32 -> u16 without error handling +#![allow(clippy::cast_sign_loss)] // i64 -> u32 without error handling +#![allow(clippy::cast_possible_wrap)] // i64 -> usize without error handling use clap::{Arg, ArgAction, Command, crate_version}; use fuser::consts::FOPEN_DIRECT_IO; #[cfg(feature = "abi-7-26")] use fuser::consts::FUSE_HANDLE_KILLPRIV; -// #[cfg(feature = "abi-7-31")] -// use fuser::consts::FUSE_WRITE_KILL_PRIV; +/* +// Note: see fn write(). +#[cfg(feature = "abi-7-31")] +use fuser::consts::FUSE_WRITE_KILL_PRIV; +*/ use fuser::TimeOrNow::Now; use fuser::{ FUSE_ROOT_ID, Filesystem, KernelConfig, MountOption, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, }; -#[cfg(feature = "abi-7-26")] -use log::info; -use log::{LevelFilter, error}; -use log::{debug, warn}; +#[allow(unused_imports)] +use log::{LevelFilter, debug, error, info, warn}; use serde::{Deserialize, Serialize}; use std::cmp::min; use std::collections::BTreeMap; @@ -33,7 +37,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{env, fs, io}; -const BLOCK_SIZE: u64 = 512; +const BLOCK_SIZE: u32 = 512; const MAX_NAME_LENGTH: u32 = 255; const MAX_FILE_SIZE: u64 = 1024 * 1024 * 1024 * 1024; @@ -188,6 +192,7 @@ fn system_time_from_time(secs: i64, nsecs: u32) -> SystemTime { } } +// Time as i64: see fuse_abi::fuse_attr fn time_from_system_time(system_time: &SystemTime) -> (i64, u32) { // Convert to signed 64-bit time with epoch at 0 match system_time.duration_since(UNIX_EPOCH) { @@ -221,7 +226,7 @@ impl From for fuser::FileAttr { fuser::FileAttr { ino: attrs.inode, size: attrs.size, - blocks: attrs.size.div_ceil(BLOCK_SIZE), + blocks: attrs.size.div_ceil(u64::from(BLOCK_SIZE)), atime: system_time_from_time(attrs.last_accessed.0, attrs.last_accessed.1), mtime: system_time_from_time(attrs.last_modified.0, attrs.last_modified.1), ctime: system_time_from_time( @@ -235,7 +240,7 @@ impl From for fuser::FileAttr { uid: attrs.uid, gid: attrs.gid, rdev: 0, - blksize: BLOCK_SIZE as u32, + blksize: BLOCK_SIZE, flags: 0, } } @@ -277,10 +282,10 @@ impl SimpleFS { } fn creation_mode(&self, mode: u32) -> u16 { - if !self.suid_support { - (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16 - } else { + if self.suid_support { mode as u16 + } else { + (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16 } } @@ -316,11 +321,11 @@ impl SimpleFS { fh } - fn check_file_handle_read(&self, file_handle: u64) -> bool { + fn check_file_handle_read(file_handle: u64) -> bool { (file_handle & FILE_HANDLE_READ_BIT) != 0 } - fn check_file_handle_write(&self, file_handle: u64) -> bool { + fn check_file_handle_write(file_handle: u64) -> bool { (file_handle & FILE_HANDLE_WRITE_BIT) != 0 } @@ -340,7 +345,7 @@ impl SimpleFS { } } - fn write_directory_content(&self, inode: Inode, entries: DirectoryDescriptor) { + fn write_directory_content(&self, inode: Inode, entries: &DirectoryDescriptor) { let path = Path::new(&self.data_dir) .join("contents") .join(inode.to_string()); @@ -432,9 +437,8 @@ impl SimpleFS { let entries = self.get_directory_content(parent)?; if let Some((inode, _)) = entries.get(name.as_bytes()) { return self.get_inode(*inode); - } else { - return Err(libc::ENOENT); } + return Err(libc::ENOENT); } fn insert_link( @@ -467,7 +471,7 @@ impl SimpleFS { let mut entries = self.get_directory_content(parent).unwrap(); entries.insert(name.as_bytes().to_vec(), (inode, kind)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); Ok(()) } @@ -498,12 +502,12 @@ impl Filesystem for SimpleFS { hardlinks: 2, uid: 0, gid: 0, - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&root); let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); - self.write_directory_content(FUSE_ROOT_ID, entries); + self.write_directory_content(FUSE_ROOT_ID, &entries); } Ok(()) } @@ -541,6 +545,7 @@ impl Filesystem for SimpleFS { } } + #[allow(clippy::too_many_lines)] // To-do: refactor very long function fn setattr( &mut self, req: &Request, @@ -568,7 +573,7 @@ impl Filesystem for SimpleFS { }; if let Some(mode) = mode { - debug!("chmod() called with {:?}, {:o}", inode, mode); + debug!("chmod() called with {inode:?}, {mode:o}"); #[cfg(target_os = "freebsd")] { // FreeBSD: sticky bit only valid on directories; otherwise EFTYPE @@ -601,7 +606,7 @@ impl Filesystem for SimpleFS { } if uid.is_some() || gid.is_some() { - debug!("chown() called with {:?} {:?} {:?}", inode, uid, gid); + debug!("chown() called with {inode:?} {uid:?} {gid:?}"); if let Some(gid) = gid { // Non-root users can only change gid to a group they're in if req.uid() != 0 && !get_groups(req.pid()).contains(&gid) { @@ -648,13 +653,13 @@ impl Filesystem for SimpleFS { } if let Some(size) = size { - debug!("truncate() called with {:?} {:?}", inode, size); + debug!("truncate() called with {inode:?} {size:?}"); if let Some(handle) = fh { // If the file handle is available, check access locally. // This is important as it preserves the semantic that a file handle opened // with W_OK will never fail to truncate, even if the file has been subsequently // chmod'ed - if self.check_file_handle_write(handle) { + if Self::check_file_handle_write(handle) { if let Err(error_code) = self.truncate(inode, size, 0, 0) { reply.error(error_code); return; @@ -671,7 +676,7 @@ impl Filesystem for SimpleFS { let now = time_now(); if let Some(atime) = atime { - debug!("utimens() called with {:?}, atime={:?}", inode, atime); + debug!("utimens() called with {inode:?}, atime={atime:?}"); if attrs.uid != req.uid() && req.uid() != 0 && atime != Now { reply.error(libc::EPERM); @@ -700,7 +705,7 @@ impl Filesystem for SimpleFS { self.write_inode(&attrs); } if let Some(mtime) = mtime { - debug!("utimens() called with {:?}, mtime={:?}", inode, mtime); + debug!("utimens() called with {inode:?}, mtime={mtime:?}"); if attrs.uid != req.uid() && req.uid() != 0 && mtime != Now { reply.error(libc::EPERM); @@ -735,7 +740,7 @@ impl Filesystem for SimpleFS { } fn readlink(&mut self, _req: &Request, inode: u64, reply: ReplyData) { - debug!("readlink() called on {:?}", inode); + debug!("readlink() called on {inode:?}"); let path = self.content_path(inode); match File::open(path) { Ok(mut file) => { @@ -768,8 +773,7 @@ impl Filesystem for SimpleFS { { // TODO warn!( - "mknod() implementation is incomplete. Only supports regular files, symlinks, and directories. Got {:o}", - mode + "mknod() implementation is incomplete. Only supports regular files, symlinks, and directories. Got {mode:o}" ); reply.error(libc::EPERM); return; @@ -788,6 +792,11 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + reply.error(libc::ENOTDIR); + return; + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -833,7 +842,7 @@ impl Filesystem for SimpleFS { hardlinks: 1, uid: req.uid(), gid: creation_gid(&parent_attrs, req.gid()), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&attrs); File::create(self.content_path(inode)).unwrap(); @@ -842,12 +851,12 @@ impl Filesystem for SimpleFS { let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(inode, entries); + self.write_directory_content(inode, &entries); } let mut entries = self.get_directory_content(parent).unwrap(); entries.insert(name.as_bytes().to_vec(), (inode, attrs.kind)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); // TODO: implement flags reply.entry(&Duration::new(0, 0), &attrs.into(), 0); @@ -862,7 +871,7 @@ impl Filesystem for SimpleFS { _umask: u32, reply: ReplyEntry, ) { - debug!("mkdir() called with {:?} {:?} {:o}", parent, name, mode); + debug!("mkdir() called with {parent:?} {name:?} {mode:o}"); if self.lookup_name(parent, name).is_ok() { reply.error(libc::EEXIST); return; @@ -876,6 +885,11 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + reply.error(libc::ENOTDIR); + return; + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -902,7 +916,7 @@ impl Filesystem for SimpleFS { let attrs = InodeAttributes { inode, open_file_handles: 0, - size: BLOCK_SIZE, + size: u64::from(BLOCK_SIZE), last_accessed: time_now(), last_modified: time_now(), last_metadata_changed: time_now(), @@ -911,24 +925,24 @@ impl Filesystem for SimpleFS { hardlinks: 2, // Directories start with link count of 2, since they have a self link uid: req.uid(), gid: creation_gid(&parent_attrs, req.gid()), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&attrs); let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(inode, entries); + self.write_directory_content(inode, &entries); let mut entries = self.get_directory_content(parent).unwrap(); entries.insert(name.as_bytes().to_vec(), (inode, FileKind::Directory)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); reply.entry(&Duration::new(0, 0), &attrs.into(), 0); } fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { - debug!("unlink() called with {:?} {:?}", parent, name); + debug!("unlink() called with {parent:?} {name:?}"); let mut attrs = match self.lookup_name(parent, name) { Ok(attrs) => attrs, Err(error_code) => { @@ -979,13 +993,13 @@ impl Filesystem for SimpleFS { let mut entries = self.get_directory_content(parent).unwrap(); entries.remove(name.as_bytes()); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); reply.ok(); } fn rmdir(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { - debug!("rmdir() called with {:?} {:?}", parent, name); + debug!("rmdir() called with {parent:?} {name:?}"); let mut attrs = match self.lookup_name(parent, name) { Ok(attrs) => attrs, Err(error_code) => { @@ -1040,7 +1054,7 @@ impl Filesystem for SimpleFS { let mut entries = self.get_directory_content(parent).unwrap(); entries.remove(name.as_bytes()); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); reply.ok(); } @@ -1053,10 +1067,7 @@ impl Filesystem for SimpleFS { target: &Path, reply: ReplyEntry, ) { - debug!( - "symlink() called with {:?} {:?} {:?}", - parent, link_name, target - ); + debug!("symlink() called with {parent:?} {link_name:?} {target:?}"); let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { @@ -1065,6 +1076,11 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + reply.error(libc::ENOTDIR); + return; + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -1093,7 +1109,7 @@ impl Filesystem for SimpleFS { hardlinks: 1, uid: req.uid(), gid: creation_gid(&parent_attrs, req.gid()), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; if let Err(error_code) = self.insert_link(req, parent, link_name, inode, FileKind::Symlink) @@ -1115,6 +1131,7 @@ impl Filesystem for SimpleFS { reply.entry(&Duration::new(0, 0), &attrs.into(), 0); } + #[allow(clippy::too_many_lines)] // To-do: refactor very long function fn rename( &mut self, req: &Request, @@ -1175,6 +1192,11 @@ impl Filesystem for SimpleFS { } }; + if new_parent_attrs.kind != FileKind::Directory { + reply.error(libc::ENOTDIR); + return; + } + if !check_access( new_parent_attrs.uid, new_parent_attrs.gid, @@ -1215,14 +1237,14 @@ impl Filesystem for SimpleFS { new_name.as_bytes().to_vec(), (inode_attrs.inode, inode_attrs.kind), ); - self.write_directory_content(new_parent, entries); + self.write_directory_content(new_parent, &entries); let mut entries = self.get_directory_content(parent).unwrap(); entries.insert( name.as_bytes().to_vec(), (new_inode_attrs.inode, new_inode_attrs.kind), ); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); parent_attrs.last_metadata_changed = time_now(); parent_attrs.last_modified = time_now(); @@ -1238,12 +1260,12 @@ impl Filesystem for SimpleFS { if inode_attrs.kind == FileKind::Directory { let mut entries = self.get_directory_content(inode_attrs.inode).unwrap(); entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory)); - self.write_directory_content(inode_attrs.inode, entries); + self.write_directory_content(inode_attrs.inode, &entries); } if new_inode_attrs.kind == FileKind::Directory { let mut entries = self.get_directory_content(new_inode_attrs.inode).unwrap(); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(new_inode_attrs.inode, entries); + self.write_directory_content(new_inode_attrs.inode, &entries); } reply.ok(); @@ -1285,7 +1307,7 @@ impl Filesystem for SimpleFS { if let Ok(mut existing_inode_attrs) = self.lookup_name(new_parent, new_name) { let mut entries = self.get_directory_content(new_parent).unwrap(); entries.remove(new_name.as_bytes()); - self.write_directory_content(new_parent, entries); + self.write_directory_content(new_parent, &entries); if existing_inode_attrs.kind == FileKind::Directory { existing_inode_attrs.hardlinks = 0; @@ -1299,14 +1321,14 @@ impl Filesystem for SimpleFS { let mut entries = self.get_directory_content(parent).unwrap(); entries.remove(name.as_bytes()); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); let mut entries = self.get_directory_content(new_parent).unwrap(); entries.insert( new_name.as_bytes().to_vec(), (inode_attrs.inode, inode_attrs.kind), ); - self.write_directory_content(new_parent, entries); + self.write_directory_content(new_parent, &entries); parent_attrs.last_metadata_changed = time_now(); parent_attrs.last_modified = time_now(); @@ -1320,7 +1342,7 @@ impl Filesystem for SimpleFS { if inode_attrs.kind == FileKind::Directory { let mut entries = self.get_directory_content(inode_attrs.inode).unwrap(); entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory)); - self.write_directory_content(inode_attrs.inode, entries); + self.write_directory_content(inode_attrs.inode, &entries); } reply.ok(); @@ -1334,10 +1356,7 @@ impl Filesystem for SimpleFS { new_name: &OsStr, reply: ReplyEntry, ) { - debug!( - "link() called for {}, {}, {:?}", - inode, new_parent, new_name - ); + debug!("link() called for {inode}, {new_parent}, {new_name:?}"); let mut attrs = match self.get_inode(inode) { Ok(attrs) => attrs, Err(error_code) => { @@ -1356,7 +1375,7 @@ impl Filesystem for SimpleFS { } fn open(&mut self, req: &Request, inode: u64, flags: i32, reply: ReplyOpen) { - debug!("open() called for {:?}", inode); + debug!("open() called for {inode:?}"); let (access_mask, read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES @@ -1403,6 +1422,7 @@ impl Filesystem for SimpleFS { } } + // cast is safe because max read < max usize fn read( &mut self, _req: &Request, @@ -1414,12 +1434,9 @@ impl Filesystem for SimpleFS { _lock_owner: Option, reply: ReplyData, ) { - debug!( - "read() called on {:?} offset={:?} size={:?}", - inode, offset, size - ); + debug!("read() called on {inode:?} offset={offset:?} size={size:?}"); assert!(offset >= 0); - if !self.check_file_handle_read(fh) { + if !Self::check_file_handle_read(fh) { reply.error(libc::EACCES); return; } @@ -1449,13 +1466,13 @@ impl Filesystem for SimpleFS { offset: i64, data: &[u8], _write_flags: u32, - #[allow(unused_variables)] flags: i32, + _flags: i32, _lock_owner: Option, reply: ReplyWrite, ) { debug!("write() called with {:?} size={:?}", inode, data.len()); assert!(offset >= 0); - if !self.check_file_handle_write(fh) { + if !Self::check_file_handle_write(fh) { reply.error(libc::EACCES); return; } @@ -1506,7 +1523,7 @@ impl Filesystem for SimpleFS { } fn opendir(&mut self, req: &Request, inode: u64, flags: i32, reply: ReplyOpen) { - debug!("opendir() called on {:?}", inode); + debug!("opendir() called on {inode:?}"); let (access_mask, read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES @@ -1548,6 +1565,7 @@ impl Filesystem for SimpleFS { } } + // cast is safe because the sign of an offset has no meaning fn readdir( &mut self, _req: &Request, @@ -1556,7 +1574,7 @@ impl Filesystem for SimpleFS { offset: i64, mut reply: ReplyDirectory, ) { - debug!("readdir() called with {:?}", inode); + debug!("readdir() called with {inode:?}"); assert!(offset >= 0); let entries = match self.get_directory_content(inode) { Ok(entries) => entries, @@ -1607,9 +1625,9 @@ impl Filesystem for SimpleFS { 10_000, 1, 10_000, - BLOCK_SIZE as u32, + BLOCK_SIZE, MAX_NAME_LENGTH, - BLOCK_SIZE as u32, + BLOCK_SIZE, ); } @@ -1714,7 +1732,7 @@ impl Filesystem for SimpleFS { } fn access(&mut self, req: &Request, inode: u64, mask: i32, reply: ReplyEmpty) { - debug!("access() called with {:?} {:?}", inode, mask); + debug!("access() called with {inode:?} {mask:?}"); match self.get_inode(inode) { Ok(attr) => { if check_access(attr.uid, attr.gid, attr.mode, req.uid(), req.gid(), mask) { @@ -1737,7 +1755,7 @@ impl Filesystem for SimpleFS { flags: i32, reply: ReplyCreate, ) { - debug!("create() called with {:?} {:?}", parent, name); + debug!("create() called with {parent:?} {name:?}"); if self.lookup_name(parent, name).is_ok() { reply.error(libc::EEXIST); return; @@ -1762,6 +1780,11 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + reply.error(libc::ENOTDIR); + return; + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -1807,7 +1830,7 @@ impl Filesystem for SimpleFS { hardlinks: 1, uid: req.uid(), gid: creation_gid(&parent_attrs, req.gid()), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&attrs); File::create(self.content_path(inode)).unwrap(); @@ -1816,12 +1839,12 @@ impl Filesystem for SimpleFS { let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(inode, entries); + self.write_directory_content(inode, &entries); } let mut entries = self.get_directory_content(parent).unwrap(); entries.insert(name.as_bytes().to_vec(), (inode, attrs.kind)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); // TODO: implement flags reply.created( @@ -1881,14 +1904,13 @@ impl Filesystem for SimpleFS { reply: ReplyWrite, ) { debug!( - "copy_file_range() called with src ({}, {}, {}) dest ({}, {}, {}) size={}", - src_fh, src_inode, src_offset, dest_fh, dest_inode, dest_offset, size + "copy_file_range() called with src=({src_fh}, {src_inode}, {src_offset}) dest=({dest_fh}, {dest_inode}, {dest_offset}) size={size}" ); - if !self.check_file_handle_read(src_fh) { + if !Self::check_file_handle_read(src_fh) { reply.error(libc::EACCES); return; } - if !self.check_file_handle_write(dest_fh) { + if !Self::check_file_handle_write(dest_fh) { reply.error(libc::EACCES); return; } @@ -1931,6 +1953,8 @@ impl Filesystem for SimpleFS { } } +#[must_use] +#[allow(clippy::similar_names)] pub fn check_access( file_uid: u32, file_gid: u32, @@ -1975,9 +1999,8 @@ fn as_file_kind(mut mode: u32) -> FileKind { return FileKind::Symlink; } else if mode == libc::S_IFDIR as u32 { return FileKind::Directory; - } else { - unimplemented!("{}", mode); } + unimplemented!("{mode}"); } fn get_groups(pid: u32) -> Vec { diff --git a/src/channel.rs b/src/channel.rs index fbc4ded3..5a449b5c 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -34,6 +34,7 @@ impl Channel { /// Receives data up to the capacity of the given buffer (can block). pub fn receive(&self, buffer: &mut [u8]) -> io::Result { + #[allow(clippy::ptr_as_ptr)] // explicit mut libc pointer let rc = unsafe { libc::read( self.0.as_raw_fd(), @@ -41,6 +42,7 @@ impl Channel { buffer.len() as size_t, ) }; + #[allow(clippy::cast_sign_loss)] // explicitly handle negative values if rc < 0 { Err(io::Error::last_os_error()) } else { @@ -63,6 +65,7 @@ pub struct ChannelSender(Arc); impl ReplySender for ChannelSender { fn send(&self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> { + #[allow(clippy::ptr_as_ptr)] // explicit const libc pointer let rc = unsafe { libc::writev( self.0.as_raw_fd(), @@ -70,6 +73,7 @@ impl ReplySender for ChannelSender { bufs.len() as c_int, ) }; + #[allow(clippy::cast_sign_loss)] // explicitly handle negative values if rc < 0 { Err(io::Error::last_os_error()) } else { diff --git a/src/lib.rs b/src/lib.rs index 3616b96e..ac04e191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ use std::time::SystemTime; use std::{convert::AsRef, io::ErrorKind}; pub use crate::ll::fuse_abi::FUSE_ROOT_ID; +#[allow(clippy::wildcard_imports)] // to avoid many feature gates use crate::ll::fuse_abi::consts::*; pub use crate::ll::{TimeOrNow, fuse_abi::consts}; use crate::mnt::mount_options::check_option_conflicts; @@ -29,15 +30,27 @@ pub use mnt::mount_options::MountOption; pub use notify::{Notifier, PollHandle}; #[cfg(feature = "abi-7-40")] pub use passthrough::BackingId; +pub use reply::Reply; +pub use reply::ReplyAttr; +pub use reply::ReplyBmap; +pub use reply::ReplyCreate; +pub use reply::ReplyData; +pub use reply::ReplyDirectory; +#[cfg(feature = "abi-7-21")] +pub use reply::ReplyDirectoryPlus; +pub use reply::ReplyEmpty; +pub use reply::ReplyEntry; +pub use reply::ReplyIoctl; +pub use reply::ReplyLock; +#[cfg(feature = "abi-7-24")] +pub use reply::ReplyLseek; +pub use reply::ReplyOpen; pub use reply::ReplyPoll; +pub use reply::ReplyStatfs; +pub use reply::ReplyWrite; #[cfg(target_os = "macos")] pub use reply::ReplyXTimes; pub use reply::ReplyXattr; -pub use reply::{Reply, ReplyAttr, ReplyData, ReplyEmpty, ReplyEntry, ReplyOpen}; -pub use reply::{ - ReplyBmap, ReplyCreate, ReplyDirectory, ReplyDirectoryPlus, ReplyIoctl, ReplyLock, ReplyLseek, - ReplyStatfs, ReplyWrite, -}; pub use request::Request; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; #[cfg(feature = "abi-7-28")] @@ -85,19 +98,19 @@ const fn default_init_flags(#[allow(unused_variables)] capabilities: u64) -> u64 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub enum FileType { - /// Named pipe (S_IFIFO) + /// Named pipe (`S_IFIFO`) NamedPipe, - /// Character device (S_IFCHR) + /// Character device (`S_IFCHR`) CharDevice, - /// Block device (S_IFBLK) + /// Block device (`S_IFBLK`) BlockDevice, - /// Directory (S_IFDIR) + /// Directory (`S_IFDIR`) Directory, - /// Regular file (S_IFREG) + /// Regular file (`S_IFREG`) RegularFile, - /// Symbolic link (S_IFLNK) + /// Symbolic link (`S_IFLNK`) Symlink, - /// Unix domain socket (S_IFSOCK) + /// Unix domain socket (`S_IFSOCK`) Socket, } @@ -153,6 +166,7 @@ pub struct KernelConfig { max_stack_depth: u32, } +#[allow(clippy::cast_possible_truncation)] // truncation is used to prevent too-large values impl KernelConfig { fn new(capabilities: u64, max_readahead: u32) -> Self { Self { @@ -177,13 +191,15 @@ impl KernelConfig { /// This has to be at least 1 to support passthrough to backing files. Setting this to 0 (the /// default) effectively disables support for passthrough. /// - /// With max_stack_depth > 1, the backing files can be on a stacked fs (e.g. overlayfs) - /// themselves and with max_stack_depth == 1, this FUSE filesystem can be stacked as the + /// With `max_stack_depth` > 1, the backing files can be on a stacked fs (e.g. overlayfs) + /// themselves and with `max_stack_depth` == 1, this FUSE filesystem can be stacked as the /// underlying fs of a stacked fs (e.g. overlayfs). /// /// The kernel currently has a hard maximum value of 2. Anything higher won't work. /// - /// On success, returns the previous value. On error, returns the nearest value which will succeed. + /// On success, returns the previous value. + /// # Errors + /// If argument is too large, returns the nearest value which will succeed. #[cfg(feature = "abi-7-40")] pub fn set_max_stack_depth(&mut self, value: u32) -> Result { // https://lore.kernel.org/linux-fsdevel/CAOYeF9V_n93OEF_uf0Gwtd=+da0ReX8N2aaT6RfEJ9DPvs8O2w@mail.gmail.com/ @@ -202,7 +218,9 @@ impl KernelConfig { /// /// Must be a power of 10 nanoseconds. i.e. 1s, 0.1s, 0.01s, 1ms, 0.1ms...etc /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// On success returns the previous value. + /// # Errors + /// If the argument does not match any valid granularity, returns the nearest value which will succeed. #[cfg(feature = "abi-7-23")] pub fn set_time_granularity(&mut self, value: Duration) -> Result { if value.as_nanos() == 0 { @@ -226,7 +244,9 @@ impl KernelConfig { /// Set the maximum write size for a single request /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// On success returns the previous value. + /// # Errors + /// If the argument is too large, returns the nearest value which will succeed. pub fn set_max_write(&mut self, value: u32) -> Result { if value == 0 { return Err(1); @@ -241,7 +261,9 @@ impl KernelConfig { /// Set the maximum readahead size /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// On success returns the previous value. + /// # Errors + /// If the argument is too large, returns the nearest value which will succeed. pub fn set_max_readahead(&mut self, value: u32) -> Result { if value == 0 { return Err(1); @@ -256,7 +278,8 @@ impl KernelConfig { /// Add a set of capabilities. /// - /// On success returns Ok, else return bits of capabilities not supported when capabilities you provided are not all supported by kernel. + /// # Errors + /// When the argument includes capabilities not supported by the kernel, returns the bits of the capabilities not supported. pub fn add_capabilities(&mut self, capabilities_to_add: u64) -> Result<(), u64> { if capabilities_to_add & self.capabilities != capabilities_to_add { return Err(capabilities_to_add - (capabilities_to_add & self.capabilities)); @@ -267,7 +290,9 @@ impl KernelConfig { /// Set the maximum number of pending background requests. Such as readahead requests. /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// On success returns the previous value. + /// # Errors + /// If the argument is too small, returns the nearest value which will succeed. pub fn set_max_background(&mut self, value: u16) -> Result { if value == 0 { return Err(1); @@ -280,7 +305,9 @@ impl KernelConfig { /// Set the threshold of background requests at which the kernel will consider the filesystem /// request queue congested. (it may then switch to sleeping instead of spin-waiting, for example) /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// On success returns the previous value. + /// # Errors + /// If the argument is too small, returns the nearest value which will succeed. pub fn set_congestion_threshold(&mut self, value: u16) -> Result { if value == 0 { return Err(1); @@ -293,7 +320,7 @@ impl KernelConfig { fn congestion_threshold(&self) -> u16 { match self.congestion_threshold { // Default to a threshold of 3/4 of the max background threads - None => (self.max_background as u32 * 3 / 4) as u16, + None => (u32::from(self.max_background) * 3 / 4) as u16, Some(value) => min(value, self.max_background), } } @@ -307,15 +334,17 @@ impl KernelConfig { /// Filesystem trait. /// /// This trait must be implemented to provide a userspace filesystem via FUSE. -/// These methods correspond to fuse_lowlevel_ops in libfuse. Reasonable default +/// These methods correspond to `fuse_lowlevel_ops` in libfuse. Reasonable default /// implementations are provided here to get a mountable filesystem that does /// nothing. #[allow(clippy::too_many_arguments)] +#[allow(unused_variables)] // This is the main API, so variables are named without the underscore for generality. +#[allow(clippy::missing_errors_doc)] // These default implementations do not define the conditions under which errors could occur. pub trait Filesystem { /// Initialize filesystem. /// Called before any other filesystem method. - /// The kernel module connection can be configured using the KernelConfig object - fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), c_int> { + /// The kernel module connection can be configured using the `KernelConfig` object + fn init(&mut self, req: &Request<'_>, config: &mut KernelConfig) -> Result<(), c_int> { Ok(()) } @@ -325,10 +354,7 @@ pub trait Filesystem { /// Look up a directory entry by name and get its attributes. fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { - warn!( - "[Not Implemented] lookup(parent: {:#x?}, name {:?})", - parent, name - ); + warn!("[Not Implemented] lookup(parent: {parent:#x?}, name {name:?})"); reply.error(ENOSYS); } @@ -339,7 +365,7 @@ pub trait Filesystem { /// each forget. The filesystem may ignore forget calls, if the inodes don't need to /// have a limited lifetime. On unmount it is not guaranteed, that all referenced /// inodes will receive a forget message. - fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {} + fn forget(&mut self, req: &Request<'_>, ino: u64, nlookup: u64) {} /// Like forget, but take multiple forget requests at once for performance. The default /// implementation will fallback to forget. @@ -350,44 +376,40 @@ pub trait Filesystem { } /// Get file attributes. - fn getattr(&mut self, _req: &Request<'_>, ino: u64, fh: Option, reply: ReplyAttr) { - warn!( - "[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", - ino, fh - ); + fn getattr(&mut self, req: &Request<'_>, ino: u64, fh: Option, reply: ReplyAttr) { + warn!("[Not Implemented] getattr(ino: {ino:#x?}, fh: {fh:#x?})"); reply.error(ENOSYS); } /// Set file attributes. fn setattr( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, mode: Option, uid: Option, gid: Option, size: Option, - _atime: Option, - _mtime: Option, - _ctime: Option, + atime: Option, + mtime: Option, + ctime: Option, fh: Option, - _crtime: Option, - _chgtime: Option, - _bkuptime: Option, + crtime: Option, + chgtime: Option, + bkuptime: Option, flags: Option, reply: ReplyAttr, ) { warn!( - "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ - gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", - ino, mode, uid, gid, size, fh, flags + "[Not Implemented] setattr(ino: {ino:#x?}, mode: {mode:?}, uid: {uid:?}, \ + gid: {gid:?}, size: {size:?}, fh: {fh:?}, flags: {flags:?})" ); reply.error(ENOSYS); } /// Read symbolic link. - fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyData) { - warn!("[Not Implemented] readlink(ino: {:#x?})", ino); + fn readlink(&mut self, req: &Request<'_>, ino: u64, reply: ReplyData) { + warn!("[Not Implemented] readlink(ino: {ino:#x?})"); reply.error(ENOSYS); } @@ -395,7 +417,7 @@ pub trait Filesystem { /// Create a regular file, character device, block device, fifo or socket node. fn mknod( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, @@ -404,9 +426,8 @@ pub trait Filesystem { reply: ReplyEntry, ) { warn!( - "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ - umask: {:#x?}, rdev: {})", - parent, name, mode, umask, rdev + "[Not Implemented] mknod(parent: {parent:#x?}, name: {name:?}, \ + mode: {mode}, umask: {umask:#x?}, rdev: {rdev})" ); reply.error(ENOSYS); } @@ -414,7 +435,7 @@ pub trait Filesystem { /// Create a directory. fn mkdir( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, @@ -422,42 +443,34 @@ pub trait Filesystem { reply: ReplyEntry, ) { warn!( - "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", - parent, name, mode, umask + "[Not Implemented] mkdir(parent: {parent:#x?}, name: {name:?}, mode: {mode}, umask: {umask:#x?})" ); reply.error(ENOSYS); } /// Remove a file. - fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { - warn!( - "[Not Implemented] unlink(parent: {:#x?}, name: {:?})", - parent, name, - ); + fn unlink(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + warn!("[Not Implemented] unlink(parent: {parent:#x?}, name: {name:?})",); reply.error(ENOSYS); } /// Remove a directory. - fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { - warn!( - "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", - parent, name, - ); + fn rmdir(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + warn!("[Not Implemented] rmdir(parent: {parent:#x?}, name: {name:?})",); reply.error(ENOSYS); } /// Create a symbolic link. fn symlink( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, link_name: &OsStr, target: &Path, reply: ReplyEntry, ) { warn!( - "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", - parent, link_name, target, + "[Not Implemented] symlink(parent: {parent:#x?}, link_name: {link_name:?}, target: {target:?})", ); reply.error(EPERM); } @@ -465,7 +478,7 @@ pub trait Filesystem { /// Rename a file. fn rename( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, newparent: u64, @@ -474,9 +487,8 @@ pub trait Filesystem { reply: ReplyEmpty, ) { warn!( - "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ - newname: {:?}, flags: {})", - parent, name, newparent, newname, flags, + "[Not Implemented] rename(parent: {parent:#x?}, name: {name:?}, \ + newparent: {newparent:#x?}, newname: {newname:?}, flags: {flags})", ); reply.error(ENOSYS); } @@ -484,44 +496,43 @@ pub trait Filesystem { /// Create a hard link. fn link( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, newparent: u64, newname: &OsStr, reply: ReplyEntry, ) { warn!( - "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", - ino, newparent, newname + "[Not Implemented] link(ino: {ino:#x?}, newparent: {newparent:#x?}, newname: {newname:?})" ); reply.error(EPERM); } /// Open a file. - /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are + /// Open flags (with the exception of `O_CREAT`, `O_EXCL`, `O_NOCTTY` and `O_TRUNC`) are /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, /// etc) in fh, and use this in other all other file operations (read, write, flush, /// release, fsync). Filesystem may also implement stateless file I/O and not store - /// anything in fh. There are also some flags (direct_io, keep_cache) which the - /// filesystem may set, to change the way the file is opened. See fuse_file_info - /// structure in for more details. - fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) { + /// anything in fh. There are also some flags (`direct_io`, `keep_cache`) which the + /// filesystem may set, to change the way the file is opened. See `fuse_file_info` + /// structure in <`fuse_common.h`> for more details. + fn open(&mut self, req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { reply.opened(0, 0); } /// Read data. /// Read should send exactly the number of bytes requested except on EOF or error, /// otherwise the rest of the data will be substituted with zeroes. An exception to - /// this is when the file has been opened in 'direct_io' mode, in which case the + /// this is when the file has been opened in `direct_io` mode, in which case the /// return value of the read system call will reflect the return value of this /// operation. fh will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. /// - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 - /// lock_owner: only supported with ABI >= 7.9 + /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 + /// `lock_owner`: only supported with ABI >= 7.9 fn read( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, offset: i64, @@ -531,28 +542,27 @@ pub trait Filesystem { reply: ReplyData, ) { warn!( - "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ - flags: {:#x?}, lock_owner: {:?})", - ino, fh, offset, size, flags, lock_owner + "[Not Implemented] read(ino: {ino:#x?}, fh: {fh}, offset: {offset}, \ + size: {size}, flags: {flags:#x?}, lock_owner: {lock_owner:?})" ); reply.error(ENOSYS); } /// Write data. /// Write should return exactly the number of bytes requested except on error. An - /// exception to this is when the file has been opened in 'direct_io' mode, in + /// exception to this is when the file has been opened in `direct_io` mode, in /// which case the return value of the write system call will reflect the return /// value of this operation. fh will contain the value set by the open method, or /// will be undefined if the open method didn't set any value. /// - /// write_flags: will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, + /// `write_flags`: will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set, /// the pid, uid, gid, and fh may not match the value that would have been sent if write cachin /// is disabled - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 - /// lock_owner: only supported with ABI >= 7.9 + /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 + /// `lock_owner`: only supported with ABI >= 7.9 fn write( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, offset: i64, @@ -563,21 +573,16 @@ pub trait Filesystem { reply: ReplyWrite, ) { warn!( - "[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \ - write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})", - ino, - fh, - offset, - data.len(), - write_flags, - flags, - lock_owner + "[Not Implemented] write(ino: {ino:#x?}, fh: {fh}, offset: {offset}, \ + data.len(): {}, write_flags: {write_flags:#x?}, flags: {flags:#x?}, \ + lock_owner: {lock_owner:?})", + data.len() ); reply.error(ENOSYS); } /// Flush method. - /// This is called on each close() of the opened file. Since file descriptors can + /// This is called on each `close()` of the opened file. Since file descriptors can /// be duplicated (dup, dup2, fork), for one open call there may be many flush /// calls. Filesystems shouldn't assume that flush will always be called after some /// writes, or that if will be called at all. fh will contain the value set by the @@ -585,12 +590,9 @@ pub trait Filesystem { /// NOTE: the name of the method is misleading, since (unlike fsync) the filesystem /// is not forced to flush pending writes. One reason to flush data, is if the /// filesystem wants to return write errors. If the filesystem supports file locking - /// operations (setlk, getlk) it should remove all locks belonging to 'lock_owner'. - fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { - warn!( - "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", - ino, fh, lock_owner - ); + /// operations (`setlk`, `getlk`) it should remove all locks belonging to `lock_owner`. + fn flush(&mut self, req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { + warn!("[Not Implemented] flush(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner:?})"); reply.error(ENOSYS); } @@ -598,18 +600,18 @@ pub trait Filesystem { /// Release is called when there are no more references to an open file: all file /// descriptors are closed and all memory mappings are unmapped. For every open /// call there will be exactly one release call. The filesystem may reply with an - /// error, but error values are not returned to close() or munmap() which triggered + /// error, but error values are not returned to `close()` or `munmap()` which triggered /// the release. fh will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. flags will contain the same flags as for /// open. fn release( &mut self, - _req: &Request<'_>, - _ino: u64, - _fh: u64, - _flags: i32, - _lock_owner: Option, - _flush: bool, + req: &Request<'_>, + ino: u64, + fh: u64, + flags: i32, + lock_owner: Option, + flush: bool, reply: ReplyEmpty, ) { reply.ok(); @@ -618,11 +620,8 @@ pub trait Filesystem { /// Synchronize file contents. /// If the datasync parameter is non-zero, then only the user data should be flushed, /// not the meta data. - fn fsync(&mut self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { - warn!( - "[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", - ino, fh, datasync - ); + fn fsync(&mut self, req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { + warn!("[Not Implemented] fsync(ino: {ino:#x?}, fh: {fh}, datasync: {datasync})"); reply.error(ENOSYS); } @@ -633,47 +632,42 @@ pub trait Filesystem { /// anything in fh, though that makes it impossible to implement standard conforming /// directory stream operations in case the contents of the directory can change /// between opendir and releasedir. - fn opendir(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) { + fn opendir(&mut self, req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { reply.opened(0, 0); } /// Read directory. - /// Send a buffer filled using buffer.fill(), with size not exceeding the + /// Send a buffer filled using `buffer.fill()`, with size not exceeding the /// requested size. Send an empty buffer on end of stream. fh will contain the /// value set by the opendir method, or will be undefined if the opendir method /// didn't set any value. fn readdir( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, offset: i64, reply: ReplyDirectory, ) { - warn!( - "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", - ino, fh, offset - ); + warn!("[Not Implemented] readdir(ino: {ino:#x?}, fh: {fh}, offset: {offset})"); reply.error(ENOSYS); } /// Read directory. - /// Send a buffer filled using buffer.fill(), with size not exceeding the + /// Send a buffer filled using `buffer.fill()`, with size not exceeding the /// requested size. Send an empty buffer on end of stream. fh will contain the /// value set by the opendir method, or will be undefined if the opendir method /// didn't set any value. + #[cfg(feature = "abi-7-21")] fn readdirplus( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, offset: i64, reply: ReplyDirectoryPlus, ) { - warn!( - "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})", - ino, fh, offset - ); + warn!("[Not Implemented] readdirplus(ino: {ino:#x?}, fh: {fh}, offset: {offset})"); reply.error(ENOSYS); } @@ -681,14 +675,7 @@ pub trait Filesystem { /// For every opendir call there will be exactly one releasedir call. fh will /// contain the value set by the opendir method, or will be undefined if the /// opendir method didn't set any value. - fn releasedir( - &mut self, - _req: &Request<'_>, - _ino: u64, - _fh: u64, - _flags: i32, - reply: ReplyEmpty, - ) { + fn releasedir(&mut self, req: &Request<'_>, ino: u64, fh: u64, flags: i32, reply: ReplyEmpty) { reply.ok(); } @@ -698,38 +685,35 @@ pub trait Filesystem { /// method, or will be undefined if the opendir method didn't set any value. fn fsyncdir( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty, ) { - warn!( - "[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})", - ino, fh, datasync - ); + warn!("[Not Implemented] fsyncdir(ino: {ino:#x?}, fh: {fh}, datasync: {datasync})"); reply.error(ENOSYS); } /// Get file system statistics. - fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) { + fn statfs(&mut self, req: &Request<'_>, ino: u64, reply: ReplyStatfs) { reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); } /// Set an extended attribute. fn setxattr( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, name: &OsStr, - _value: &[u8], + value: &[u8], flags: i32, position: u32, reply: ReplyEmpty, ) { warn!( - "[Not Implemented] setxattr(ino: {:#x?}, name: {:?}, flags: {:#x?}, position: {})", - ino, name, flags, position + "[Not Implemented] setxattr(ino: {ino:#x?}, name: {name:?}, \ + flags: {flags:#x?}, position: {position})" ); reply.error(ENOSYS); } @@ -740,16 +724,13 @@ pub trait Filesystem { /// `reply.error(ERANGE)` if it doesn't. fn getxattr( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, name: &OsStr, size: u32, reply: ReplyXattr, ) { - warn!( - "[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})", - ino, name, size - ); + warn!("[Not Implemented] getxattr(ino: {ino:#x?}, name: {name:?}, size: {size})"); reply.error(ENOSYS); } @@ -757,45 +738,39 @@ pub trait Filesystem { /// If `size` is 0, the size of the value should be sent with `reply.size()`. /// If `size` is not 0, and the value fits, send it with `reply.data()`, or /// `reply.error(ERANGE)` if it doesn't. - fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32, reply: ReplyXattr) { - warn!( - "[Not Implemented] listxattr(ino: {:#x?}, size: {})", - ino, size - ); + fn listxattr(&mut self, req: &Request<'_>, ino: u64, size: u32, reply: ReplyXattr) { + warn!("[Not Implemented] listxattr(ino: {ino:#x?}, size: {size})"); reply.error(ENOSYS); } /// Remove an extended attribute. - fn removexattr(&mut self, _req: &Request<'_>, ino: u64, name: &OsStr, reply: ReplyEmpty) { - warn!( - "[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", - ino, name - ); + fn removexattr(&mut self, req: &Request<'_>, ino: u64, name: &OsStr, reply: ReplyEmpty) { + warn!("[Not Implemented] removexattr(ino: {ino:#x?}, name: {name:?})"); reply.error(ENOSYS); } /// Check file access permissions. - /// This will be called for the access() system call. If the 'default_permissions' + /// This will be called for the `access()` system call. If the `default_permissions` /// mount option is given, this method is not called. This method is not called /// under Linux kernel versions 2.4.x - fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) { - warn!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); + fn access(&mut self, req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) { + warn!("[Not Implemented] access(ino: {ino:#x?}, mask: {mask})"); reply.error(ENOSYS); } /// Create and open a file. /// If the file does not exist, first create it with the specified mode, and then - /// open it. You can use any open flags in the flags parameter except O_NOCTTY. + /// open it. You can use any open flags in the flags parameter except `O_NOCTTY`. /// The filesystem can store any type of file handle (such as a pointer or index) /// in fh, which can then be used across all subsequent file operations including /// read, write, flush, release, and fsync. Additionally, the filesystem may set - /// certain flags like direct_io and keep_cache to change the way the file is - /// opened. See fuse_file_info structure in for more details. If + /// certain flags like `direct_io` and `keep_cache` to change the way the file is + /// opened. See `fuse_file_info` structure in <`fuse_common.h`> for more details. If /// this method is not implemented or under Linux kernel versions earlier than - /// 2.6.15, the mknod() and open() methods will be called instead. + /// 2.6.15, the `mknod()` and `open()` methods will be called instead. fn create( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, @@ -804,9 +779,8 @@ pub trait Filesystem { reply: ReplyCreate, ) { warn!( - "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ - flags: {:#x?})", - parent, name, mode, umask, flags + "[Not Implemented] create(parent: {parent:#x?}, name: {name:?}, mode: {mode}, \ + umask: {umask:#x?}, flags: {flags:#x?})" ); reply.error(ENOSYS); } @@ -814,7 +788,7 @@ pub trait Filesystem { /// Test for a POSIX file lock. fn getlk( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, @@ -825,9 +799,8 @@ pub trait Filesystem { reply: ReplyLock, ) { warn!( - "[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ - end: {}, typ: {}, pid: {})", - ino, fh, lock_owner, start, end, typ, pid + "[Not Implemented] getlk(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner}, \ + start: {start}, end: {end}, typ: {typ}, pid: {pid})" ); reply.error(ENOSYS); } @@ -835,13 +808,13 @@ pub trait Filesystem { /// Acquire, modify or release a POSIX file lock. /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but /// otherwise this is not always the case. For checking lock ownership, - /// 'fi->owner' must be used. The l_pid field in 'struct flock' should only be - /// used to fill in this field in getlk(). Note: if the locking methods are not + /// 'fi->owner' must be used. The `l_pid` field in 'struct flock' should only be + /// used to fill in this field in `getlk()`. Note: if the locking methods are not /// implemented, the kernel will still allow file locking to work locally. /// Hence these are only interesting for network filesystems and similar. fn setlk( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, @@ -853,9 +826,8 @@ pub trait Filesystem { reply: ReplyEmpty, ) { warn!( - "[Not Implemented] setlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ - end: {}, typ: {}, pid: {}, sleep: {})", - ino, fh, lock_owner, start, end, typ, pid, sleep + "[Not Implemented] setlk(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner}, \ + start: {start}, end: {end}, typ: {typ}, pid: {pid}, sleep: {sleep})" ); reply.error(ENOSYS); } @@ -863,18 +835,15 @@ pub trait Filesystem { /// Map block index within file to block index within device. /// Note: This makes sense only for block device backed filesystems mounted /// with the 'blkdev' option - fn bmap(&mut self, _req: &Request<'_>, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) { - warn!( - "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", - ino, blocksize, idx, - ); + fn bmap(&mut self, req: &Request<'_>, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) { + warn!("[Not Implemented] bmap(ino: {ino:#x?}, blocksize: {blocksize}, idx: {idx})",); reply.error(ENOSYS); } /// control device fn ioctl( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, flags: u32, @@ -884,14 +853,9 @@ pub trait Filesystem { reply: ReplyIoctl, ) { warn!( - "[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \ - in_data.len(): {}, out_size: {})", - ino, - fh, - flags, - cmd, - in_data.len(), - out_size, + "[Not Implemented] ioctl(ino: {ino:#x?}, fh: {fh}, flags: {flags}, \ + cmd: {cmd}, in_data.len(): {}, out_size: {out_size})", + in_data.len() ); reply.error(ENOSYS); } @@ -899,7 +863,7 @@ pub trait Filesystem { /// Poll for events fn poll( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, ph: PollHandle, @@ -908,8 +872,8 @@ pub trait Filesystem { reply: ReplyPoll, ) { warn!( - "[Not Implemented] poll(ino: {:#x?}, fh: {}, ph: {:?}, events: {}, flags: {})", - ino, fh, ph, events, flags + "[Not Implemented] poll(ino: {ino:#x?}, fh: {fh}, \ + ph: {ph:?}, events: {events}, flags: {flags})" ); reply.error(ENOSYS); } @@ -917,7 +881,7 @@ pub trait Filesystem { /// Preallocate or deallocate space to a file fn fallocate( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, offset: i64, @@ -926,17 +890,17 @@ pub trait Filesystem { reply: ReplyEmpty, ) { warn!( - "[Not Implemented] fallocate(ino: {:#x?}, fh: {}, offset: {}, \ - length: {}, mode: {})", - ino, fh, offset, length, mode + "[Not Implemented] fallocate(ino: {ino:#x?}, fh: {fh}, \ + offset: {offset}, length: {length}, mode: {mode})" ); reply.error(ENOSYS); } /// Reposition read/write file offset + #[cfg(feature = "abi-7-24")] fn lseek( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, fh: u64, offset: i64, @@ -944,8 +908,8 @@ pub trait Filesystem { reply: ReplyLseek, ) { warn!( - "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", - ino, fh, offset, whence + "[Not Implemented] lseek(ino: {ino:#x?}, fh: {fh}, \ + offset: {offset}, whence: {whence})" ); reply.error(ENOSYS); } @@ -953,7 +917,7 @@ pub trait Filesystem { /// Copy the specified range from the source inode to the destination inode fn copy_file_range( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino_in: u64, fh_in: u64, offset_in: i64, @@ -965,19 +929,18 @@ pub trait Filesystem { reply: ReplyWrite, ) { warn!( - "[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \ - offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \ - len: {}, flags: {})", - ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags + "[Not Implemented] copy_file_range(ino_in: {ino_in:#x?}, fh_in: {fh_in}, \ + offset_in: {offset_in}, ino_out: {ino_out:#x?}, fh_out: {fh_out}, \ + offset_out: {offset_out}, len: {len}, flags: {flags})" ); reply.error(ENOSYS); } - /// macOS only: Rename the volume. Set fuse_init_out.flags during init to - /// FUSE_VOL_RENAME to enable + /// macOS only: Rename the volume. Set `fuse_init_out.flags` during init to + /// `FUSE_VOL_RENAME` to enable #[cfg(target_os = "macos")] - fn setvolname(&mut self, _req: &Request<'_>, name: &OsStr, reply: ReplyEmpty) { - warn!("[Not Implemented] setvolname(name: {:?})", name); + fn setvolname(&mut self, req: &Request<'_>, name: &OsStr, reply: ReplyEmpty) { + warn!("[Not Implemented] setvolname(name: {name:?})"); reply.error(ENOSYS); } @@ -985,7 +948,7 @@ pub trait Filesystem { #[cfg(target_os = "macos")] fn exchange( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, newparent: u64, @@ -994,18 +957,17 @@ pub trait Filesystem { reply: ReplyEmpty, ) { warn!( - "[Not Implemented] exchange(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ - newname: {:?}, options: {})", - parent, name, newparent, newname, options + "[Not Implemented] exchange(parent: {parent:#x?}, name: {name:?}, \ + newparent: {newparent:#x?}, newname: {newname:?}, options: {options})" ); reply.error(ENOSYS); } - /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags - /// during init to FUSE_XTIMES to enable + /// macOS only: Query extended times (`bkuptime` and `crtime`). Set `fuse_init_out.flags` + /// during init to `FUSE_XTIMES` to enable #[cfg(target_os = "macos")] - fn getxtimes(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyXTimes) { - warn!("[Not Implemented] getxtimes(ino: {:#x?})", ino); + fn getxtimes(&mut self, req: &Request<'_>, ino: u64, reply: ReplyXTimes) { + warn!("[Not Implemented] getxtimes(ino: {ino:#x?})"); reply.error(ENOSYS); } } @@ -1014,6 +976,10 @@ pub trait Filesystem { /// not return until the filesystem is unmounted. /// /// Note that you need to lead each option with a separate `"-o"` string. +/// # Errors +/// Returns an error if the options are incorrect, or if the fuse device can't be mounted, +/// and any final error when the session comes to an end. +/// Propagates errors due to communicating with the fuse device. #[deprecated(note = "use mount2() instead")] pub fn mount>( filesystem: FS, @@ -1027,7 +993,11 @@ pub fn mount>( /// Mount the given filesystem to the given mountpoint. This function will /// not return until the filesystem is unmounted. /// -/// NOTE: This will eventually replace mount(), once the API is stable +/// NOTE: This will eventually replace `mount()`, once the API is stable +/// # Errors +/// Returns an error if the options are incorrect, or if the fuse device can't be mounted, +/// and any final error when the session comes to an end. +/// Propagates errors due to communicating with the fuse device. pub fn mount2>( filesystem: FS, mountpoint: P, @@ -1042,6 +1012,9 @@ pub fn mount2>( /// and therefore returns immediately. The returned handle should be stored /// to reference the mounted filesystem. If it's dropped, the filesystem will /// be unmounted. +/// # Errors +/// Returns an error if the options are incorrect, or if the fuse device can't be mounted. +/// Propagates errors due to communicating with the fuse device. #[deprecated(note = "use spawn_mount2() instead")] pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, @@ -1053,7 +1026,8 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( .map(|x| Some(MountOption::from_str(x.to_str()?))) .collect(); let options = options.ok_or(ErrorKind::InvalidData)?; - Session::new(filesystem, mountpoint.as_ref(), options.as_ref()).and_then(|se| se.spawn()) + Session::new(filesystem, mountpoint.as_ref(), options.as_ref()) + .and_then(session::Session::spawn) } /// Mount the given filesystem to the given mountpoint. This function spawns @@ -1063,11 +1037,14 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( /// be unmounted. /// /// NOTE: This is the corresponding function to mount2. +/// # Errors +/// Returns an error if the options are incorrect, or if the fuse device can't be mounted. +/// Propagates errors due to communicating with the fuse device. pub fn spawn_mount2<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, mountpoint: P, options: &[MountOption], ) -> io::Result { check_option_conflicts(options)?; - Session::new(filesystem, mountpoint.as_ref(), options).and_then(|se| se.spawn()) + Session::new(filesystem, mountpoint.as_ref(), options).and_then(session::Session::spawn) } diff --git a/src/ll/argument.rs b/src/ll/argument.rs index 05d70e1c..510a931e 100644 --- a/src/ll/argument.rs +++ b/src/ll/argument.rs @@ -86,7 +86,6 @@ impl<'a> ArgumentIterator<'a> { #[cfg(test)] pub mod tests { - use std::ops::Deref; use super::super::test::AlignedData; use super::*; @@ -105,7 +104,7 @@ pub mod tests { #[test] fn all_data() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); it.fetch_str().unwrap(); let arg = it.fetch_all(); assert_eq!(arg, [0x62, 0x61, 0x72, 0x00, 0x62, 0x61]); @@ -113,7 +112,7 @@ pub mod tests { #[test] fn generic_argument() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); let arg: &TestArgument = it.fetch().unwrap(); assert_eq!(arg.p1, 0x66); assert_eq!(arg.p2, 0x6f); @@ -127,7 +126,7 @@ pub mod tests { #[test] fn string_argument() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); let arg = it.fetch_str().unwrap(); assert_eq!(arg, "foo"); let arg = it.fetch_str().unwrap(); @@ -137,7 +136,7 @@ pub mod tests { #[test] fn mixed_arguments() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); let arg: &TestArgument = it.fetch().unwrap(); assert_eq!(arg.p1, 0x66); assert_eq!(arg.p2, 0x6f); @@ -150,7 +149,7 @@ pub mod tests { #[test] fn out_of_data() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); it.fetch::().unwrap(); let arg: Option<&TestArgument> = it.fetch(); assert!(arg.is_none()); diff --git a/src/ll/fuse_abi.rs b/src/ll/fuse_abi.rs index f35a09af..6c6cf8e3 100644 --- a/src/ll/fuse_abi.rs +++ b/src/ll/fuse_abi.rs @@ -20,6 +20,8 @@ #![warn(missing_debug_implementations)] #![allow(missing_docs)] +// TODO: fix all these non camel case types +#![allow(non_camel_case_types)] use crate::consts::{FATTR_ATIME_NOW, FATTR_MTIME_NOW}; use std::convert::TryFrom; @@ -113,10 +115,15 @@ pub struct fuse_kstatfs { #[repr(C)] #[derive(Debug, IntoBytes, FromBytes, KnownLayout, Immutable)] pub struct fuse_file_lock { + /// start of locked byte range pub start: u64, + /// end of locked byte range pub end: u64, // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed + // TODO enum {F_RDLCK, F_WRLCK, F_UNLCK} + /// kind of lock (read and/or write) pub typ: i32, + /// PID of process blocking our lock pub pid: u32, } @@ -452,7 +459,7 @@ pub struct fuse_forget_in { } #[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] +#[derive(Debug, FromBytes, KnownLayout, Immutable, Clone)] pub struct fuse_forget_one { pub nodeid: u64, pub nlookup: u64, @@ -629,6 +636,7 @@ pub struct fuse_open_out { pub open_flags: u32, #[cfg(not(feature = "abi-7-40"))] pub padding: u32, + /// The `backing_id` field is used to pass a backing file descriptor to the kernel. #[cfg(feature = "abi-7-40")] pub backing_id: u32, } @@ -915,7 +923,7 @@ pub struct fuse_fallocate_in { } #[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] +#[derive(Clone, Debug, FromBytes, KnownLayout, Immutable)] pub struct fuse_in_header { pub len: u32, pub opcode: u32, diff --git a/src/ll/mod.rs b/src/ll/mod.rs index e97ae2f9..adef12c1 100644 --- a/src/ll/mod.rs +++ b/src/ll/mod.rs @@ -4,14 +4,14 @@ mod argument; pub mod fuse_abi; pub(crate) mod notify; pub(crate) mod reply; -mod request; +pub(crate) mod request; use std::{convert::TryInto, num::NonZeroI32, time::SystemTime}; pub use reply::Response; pub use request::{AnyRequest, FileHandle, INodeNo, Lock, Operation, Request, RequestId, Version}; -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] /// Possible input arguments for atime & mtime, which can either be set to a specified time, /// or to the current time pub enum TimeOrNow { @@ -35,8 +35,12 @@ macro_rules! errno { }; } +macro_rules! no_xattr_doc { + () => {"Use this as an error return from getxattr/removexattr to indicate that the xattr doesn't exist. This resolves to the appropriate platform-specific error code."} +} + /// Represents an error code to be returned to the caller -#[derive(Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Errno(pub NonZeroI32); impl Errno { /// Operation not permitted @@ -213,19 +217,20 @@ impl Errno { /// No data available #[cfg(target_os = "linux")] pub const ENODATA: Errno = errno!(libc::ENODATA); + #[doc = no_xattr_doc!()] + #[cfg(target_os = "linux")] + pub const NO_XATTR: Errno = Self::ENODATA; + /// Attribute not found #[cfg(not(target_os = "linux"))] pub const ENOATTR: Errno = errno!(libc::ENOATTR); - - /// Use this as an error return from getxattr/removexattr to indicate that the xattr doesn't - /// exist. This resolves to the appropriate platform specific error code. - #[cfg(target_os = "linux")] - pub const NO_XATTR: Errno = Self::ENODATA; + #[doc = no_xattr_doc!()] #[cfg(not(target_os = "linux"))] pub const NO_XATTR: Errno = Self::ENOATTR; + /// Convert libc-style error codes into `fuser::Errno` pub fn from_i32(err: i32) -> Errno { - err.try_into().ok().map(Errno).unwrap_or(Errno::EIO) + err.try_into().ok().map_or(Errno::EIO, Errno) } } impl From for Errno { @@ -262,7 +267,7 @@ impl From for i32 { /// mount time). So if the file system reuses an inode after it has been /// deleted, it must assign a new, previously unused generation number to the /// inode at the same time. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct Generation(pub u64); impl From for u64 { fn from(fh: Generation) -> Self { diff --git a/src/ll/reply.rs b/src/ll/reply.rs index ed525bd4..bfa6e5c1 100644 --- a/src/ll/reply.rs +++ b/src/ll/reply.rs @@ -188,6 +188,7 @@ impl<'a> Response<'a> { } // TODO: Can flags be more strongly typed? + #[allow(clippy::too_many_arguments)] pub(crate) fn new_create( ttl: &Duration, attr: &Attr, @@ -228,12 +229,13 @@ impl<'a> Response<'a> { // these fields are only needed for unrestricted ioctls flags: 0, in_iovs: 1, - out_iovs: if !data.is_empty() { 1 } else { 0 }, + // boolean to integer + out_iovs: u32::from(!data.is_empty()), }; // TODO: Don't copy this data let mut v: ResponseBuf = ResponseBuf::from_slice(r.as_bytes()); for x in data { - v.extend_from_slice(x) + v.extend_from_slice(x); } Self::Data(v) } @@ -246,7 +248,7 @@ impl<'a> Response<'a> { Self::from_struct(&r) } - fn new_directory(list: EntListBuf) -> Self { + pub(crate) fn new_directory(list: EntListBuf) -> Self { assert!(list.buf.len() <= list.max_size); Self::Data(list.buf) } @@ -256,16 +258,18 @@ impl<'a> Response<'a> { Self::from_struct(&r) } + #[cfg(feature = "abi-7-24")] pub(crate) fn new_lseek(offset: i64) -> Self { let r = abi::fuse_lseek_out { offset }; Self::from_struct(&r) } - fn from_struct(data: &T) -> Self { + pub(crate) fn from_struct(data: &T) -> Self { Self::Data(SmallVec::from_slice(data.as_bytes())) } } +#[allow(clippy::cast_possible_wrap)] // See abi::fuse_attr pub(crate) fn time_from_system_time(system_time: &SystemTime) -> (i64, u32) { // Convert to signed 64-bit time with epoch at 0 match system_time.duration_since(UNIX_EPOCH) { @@ -291,9 +295,9 @@ pub(crate) fn mode_from_kind_and_perm(kind: FileType, perm: u16) -> u32 { FileType::Symlink => libc::S_IFLNK, FileType::Socket => libc::S_IFSOCK, }) as u32 - | perm as u32 + | u32::from(perm) } -/// Returns a fuse_attr from FileAttr +/// Returns a `fuse_attr` from `FileAttr` pub(crate) fn fuse_attr_from_attr(attr: &crate::FileAttr) -> abi::fuse_attr { let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime); let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime); @@ -348,12 +352,13 @@ impl From for Attr { } #[derive(Debug)] -struct EntListBuf { +/// A generic data buffer +pub(crate) struct EntListBuf { max_size: usize, buf: ResponseBuf, } impl EntListBuf { - fn new(max_size: usize) -> Self { + pub(crate) fn new(max_size: usize) -> Self { Self { max_size, buf: ResponseBuf::new(), @@ -364,7 +369,7 @@ impl EntListBuf { /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls #[must_use] - fn push(&mut self, ent: [&[u8]; 2]) -> bool { + pub(crate) fn push(&mut self, ent: [&[u8]; 2]) -> bool { let entlen = ent[0].len() + ent[1].len(); let entsize = (entlen + size_of::() - 1) & !(size_of::() - 1); // 64bit align if self.buf.len() + entsize > self.max_size { @@ -405,7 +410,7 @@ impl> DirEntry { } } -/// Used to respond to [ReadDirPlus] requests. +/// Data buffer used to respond to [`Readdir`] requests. #[derive(Debug)] pub struct DirEntList(EntListBuf); impl From for Response<'_> { @@ -435,6 +440,7 @@ impl DirEntList { } } +#[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct DirEntryPlus> { #[allow(unused)] // We use `attr.ino` instead @@ -447,6 +453,7 @@ pub struct DirEntryPlus> { attr_valid: Duration, } +#[cfg(feature = "abi-7-21")] impl> DirEntryPlus { pub fn new( ino: INodeNo, @@ -469,9 +476,12 @@ impl> DirEntryPlus { } } -/// Used to respond to [ReadDir] requests. +/// Data buffer used to respond to [`ReaddirPlus`] requests. +#[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct DirEntPlusList(EntListBuf); + +#[cfg(feature = "abi-7-21")] impl From for Response<'_> { fn from(l: DirEntPlusList) -> Self { assert!(l.0.buf.len() <= l.0.max_size); @@ -479,6 +489,7 @@ impl From for Response<'_> { } } +#[cfg(feature = "abi-7-21")] impl DirEntPlusList { pub(crate) fn new(max_size: usize) -> Self { Self(EntListBuf::new(max_size)) @@ -511,10 +522,13 @@ impl DirEntPlusList { } #[cfg(test)] +#[allow(clippy::cast_possible_truncation)] // these byte literals are not in danger of being truncated +#[allow(clippy::unreadable_literal)] // test literals don't need to be beautiful mod test { use std::num::NonZeroI32; use super::super::test::ioslice_to_vec; + #[allow(clippy::wildcard_imports)] use super::*; #[test] diff --git a/src/ll/request.rs b/src/ll/request.rs index 066ab78b..05be0d97 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -32,11 +32,11 @@ pub enum RequestError { /// distinguish between multiple concurrent requests. The unique id of a request may be /// reused in later requests after it has completed. /// -/// This can be retrieve for any request using [Request::unique]. The kernel -/// will send an [Interrupt] request to cancel requests in progress. It's +/// This can be retrieve for any request using [`Request::unique`]. The kernel +/// will send an [`Interrupt`] request to cancel requests in progress. It's /// important to handle this for any requests that may block indefinitely, like -/// [SetLkW]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// [`SetLkW`]. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct RequestId(pub u64); impl From for u64 { @@ -48,27 +48,27 @@ impl From for u64 { /// A newtype for inode numbers /// /// These are generated by the filesystem implementation and returned to the -/// kernel in response to a call to [Lookup], [Create], [MkNod], [MkDir] or -/// [SymLink]. The kernel will then pass these numbers back to the filesystem +/// kernel in response to a call to [`Lookup`], [`Create`], [`MkNod`], [`MkDir`] or +/// [`SymLink`]. The kernel will then pass these numbers back to the filesystem /// implementation when it needs to refer to a given file. Every request has -/// an associated [INodeNo], accessible as [Request::nodeid]. +/// an associated [`INodeNo`], accessible as [`Request::nodeid`]. /// /// Reference Counting /// ------------------ /// /// Every time the kernel receives a given inode number in a response to a -/// [Lookup], [Create], [MkNod], [MkDir] or [SymLink] request it increments an +/// [`Lookup`], [`Create`], [`MkNod`], [`MkDir`] or [`SymLink`] request it increments an /// internal counter for that inode. The filesystem implementation should do /// the same. When the kernel is no longer interested in this inode it will -/// send a [Forget] message with that counter. The filesystem implementation +/// send a [`Forget`] message with that counter. The filesystem implementation /// should decrement its own counter and if it reaches 0 then the inode number /// may be recycled and your filesystem implementation may clean up its /// internal data-structures relating to that inode. /// -/// We implement conversion from [INodeNo] to [u64] but not vice-versa because -/// not all [u64]s are valid [INodeNo]s, but the reverse is true. So to produce -/// a [INodeNo] from a [u64] we must be explicit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// We implement conversion from [`INodeNo`] to [`u64`] but not vice-versa because +/// not all [`u64`]s are valid [`INodeNo`]s, but the reverse is true. So to produce +/// a [`INodeNo`] from a [`u64`] we must be explicit. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct INodeNo(pub u64); impl From for u64 { @@ -80,39 +80,39 @@ impl From for u64 { /// A newtype for file handles /// /// This corresponds to a single file description in a client program. These -/// are generated by the filesystem implementation in replies to [Open], -/// [OpenDir] and [Create] requests. It's used as a correlation id across -/// [Read], [Write], [FSync], [IoCtl], [Poll], [FAllocate], [ReadDir], -/// [FSyncDir], [GetLk], [SetLk], [SetLkW], [ReadDirPlus], [Lseek] and -/// [CopyFileRange] requests. +/// are generated by the filesystem implementation in replies to [`Open`], +/// [`OpenDir`] and [`Create`] requests. It's used as a correlation id across +/// [`Read`], [`Write`], [`FSync`], [`IoCtl`], [`Poll`], [`FAllocate`], [`ReadDir`], +/// [`FSyncDir`], [`GetLk`], [`SetLk`], [`SetLkW`], [`ReadDirPlus`], [`Lseek`] and +/// [`CopyFileRange`] requests. /// -/// A filesystem implementation may store arbitrary data as the [FileHandle], as +/// A filesystem implementation may store arbitrary data as the [`FileHandle`], as /// long as it fits into 64-bits and doesn't need to change for over the lifetime -/// of the [FileHandle]. Typically this might consist of an index into an array -/// of [FileHandle]s that the filesystem implementation maintains. +/// of the [`FileHandle`]. Typically this might consist of an index into an array +/// of [`FileHandle`]s that the filesystem implementation maintains. /// /// Filesystems may instead implement stateless file I/O and use `0` as the -/// [FileHandle] - although this makes it impossible to correctly implement -/// resumable [ReadDir] in the presence of mutable directories (see [OpenDir]). +/// [`FileHandle`] - although this makes it impossible to correctly implement +/// resumable [`ReadDir`] in the presence of mutable directories (see [`OpenDir`]). /// /// Lifecycle /// --------- /// -/// A [FileHandle] is owned by one or more file-descriptors (or memory +/// A [`FileHandle`] is owned by one or more file-descriptors (or memory /// mappings) in the client program. Multiple file descriptors can point to -/// the same [FileHandle], just as a single INode can have multiple -/// [FileHandle]s open at one time. Every time a single file-descriptor is -/// closed a [Flush] request is made. This gives filesystem implementations +/// the same [`FileHandle`], just as a single `INode` can have multiple +/// [`FileHandle`]s open at one time. Every time a single file-descriptor is +/// closed a [`Flush`] request is made. This gives filesystem implementations /// an opportunity to return an error message from that `close()` call. After -/// all the file-descriptors are closed that own a given [FileHandle] the -/// [Release]/[ReleaseDir] request will be made. This is an opportunity for +/// all the file-descriptors are closed that own a given [`FileHandle`] the +/// [`Release`]/[`ReleaseDir`] request will be made. This is an opportunity for /// the filesystem implementation to free any internal per-FileHandle data /// structures it has allocated. /// -/// We implement conversion from FileHandle to u64 but not vice-versa because -/// not all u64s are valid FileHandles, but the reverse is true. So to produce -/// a FileHandle from a u64 we must be explicit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// We implement conversion from `FileHandle` to[ `u64`] but not vice-versa because +/// not all [`u64`]s are valid `FileHandles`, but the reverse is true. So to produce +/// a `FileHandle` from a [`u64`] we must be explicit. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct FileHandle(pub u64); @@ -126,12 +126,12 @@ impl From for u64 { /// /// TODO: Document lock lifecycle and how and when to implement file locking. /// -/// See [Read], [Write], [Release], [Flush], [GetLk], [SetLk], [SetLkW]. +/// See [`Read`], [`Write`], [`Release`], [`Flush`], [`GetLk`], [`SetLk`], [`SetLkW`]. /// -/// We implement conversion from [LockOwner] to [u64] but not vice-versa -/// because all LockOwners are valid [u64]s, but not vice-versa. So to produce -/// a [LockOwner] from a [u64] we must be explicit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// We implement conversion from [`LockOwner`] to [`u64`] but not vice-versa +/// because all `LockOwners` are valid [`u64`]s, but not vice-versa. So to produce +/// a [`LockOwner`] from a [`u64`] we must be explicit. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct LockOwner(pub u64); @@ -141,7 +141,7 @@ impl From for u64 { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct Lock { // Unfortunately this can't be a std::ops::Range because Range is not Copy: // https://github.com/rust-lang/rfcs/issues/2848 @@ -161,14 +161,14 @@ impl Lock { } /// A newtype for ABI version -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct Version(pub u32, pub u32); impl Version { - pub fn major(&self) -> u32 { + pub fn major(self) -> u32 { self.0 } - pub fn minor(&self) -> u32 { + pub fn minor(self) -> u32 { self.1 } } @@ -179,7 +179,7 @@ impl Display for Version { } /// Represents a filename in a directory -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct FilenameInDir<'a> { /// The Inode number of the directory pub dir: INodeNo, @@ -264,6 +264,8 @@ macro_rules! impl_request { }; } +#[allow(clippy::cast_possible_truncation)] // some builtin types have excess capacity +#[allow(clippy::unused_self)] // many functions are feature-gated into trivial functions mod op { use crate::ll::Response; @@ -271,6 +273,7 @@ mod op { super::{TimeOrNow, argument::ArgumentIterator}, FilenameInDir, Request, }; + #[allow(clippy::wildcard_imports)] use super::{ FileHandle, INodeNo, Lock, LockOwner, Operation, RequestId, abi::consts::*, abi::*, }; @@ -286,9 +289,9 @@ mod op { /// Look up a directory entry by name and get its attributes. /// - /// Implementations allocate and assign [INodeNo]s in this request. Learn more - /// about INode lifecycle and the relationship between [Lookup] and [Forget] in the - /// documentation for [INodeNo]. + /// Implementations allocate and assign [`INodeNo`]s in this request. Learn more + /// about `INode` lifecycle and the relationship between [`Lookup`] and [`Forget`] in the + /// documentation for [`INodeNo`]. #[derive(Debug)] pub struct Lookup<'a> { header: &'a fuse_in_header, @@ -302,13 +305,13 @@ mod op { } /// Forget about an inode. /// - /// The nlookup parameter indicates the number of lookups previously performed on + /// The `nlookup` parameter indicates the number of lookups previously performed on /// this inode. If the filesystem implements inode lifetimes, it is recommended that /// inodes acquire a single reference on each lookup, and lose nlookup references on /// each forget. The filesystem may ignore forget calls, if the inodes don't need to /// have a limited lifetime. /// - /// Learn more about INode lifecycle in the documentation for [INodeNo]. + /// Learn more about `INode` lifecycle in the documentation for [`INodeNo`]. /// /// On unmount it is not guaranteed, that all referenced inodes will receive a forget /// message. @@ -336,7 +339,7 @@ mod op { impl GetAttr<'_> { pub fn file_handle(&self) -> Option { - if self.arg.getattr_flags & crate::FUSE_GETATTR_FH != 0 { + if self.arg.getattr_flags & crate::consts::FUSE_GETATTR_FH != 0 { Some(FileHandle(self.arg.fh)) } else { None @@ -411,10 +414,10 @@ mod op { #[cfg(not(feature = "abi-7-23"))] None } - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. /// /// This will only be set if the user passed a file-descriptor to set the - /// attributes - i.e. they used [libc::fchmod] rather than [libc::chmod]. + /// attributes - i.e. they used [`libc::fchmod`] rather than [`libc::chmod`]. pub fn file_handle(&self) -> Option { match self.arg.valid & FATTR_FH { 0 => None, @@ -640,9 +643,9 @@ mod op { /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, /// etc) in fh, and use this in other all other file operations (read, write, flush, /// release, fsync). Filesystem may also implement stateless file I/O and not store - /// anything in fh. There are also some flags (direct_io, keep_cache) which the - /// filesystem may set, to change the way the file is opened. See fuse_file_info - /// structure in for more details. + /// anything in fh. There are also some flags (`direct_io`, `keep_cache`) which the + /// filesystem may set, to change the way the file is opened. See `fuse_file_info` + /// structure in <`fuse_common.h`> for more details. #[derive(Debug)] pub struct Open<'a> { header: &'a fuse_in_header, @@ -659,7 +662,7 @@ mod op { /// /// Read should send exactly the number of bytes requested except on EOF or error, /// otherwise the rest of the data will be substituted with zeroes. An exception to - /// this is when the file has been opened in 'direct_io' mode, in which case the + /// this is when the file has been opened in `direct_io` mode, in which case the /// return value of the read system call will reflect the return value of this /// operation. #[derive(Debug)] @@ -696,7 +699,7 @@ mod op { /// Write data. /// /// Write should return exactly the number of bytes requested except on error. An - /// exception to this is when the file has been opened in 'direct_io' mode, in + /// exception to this is when the file has been opened in `direct_io` mode, in /// which case the return value of the write system call will reflect the return /// value of this operation. #[derive(Debug)] @@ -717,15 +720,15 @@ mod op { pub fn data(&self) -> &'a [u8] { self.data } - /// Will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, + /// Will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set, /// the pid, uid, gid, and fh may not match the value that would have been sent if write caching /// is disabled /// - /// TODO: WriteFlags type or remove this + /// TODO: `WriteFlags` type or remove this pub fn write_flags(&self) -> u32 { self.arg.write_flags } - /// lock_owner: only supported with ABI >= 7.9 + /// `lock_owner`: only supported with ABI >= 7.9 pub fn lock_owner(&self) -> Option { if self.arg.write_flags & FUSE_WRITE_LOCKOWNER != 0 { Some(LockOwner(self.arg.lock_owner)) @@ -733,7 +736,7 @@ mod op { None } } - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 /// TODO: Make a Flags type specifying valid values pub fn flags(&self) -> i32 { self.arg.flags @@ -750,7 +753,7 @@ mod op { /// Release an open file. /// /// Release is called when there are no more references to an open file: all file - /// descriptors are closed and all memory mappings are unmapped. For every [Open] + /// descriptors are closed and all memory mappings are unmapped. For every [`Open`] /// call there will be exactly one release call. The filesystem may reply with an /// error, but error values are not returned to `close()` or `munmap()` which /// triggered the release. @@ -764,7 +767,7 @@ mod op { pub fn flush(&self) -> bool { self.arg.release_flags & FUSE_RELEASE_FLUSH != 0 } - /// The value set by the [Open] method. + /// The value set by the [`Open`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -790,7 +793,7 @@ mod op { } impl_request!(FSync<'a>); impl FSync<'_> { - /// The value set by the [Open] method. + /// The value set by the [`Open`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -820,7 +823,7 @@ mod op { pub fn flags(&self) -> i32 { self.arg.flags } - /// This will always be 0 except on MacOS. It's recommended that + /// This will always be 0 except on `MacOS`. It's recommended that /// implementations return EINVAL if this is not 0. pub fn position(&self) -> u32 { #[cfg(target_os = "macos")] @@ -832,7 +835,7 @@ mod op { /// Get an extended attribute. /// - /// If the requested XAttr doesn't exist return [Err(Errno::NO_XATTR)] which will + /// If the requested `XAttr` doesn't exist, return [`Err(Errno::NO_XATTR)`] which will /// map to the right platform-specific error code. #[derive(Debug)] pub struct GetXAttr<'a> { @@ -842,32 +845,32 @@ mod op { } impl_request!(GetXAttr<'a>); - /// Type for [GetXAttrSizeEnum::GetSize]. + /// Type for [`GetXAttrSizeEnum::GetSize`]. /// - /// Represents a request from the user to get the size of the data stored in the XAttr. + /// Represents a request from the user to get the size of the data stored in the `XAttr`. #[derive(Debug)] pub struct GetXAttrSize(); #[derive(Debug)] - /// Return type for [GetXAttr::size]. + /// Return type for [`GetXAttr::size`]. pub enum GetXAttrSizeEnum { - /// User is requesting the size of the data stored in the XAttr + /// User is requesting the size of the data stored in the `XAttr` GetSize(GetXAttrSize), - /// User is requesting the data stored in the XAttr. If the data will fit - /// in this number of bytes it should be returned, otherwise return [Err(Errno::ERANGE)]. + /// User is requesting the data stored in the `XAttr`. If the data will fit + /// in this number of bytes it should be returned, otherwise return [`Err(Errno::ERANGE)`]. #[allow(dead_code)] Size(NonZeroU32), } impl<'a> GetXAttr<'a> { - /// Name of the XAttr + /// Name of the `XAttr` pub fn name(&self) -> &'a OsStr { self.name } - /// See [GetXAttrSizeEnum]. + /// See [`GetXAttrSizeEnum`]. /// /// You only need to check this value as an optimisation where there's a - /// cost difference between checking the size of the data stored in an XAttr - /// and actually providing the data. Otherwise just call [reply()] with the + /// cost difference between checking the size of the data stored in an `XAttr` + /// and actually providing the data. Otherwise just call [`reply()`] with the /// data and it will do the right thing. pub fn size(&self) -> GetXAttrSizeEnum { let s: Result = self.arg.size.try_into(); @@ -876,7 +879,7 @@ mod op { Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()), } } - /// The size of the buffer the user has allocated to store the XAttr value. + /// The size of the buffer the user has allocated to store the `XAttr` value. pub(crate) fn size_u32(&self) -> u32 { self.arg.size } @@ -891,7 +894,7 @@ mod op { impl_request!(ListXAttr<'a>); impl ListXAttr<'_> { /// The size of the buffer the caller has allocated to receive the list of - /// XAttrs. If this is 0 the user is just probing to find how much space is + /// `XAttrs`. If this is 0 the user is just probing to find how much space is /// required to fit the whole list. /// /// You don't need to worry about this except as an optimisation. @@ -902,8 +905,8 @@ mod op { /// Remove an extended attribute. /// - /// Return [Err(Errno::NO_XATTR)] if the xattr doesn't exist - /// Return [Err(Errno::ENOTSUP)] if this filesystem doesn't support XAttrs + /// Return [`Err(Errno::NO_XATTR)`] if the xattr doesn't exist + /// Return [`Err(Errno::ENOTSUP)`] if this filesystem doesn't support `XAttrs` #[derive(Debug)] pub struct RemoveXAttr<'a> { header: &'a fuse_in_header, @@ -911,7 +914,7 @@ mod op { } impl_request!(RemoveXAttr<'a>); impl<'a> RemoveXAttr<'a> { - /// Name of the XAttr to remove + /// Name of the `XAttr` to remove pub fn name(&self) -> &'a OsStr { self.name } @@ -919,15 +922,15 @@ mod op { /// Flush method. /// - /// This is called on each close() of the opened file. Since file descriptors can + /// This is called on each `close()` of the opened file. Since file descriptors can /// be duplicated (dup, dup2, fork), for one open call there may be many flush /// calls. Filesystems shouldn't assume that flush will always be called after some /// writes, or that if will be called at all. /// - /// NOTE: the name of the method is misleading, since (unlike fsync) the filesystem + /// NOTE: the name of the method is misleading, since (unlike `fsync`) the filesystem /// is not forced to flush pending writes. One reason to flush data, is if the /// filesystem wants to return write errors. If the filesystem supports file locking - /// operations (setlk, getlk) it should remove all locks belonging to 'lock_owner'. + /// operations (`setlk`, `getlk`) it should remove all locks belonging to `lock_owner`. #[derive(Debug)] pub struct Flush<'a> { header: &'a fuse_in_header, @@ -951,12 +954,13 @@ mod op { } impl_request!(Init<'a>); impl<'a> Init<'a> { + #[allow(clippy::cast_possible_truncation)] // truncation is a feature of this computation pub fn capabilities(&self) -> u64 { #[cfg(feature = "abi-7-36")] if self.arg.flags & (FUSE_INIT_EXT as u32) != 0 { - return (self.arg.flags as u64) | ((self.arg.flags2 as u64) << 32); + return u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32); } - self.arg.flags as u64 + u64::from(self.arg.flags) } pub fn max_readahead(&self) -> u32 { self.arg.max_readahead @@ -966,7 +970,8 @@ mod op { } pub fn reply(&self, config: &crate::KernelConfig) -> Response<'a> { - let flags = self.capabilities() & config.requested; // use requested features and reported as capable + // use requested features and reported as capable + let flags = self.capabilities() & config.requested; let init = fuse_init_out { major: FUSE_KERNEL_VERSION, @@ -1005,11 +1010,11 @@ mod op { /// Open a directory. /// /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and - /// use this in other all other directory stream operations ([ReadDir], [ReleaseDir], - /// [FSyncDir]). Filesystem may also implement stateless directory I/O and not store - /// anything in fh, though that makes it impossible to implement standard conforming + /// use this in other all other directory stream operations ([`ReadDir`], [`ReleaseDir`], + /// [`FSyncDir`]). Filesystem may also implement stateless directory I/O and not store + /// anything in `fh`, though that makes it impossible to implement standard conforming /// directory stream operations in case the contents of the directory can change - /// between [OpenDir] and [ReleaseDir]. + /// between [`OpenDir`] and [`ReleaseDir`]. /// /// TODO: Document how to implement "standard conforming directory stream operations" #[derive(Debug)] @@ -1033,7 +1038,7 @@ mod op { } impl_request!(ReadDir<'a>); impl ReadDir<'_> { - /// The value set by the [OpenDir] method. + /// The value set by the [`OpenDir`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1047,7 +1052,7 @@ mod op { /// Release an open directory. /// - /// For every [OpenDir] call there will be exactly one [ReleaseDir] call. + /// For every [`OpenDir`] call there will be exactly one [`ReleaseDir`] call. #[derive(Debug)] pub struct ReleaseDir<'a> { header: &'a fuse_in_header, @@ -1055,7 +1060,7 @@ mod op { } impl_request!(ReleaseDir<'a>); impl ReleaseDir<'_> { - /// The value set by the [OpenDir] method. + /// The value set by the [`OpenDir`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1083,7 +1088,7 @@ mod op { } impl_request!(FSyncDir<'a>); impl FSyncDir<'_> { - /// The value set by the [OpenDir] method. See [FileHandle]. + /// The value set by the [`OpenDir`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1101,7 +1106,7 @@ mod op { } impl_request!(GetLk<'a>); impl GetLk<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1117,8 +1122,8 @@ mod op { /// /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but /// otherwise this is not always the case. For checking lock ownership, - /// 'fi->owner' must be used. The l_pid field in 'struct flock' should only be - /// used to fill in this field in getlk(). Note: if the locking methods are not + /// 'fi->owner' must be used. The `l_pid` field in 'struct `flock`' should only be + /// used to fill in this field in `getlk()`. Note: if the locking methods are not /// implemented, the kernel will still allow file locking to work locally. /// Hence these are only interesting for network filesystems and similar. #[derive(Debug)] @@ -1128,7 +1133,7 @@ mod op { } impl_request!(SetLk<'a>); impl SetLk<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1146,7 +1151,7 @@ mod op { } impl_request!(SetLkW<'a>); impl SetLkW<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1160,7 +1165,7 @@ mod op { /// Check file access permissions. /// - /// This will be called for the `access()` system call. If the 'default_permissions' + /// This will be called for the `access()` system call. If the `default_permissions` /// mount option is given, this method is not called. #[derive(Debug)] pub struct Access<'a> { @@ -1179,12 +1184,12 @@ mod op { /// If the file does not exist, first create it with the specified mode, and then /// open it. Open flags (with the exception of `O_NOCTTY`) are available in flags. /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, - /// and use this in other all other file operations ([Read], [Write], [Flush], [Release], - /// [FSync]). There are also some flags (direct_io, keep_cache) which the - /// filesystem may set, to change the way the file is opened. See fuse_file_info - /// structure in for more details. If this method is not - /// implemented or under Linux kernel versions earlier than 2.6.15, the [MkNod] - /// and [Open] methods will be called instead. + /// and use this in other all other file operations ([`Read`], [`Write`], [`Flush`], [`Release`], + /// [`FSync`]). There are also some flags (`direct_io`, `keep_cache`) which the + /// filesystem may set, to change the way the file is opened. See `fuse_file_info` + /// structure in <`fuse_common.h`> for more details. If this method is not + /// implemented or under Linux kernel versions earlier than 2.6.15, the [`MkNod`] + /// and [`Open`] methods will be called instead. #[derive(Debug)] pub struct Create<'a> { header: &'a fuse_in_header, @@ -1199,7 +1204,7 @@ mod op { pub fn mode(&self) -> u32 { self.arg.mode } - /// Flags as passed to the creat() call + /// Flags as passed to the `create()` call pub fn flags(&self) -> i32 { self.arg.flags } @@ -1223,28 +1228,28 @@ mod op { /// 3) If the request is already sent to userspace, then an INTERRUPT /// request is queued. /// - /// [Interrupt] requests take precedence over other requests, so the - /// userspace filesystem will receive queued [Interrupt]s before any others. + /// [`Interrupt`] requests take precedence over other requests, so the + /// userspace filesystem will receive queued [`Interrupt`]s before any others. /// - /// The userspace filesystem may ignore the [Interrupt] requests entirely, + /// The userspace filesystem may ignore the [`Interrupt`] requests entirely, /// or may honor them by sending a reply to the **original** request, with - /// the error set to [Errno::EINTR]. + /// the error set to [`Errno::EINTR`]. /// /// It is also possible that there's a race between processing the - /// original request and its [Interrupt] request. There are two + /// original request and its [`Interrupt`] request. There are two /// possibilities: /// - /// 1. The [Interrupt] request is processed before the original request is + /// 1. The [`Interrupt`] request is processed before the original request is /// processed /// - /// 2. The [Interrupt] request is processed after the original request has + /// 2. The [`Interrupt`] request is processed after the original request has /// been answered /// /// If the filesystem cannot find the original request, it should wait for /// some timeout and/or a number of new requests to arrive, after which it - /// should reply to the [Interrupt] request with an [Errno::EAGAIN] error. - /// In case (1) the [Interrupt] request will be requeued. In case (2) the - /// [Interrupt] reply will be ignored. + /// should reply to the [`Interrupt`] request with an [`Errno::EAGAIN`] error. + /// In case (1) the [`Interrupt`] request will be requeued. In case (2) the + /// [`Interrupt`] reply will be ignored. #[derive(Debug)] pub struct Interrupt<'a> { header: &'a fuse_in_header, @@ -1259,7 +1264,7 @@ mod op { /// Map block index within file to block index within device. /// Note: This makes sense only for block device backed filesystems mounted - /// with the 'blkdev' option + /// with the `blkdev` option #[derive(Debug)] pub struct BMap<'a> { header: &'a fuse_in_header, @@ -1301,7 +1306,7 @@ mod op { pub fn unrestricted(&self) -> bool { self.arg.flags & consts::FUSE_IOCTL_UNRESTRICTED != 0 } - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1326,7 +1331,7 @@ mod op { } impl_request!(Poll<'a>); impl Poll<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1341,7 +1346,15 @@ mod op { #[cfg(feature = "abi-7-21")] return self.arg.events; #[cfg(not(feature = "abi-7-21"))] - return 0; + // For older ABIs where fuse_poll_in.events is not used or is zero, + // the kernel implies interest in basic readiness. + // Commonly, this means POLLIN, POLLOUT, and POLLPRI. + // POLLRDNORM and POLLWRNORM are often included with POLLIN/POLLOUT. + return (libc::POLLIN + | libc::POLLRDNORM + | libc::POLLOUT + | libc::POLLWRNORM + | libc::POLLPRI) as u32; } /// The poll request's flags @@ -1350,7 +1363,7 @@ mod op { } } - /// NotifyReply. TODO: currently unsupported by fuser + /// `NotifyReply`. TODO: currently unsupported by fuser #[derive(Debug)] pub struct NotifyReply<'a> { header: &'a fuse_in_header, @@ -1359,7 +1372,7 @@ mod op { } impl_request!(NotifyReply<'a>); - /// BatchForget: TODO: merge with Forget + /// `BatchForget`: TODO: merge with Forget #[derive(Debug)] pub struct BatchForget<'a> { header: &'a fuse_in_header, @@ -1369,7 +1382,7 @@ mod op { } impl_request!(BatchForget<'a>); impl<'a> BatchForget<'a> { - /// TODO: Don't return fuse_forget_one, this should be private + /// TODO: Don't return `fuse_forget_one`, this should be private pub fn nodes(&self) -> &'a [fuse_forget_one] { self.nodes } @@ -1388,7 +1401,7 @@ mod op { impl_request!(FAllocate<'a>); #[cfg(feature = "abi-7-19")] impl FAllocate<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1406,7 +1419,7 @@ mod op { /// Read directory. /// - /// TODO: Document when this is called rather than ReadDirectory + /// TODO: Document when this is called rather than `ReadDir` #[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct ReadDirPlus<'a> { @@ -1417,7 +1430,7 @@ mod op { impl_request!(ReadDirPlus<'a>); #[cfg(feature = "abi-7-21")] impl ReadDirPlus<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1431,7 +1444,7 @@ mod op { /// Rename a file. /// - /// TODO: Document the differences to [Rename] and [Exchange] + /// TODO: Document the differences to [`Rename`] and [`Exchange`] #[cfg(feature = "abi-7-23")] #[derive(Debug)] pub struct Rename2<'a> { @@ -1458,8 +1471,8 @@ mod op { } } /// Flags as passed to renameat2. As of Linux 3.18 this is - /// [libc::RENAME_EXCHANGE], [libc::RENAME_NOREPLACE] and - /// [libc::RENAME_WHITEOUT]. If you don't handle a particular flag + /// [`libc::RENAME_EXCHANGE`], [`libc::RENAME_NOREPLACE`] and + /// [`libc::RENAME_WHITEOUT`]. If you don't handle a particular flag /// reply with an EINVAL error. /// /// TODO: Replace with enum/flags type @@ -1481,7 +1494,7 @@ mod op { impl_request!(Lseek<'a>); #[cfg(feature = "abi-7-24")] impl Lseek<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1496,10 +1509,10 @@ mod op { /// Copy the specified range from the source inode to the destination inode #[cfg(feature = "abi-7-28")] - #[derive(Debug, Clone, Copy)] + #[derive(Copy, Clone, Debug)] pub struct CopyFileRangeFile { pub inode: INodeNo, - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub file_handle: FileHandle, pub offset: i64, } @@ -1556,8 +1569,8 @@ mod op { } } - /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags - /// during init to FUSE_XTIMES to enable + /// macOS only: Query extended times (`bkuptime` and `crtime`). Set `fuse_init_out.flags` + /// during init to `FUSE_XTIMES` to enable #[cfg(target_os = "macos")] #[derive(Debug)] pub struct GetXTimes<'a> { @@ -1565,7 +1578,7 @@ mod op { } #[cfg(target_os = "macos")] impl_request!(GetXTimes<'a>); - // API TODO: Consider rename2(RENAME_EXCHANGE) + // API TODO: Consider `rename2(RENAME_EXCHANGE)` /// macOS only (undocumented) #[cfg(target_os = "macos")] #[derive(Debug)] @@ -1604,6 +1617,7 @@ mod op { } impl_request!(CuseInit<'a>); + #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] // See abi::fuse_attr fn system_time_from_time(secs: i64, nsecs: u32) -> SystemTime { if secs >= 0 { SystemTime::UNIX_EPOCH + Duration::new(secs as u64, nsecs) @@ -1611,6 +1625,7 @@ mod op { SystemTime::UNIX_EPOCH - Duration::new((-secs) as u64, nsecs) } } + #[allow(clippy::too_many_lines)] // Very long match statement pub(crate) fn parse<'a>( header: &'a fuse_in_header, opcode: &fuse_opcode, @@ -1845,6 +1860,7 @@ mod op { }) } } +#[allow(clippy::wildcard_imports)] use op::*; /// Filesystem operation (and arguments) the kernel driver wants us to perform. The fields of each @@ -1917,6 +1933,7 @@ pub enum Operation<'a> { CuseInit(CuseInit<'a>), } +#[allow(clippy::too_many_lines)] // Very long match statement impl fmt::Display for Operation<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -2175,8 +2192,9 @@ mod tests { use super::*; use std::ffi::OsStr; - #[cfg(target_endian = "big")] + #[cfg(all(target_endian = "big", not(feature = "abi-7-36")))] const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([ + // decimal 56 == hex 0x38 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1a, // len, opcode 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid @@ -2186,8 +2204,9 @@ mod tests { 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags ]); - #[cfg(target_endian = "little")] + #[cfg(all(target_endian = "little", not(feature = "abi-7-36")))] const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([ + // decimal 56 == hex 0x38 0x38, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid @@ -2197,6 +2216,40 @@ mod tests { 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags ]); + #[cfg(all(target_endian = "big", feature = "abi-7-36"))] + const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([ + // decimal 104 == hex 0x68 + 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1a, // len, opcode + 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid + 0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid + 0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags + 0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data + 0x00, 0x00, 0x00, 0x00, // eleven unused fields + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + + #[cfg(all(target_endian = "little", feature = "abi-7-36"))] + const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([ + // decimal 104 == hex 0x68 + 0x68, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode + 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid + 0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid + 0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags + 0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data + 0x00, 0x00, 0x00, 0x00, // eleven unused fields + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + #[cfg(target_endian = "big")] const MKNOD_REQUEST: AlignedData<[u8; 56]> = [ 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x08, // len, opcode @@ -2231,7 +2284,10 @@ mod tests { #[test] fn short_read() { match AnyRequest::try_from(&INIT_REQUEST[..48]) { + #[cfg(not(feature = "abi-7-36"))] Err(RequestError::ShortRead(48, 56)) => (), + #[cfg(feature = "abi-7-36")] + Err(RequestError::ShortRead(48, 104)) => (), _ => panic!("Unexpected request parsing result"), } } @@ -2239,7 +2295,10 @@ mod tests { #[test] fn init() { let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap(); + #[cfg(not(feature = "abi-7-36"))] assert_eq!(req.header.len, 56); + #[cfg(feature = "abi-7-36")] + assert_eq!(req.header.len, 104); assert_eq!(req.header.opcode, 26); assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d)); assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788)); diff --git a/src/mnt/fuse2_sys.rs b/src/mnt/fuse2_sys.rs index 595447c6..aedb8df9 100644 --- a/src/mnt/fuse2_sys.rs +++ b/src/mnt/fuse2_sys.rs @@ -5,6 +5,8 @@ #![warn(missing_debug_implementations)] #![allow(missing_docs)] +// TODO: fix all these non camel case types +#![allow(non_camel_case_types)] use libc::{c_char, c_int}; diff --git a/src/mnt/fuse3.rs b/src/mnt/fuse3.rs index 39153928..05fbe455 100644 --- a/src/mnt/fuse3.rs +++ b/src/mnt/fuse3.rs @@ -71,7 +71,7 @@ impl Drop for Mount { return; } } - warn!("umount failed with {:?}", err); + warn!("umount failed with {err:?}"); } } } diff --git a/src/mnt/mod.rs b/src/mnt/mod.rs index c35cc5c5..b0fda3c5 100644 --- a/src/mnt/mod.rs +++ b/src/mnt/mod.rs @@ -24,7 +24,7 @@ use std::io; #[cfg(any(feature = "libfuse", test))] use mount_options::MountOption; -/// Helper function to provide options as a fuse_args struct +/// Helper function to provide options as a `fuse_args` struct /// (which contains an argc count and an argv pointer) #[cfg(any(feature = "libfuse", test))] fn with_fuse_args T>(options: &[MountOption], f: F) -> T { @@ -39,6 +39,9 @@ fn with_fuse_args T>(options: &[MountOption], f: F) ]); } let argptrs: Vec<_> = args.iter().map(|s| s.as_ptr()).collect(); + // Max args < max i32 + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_possible_wrap)] f(&fuse_args { argc: argptrs.len() as i32, argv: argptrs.as_ptr(), @@ -101,12 +104,11 @@ fn is_mounted(fuse_device: &File) -> bool { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::Interrupted { continue; - } else { - // This should never happen. The fd is guaranteed good as `File` owns it. - // According to man poll ENOMEM is the only error code unhandled, so we panic - // consistent with rust's usual ENOMEM behaviour. - panic!("Poll failed with error {}", err) } + // This should never happen. The fd is guaranteed good as `File` owns it. + // According to man poll ENOMEM is the only error code unhandled, so we panic + // consistent with rust's usual ENOMEM behaviour. + panic!("Poll failed with error {err}") } _ => unreachable!(), }; diff --git a/src/mnt/mount_options.rs b/src/mnt/mount_options.rs index 1778d28e..76a57251 100644 --- a/src/mnt/mount_options.rs +++ b/src/mnt/mount_options.rs @@ -91,25 +91,26 @@ pub fn check_option_conflicts(options: &[MountOption]) -> Result<(), io::Error> options_set.extend(options.iter().cloned()); let conflicting: HashSet = options.iter().flat_map(conflicts_with).collect(); let intersection: Vec = conflicting.intersection(&options_set).cloned().collect(); - if !intersection.is_empty() { + if intersection.is_empty() { + Ok(()) + } else { Err(io::Error::new( ErrorKind::InvalidInput, format!("Conflicting mount options found: {intersection:?}"), )) - } else { - Ok(()) } } fn conflicts_with(option: &MountOption) -> Vec { match option { - MountOption::FSName(_) => vec![], - MountOption::Subtype(_) => vec![], - MountOption::CUSTOM(_) => vec![], + MountOption::FSName(_) + | MountOption::Subtype(_) + | MountOption::CUSTOM(_) + | MountOption::DirSync + | MountOption::AutoUnmount + | MountOption::DefaultPermissions => vec![], MountOption::AllowOther => vec![MountOption::AllowRoot], MountOption::AllowRoot => vec![MountOption::AllowOther], - MountOption::AutoUnmount => vec![], - MountOption::DefaultPermissions => vec![], MountOption::Dev => vec![MountOption::NoDev], MountOption::NoDev => vec![MountOption::Dev], MountOption::Suid => vec![MountOption::NoSuid], @@ -120,7 +121,6 @@ fn conflicts_with(option: &MountOption) -> Vec { MountOption::NoExec => vec![MountOption::Exec], MountOption::Atime => vec![MountOption::NoAtime], MountOption::NoAtime => vec![MountOption::Atime], - MountOption::DirSync => vec![], MountOption::Sync => vec![MountOption::Async], MountOption::Async => vec![MountOption::Sync], } @@ -133,10 +133,10 @@ pub fn option_to_string(option: &MountOption) -> String { MountOption::Subtype(subtype) => format!("subtype={subtype}"), MountOption::CUSTOM(value) => value.to_string(), MountOption::AutoUnmount => "auto_unmount".to_string(), - MountOption::AllowOther => "allow_other".to_string(), + MountOption::AllowRoot | // AllowRoot is implemented by allowing everyone access and then restricting to // root + owner within fuser - MountOption::AllowRoot => "allow_other".to_string(), + MountOption::AllowOther => "allow_other".to_string(), MountOption::DefaultPermissions => "default_permissions".to_string(), MountOption::Dev => "dev".to_string(), MountOption::NoDev => "nodev".to_string(), @@ -156,8 +156,9 @@ pub fn option_to_string(option: &MountOption) -> String { /// Parses mount command args. /// -/// Input: ["-o", "suid", "-o", "ro,nodev,noexec", "-osync"] -/// Output Ok([Suid, RO, NoDev, NoExec, Sync]) +/// Input: `"-o", "suid", "-o", "ro,nodev,noexec", "-osync"` +/// Output Ok([`Suid`, `RO`, `NoDev`, `NoExec`, `Sync`]) +#[allow(clippy::similar_names)] pub(crate) fn parse_options_from_args(args: &[&OsStr]) -> io::Result> { let err = |x| io::Error::new(ErrorKind::InvalidInput, x); let args: Option> = args.iter().map(|x| x.to_str()).collect(); @@ -174,7 +175,7 @@ pub(crate) fn parse_options_from_args(args: &[&OsStr]) -> io::Result return Err(err(format!("Error parsing args: expected -o, got {x}"))), }; for x in opt.split(',') { - out.push(MountOption::from_str(x)) + out.push(MountOption::from_str(x)); } } Ok(out) @@ -194,7 +195,7 @@ mod test { #[test] fn option_round_trip() { use super::MountOption::*; - for x in [ + for x in &[ FSName("Blah".to_owned()), Subtype("Bloo".to_owned()), CUSTOM("bongos".to_owned()), @@ -214,10 +215,8 @@ mod test { DirSync, Sync, Async, - ] - .iter() - { - assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref())) + ] { + assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref())); } } diff --git a/src/notify.rs b/src/notify.rs index ac0f2d3d..b313e696 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -1,8 +1,6 @@ +use std::ffi::OsStr; use std::io; -#[allow(unused)] -use std::{convert::TryInto, ffi::OsStr}; - use crate::{ channel::ChannelSender, ll::{fuse_abi::fuse_notify_code as notify_code, notify::Notification}, @@ -13,7 +11,7 @@ use crate::{ reply::ReplySender, }; -/// A handle to a pending poll() request. Can be saved and used to notify the +/// A handle to a pending `poll()` request. Can be saved and used to notify the /// kernel when a poll is ready. #[derive(Clone)] pub struct PollHandle { @@ -30,6 +28,8 @@ impl PollHandle { } /// Notify the kernel that the associated file handle is ready to be polled. + /// # Errors + /// Propagates errors due to communicating with the fuse device. pub fn notify(self) -> io::Result<()> { self.notifier.poll(self.handle) } @@ -57,12 +57,17 @@ impl Notifier { } /// Notify poll clients of I/O readiness + /// # Errors + /// Propagates errors due to communicating with the fuse device. pub fn poll(&self, kh: u64) -> io::Result<()> { let notif = Notification::new_poll(kh); self.send(notify_code::FUSE_POLL, ¬if) } /// Invalidate the kernel cache for a given directory entry + /// # Errors + /// Returns an error if the notification data is too large. + /// Propagates errors due to communicating with the fuse device. pub fn inval_entry(&self, parent: u64, name: &OsStr) -> io::Result<()> { let notif = Notification::new_inval_entry(parent, name).map_err(Self::too_big_err)?; self.send_inval(notify_code::FUSE_NOTIFY_INVAL_ENTRY, ¬if) @@ -70,12 +75,17 @@ impl Notifier { /// Invalidate the kernel cache for a given inode (metadata and /// data in the given range) + /// # Errors + /// Propagates errors due to communicating with the fuse device. pub fn inval_inode(&self, ino: u64, offset: i64, len: i64) -> io::Result<()> { let notif = Notification::new_inval_inode(ino, offset, len); self.send_inval(notify_code::FUSE_NOTIFY_INVAL_INODE, ¬if) } /// Update the kernel's cached copy of a given inode's data + /// # Errors + /// Returns an error if the notification data is too large. + /// Propagates errors due to communicating with the fuse device. pub fn store(&self, ino: u64, offset: u64, data: &[u8]) -> io::Result<()> { let notif = Notification::new_store(ino, offset, data).map_err(Self::too_big_err)?; // Not strictly an invalidate, but the inode we're operating @@ -85,12 +95,14 @@ impl Notifier { /// Invalidate the kernel cache for a given directory entry and inform /// inotify watchers of a file deletion. + /// # Errors + /// Returns an error if the notification data is too large. + /// Propagates errors due to communicating with the fuse device. pub fn delete(&self, parent: u64, child: u64, name: &OsStr) -> io::Result<()> { let notif = Notification::new_delete(parent, child, name).map_err(Self::too_big_err)?; self.send_inval(notify_code::FUSE_NOTIFY_DELETE, ¬if) } - #[allow(unused)] fn send_inval(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { match self.send(code, notification) { // ENOENT is harmless for an invalidation (the @@ -111,6 +123,6 @@ impl Notifier { /// would exceed the capacity that its length descriptor field is /// capable of encoding. fn too_big_err(tfie: std::num::TryFromIntError) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("Data too large: {}", tfie)) + io::Error::new(io::ErrorKind::Other, format!("Data too large: {tfie:?}")) } } diff --git a/src/passthrough.rs b/src/passthrough.rs index 0c36c724..399df2e1 100644 --- a/src/passthrough.rs +++ b/src/passthrough.rs @@ -33,24 +33,25 @@ nix::ioctl_write_ptr!( /// `ReplyOpen::opened_passthrough()`. /// /// When working with backing IDs you need to ensure that they live "long enough". A good practice -/// is to create them in the Filesystem::open() impl, store them in the struct of your Filesystem -/// impl, then drop them in the Filesystem::release() impl. Dropping them immediately after -/// sending them in the Filesystem::open() impl can lead to the kernel returning EIO when userspace +/// is to create them in the `Filesystem::open()` impl, store them in the struct of your Filesystem +/// impl, then drop them in the `Filesystem::release()` impl. Dropping them immediately after +/// sending them in the `Filesystem::open()` impl can lead to the kernel returning EIO when userspace /// attempts to access the file. /// -/// This is implemented as a safe wrapper around the backing_id field of the fuse_backing_map +/// This is implemented as a safe wrapper around the `backing_id` field of the `fuse_backing_map` /// struct used by the ioctls involved in fd passthrough. It is created by performing a -/// FUSE_DEV_IOC_BACKING_OPEN ioctl on an fd and has a Drop trait impl which makes a matching -/// FUSE_DEV_IOC_BACKING_CLOSE call. It holds a weak reference on the fuse channel to allow it to +/// `FUSE_DEV_IOC_BACKING_OPEN` ioctl on an fd and has a Drop trait impl which makes a matching +/// `FUSE_DEV_IOC_BACKING_CLOSE` call. It holds a weak reference on the fuse channel to allow it to /// make that call (if the channel hasn't already been closed). #[derive(Debug)] pub struct BackingId { - channel: Weak, - /// The backing_id field passed to and from the kernel + pub(crate) channel: Weak, + /// The `backing_id` field passed to and from the kernel pub(crate) backing_id: u32, } impl BackingId { + #[allow(clippy::cast_sign_loss)] // the sign of an fd or id has no meaning pub(crate) fn create(channel: &Arc, fd: impl AsFd) -> std::io::Result { let map = fuse_backing_map { fd: fd.as_fd().as_raw_fd() as u32, diff --git a/src/reply.rs b/src/reply.rs index dae29c76..dae5cd9d 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -3,17 +3,13 @@ //! A reply is passed to filesystem operation implementations and must be used to send back the //! result of an operation. The reply can optionally be sent to another thread to asynchronously //! work on an operation and provide the result later. Also it allows replying with a block of -//! data without cloning the data. A reply *must always* be used (by calling either ok() or -//! error() exactly once). - -use crate::ll::{ - self, Generation, - reply::{DirEntPlusList, DirEntryPlus}, -}; -use crate::ll::{ - INodeNo, - reply::{DirEntList, DirEntOffset, DirEntry}, -}; +//! data without cloning the data. A reply *must always* be used (by calling either `ok()` or +//! `error()` exactly once). + +use crate::ll; // too many structs to list +use crate::ll::reply::{DirEntList, DirEntOffset, DirEntry}; +#[cfg(feature = "abi-7-21")] +use crate::ll::reply::{DirEntPlusList, DirEntryPlus}; #[cfg(feature = "abi-7-40")] use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; use libc::c_int; @@ -81,11 +77,11 @@ impl ReplyRaw { let sender = self.sender.take().unwrap(); let res = response.with_iovec(self.unique, |iov| sender.send(iov)); if let Err(err) = res { - error!("Failed to send FUSE reply: {}", err); + error!("Failed to send FUSE reply: {err}"); } } fn send_ll(mut self, response: &ll::Response<'_>) { - self.send_ll_mut(response) + self.send_ll_mut(response); } /// Reply to a request with the given error code @@ -276,23 +272,29 @@ impl Reply for ReplyOpen { impl ReplyOpen { /// Reply to a request with the given open result + /// # Panics + /// When attempting to use kernel passthrough. Use `opened_passthrough()` instead. pub fn opened(self, fh: u64, flags: u32) { #[cfg(feature = "abi-7-40")] assert_eq!(flags & FOPEN_PASSTHROUGH, 0); self.reply - .send_ll(&ll::Response::new_open(ll::FileHandle(fh), flags, 0)) + .send_ll(&ll::Response::new_open(ll::FileHandle(fh), flags, 0)); } /// Registers a fd for passthrough, returning a `BackingId`. Once you have the backing ID, /// you can pass it as the 3rd parameter of `OpenReply::opened_passthrough()`. This is done in /// two separate steps because it may make sense to reuse backing IDs (to avoid having to /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// # Errors + /// Propagates errors due to communicating with the fuse device. + /// # Panics + /// Panics if this reply object has already been used. #[cfg(feature = "abi-7-40")] pub fn open_backing(&self, fd: impl std::os::fd::AsFd) -> std::io::Result { self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) } - /// Reply to a request with an opened backing id. Call ReplyOpen::open_backing() to get one of + /// Reply to a request with an opened backing id. Call `ReplyOpen::open_backing()` to get one of /// these. #[cfg(feature = "abi-7-40")] pub fn opened_passthrough(self, fh: u64, flags: u32, backing_id: &BackingId) { @@ -300,7 +302,7 @@ impl ReplyOpen { ll::FileHandle(fh), flags | FOPEN_PASSTHROUGH, backing_id.backing_id, - )) + )); } /// Reply to a request with the given error code @@ -326,9 +328,9 @@ impl Reply for ReplyWrite { } impl ReplyWrite { - /// Reply to a request with the given open result + /// Reply to a request with the number of bytes written pub fn written(self, size: u32) { - self.reply.send_ll(&ll::Response::new_write(size)) + self.reply.send_ll(&ll::Response::new_write(size)); } /// Reply to a request with the given error code @@ -354,7 +356,7 @@ impl Reply for ReplyStatfs { } impl ReplyStatfs { - /// Reply to a request with the given open result + /// Reply to a statfs request with filesystem information #[allow(clippy::too_many_arguments)] pub fn statfs( self, @@ -369,7 +371,7 @@ impl ReplyStatfs { ) { self.reply.send_ll(&ll::Response::new_statfs( blocks, bfree, bavail, files, ffree, bsize, namelen, frsize, - )) + )); } /// Reply to a request with the given error code @@ -395,7 +397,9 @@ impl Reply for ReplyCreate { } impl ReplyCreate { - /// Reply to a request with the given entry + /// Reply to a request with a newly created file entry and its newly open file handle + /// # Panics + /// When attempting to use kernel passthrough. Use `opened_passthrough()` instead. pub fn created(self, ttl: &Duration, attr: &FileAttr, generation: u64, fh: u64, flags: u32) { #[cfg(feature = "abi-7-40")] assert_eq!(flags & FOPEN_PASSTHROUGH, 0); @@ -406,7 +410,7 @@ impl ReplyCreate { ll::FileHandle(fh), flags, 0, - )) + )); } /// Reply to a request with the given error code @@ -432,13 +436,13 @@ impl Reply for ReplyLock { } impl ReplyLock { - /// Reply to a request with the given open result + /// Reply to a request with a file lock pub fn locked(self, start: u64, end: u64, typ: i32, pid: u32) { self.reply.send_ll(&ll::Response::new_lock(&ll::Lock { range: (start, end), typ, pid, - })) + })); } /// Reply to a request with the given error code @@ -464,9 +468,9 @@ impl Reply for ReplyBmap { } impl ReplyBmap { - /// Reply to a request with the given open result + /// Reply to a request with a bmap pub fn bmap(self, block: u64) { - self.reply.send_ll(&ll::Response::new_bmap(block)) + self.reply.send_ll(&ll::Response::new_bmap(block)); } /// Reply to a request with the given error code @@ -492,7 +496,7 @@ impl Reply for ReplyIoctl { } impl ReplyIoctl { - /// Reply to a request with the given open result + /// Reply to a request with an ioctl pub fn ioctl(self, result: i32, data: &[u8]) { self.reply .send_ll(&ll::Response::new_ioctl(result, &[IoSlice::new(data)])); @@ -521,9 +525,9 @@ impl Reply for ReplyPoll { } impl ReplyPoll { - /// Reply to a request with the given poll result + /// Reply to a request with ready poll events pub fn poll(self, revents: u32) { - self.reply.send_ll(&ll::Response::new_poll(revents)) + self.reply.send_ll(&ll::Response::new_poll(revents)); } /// Reply to a request with the given error code @@ -542,7 +546,7 @@ pub struct ReplyDirectory { } impl ReplyDirectory { - /// Creates a new ReplyDirectory with a specified buffer size. + /// Creates a new `ReplyDirectory` with a specified buffer size. pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectory { ReplyDirectory { reply: Reply::new(unique, sender), @@ -557,7 +561,7 @@ impl ReplyDirectory { pub fn add>(&mut self, ino: u64, offset: i64, kind: FileType, name: T) -> bool { let name = name.as_ref(); self.data.push(&DirEntry::new( - INodeNo(ino), + ll::INodeNo(ino), DirEntOffset(offset), kind, name, @@ -576,16 +580,18 @@ impl ReplyDirectory { } /// -/// DirectoryPlus reply +/// `DirectoryPlus` reply /// +#[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct ReplyDirectoryPlus { reply: ReplyRaw, buf: DirEntPlusList, } +#[cfg(feature = "abi-7-21")] impl ReplyDirectoryPlus { - /// Creates a new ReplyDirectory with a specified buffer size. + /// Creates a new `ReplyDirectory` with a specified buffer size. pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectoryPlus { ReplyDirectoryPlus { reply: Reply::new(unique, sender), @@ -607,8 +613,8 @@ impl ReplyDirectoryPlus { ) -> bool { let name = name.as_ref(); self.buf.push(&DirEntryPlus::new( - INodeNo(ino), - Generation(generation), + ll::INodeNo(ino), + ll::Generation(generation), DirEntOffset(offset), name, *ttl, @@ -645,14 +651,14 @@ impl Reply for ReplyXattr { } impl ReplyXattr { - /// Reply to a request with the size of the xattr. + /// Reply to a request with the size of an extended attribute pub fn size(self, size: u32) { - self.reply.send_ll(&ll::Response::new_xattr_size(size)) + self.reply.send_ll(&ll::Response::new_xattr_size(size)); } - /// Reply to a request with the data in the xattr. + /// Reply to a request with the data of an extended attribute pub fn data(self, data: &[u8]) { - self.reply.send_ll(&ll::Response::new_slice(data)) + self.reply.send_ll(&ll::Response::new_slice(data)); } /// Reply to a request with the given error code. @@ -664,11 +670,13 @@ impl ReplyXattr { /// /// Lseek Reply /// +#[cfg(feature = "abi-7-24")] #[derive(Debug)] pub struct ReplyLseek { reply: ReplyRaw, } +#[cfg(feature = "abi-7-24")] impl Reply for ReplyLseek { fn new(unique: u64, sender: S) -> ReplyLseek { ReplyLseek { @@ -677,10 +685,11 @@ impl Reply for ReplyLseek { } } +#[cfg(feature = "abi-7-24")] impl ReplyLseek { /// Reply to a request with seeked offset pub fn offset(self, offset: i64) { - self.reply.send_ll(&ll::Response::new_lseek(offset)) + self.reply.send_ll(&ll::Response::new_lseek(offset)); } /// Reply to a request with the given error code @@ -690,7 +699,10 @@ impl ReplyLseek { } #[cfg(test)] +#[allow(clippy::unreadable_literal)] // ugly hardcoded literals for testing +#[allow(clippy::cast_possible_truncation)] // predetermined literals will not be truncated mod test { + #[allow(clippy::wildcard_imports)] use super::*; use crate::{FileAttr, FileType}; use std::io::IoSlice; @@ -736,7 +748,7 @@ mod test { fn send(&self, data: &[IoSlice<'_>]) -> std::io::Result<()> { let mut v = vec![]; for x in data { - v.extend_from_slice(x) + v.extend_from_slice(x); } assert_eq!(self.expected, v); Ok(()) @@ -801,116 +813,135 @@ mod test { reply.data(&[0xde, 0xad, 0xbe, 0xef]); } + macro_rules! default_attr_struct { + () => {{ + let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); + FileAttr { + ino: 0x11, + size: 0x22, + blocks: 0x33, + atime: time, + mtime: time, + ctime: time, + crtime: time, + kind: FileType::RegularFile, + perm: 0o644, + nlink: 0x55, + uid: 0x66, + gid: 0x77, + rdev: 0x88, + flags: 0x99, + blksize: 0xbb, + } + }}; + } + + macro_rules! default_attr_bytes { + () => {{ + let mut expected = Vec::new(); + expected.extend_from_slice(&[ + // inode attributes + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ino */ + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* size */ + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* blocks */ + ]); + expected.extend_from_slice(&[ + // timestamps (s) + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* atime */ + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* mtime */ + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ctime */ + ]); + #[cfg(target_os = "macos")] + expected.extend_from_slice(&[ + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* crtime */ + ]); + expected.extend_from_slice(&[ + // timestamps (nanos) + 0x78, 0x56, 0x00, 0x00, /* atime */ + 0x78, 0x56, 0x00, 0x00, /* mtime */ + 0x78, 0x56, 0x00, 0x00, /* ctime */ + ]); + #[cfg(target_os = "macos")] + expected.extend_from_slice(&[0x78, 0x56, 0x00, 0x00 /* crtime */]); + expected.extend_from_slice(&[ + // access attributes + 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, + 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + expected.extend_from_slice(&[ + // macos flags + 0x99, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // block size + 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + // return + expected + }}; + } + #[test] fn reply_entry() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; - - expected.extend(vec![0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + // prepare the expected message + let mut expected = Vec::new(); + expected.extend_from_slice(&[ + // FUSE header + 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* size */ + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, /* request id */ + ]); + expected.extend_from_slice(&[ + // ino + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // generation + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // file ttl + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* seconds */ + ]); + expected.extend_from_slice(&[ + // attr ttl + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* whole seconds */ + 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, /* nanoseconds */ + ]); + expected.extend(default_attr_bytes!().iter()); + // correct the header using the actual length expected[0] = (expected.len()) as u8; - + // test reply will be compare with the expected message let sender = AssertSender { expected }; + // prepare the test reply let reply: ReplyEntry = Reply::new(0xdeadbeef, sender); - let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); - let attr = FileAttr { - ino: 0x11, - size: 0x22, - blocks: 0x33, - atime: time, - mtime: time, - ctime: time, - crtime: time, - kind: FileType::RegularFile, - perm: 0o644, - nlink: 0x55, - uid: 0x66, - gid: 0x77, - rdev: 0x88, - flags: 0x99, - blksize: 0xbb, - }; + let attr = default_attr_struct!(); + // send the test reply reply.entry(&ttl, &attr, 0xaa); } #[test] fn reply_attr() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, - 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, - 0x00, 0x00, - ] - } else { - vec![ - 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, - 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; - - expected.extend_from_slice(&[0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + let mut expected = vec![ + // FUSE header + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* size */ + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, /* request id */ + ]; + expected.extend_from_slice(&[ + // ttl + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* seconds */ + 0x21, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* nanoseconds */ + ]); + expected.extend(default_attr_bytes!().iter()); + + // correct size field of header expected[0] = expected.len() as u8; let sender = AssertSender { expected }; let reply: ReplyAttr = Reply::new(0xdeadbeef, sender); - let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); - let attr = FileAttr { - ino: 0x11, - size: 0x22, - blocks: 0x33, - atime: time, - mtime: time, - ctime: time, - crtime: time, - kind: FileType::RegularFile, - perm: 0o644, - nlink: 0x55, - uid: 0x66, - gid: 0x77, - rdev: 0x88, - flags: 0x99, - blksize: 0xbb, - }; + let attr = default_attr_struct!(); reply.attr(&ttl, &attr); } @@ -929,19 +960,74 @@ mod test { reply.xtimes(time, time); } + macro_rules! default_open_tuple { + (with_backing) => {{ + ( + /* fh */ 0x1122, + /* flags */ 0x33, + /* backing_id*/ 0x44 as u32, + ) + }}; + () => {{ + (/* fh */ 0x1122, /* flags */ 0x33) + }}; + } + + macro_rules! default_open_bytes { + (with_backing) => { + default_open_bytes!(0x44, 0x33 | (1 << 7)) + }; + () => { + default_open_bytes!(0x00, 0x33) + }; + ($id:expr, $flag:expr) => {{ + let mut expected = vec![ + // file handle + 0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + expected.extend_from_slice(&[ + // flags + $flag, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // backing id + $id, 0x00, 0x00, 0x00, + ]); + // return + expected + }}; + } #[test] fn reply_open() { - let sender = AssertSender { - expected: vec![ - 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ], - }; + let mut expected = vec![ + // FUSE header + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // request id + ]; + expected.extend(&default_open_bytes!()); + let sender = AssertSender { expected }; let reply: ReplyOpen = Reply::new(0xdeadbeef, sender); - reply.opened(0x1122, 0x33); + let (fh, flags) = default_open_tuple!(); + reply.opened(fh, flags); + } + #[test] + #[cfg(feature = "abi-7-40")] + fn reply_open_passthrough() { + let mut expected = vec![ + // FUSE header + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // request id + ]; + expected.extend(&default_open_bytes!(with_backing)); + let sender = AssertSender { expected }; + let reply: ReplyOpen = Reply::new(0xdeadbeef, sender); + let (fh, flags, backing_id) = default_open_tuple!(with_backing); + let backing = BackingId { + channel: std::sync::Weak::new(), + backing_id, + }; + reply.opened_passthrough(fh, flags, &backing); } - #[test] fn reply_write() { let sender = AssertSender { @@ -973,66 +1059,38 @@ mod test { #[test] fn reply_create() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0xbb, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - }; - - let insert_at = expected.len() - 16; - expected.splice( - insert_at..insert_at, - vec![0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], - ); + let mut expected = vec![ + // FUSE header + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // request id + ]; + expected.extend_from_slice(&[ + // ino + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // generation + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // file ttl + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* seconds */ + ]); + expected.extend_from_slice(&[ + // attr ttl + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* whole seconds */ + 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, /* nanoseconds */ + ]); + expected.extend(&default_attr_bytes!()); + expected.extend(&default_open_bytes!()); expected[0] = (expected.len()) as u8; let sender = AssertSender { expected }; let reply: ReplyCreate = Reply::new(0xdeadbeef, sender); - let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); - let attr = FileAttr { - ino: 0x11, - size: 0x22, - blocks: 0x33, - atime: time, - mtime: time, - ctime: time, - crtime: time, - kind: FileType::RegularFile, - perm: 0o644, - nlink: 0x55, - uid: 0x66, - gid: 0x77, - rdev: 0x88, - flags: 0x99, - blksize: 0xdd, - }; - reply.created(&ttl, &attr, 0xaa, 0xbb, 0xcc); + let attr = default_attr_struct!(); + let (fh, flags) = default_open_tuple!(); + reply.created(&ttl, &attr, 0xaa, fh, flags); } #[test] @@ -1078,6 +1136,95 @@ mod test { reply.ok(); } + #[test] + #[cfg(feature = "abi-7-21")] + fn reply_directory_plus() { + // prepare the expected file attribute portion of the message + // see test::reply_entry() for details + let mut entry_bytes = Vec::new(); + entry_bytes.extend_from_slice(&[ + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + ]); + let mut attr_bytes = default_attr_bytes!(); + + let mut expected = Vec::new(); + + expected.extend_from_slice(&[ + // FUSE header + 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, + ]); + + /* ------ file 1 ------- */ + // entry 1 and attr 1 get a specific ino value + entry_bytes[0] = 0xbb; + entry_bytes[1] = 0xaa; + attr_bytes[0] = 0xbb; + attr_bytes[1] = 0xaa; + // entry 1 and attr 1 have the directory type + let i = if cfg!(target_os = "macos") { 73 } else { 61 }; + attr_bytes[i] = 0x41; + expected.extend_from_slice(&entry_bytes); + expected.extend_from_slice(&attr_bytes); + // dirent 1 + // see test::reply_directory() for details + expected.extend_from_slice(&[ + 0xbb, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, + 0x6f, 0x00, 0x00, 0x00, + ]); + + /* ------ file 2 ------- */ + let mut attr_bytes = default_attr_bytes!(); + // entry 2 and attr 2 get a specific ino value + entry_bytes[0] = 0xdd; + entry_bytes[1] = 0xcc; + attr_bytes[0] = 0xdd; + attr_bytes[1] = 0xcc; + expected.extend_from_slice(&entry_bytes); + expected.extend_from_slice(&attr_bytes); + // dirent 2 + expected.extend_from_slice(&[ + 0xdd, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x2e, 0x72, 0x73, + ]); + // correct the header + expected[0] = (expected.len()) as u8; + // test reply will be compared to expected + let sender = AssertSender { expected }; + let mut reply = + ReplyDirectoryPlus::new(0xdeadbeef, sender, std::mem::size_of::() * 4096); + let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); + let ttl = Duration::new(0x8765, 0x4321); + let attr1 = FileAttr { + ino: 0xaabb, + size: 0x22, + blocks: 0x33, + atime: time, + mtime: time, + ctime: time, + crtime: time, + kind: FileType::Directory, + perm: 0o644, + nlink: 0x55, + uid: 0x66, + gid: 0x77, + rdev: 0x88, + flags: 0x99, + blksize: 0xbb, + }; + let mut attr2 = attr1; //implicit copy + attr2.ino = 0xccdd; + attr2.kind = FileType::RegularFile; + let generation = 0xaa; + assert!(!reply.add(0xaabb, 1, "hello", &ttl, &attr1, generation,)); + assert!(!reply.add(0xccdd, 2, "world.rs", &ttl, &attr2, generation,)); + reply.ok(); + } + #[test] fn reply_xattr_size() { let sender = AssertSender { @@ -1115,7 +1262,7 @@ mod test { } #[test] - fn async_reply() { + fn threaded_reply() { let (tx, rx) = sync_channel::<()>(1); let reply: ReplyEmpty = Reply::new(0xdeadbeef, tx); thread::spawn(move || { diff --git a/src/request.rs b/src/request.rs index 1da221b3..9a22c7b4 100644 --- a/src/request.rs +++ b/src/request.rs @@ -40,7 +40,7 @@ impl<'a> Request<'a> { let request = match ll::AnyRequest::try_from(data) { Ok(request) => request, Err(err) => { - error!("{}", err); + error!("{err}"); return None; } }; @@ -63,10 +63,11 @@ impl<'a> Request<'a> { .with_iovec(unique, |iov| self.ch.send(iov)); if let Err(err) = res { - warn!("Request {:?}: Failed to send reply: {}", unique, err) + warn!("Request {unique:?}: Failed to send reply: {err}"); } } + #[allow(clippy::too_many_lines)] // Very long match statement fn dispatch_req( &self, se: &mut Session, @@ -126,7 +127,7 @@ impl<'a> Request<'a> { // We don't support ABI versions before 7.6 let v = x.version(); if v < ll::Version(7, 6) { - error!("Unsupported FUSE ABI version {}", v); + error!("Unsupported FUSE ABI version {v}"); return Err(Errno::EPROTO); } // Remember ABI version supported by kernel @@ -187,11 +188,11 @@ impl<'a> Request<'a> { se.filesystem .forget(self, self.request.nodeid().into(), x.nlookup()); // no reply } - ll::Operation::GetAttr(_attr) => { + ll::Operation::GetAttr(x) => { se.filesystem.getattr( self, self.request.nodeid().into(), - _attr.file_handle().map(|fh| fh.into()), + x.file_handle().map(std::convert::Into::into), self.reply(), ); } @@ -206,7 +207,7 @@ impl<'a> Request<'a> { x.atime(), x.mtime(), x.ctime(), - x.file_handle().map(|fh| fh.into()), + x.file_handle().map(std::convert::Into::into), x.crtime(), x.chgtime(), x.bkuptime(), @@ -296,7 +297,7 @@ impl<'a> Request<'a> { x.offset(), x.size(), x.flags(), - x.lock_owner().map(|l| l.into()), + x.lock_owner().map(std::convert::Into::into), self.reply(), ); } @@ -309,7 +310,7 @@ impl<'a> Request<'a> { x.data(), x.write_flags(), x.flags(), - x.lock_owner().map(|l| l.into()), + x.lock_owner().map(std::convert::Into::into), self.reply(), ); } @@ -328,7 +329,7 @@ impl<'a> Request<'a> { self.request.nodeid().into(), x.file_handle().into(), x.flags(), - x.lock_owner().map(|x| x.into()), + x.lock_owner().map(std::convert::Into::into), x.flush(), self.reply(), ); @@ -482,18 +483,17 @@ impl<'a> Request<'a> { ll::Operation::IoCtl(x) => { if x.unrestricted() { return Err(Errno::ENOSYS); - } else { - se.filesystem.ioctl( - self, - self.request.nodeid().into(), - x.file_handle().into(), - x.flags(), - x.command(), - x.in_data(), - x.out_size(), - self.reply(), - ); } + se.filesystem.ioctl( + self, + self.request.nodeid().into(), + x.file_handle().into(), + x.flags(), + x.command(), + x.in_data(), + x.out_size(), + self.reply(), + ); } ll::Operation::Poll(x) => { let ph = PollHandle::new(se.ch.sender(), x.kernel_handle()); @@ -617,24 +617,28 @@ impl<'a> Request<'a> { } /// Returns the unique identifier of this request + #[must_use] #[inline] pub fn unique(&self) -> u64 { self.request.unique().into() } /// Returns the uid of this request + #[must_use] #[inline] pub fn uid(&self) -> u32 { self.request.uid() } /// Returns the gid of this request + #[must_use] #[inline] pub fn gid(&self) -> u32 { self.request.gid() } /// Returns the pid of this request + #[must_use] #[inline] pub fn pid(&self) -> u32 { self.request.pid() diff --git a/src/session.rs b/src/session.rs index 1b989b5a..df97ffe4 100644 --- a/src/session.rs +++ b/src/session.rs @@ -9,11 +9,11 @@ use libc::{EAGAIN, EINTR, ENODEV, ENOENT}; use log::{info, warn}; use nix::unistd::geteuid; use std::fmt; +use std::io; use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; -use std::{io, ops::DerefMut}; use crate::Filesystem; use crate::MountOption; @@ -28,7 +28,7 @@ use crate::{channel::ChannelSender, notify::Notifier}; pub const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024; /// Size of the buffer for reading a request from the kernel. Since the kernel may send -/// up to MAX_WRITE_SIZE bytes in a write request, we use that value plus some extra space. +/// up to `MAX_WRITE_SIZE` bytes in a write request, we use that value plus some extra space. const BUFFER_SIZE: usize = MAX_WRITE_SIZE + 4096; #[derive(Default, Debug, Eq, PartialEq)] @@ -53,7 +53,7 @@ pub struct Session { /// Handle to the mount. Dropping this unmounts. mount: Arc>>, /// Whether to restrict access to owner, root + owner, or unrestricted - /// Used to implement allow_root and auto_unmount + /// Used to implement `allow_root` and `auto_unmount` pub(crate) allowed: SessionACL, /// User that launched the fuser process pub(crate) session_owner: u32, @@ -75,6 +75,9 @@ impl AsFd for Session { impl Session { /// Create a new session by mounting the given filesystem to the given mountpoint + /// # Errors + /// Returns an error if the options are incorrect, or if the fuse device can't be mounted. + /// Propagates errors due to communicating with the fuse device. pub fn new>( filesystem: FS, mountpoint: P, @@ -142,14 +145,14 @@ impl Session { /// calls into the filesystem. This read-dispatch-loop is non-concurrent to prevent /// having multiple buffers (which take up much memory), but the filesystem methods /// may run concurrent by spawning threads. + /// # Errors + /// Returns any final error when the session comes to an end. + /// Propagates errors due to communicating with the fuse device. pub fn run(&mut self) -> io::Result<()> { // Buffer for receiving requests from the kernel. Only one is allocated and // it is reused immediately after dispatching to conserve memory and allocations. let mut buffer = vec![0; BUFFER_SIZE]; - let buf = aligned_sub_buf( - buffer.deref_mut(), - std::mem::align_of::(), - ); + let buf = aligned_sub_buf(&mut buffer, std::mem::align_of::()); loop { // Read the next request from the given channel to kernel driver // The kernel driver makes sure that we get exactly one request per read @@ -161,13 +164,11 @@ impl Session { None => break, }, Err(err) => match err.raw_os_error() { - // Operation interrupted. Accordingly to FUSE, this is safe to retry - Some(ENOENT) => continue, - // Interrupted system call, retry - Some(EINTR) => continue, - // Explicitly try again - Some(EAGAIN) => continue, - // Filesystem was unmounted, quit the loop + Some( + ENOENT // Operation interrupted. Accordingly to FUSE, this is safe to retry + | EINTR // Interrupted system call, retry + | EAGAIN // Explicitly instructed to try again + ) => continue, Some(ENODEV) => break, // Unhandled error _ => return Err(err), @@ -178,6 +179,8 @@ impl Session { } /// Unmount the filesystem + /// # Panics + /// Panics if an unmount is already in progress. pub fn unmount(&mut self) { drop(std::mem::take(&mut *self.mount.lock().unwrap())); } @@ -203,7 +206,12 @@ pub struct SessionUnmounter { impl SessionUnmounter { /// Unmount the filesystem + /// # Errors + /// Does not error. + /// # Panics + /// Panics if an unmount is already in progress. pub fn unmount(&mut self) -> io::Result<()> { + // TODO: error instead of panic drop(std::mem::take(&mut *self.mount.lock().unwrap())); Ok(()) } @@ -220,6 +228,10 @@ fn aligned_sub_buf(buf: &mut [u8], alignment: usize) -> &mut [u8] { impl Session { /// Run the session loop in a background thread + /// # Errors + /// Does not error. + /// # Panics + /// Panics if an unmount is already in progress. pub fn spawn(self) -> io::Result { BackgroundSession::new(self) } @@ -245,16 +257,21 @@ pub struct BackgroundSession { /// Object for creating Notifiers for client use sender: ChannelSender, /// Ensures the filesystem is unmounted when the session ends - _mount: Option, + mount: Option, } impl BackgroundSession { /// Create a new background session for the given session by running its /// session loop in a background thread. If the returned handle is dropped, /// the filesystem is unmounted and the given session ends. + /// # Errors + /// Does not error. + /// # Panics + /// Panics if an unmount is already in progress. pub fn new(se: Session) -> io::Result { let sender = se.ch.sender(); // Take the fuse_session, so that we can unmount it + // TODO: error instead of panic. let mount = std::mem::take(&mut *se.mount.lock().unwrap()).map(|(_, mount)| mount); let guard = thread::spawn(move || { let mut se = se; @@ -263,21 +280,27 @@ impl BackgroundSession { Ok(BackgroundSession { guard, sender, - _mount: mount, + mount, }) } /// Unmount the filesystem and join the background thread. + /// Logs any final error when the session ends. + /// # Panics + /// Panics if the background thread can't be recovered. pub fn join(self) { let Self { guard, sender: _, - _mount, + mount, } = self; - drop(_mount); - guard.join().unwrap().unwrap(); + drop(mount); + let res = guard.join().expect("Failed to join the background thread"); + // An error is expected, since the thread was active when the unmount occured. + info!("Session loop end with result {res:?}."); } /// Returns an object that can be used to send notifications to the kernel + #[must_use] pub fn notifier(&self) -> Notifier { Notifier::new(self.sender.clone()) } diff --git a/tests/test_passthrough.sh b/tests/test_passthrough.sh index 0224af33..111be92b 100755 --- a/tests/test_passthrough.sh +++ b/tests/test_passthrough.sh @@ -34,7 +34,7 @@ for x in $(seq 10); do sleep 1 done -expected="$(sha256sum - < /usr/lib/os-release)" +expected="$(sha256sum - < /etc/profile)" # Check that it's equal to the underlying file test "$(sha256sum - < "${mnt}/passthrough")" = "${expected}"