Skip to content

Commit a1fee6c

Browse files
committed
fs: introduce name_to_handle_at()
1 parent 5e4acde commit a1fee6c

File tree

4 files changed

+220
-2
lines changed

4 files changed

+220
-2
lines changed

src/backend/libc/fs/syscalls.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ use crate::fs::FallocateFlags;
3131
target_os = "wasi"
3232
)))]
3333
use crate::fs::FlockOperation;
34+
#[cfg(target_os = "linux")]
35+
use crate::fs::HandleFlags;
3436
#[cfg(any(linux_kernel, target_os = "freebsd"))]
3537
use crate::fs::MemfdFlags;
3638
#[cfg(any(linux_kernel, apple))]
@@ -1880,6 +1882,35 @@ const SYS_OPENAT2: i32 = 437;
18801882
#[cfg(all(linux_kernel, target_pointer_width = "64"))]
18811883
const SYS_OPENAT2: i64 = 437;
18821884

1885+
#[cfg(target_os = "linux")]
1886+
pub(crate) fn name_to_handle_at(
1887+
dirfd: BorrowedFd<'_>,
1888+
path: &CStr,
1889+
handle: *mut ffi::c_void,
1890+
mount_id: *mut ffi::c_void,
1891+
flags: HandleFlags,
1892+
) -> io::Result<()> {
1893+
syscall! {
1894+
fn name_to_handle_at(
1895+
dir_fd: c::c_int,
1896+
path: *const ffi::c_char,
1897+
handle: *mut ffi::c_void,
1898+
mount_id: *mut ffi::c_void,
1899+
flags: u32
1900+
) via SYS_name_to_handle_at -> c::c_int
1901+
}
1902+
1903+
unsafe {
1904+
ret(name_to_handle_at(
1905+
borrowed_fd(dirfd),
1906+
c_str(path),
1907+
handle,
1908+
mount_id,
1909+
flags.bits(),
1910+
))
1911+
}
1912+
}
1913+
18831914
#[cfg(target_os = "linux")]
18841915
pub(crate) fn sendfile(
18851916
out_fd: BorrowedFd<'_>,

src/backend/linux_raw/fs/syscalls.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use crate::ffi::CStr;
2828
use crate::fs::CWD;
2929
use crate::fs::{
3030
inotify, Access, Advice, AtFlags, FallocateFlags, FileType, FlockOperation, Fsid, Gid,
31-
MemfdFlags, Mode, OFlags, RenameFlags, ResolveFlags, SealFlags, SeekFrom, Stat, StatFs,
32-
StatVfs, StatVfsMountFlags, Statx, StatxFlags, Timestamps, Uid, XattrFlags,
31+
HandleFlags, MemfdFlags, Mode, OFlags, RenameFlags, ResolveFlags, SealFlags, SeekFrom, Stat,
32+
StatFs, StatVfs, StatVfsMountFlags, Statx, StatxFlags, Timestamps, Uid, XattrFlags,
3333
};
3434
use crate::io;
3535
use core::mem::MaybeUninit;
@@ -1657,6 +1657,26 @@ pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
16571657
unsafe { ret(syscall_readonly!(__NR_fremovexattr, fd, name)) }
16581658
}
16591659

1660+
#[inline]
1661+
pub(crate) fn name_to_handle_at(
1662+
dirfd: BorrowedFd<'_>,
1663+
path: &CStr,
1664+
file_handle: *mut core::ffi::c_void,
1665+
mount_id: *mut core::ffi::c_void,
1666+
flags: HandleFlags,
1667+
) -> io::Result<()> {
1668+
unsafe {
1669+
ret(syscall!(
1670+
__NR_name_to_handle_at,
1671+
dirfd,
1672+
path,
1673+
file_handle,
1674+
mount_id,
1675+
flags
1676+
))
1677+
}
1678+
}
1679+
16601680
// Some linux_raw_sys structs have unsigned types for values which are
16611681
// interpreted as signed. This defines a utility or casting to the
16621682
// same-sized signed type.

