Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ use crate::fs::FallocateFlags;
target_os = "wasi"
)))]
use crate::fs::FlockOperation;
#[cfg(all(target_os = "linux", feature = "alloc"))]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If a BorrowedFileHandle type is later added, the feature = "alloc" check can be made specifically for just struct FileHandle and removed from all of these places...

use crate::fs::HandleFlags;
#[cfg(any(linux_kernel, target_os = "freebsd"))]
use crate::fs::MemfdFlags;
#[cfg(any(linux_kernel, apple, target_os = "redox"))]
Expand Down Expand Up @@ -1898,6 +1900,58 @@ const SYS_OPENAT2: i32 = 437;
#[cfg(all(linux_kernel, target_pointer_width = "64"))]
const SYS_OPENAT2: i64 = 437;

#[cfg(all(target_os = "linux", feature = "alloc"))]
pub(crate) fn name_to_handle_at(
dirfd: BorrowedFd<'_>,
path: &CStr,
handle: *mut ffi::c_void,
mount_id: *mut ffi::c_void,
flags: HandleFlags,
) -> io::Result<()> {
syscall! {
fn name_to_handle_at(
dir_fd: c::c_int,
path: *const ffi::c_char,
handle: *mut ffi::c_void,
mount_id: *mut ffi::c_void,
flags: u32
) via SYS_name_to_handle_at -> c::c_int
}

unsafe {
ret(name_to_handle_at(
borrowed_fd(dirfd),
c_str(path),
handle,
mount_id,
flags.bits(),
))
}
}

#[cfg(all(target_os = "linux", feature = "alloc"))]
pub(crate) fn open_by_handle_at(
mount_fd: BorrowedFd<'_>,
handle: *const core::ffi::c_void,
flags: OFlags,
) -> io::Result<OwnedFd> {
syscall! {
fn open_by_handle_at(
mount_fd: c::c_int,
handle: *const ffi::c_void,
flags: u32
) via SYS_open_by_handle_at -> c::c_int
}

unsafe {
ret_owned_fd(open_by_handle_at(
borrowed_fd(mount_fd),
handle,
flags.bits(),
))
}
}

#[cfg(target_os = "linux")]
pub(crate) fn sendfile(
out_fd: BorrowedFd<'_>,
Expand Down
28 changes: 28 additions & 0 deletions src/backend/libc/fs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,34 @@ bitflags! {
}
}

#[cfg(target_os = "linux")]
bitflags! {
/// `AT_*` constants for use with [`name_to_handle_at`]
///
/// [`name_to_handle_at`]: crate::fs::name_to_handle_at
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct HandleFlags: u32 {
/// `AT_HANDLE_FID`
const FID = linux_raw_sys::general::AT_HANDLE_FID;

/// `AT_HANDLE_MNT_ID_UNIQUE`
const MNT_ID_UNIQUE = linux_raw_sys::general::AT_HANDLE_MNT_ID_UNIQUE;

/// `AT_HANDLE_CONNECTABLE`
const CONNECTABLE = linux_raw_sys::general::AT_HANDLE_CONNECTABLE;

/// `AT_SYMLINK_FOLLOW`
const SYMLINK_FOLLOW = linux_raw_sys::general::AT_SYMLINK_FOLLOW;

/// `AT_EMPTY_PATH`
const EMPTY_PATH = linux_raw_sys::general::AT_EMPTY_PATH;

/// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
const _ = !0;
}
}

/// `S_IF*` constants for use with [`mknodat`] and [`Stat`]'s `st_mode` field.
///
/// [`mknodat`]: crate::fs::mknodat
Expand Down
7 changes: 7 additions & 0 deletions src/backend/linux_raw/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,13 @@ pub(crate) mod fs {
}
}

impl<'a, Num: ArgNumber> From<crate::fs::HandleFlags> for ArgReg<'a, Num> {
#[inline]
fn from(flags: crate::fs::HandleFlags) -> Self {
c_uint(flags.bits())
}
}

