Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b05f59b
Remove all set_creds() calls from sync_io.rs
ejc3 Dec 3, 2025
b206a87
Replace setresuid/setresgid with setfsuid/setfsgid for credential swi…
ejc3 Dec 7, 2025
2548bbc
Add set_creds() to setattr for truncate and utimes
ejc3 Dec 7, 2025
e8118aa
Add set_creds() to all filesystem operations requiring permission checks
ejc3 Dec 7, 2025
d248a43
Add set_creds() to chmod operation in setattr
ejc3 Dec 7, 2025
c12f6d3
Add set_creds() to chown operation in setattr
ejc3 Dec 7, 2025
87caf46
Add set_creds() to lookup for directory search permission check
ejc3 Dec 7, 2025
609391a
Fix link and ftruncate operations for FUSE default_permissions
ejc3 Dec 7, 2025
ca5398a
Add supplementary groups support via /proc parsing
ejc3 Dec 7, 2025
221a76b
Use forwarded supplementary groups for remote filesystems
ejc3 Dec 8, 2025
7159787
Remove /proc fallback for supplementary groups
ejc3 Dec 8, 2025
1d089fe
Add signed lseek hook for backward-compatible negative offsets
ejc3 Dec 9, 2025
da79781
Format debug logging statements
ejc3 Dec 9, 2025
8576023
Add copy_file_range support to FileSystem trait
ejc3 Dec 26, 2025
956c1a6
Add remap_file_range support for FICLONE/FICLONERANGE
ejc3 Dec 26, 2025
3a212e5
Fix remap_file_range: proper return value and ioctl constants
ejc3 Dec 26, 2025
a8e8d0c
Fix ioctl type for ARM64 compatibility
ejc3 Jan 10, 2026
1b001da
Fix O_WRONLY open for files without read permission
ejc3 Jan 10, 2026
f42317d
passthrough: skip per-request fs-cred switch when lacking CAP_SETUID/…
ejc3 Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/api/filesystem/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,20 @@ pub trait AsyncFileSystem: FileSystem {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Reposition read/write file offset with a signed offset.
///
/// Default implementation forwards to [`lseek`] for backward compatibility.
fn lseek_signed(
&self,
ctx: Context,
inode: Self::Inode,
handle: Self::Handle,
offset: i64,
whence: u32,
) -> io::Result<u64> {
self.lseek(ctx, inode, handle, offset as u64, whence)
}

/// TODO: support this
fn getlk(&self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
Expand Down
9 changes: 8 additions & 1 deletion src/api/filesystem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ pub trait ZeroCopyWriter: io::Write {
}

/// Additional context associated with requests.
#[derive(Default, Clone, Copy, Debug)]
#[derive(Default, Clone, Debug)]
pub struct Context {
/// The user ID of the calling process.
pub uid: libc::uid_t,
Expand All @@ -393,6 +393,12 @@ pub struct Context {

/// The thread group ID of the calling process.
pub pid: libc::pid_t,

/// Supplementary groups of the calling process.
///
/// When set, these groups are used directly instead of reading from /proc/<pid>/status.
/// This is essential for remote filesystems where the PID doesn't exist on the server.
pub supplementary_groups: Option<Vec<libc::gid_t>>,
}

impl Context {
Expand All @@ -408,6 +414,7 @@ impl From<&fuse::InHeader> for Context {
uid: source.uid,
gid: source.gid,
pid: source.pid as i32,
supplementary_groups: None,
}
}
}
Expand Down
111 changes: 111 additions & 0 deletions src/api/filesystem/sync_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,22 @@ pub trait FileSystem {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Reposition read/write file offset with a signed offset.
///
/// Default implementation forwards to [`lseek`] for backward compatibility.
/// Filesystems that need negative offsets (e.g. SEEK_END) can override this
/// to receive the signed value directly.
fn lseek_signed(
&self,
ctx: &Context,
inode: Self::Inode,
handle: Self::Handle,
offset: i64,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this signed offset? It is not part of the posix lseek definition.

whence: u32,
) -> io::Result<u64> {
self.lseek(ctx, inode, handle, offset as u64, whence)
}

/// Query file lock status
fn getlk(
&self,
Expand Down Expand Up @@ -852,6 +868,53 @@ pub trait FileSystem {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Copy a range of data from one file to another.
///
/// Performs an optimized copy between two file descriptors. On filesystems
/// that support it (like btrfs), this creates a reflink (copy-on-write clone)
/// which is nearly instantaneous regardless of file size.
///
/// Returns the number of bytes copied.
#[allow(clippy::too_many_arguments)]
fn copy_file_range(
&self,
ctx: &Context,
inode_in: Self::Inode,
handle_in: Self::Handle,
offset_in: u64,
inode_out: Self::Inode,
handle_out: Self::Handle,
offset_out: u64,
len: u64,
flags: u64,
) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Remap file ranges (FICLONE/FICLONERANGE) for copy-on-write filesystems.
///
/// This is the server-side implementation of the FUSE_REMAP_FILE_RANGE opcode,
/// which enables FICLONE and FICLONERANGE ioctls through FUSE. On btrfs and
/// other CoW filesystems, this creates reflinks - instant copies that share
/// the same physical storage until modified.
///
/// Returns the number of bytes remapped.
#[allow(clippy::too_many_arguments)]
fn remap_file_range(
&self,
ctx: &Context,
inode_in: Self::Inode,
handle_in: Self::Handle,
offset_in: u64,
inode_out: Self::Inode,
handle_out: Self::Handle,
offset_out: u64,
len: u64,
flags: u32,
) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}

/// send ioctl to the file
#[allow(clippy::too_many_arguments)]
fn ioctl(
Expand Down Expand Up @@ -1263,6 +1326,18 @@ impl<FS: FileSystem> FileSystem for Arc<FS> {
self.deref().lseek(ctx, inode, handle, offset, whence)
}

fn lseek_signed(
&self,
ctx: &Context,
inode: Self::Inode,
handle: Self::Handle,
offset: i64,
whence: u32,
) -> io::Result<u64> {
self.deref()
.lseek_signed(ctx, inode, handle, offset, whence)
}

/// Query file lock status
fn getlk(
&self,
Expand Down Expand Up @@ -1352,4 +1427,40 @@ impl<FS: FileSystem> FileSystem for Arc<FS> {
fn id_remap(&self, ctx: &mut Context) -> io::Result<()> {
self.deref().id_remap(ctx)
}

#[allow(clippy::too_many_arguments)]
fn copy_file_range(
&self,
ctx: &Context,
inode_in: Self::Inode,
handle_in: Self::Handle,
offset_in: u64,
inode_out: Self::Inode,
handle_out: Self::Handle,
offset_out: u64,
len: u64,
flags: u64,
) -> io::Result<usize> {
self.deref().copy_file_range(
ctx, inode_in, handle_in, offset_in, inode_out, handle_out, offset_out, len, flags,
)
}

#[allow(clippy::too_many_arguments)]
fn remap_file_range(
&self,
ctx: &Context,
inode_in: Self::Inode,
handle_in: Self::Handle,
offset_in: u64,
inode_out: Self::Inode,
handle_out: Self::Handle,
offset_out: u64,
len: u64,
flags: u32,
) -> io::Result<usize> {
self.deref().remap_file_range(
ctx, inode_in, handle_in, offset_in, inode_out, handle_out, offset_out, len, flags,
)
}
}
12 changes: 8 additions & 4 deletions src/api/server/sync_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,11 +1193,15 @@ impl<F: FileSystem + Sync> Server<F> {
let LseekIn {
fh, offset, whence, ..
} = ctx.r.read_obj().map_err(Error::DecodeMessage)?;
let offset_signed = offset as i64;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LseekIn is passing offset: u64. It is wrong to cast it to i64 that can cause overflow.


match self
.fs
.lseek(ctx.context(), ctx.nodeid(), fh.into(), offset, whence)
{
match self.fs.lseek_signed(
ctx.context(),
ctx.nodeid(),
fh.into(),
offset_signed,
whence,
) {
Ok(offset) => {
let out = LseekOut { offset };

Expand Down
Loading
Loading