Skip to content

Commit d67e029

Browse files
committed
Add getxattrat and listxattrat support
Expose directory-relative getxattrat and listxattrat wrappers for both Linux backends. Test no-follow and empty-path behavior, buffer bounds, initialized prefixes, and invalid requests.
1 parent a9620e8 commit d67e029

4 files changed

Lines changed: 487 additions & 2 deletions

File tree

src/backend/libc/fs/syscalls.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,6 +2475,46 @@ pub(crate) unsafe fn fgetxattr(
24752475
}
24762476
}
24772477

2478+
#[cfg(linux_kernel)]
2479+
pub(crate) unsafe fn getxattrat(
2480+
dirfd: BorrowedFd<'_>,
2481+
path: &CStr,
2482+
name: &CStr,
2483+
value: (*mut u8, usize),
2484+
flags: AtFlags,
2485+
) -> io::Result<usize> {
2486+
use linux_raw_sys::general::xattr_args;
2487+
2488+
const SYS_GETXATTRAT: c::c_long = linux_raw_sys::general::__NR_getxattrat as c::c_long;
2489+
syscall! {
2490+
fn getxattrat_syscall(
2491+
base_dirfd: c::c_int,
2492+
path: *const ffi::c_char,
2493+
at_flags: c::c_uint,
2494+
name: *const ffi::c_char,
2495+
args: *const xattr_args,
2496+
args_size: usize
2497+
) via SYS_GETXATTRAT -> c::ssize_t
2498+
}
2499+
2500+
let value_size = value.1.try_into().map_err(|_| io::Errno::OVERFLOW)?;
2501+
let value_addr = u64::try_from(value.0 as usize).map_err(|_| io::Errno::OVERFLOW)?;
2502+
let args = xattr_args {
2503+
value: value_addr,
2504+
size: value_size,
2505+
flags: 0,
2506+
};
2507+
2508+
ret_usize(getxattrat_syscall(
2509+
borrowed_fd(dirfd),
2510+
c_str(path),
2511+
flags.bits(),
2512+
c_str(name),
2513+
&args,
2514+
core::mem::size_of::<xattr_args>(),
2515+
))
2516+
}
2517+
24782518
#[cfg(any(apple, linux_kernel, target_os = "hurd"))]
24792519
pub(crate) fn setxattr(
24802520
path: &CStr,
@@ -2627,6 +2667,33 @@ pub(crate) unsafe fn flistxattr(fd: BorrowedFd<'_>, list: (*mut u8, usize)) -> i
26272667
}
26282668
}
26292669

2670+
#[cfg(linux_kernel)]
2671+
pub(crate) unsafe fn listxattrat(
2672+
dirfd: BorrowedFd<'_>,
2673+
path: &CStr,
2674+
list: (*mut u8, usize),
2675+
flags: AtFlags,
2676+
) -> io::Result<usize> {
2677+
const SYS_LISTXATTRAT: c::c_long = linux_raw_sys::general::__NR_listxattrat as c::c_long;
2678+
syscall! {
2679+
fn listxattrat_syscall(
2680+
base_dirfd: c::c_int,
2681+
path: *const ffi::c_char,
2682+
at_flags: c::c_uint,
2683+
list: *mut ffi::c_char,
2684+
size: usize
2685+
) via SYS_LISTXATTRAT -> c::ssize_t
2686+
}
2687+
2688+
ret_usize(listxattrat_syscall(
2689+
borrowed_fd(dirfd),
2690+
c_str(path),
2691+
flags.bits(),
2692+
list.0.cast::<ffi::c_char>(),
2693+
list.1,
2694+
))
2695+
}
2696+
26302697
#[cfg(any(apple, linux_kernel, target_os = "hurd"))]
26312698
pub(crate) fn removexattr(path: &CStr, name: &CStr) -> io::Result<()> {
26322699
#[cfg(not(apple))]

src/backend/linux_raw/fs/syscalls.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ use core::num::NonZeroU64;
4141
#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
4242
use linux_raw_sys::general::stat as linux_stat64;
4343
use linux_raw_sys::general::{
44-
open_how, AT_EACCESS, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_NOFOLLOW, F_ADD_SEALS, F_GETFL,
45-
F_GET_SEALS, F_SETFL, SEEK_CUR, SEEK_DATA, SEEK_END, SEEK_HOLE, SEEK_SET, STATX__RESERVED,
44+
open_how, xattr_args, AT_EACCESS, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_NOFOLLOW, F_ADD_SEALS,
45+
F_GETFL, F_GET_SEALS, F_SETFL, SEEK_CUR, SEEK_DATA, SEEK_END, SEEK_HOLE, SEEK_SET,
46+
STATX__RESERVED,
4647
};
4748
#[cfg(target_pointer_width = "32")]
4849
use {
@@ -1575,6 +1576,40 @@ pub(crate) unsafe fn fgetxattr(
15751576
))
15761577
}
15771578