impl<'a, Num: ArgNumber> From<crate::fs::XattrFlags> for ArgReg<'a, Num> {
#[inline]
fn from(flags: crate::fs::XattrFlags) -> Self {
Expand Down
33 changes: 33 additions & 0 deletions src/backend/linux_raw/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ use crate::backend::conv::{
use crate::backend::conv::{loff_t, loff_t_from_u64, ret_u64};
use crate::fd::{BorrowedFd, OwnedFd};
use crate::ffi::CStr;
#[cfg(feature = "alloc")]
use crate::fs::HandleFlags;
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
use crate::fs::CWD;
use crate::fs::{
Expand Down Expand Up @@ -1665,6 +1667,37 @@ pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
unsafe { ret(syscall_readonly!(__NR_fremovexattr, fd, name)) }
}

#[cfg(feature = "alloc")]
#[inline]
pub(crate) fn name_to_handle_at(
dirfd: BorrowedFd<'_>,
path: &CStr,
file_handle: *mut core::ffi::c_void,
mount_id: *mut core::ffi::c_void,
flags: HandleFlags,
) -> io::Result<()> {
unsafe {
ret(syscall!(
__NR_name_to_handle_at,
dirfd,
path,
file_handle,
mount_id,
flags
))
}
}

#[cfg(feature = "alloc")]
#[inline]
pub(crate) fn open_by_handle_at(
mount_fd: BorrowedFd<'_>,
handle: *const core::ffi::c_void,
flags: OFlags,
) -> io::Result<OwnedFd> {
unsafe { ret_owned_fd(syscall!(__NR_open_by_handle_at, mount_fd, handle, flags)) }
}

// Some linux_raw_sys structs have unsigned types for values which are
// interpreted as signed. This defines a utility or casting to the
// same-sized signed type.
Expand Down
28 changes: 28 additions & 0 deletions src/backend/linux_raw/fs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,34 @@ bitflags! {
}
}

#[cfg(target_os = "linux")]
bitflags! {
/// `AT_*` constants for use with [`name_to_handle_at`]
///
/// [`name_to_handle_at`]: crate::fs::name_to_handle_at
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct HandleFlags: ffi::c_uint {
/// `AT_HANDLE_FID`
const FID = linux_raw_sys::general::AT_HANDLE_FID;

/// `AT_HANDLE_MNT_ID_UNIQUE`
const MNT_ID_UNIQUE = linux_raw_sys::general::AT_HANDLE_MNT_ID_UNIQUE;

/// `AT_HANDLE_CONNECTABLE`
const CONNECTABLE = linux_raw_sys::general::AT_HANDLE_CONNECTABLE;

/// `AT_SYMLINK_FOLLOW`
const SYMLINK_FOLLOW = linux_raw_sys::general::AT_SYMLINK_FOLLOW;

/// `AT_EMPTY_PATH`
const EMPTY_PATH = linux_raw_sys::general::AT_EMPTY_PATH;

/// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
const _ = !0;
}
}

/// `S_IF*` constants for use with [`mknodat`] and [`Stat`]'s `st_mode` field.
///
/// [`mknodat`]: crate::fs::mknodat
Expand Down
191 changes: 191 additions & 0 deletions src/fs/filehandle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use alloc::{boxed::Box, vec};
use core::mem::size_of;

use crate::{backend, ffi, io, path};
use backend::fd::{AsFd, OwnedFd};
use backend::fs::types::{HandleFlags, OFlags};

/// This maximum is more of a "guideline"; the man page for name_to_handle_at(2) indicates it could
/// increase in the future. This value is defined in libc `fcntl.h`.
const MAX_HANDLE_SIZE: usize = 128;
Comment thread
bertschingert marked this conversation as resolved.

/// The minimum size of a `struct file_handle` is the size of an int and an unsigned int, for the
/// length and type fields.
const HANDLE_STRUCT_SIZE: usize = size_of::<ffi::c_uint>() + size_of::<ffi::c_int>();

/// An opaque identifier for a file.
///
/// While the C struct definition in `fcntl.h` exposes the length and type fields,
/// user applications cannot usefully interpret (or modify) those fields of a file handle, so
/// this implementation does not expose them.
#[derive(Debug)]
pub struct FileHandle {
raw: Box<[u8]>,
}

impl FileHandle {
fn new(size: usize) -> Self {
let handle_allocation_size: usize = HANDLE_STRUCT_SIZE + size;
let bytes = vec![0; handle_allocation_size];

let mut handle = Self {
raw: Box::from(bytes),
};
handle.set_handle_len(size);

handle
}

/// Create a file handle from a sequence of bytes.
///
/// # Panics
///
/// Panics if the given handle is malformed, suggesting that it did not originate from a
/// previous call to name_to_handle_at().
pub fn from_raw(raw: Box<[u8]>) -> Self {
assert!(raw.len() >= HANDLE_STRUCT_SIZE);

let handle = Self { raw };

assert!(handle.raw.len() >= handle.get_handle_len() + HANDLE_STRUCT_SIZE);

handle
}

/// Get the raw bytes of a file handle.
pub fn into_raw(self) -> Box<[u8]> {
self.raw
}
Comment thread
bertschingert marked this conversation as resolved.

/// Borrow the raw bytes of a file handle.
pub fn as_raw(&self) -> &[u8] {
&self.raw
}

/// Get the `f_handle` field, i.e. the actual file handle contents, as a byte slice.
pub fn get_handle_contents(&self) -> &[u8] {
&self.raw[HANDLE_STRUCT_SIZE..]
}

/// We allocate the "maximum" size for a file handle straight away in order to avoid needing
/// multiple syscalls / reallocations whenever possible. However, that leaves raw.len()
/// excessively high when the filehandle will usually be much smaller than MAX_HANDLE_SIZE.
/// This function "trims" the filehandle so that the slice is only as large as it needs to be.
fn trim(&mut self) {
let len = self.get_handle_len() + HANDLE_STRUCT_SIZE;

self.raw = Box::from(&self.raw[0..len]);
}

/// Set the `handle_bytes` field (first 4 bytes of the struct) to the given length.
fn set_handle_len(&mut self, size: usize) {
self.raw[0..size_of::<ffi::c_uint>()].copy_from_slice(&(size as ffi::c_uint).to_ne_bytes());
}

/// Get the length of the file handle data by reading the `handle_bytes` field
fn get_handle_len(&self) -> usize {
ffi::c_uint::from_ne_bytes(
self.raw[0..size_of::<ffi::c_uint>()]
.try_into()
.expect("Vector should be long enough"),
) as usize
}

fn as_mut_ptr(&mut self) -> *mut ffi::c_void {
self.raw.as_mut_ptr() as *mut _
}

fn as_ptr(&self) -> *const ffi::c_void {
self.raw.as_ptr() as *const _
}
}

/// `name_to_handle_at(dirfd, path, flags)` - Gets a filehandle given a path.
///
/// # References
/// - [Linux]
///
/// [Linux]: https://man7.org/linux/man-pages/man2/open_by_handle_at.2.html
pub fn name_to_handle_at<Fd: AsFd, P: path::Arg>(
dirfd: Fd,
path: P,
flags: HandleFlags,
) -> io::Result<(FileHandle, u64)> {
// name_to_handle_at(2) takes the mount_id parameter as either a 32-bit or 64-bit int pointer
// depending on the flag AT_HANDLE_MNT_ID_UNIQUE
let mount_id_unique: bool = flags.contains(HandleFlags::MNT_ID_UNIQUE);
let mut mount_id_64: u64 = 0;
let mut mount_id_int: ffi::c_int = 0;

let mount_id_ptr = if mount_id_unique {
&mut mount_id_64 as *mut u64 as *mut _
} else {
&mut mount_id_int as *mut ffi::c_int as *mut _
};

// The MAX_HANDLE_SZ constant is not a fixed upper bound, because the kernel is permitted to
// increase it in the future. So, the loop is needed in the rare case that MAX_HANDLE_SZ was
// insufficient.
let mut handle_size: usize = MAX_HANDLE_SIZE;
path.into_with_c_str(|path| loop {
let mut file_handle = FileHandle::new(handle_size);

let ret = backend::fs::syscalls::name_to_handle_at(
dirfd.as_fd(),
path,
file_handle.as_mut_ptr(),
mount_id_ptr,
flags,
);

// If EOVERFLOW was returned, and the handle size was increased, we need to try again with
// a larger handle. If the handle size was not increased, EOVERFLOW was due to some other
// cause, and should be returned to the user.
if let Err(e) = ret {
if e == io::Errno::OVERFLOW && file_handle.get_handle_len() > handle_size {
handle_size = file_handle.get_handle_len();
continue;
}
}

let mount_id: u64 = if mount_id_unique {
mount_id_64
} else {
mount_id_int as u64
};

// Ensure the slice is only as large as it needs to be before returning it to the user.
file_handle.trim();

return ret.map(|_| (file_handle, mount_id));
})
}

/// `open_by_handle_at(mount_fd, handle, flags)` - Open a file by filehandle.
///
/// # References
/// - [Linux]
///
/// [Linux]: https://man7.org/linux/man-pages/man2/open_by_handle_at.2.html
pub fn open_by_handle_at<Fd: AsFd>(
mount_fd: Fd,
handle: &FileHandle,
flags: OFlags,
) -> io::Result<OwnedFd> {
backend::fs::syscalls::open_by_handle_at(mount_fd.as_fd(), handle.as_ptr(), flags)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_name_to_handle() {
// On a new enough kernel, AT_HANDLE_MNT_ID_UNIQUE should succeed, but it should be rejected
// with -EINVAL on an older kernel:
if let Err(e) = name_to_handle_at(crate::fs::CWD, "Cargo.toml", HandleFlags::MNT_ID_UNIQUE)
{
assert!(e == io::Errno::INVAL);
}
}
}
Loading
Loading