Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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