1579+
#[inline]
1580+
pub(crate) unsafe fn getxattrat(
1581+
dirfd: BorrowedFd<'_>,
1582+
path: &CStr,
1583+
name: &CStr,
1584+
value: (*mut u8, usize),
1585+
flags: AtFlags,
1586+
) -> io::Result<usize> {
1587+
let value_size = value.1.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1588+
let value_addr = u64::try_from(value.0 as usize).map_err(|_| io::Errno::OVERFLOW)?;
1589+
let args = xattr_args {
1590+
value: value_addr,
1591+
size: value_size,
1592+
flags: 0,
1593+
};
1594+
1595+
let result = ret_usize(syscall!(
1596+
__NR_getxattrat,
1597+
dirfd,
1598+
path,
1599+
flags,
1600+
name,
1601+
by_ref(&args),
1602+
size_of::<xattr_args, _>()
1603+
));
1604+
1605+
#[cfg(sanitize_memory)]
1606+
if let Ok(len) = result {
1607+
crate::msan::unpoison(value.0.cast(), len.min(value.1));
1608+
}
1609+
1610+
result
1611+
}
1612+
15781613
#[inline]
15791614
pub(crate) fn setxattr(
15801615
path: &CStr,
@@ -1650,6 +1685,30 @@ pub(crate) unsafe fn flistxattr(fd: BorrowedFd<'_>, list: (*mut u8, usize)) -> i
16501685
ret_usize(syscall!(__NR_flistxattr, fd, list.0, pass_usize(list.1)))
16511686
}
16521687

1688+
#[inline]
1689+
pub(crate) unsafe fn listxattrat(
1690+
dirfd: BorrowedFd<'_>,
1691+
path: &CStr,
1692+
list: (*mut u8, usize),
1693+
flags: AtFlags,
1694+
) -> io::Result<usize> {
1695+
let result = ret_usize(syscall!(
1696+
__NR_listxattrat,
1697+
dirfd,
1698+
path,
1699+
flags,
1700+
list.0,
1701+
pass_usize(list.1)
1702+
));
1703+
1704+
#[cfg(sanitize_memory)]
1705+
if let Ok(len) = result {
1706+
crate::msan::unpoison(list.0.cast(), len.min(list.1));
1707+
}
1708+
1709+
result
1710+
}
1711+
16531712
#[inline]
16541713
pub(crate) fn removexattr(path: &CStr, name: &CStr) -> io::Result<()> {
16551714
unsafe { ret(syscall_readonly!(__NR_removexattr, path, name)) }

src/fs/xattr.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#![allow(unsafe_code)]
44

55
use crate::buffer::Buffer;
6+
#[cfg(linux_kernel)]
7+
use crate::fs::AtFlags;
68
use crate::{backend, ffi, io, path};
79
use backend::c;
810
use backend::fd::AsFd;
@@ -100,6 +102,50 @@ pub fn fgetxattr<Fd: AsFd, Name: path::Arg, Buf: Buffer<u8>>(
100102
})
101103
}
102104