src/fs/filehandle.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use core::mem::size_of;
2+
3+
use crate::{backend, ffi, io, path};
4+
use backend::fd::{AsFd, OwnedFd};
5+
use backend::fs::types::{HandleFlags, OFlags};
6+
7+
/// This maximum is more of a "guideline"; the man page for name_to_handle_at(2) indicates it could
8+
/// increase in the future.
9+
const MAX_HANDLE_SIZE: usize = 128;
10+
11+
/// The minimum size of a `struct file_handle` is the size of an int and an unsigned int, for the
12+
/// length and type fields.
13+
const HANDLE_STRUCT_SIZE: usize = size_of::<ffi::c_uint>() + size_of::<ffi::c_int>();
14+
15+
/// An opaque identifier for a file.
16+
///
17+
/// While the C struct definition in `fcntl.h` exposes fields like length and type, in reality,
18+
/// user applications cannot usefully interpret (or modify) the separate fields of a file handle, so
19+
/// this implementation treats the file handle as an entirely opaque sequence of bytes.
20+
#[derive(Debug)]
21+
pub struct FileHandle {
22+
raw: Box<[u8]>,
23+
}
24+
25+
impl FileHandle {
26+
fn new(size: usize) -> Self {
27+
let handle_allocation_size: usize = HANDLE_STRUCT_SIZE + size;
28+
let bytes = vec![0; handle_allocation_size];
29+
30+
let mut handle = Self {
31+
raw: Box::from(bytes),
32+
};
33+
handle.set_handle_bytes(size);
34+
35+
handle
36+
}
37+
38+
/// Create a file handle from a sequence of bytes.
39+
///
40+
/// # Panics
41+
///
42+
/// Panics if the given handle is malformed, suggesting that it did not originate from a
43+
/// previous call to name_to_handle_at().
44+
pub fn from_raw(raw: Box<[u8]>) -> Self {
45+
assert!(raw.len() >= HANDLE_STRUCT_SIZE);
46+
47+
let handle = Self { raw };
48+
49+
assert!(handle.raw.len() >= handle.get_handle_bytes() + HANDLE_STRUCT_SIZE);
50+
51+
handle
52+
}
53+
54+
/// Get the raw bytes of a file handle.
55+
pub fn into_raw(self) -> Box<[u8]> {
56+
self.raw
57+
}
58+
59+
/// Set the `handle_bytes` field (first 4 bytes of the struct) to the given length.
60+
fn set_handle_bytes(&mut self, size: usize) {
61+
self.raw[0..size_of::<ffi::c_uint>()].copy_from_slice(&(size as ffi::c_uint).to_ne_bytes());
62+
}
63+
64+
/// Get the length of the file handle data by reading the `handle_bytes` field
65+
fn get_handle_bytes(&self) -> usize {
66+
ffi::c_uint::from_ne_bytes(
67+
self.raw[0..size_of::<ffi::c_uint>()]
68+
.try_into()
69+
.expect("Vector should be long enough"),
70+
) as usize
71+
}
72+
73+
fn as_mut_ptr(&mut self) -> *mut ffi::c_void {
74+
self.raw.as_mut_ptr() as *mut _
75+
}
76+
}
77+
78+
/// An identifier for a mount that is returned by [`name_to_handle_at`].
79+
///
80+
/// [`name_to_handle_at`]: crate::fs::name_to_handle_at
81+
#[derive(Debug)]
82+
pub enum MountId {
83+
/// By default a MountId is a C int.
84+
Regular(ffi::c_int),
85+
/// When `AT_HANDLE_MNT_ID_UNIQUE` is passed in `HandleFlags`, MountId is a u64.
86+
Unique(u64),
87+
}
88+
89+
/// `name_to_handle_at(dirfd, path, flags)` - Gets a filehandle given a path.
90+
///
91+
/// # References
92+
/// - [Linux]
93+
///
94+
/// [Linux]: https://man7.org/linux/man-pages/man2/open_by_handle_at.2.html
95+
pub fn name_to_handle_at<Fd: AsFd, P: path::Arg>(
96+
dirfd: Fd,
97+
path: P,
98+
flags: HandleFlags,
99+
) -> io::Result<(FileHandle, MountId)> {
100+
// name_to_handle_at(2) takes the mount_id parameter as either a 32-bit or 64-bit int pointer
101+
// depending on the flag AT_HANDLE_MNT_ID_UNIQUE
102+
let mount_id_unique: bool = flags.contains(HandleFlags::MNT_ID_UNIQUE);
103+
let mut mount_id_int: ffi::c_int = 0;
104+
let mut mount_id_64: u64 = 0;
105+
let mount_id_ptr: *mut ffi::c_void = if mount_id_unique {
106+
&mut mount_id_64 as *mut u64 as *mut _
107+
} else {
108+
&mut mount_id_int as *mut ffi::c_int as *mut _
109+
};
110+
111+
// The MAX_HANDLE_SZ constant is not a fixed upper bound, because the kernel is permitted to
112+
// increase it in the future. So, the loop is needed in the rare case that MAX_HANDLE_SZ was
113+
// insufficient.
114+
let mut handle_size: usize = MAX_HANDLE_SIZE;
115+
path.into_with_c_str(|path| loop {
116+
let mut file_handle = FileHandle::new(handle_size);
117+
118+
let ret = backend::fs::syscalls::name_to_handle_at(
119+
dirfd.as_fd(),
120+
path,
121+
file_handle.as_mut_ptr(),
122+
mount_id_ptr,
123+
flags,
124+
);
125+
126+
// If EOVERFLOW was returned, and the handle size was increased, we need to try again with
127+
// a larger handle. If the handle size was not increased, EOVERFLOW was due to some other
128+
// cause, and should be returned to the user.
129+
if let Err(e) = ret {
130+
if e == io::Errno::OVERFLOW && file_handle.get_handle_bytes() > handle_size {
131+
handle_size = file_handle.get_handle_bytes();
132+
continue;
133+
}
134+
}
135+
136+
let mount_id = if mount_id_unique {
137+
MountId::Unique(mount_id_64)
138+
} else {
139+
MountId::Regular(mount_id_int)
140+
};
141+
142+
return ret.map(|_| (file_handle, mount_id));
143+
})
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use super::*;
149+
150+
#[test]
151+
fn test_name_to_handle() {
152+
let (_, mount_id) =
153+
name_to_handle_at(crate::fs::CWD, "Cargo.toml", HandleFlags::empty()).unwrap();
154+
assert!(matches!(mount_id, MountId::Regular(_)));
155+
156+
match name_to_handle_at(crate::fs::CWD, "Cargo.toml", HandleFlags::MNT_ID_UNIQUE) {
157+
// On a new enough kernel, AT_HANDLE_MNT_ID_UNIQUE should succeed:
158+
Ok((_, mount_id)) => assert!(matches!(mount_id, MountId::Unique(_))),
159+
// But it should be rejected with -EINVAL on an older kernel:
160+
Err(e) => assert!(e == io::Errno::EINVAL),
161+
}
162+
}
163+
}

src/fs/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ mod fcntl_apple;
2626
#[cfg(apple)]
2727
mod fcopyfile;
2828
pub(crate) mod fd;
29+
#[cfg(target_os = "linux")]
30+
mod filehandle;
2931
#[cfg(all(apple, feature = "alloc"))]
3032
mod getpath;
3133
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
@@ -93,6 +95,8 @@ pub use fcntl_apple::*;
9395
#[cfg(apple)]
9496
pub use fcopyfile::*;
9597
pub use fd::*;
98+
#[cfg(target_os = "linux")]
99+
pub use filehandle::*;
96100
#[cfg(all(apple, feature = "alloc"))]
97101
pub use getpath::getpath;
98102
#[cfg(not(target_os = "wasi"))]

0 commit comments

Comments
 (0)