105+
/// `getxattrat(dirfd, path, name, value, flags)`—Get extended filesystem
106+
/// attributes relative to an open directory.
107+
///
108+
/// `flags` may include [`AtFlags::SYMLINK_NOFOLLOW`] to operate on a symlink
109+
/// itself, or [`AtFlags::EMPTY_PATH`] to operate on `dirfd` when `path` is
110+
/// empty. Linux rejects [`AtFlags::EMPTY_PATH`] with [`io::Errno::BADF`] when
111+
/// `dirfd` is an `O_PATH` descriptor.
112+
///
113+
/// # Errors
114+
///
115+
/// This returns [`io::Errno::RANGE`] if the syscall reports a required length
116+
/// larger than the supplied buffer capacity, including a size-zero query for
117+
/// a nonempty value.
118+
///
119+
/// # References
120+
/// - [Linux]
121+
///
122+
/// [Linux]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/xattr.c?h=v6.13
123+
#[cfg(linux_kernel)]
124+
#[inline]
125+
pub fn getxattrat<Fd: AsFd, P: path::Arg, Name: path::Arg, Buf: Buffer<u8>>(
126+
dirfd: Fd,
127+
path: P,
128+
name: Name,
129+
mut value: Buf,
130+
flags: AtFlags,
131+
) -> io::Result<Buf::Output> {
132+
path.into_with_c_str(|path| {
133+
name.into_with_c_str(|name| {
134+
let value_parts = value.parts_mut();
135+
let value_capacity = value_parts.1;
136+
// SAFETY: `getxattrat` initializes the returned number of bytes.
137+
let len = unsafe {
138+
backend::fs::syscalls::getxattrat(dirfd.as_fd(), path, name, value_parts, flags)?
139+
};
140+
if len > value_capacity {
141+
return Err(io::Errno::RANGE);
142+
}
143+
// SAFETY: `getxattrat` returned the number of initialized bytes.
144+
unsafe { Ok(value.assume_init(len)) }
145+
})
146+
})
147+
}
148+
103149
/// `setxattr(path, name, value.as_ptr(), value.len(), flags)`—Set extended
104150
/// filesystem attributes.
105151
///
@@ -215,6 +261,46 @@ pub fn flistxattr<Fd: AsFd, Buf: Buffer<u8>>(fd: Fd, mut list: Buf) -> io::Resul
215261
unsafe { Ok(list.assume_init(len)) }
216262
}
217263

264+
/// `listxattrat(dirfd, path, list, flags)`—List extended filesystem
265+
/// attributes relative to an open directory.
266+
///
267+
/// `flags` may include [`AtFlags::SYMLINK_NOFOLLOW`] to operate on a symlink
268+
/// itself, or [`AtFlags::EMPTY_PATH`] to operate on `dirfd` when `path` is
269+
/// empty. Linux rejects [`AtFlags::EMPTY_PATH`] with [`io::Errno::BADF`] when
270+
/// `dirfd` is an `O_PATH` descriptor.
271+
///
272+
/// # Errors
273+
///
274+
/// This returns [`io::Errno::RANGE`] if the syscall reports a required length
275+
/// larger than the supplied buffer capacity, including a size-zero query for
276+
/// a nonempty list.
277+
///
278+
/// # References
279+
/// - [Linux]
280+
///
281+
/// [Linux]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/xattr.c?h=v6.13
282+
#[cfg(linux_kernel)]
283+
#[inline]
284+
pub fn listxattrat<Fd: AsFd, P: path::Arg, Buf: Buffer<u8>>(
285+
dirfd: Fd,
286+
path: P,
287+
mut list: Buf,
288+
flags: AtFlags,
289+
) -> io::Result<Buf::Output> {
290+
path.into_with_c_str(|path| {
291+
let list_parts = list.parts_mut();
292+
let list_capacity = list_parts.1;
293+
// SAFETY: `listxattrat` initializes the returned number of bytes.
294+
let len =
295+
unsafe { backend::fs::syscalls::listxattrat(dirfd.as_fd(), path, list_parts, flags)? };
296+
if len > list_capacity {
297+
return Err(io::Errno::RANGE);
298+
}
299+
// SAFETY: `listxattrat` returned the number of initialized bytes.
300+
unsafe { Ok(list.assume_init(len)) }
301+
})
302+
}
303+
218304
/// `removexattr(path, name)`—Remove an extended filesystem attribute.
219305
///
220306
/// # References

0 commit comments

Comments
 (0)