/// Use a direct syscall (via libc) for `open`. /// /// This is only currently necessary as a workaround for old glibc; see below. #[cfg(all(unix, target_env = "gnu"))] fn open_via_syscall(path: &CStr, oflags: OFlags, mode: Mode) -> io::Result<OwnedFd> { // Linux on aarch64, loongarch64 and riscv64 has no `open` syscall so use // `openat`. #[cfg(any(
target_arch = "aarch64",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "csky",
target_arch = "loongarch64"
))]
{
openat_via_syscall(CWD, path, oflags, mode)
}
// On these platforms, `mode_t` is `u16` and can't be passed directly to a // variadic function. #[cfg(any(
apple,
freebsdlike,
all(target_os = "android", target_pointer_width = "32")
))] let mode: c::c_uint = mode.bits().into();
// Otherwise, cast to `mode_t` as that's what `open` is documented to take. #[cfg(not(any(
apple,
freebsdlike,
all(target_os = "android", target_pointer_width = "32")
)))] let mode: c::mode_t = mode.bits() as _;
/// Use a direct syscall (via libc) for `openat`. /// /// This is only currently necessary as a workaround for old glibc; see below. #[cfg(all(unix, target_env = "gnu", not(target_os = "hurd")))] fn openat_via_syscall(
dirfd: BorrowedFd<'_>,
path: &CStr,
oflags: OFlags,
mode: Mode,
) -> io::Result<OwnedFd> {
syscall! { fn openat(
base_dirfd: c::c_int,
pathname: *const c::c_char,
oflags: c::c_int,
mode: c::mode_t
) via SYS_openat -> c::c_int
}
// On these platforms, `mode_t` is `u16` and can't be passed directly to a // variadic function. #[cfg(any(
apple,
freebsdlike,
all(target_os = "android", target_pointer_width = "32")
))] let mode: c::c_uint = mode.bits().into();
// Otherwise, cast to `mode_t` as that's what `open` is documented to take. #[cfg(not(any(
apple,
freebsdlike,
all(target_os = "android", target_pointer_width = "32")
)))] let mode: c::mode_t = mode.bits() as _;
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))] pub(crate) fn utimensat(
dirfd: BorrowedFd<'_>,
path: &CStr,
times: &Timestamps,
flags: AtFlags,
) -> io::Result<()> { // Old 32-bit version: libc has `utimensat` but it is not y2038 safe by // default. But there may be a `__utimensat64` we can use. #[cfg(all(fix_y2038, not(apple)))]
{ #[cfg(target_env = "gnu")] iflet Some(libc_utimensat) = __utimensat64.get() { let libc_times: [LibcTimespec; 2] = [
times.last_access.clone().into(),
times.last_modification.clone().into(),
];
// Main version: libc is y2038 safe and has `utimensat`. Or, the platform // is not y2038 safe and there's nothing practical we can do. #[cfg(not(any(apple, fix_y2038)))] unsafe { usecrate::utils::as_ptr;
// If we have `utimensat`, use it. iflet Some(have_utimensat) = utimensat.get() { return ret(have_utimensat(
borrowed_fd(dirfd),
c_str(path),
as_ptr(times).cast(),
bitflags_bits!(flags),
));
}
// Convert `times`. We only need this in the child, but do it before // calling `fork` because it might fail. let (attrbuf_size, times, attrs) = times_to_attrlist(times)?;
// `setattrlistat` was introduced in 10.13 along with `utimensat`, so // if we don't have `utimensat`, we don't have `setattrlistat` either. // Emulate it using `fork`, and `fchdir` and [`setattrlist`]. // // [`setattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setattrlist.2.html match c::fork() {
-1 => Err(io::Errno::IO), 0 => { if c::fchdir(borrowed_fd(dirfd)) != 0 { let code = match libc_errno::errno().0 {
c::EACCES => 2,
c::ENOTDIR => 3,
_ => 1,
};
c::_exit(code);
}
#[cfg(linux_kernel)] pub(crate) fn chmodat(
dirfd: BorrowedFd<'_>,
path: &CStr,
mode: Mode,
flags: AtFlags,
) -> io::Result<()> { // Linux's `fchmodat` does not have a flags argument. // // Use `c::syscall` rather than `c::fchmodat` because some libc // implementations, such as musl, add extra logic to `fchmod` to emulate // support for `AT_SYMLINK_NOFOLLOW`, which uses `/proc` outside our // control.
syscall! { fn fchmodat(
base_dirfd: c::c_int,
pathname: *const c::c_char,
mode: c::mode_t
) via SYS_fchmodat -> c::c_int
} if flags == AtFlags::SYMLINK_NOFOLLOW { return Err(io::Errno::OPNOTSUPP);
} if !flags.is_empty() { return Err(io::Errno::INVAL);
} unsafe {
ret(fchmodat(
borrowed_fd(dirfd),
c_str(path),
mode.bits() as c::mode_t,
))
}
}
letmut off_in_val: c::loff_t = 0; letmut off_out_val: c::loff_t = 0; // Silently cast; we'll get `EINVAL` if the value is negative. let off_in_ptr = iflet Some(off_in) = &off_in {
off_in_val = **off_in as i64;
&mut off_in_val
} else {
null_mut()
}; let off_out_ptr = iflet Some(off_out) = &off_out {
off_out_val = **off_out as i64;
&mut off_out_val
} else {
null_mut()
}; let copied = unsafe {
ret_usize(copy_file_range(
borrowed_fd(fd_in),
off_in_ptr,
borrowed_fd(fd_out),
off_out_ptr,
len, 0, // no flags are defined yet
))?
}; iflet Some(off_in) = off_in {
*off_in = off_in_val as u64;
} iflet Some(off_out) = off_out {
*off_out = off_out_val as u64;
}
Ok(copied)
}
#[cfg(not(any(
apple,
netbsdlike,
solarish,
target_os = "dragonfly",
target_os = "espidf",
target_os = "haiku",
target_os = "redox",
target_os = "vita",
)))] pub(crate) fn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice) -> io::Result<()> { let offset = offset as i64; let len = len as i64;
// FreeBSD returns `EINVAL` on invalid offsets; emulate the POSIX behavior. #[cfg(target_os = "freebsd")] let offset = if (offset as i64) < 0 {
i64::MAX
} else {
offset
};
// FreeBSD returns `EINVAL` on overflow; emulate the POSIX behavior. #[cfg(target_os = "freebsd")] let len = if len > 0 && offset.checked_add(len).is_none() {
i64::MAX - offset
} else {
len
};
let err = unsafe { c::posix_fadvise(borrowed_fd(fd), offset, len, advice as c::c_int) };
// `posix_fadvise` returns its error status rather than using `errno`. if err == 0 {
Ok(())
} else {
Err(io::Errno(err))
}
}
// When `l_len` is zero, this locks all the bytes from // `l_whence`/`l_start` to the end of the file, even as the // file grows dynamically.
lock.l_whence = SEEK_SET as _;
lock.l_start = 0;
lock.l_len = 0;
ret(c::fcntl(borrowed_fd(fd), cmd, &lock))
}
}
pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> { let (whence, offset) = match pos {
SeekFrom::Start(pos) => { let pos: u64 = pos; // Silently cast; we'll get `EINVAL` if the value is negative.
(c::SEEK_SET, pos as i64)
}
SeekFrom::End(offset) => (c::SEEK_END, offset),
SeekFrom::Current(offset) => (c::SEEK_CUR, offset), #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
SeekFrom::Data(offset) => (c::SEEK_DATA, offset), #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
SeekFrom::Hole(offset) => (c::SEEK_HOLE, offset),
};
// ESP-IDF and Vita don't support 64-bit offsets. #[cfg(any(target_os = "espidf", target_os = "vita"))] let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
let offset = unsafe { ret_off_t(c::lseek(borrowed_fd(fd), offset, whence))? };
Ok(offset as u64)
}
pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> { let offset = unsafe { ret_off_t(c::lseek(borrowed_fd(fd), 0, c::SEEK_CUR))? };
Ok(offset as u64)
}
#[cfg(linux_kernel)] pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> { // Use `c::syscall` rather than `c::fchmod` because some libc // implementations, such as musl, add extra logic to `fchmod` to emulate // support for `O_PATH`, which uses `/proc` outside our control and // interferes with our own use of `O_PATH`.
syscall! { fn fchmod(
fd: c::c_int,
mode: c::mode_t
) via SYS_fchmod -> c::c_int
} unsafe { ret(fchmod(borrowed_fd(fd), mode.bits() as c::mode_t)) }
}
#[cfg(linux_kernel)] pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> { // Use `c::syscall` rather than `c::fchown` because some libc // implementations, such as musl, add extra logic to `fchown` to emulate // support for `O_PATH`, which uses `/proc` outside our control and // interferes with our own use of `O_PATH`.
syscall! { fn fchown(
fd: c::c_int,
owner: c::uid_t,
group: c::gid_t
) via SYS_fchown -> c::c_int
} unsafe { let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
ret(fchown(borrowed_fd(fd), ow, gr))
}
}
pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> { // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use // `statx`. // // And, some old platforms don't support `statx`, and some fail with a // confusing error code, so we call `crate::fs::statx` to handle that. If // `statx` isn't available, fall back to the buggy system call. #[cfg(all(
linux_kernel,
any(
target_pointer_width = "32",
target_arch = "mips64",
target_arch = "mips64r6"
)
))]
{ matchcrate::fs::statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
Ok(x) => statx_to_stat(x),
Err(io::Errno::NOSYS) => fstat_old(fd),
Err(err) => Err(err),
}
}
// Main version: libc is y2038 safe. Or, the platform is not y2038 safe and // there's nothing practical we can do. #[cfg(not(all(
linux_kernel,
any(
target_pointer_width = "32",
target_arch = "mips64",
target_arch = "mips64r6"
)
)))] unsafe { letmut stat = MaybeUninit::<Stat>::uninit();
ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
Ok(stat.assume_init())
}
}
#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))] fn libc_statvfs_to_statvfs(from: c::statvfs) -> StatVfs {
StatVfs {
f_bsize: from.f_bsize as u64,
f_frsize: from.f_frsize as u64,
f_blocks: from.f_blocks as u64,
f_bfree: from.f_bfree as u64,
f_bavail: from.f_bavail as u64,
f_files: from.f_files as u64,
f_ffree: from.f_ffree as u64,
f_favail: from.f_ffree as u64, #[cfg(not(target_os = "aix"))]
f_fsid: from.f_fsid as u64, #[cfg(target_os = "aix")]
f_fsid: ((from.f_fsid.val[0] as u64) << 32) | from.f_fsid.val[1],
f_flag: StatVfsMountFlags::from_bits_retain(from.f_flag as u64),
f_namemax: from.f_namemax as u64,
}
}
#[cfg(not(any(target_os = "espidf", target_os = "vita")))] pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> { // Old 32-bit version: libc has `futimens` but it is not y2038 safe by // default. But there may be a `__futimens64` we can use. #[cfg(all(fix_y2038, not(apple)))]
{ #[cfg(target_env = "gnu")] iflet Some(libc_futimens) = __futimens64.get() { let libc_times: [LibcTimespec; 2] = [
times.last_access.clone().into(),
times.last_modification.clone().into(),
];
// Main version: libc is y2038 safe and has `futimens`. Or, the platform // is not y2038 safe and there's nothing practical we can do. #[cfg(not(any(apple, fix_y2038)))] unsafe { usecrate::utils::as_ptr;
/// Convert from a Linux `statx` value to rustix's `Stat`. #[cfg(all(linux_kernel, target_pointer_width = "32"))] #[allow(deprecated)] // for `st_[amc]time` u64->i64 transition fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
Ok(Stat {
st_dev: crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor).into(),
st_mode: x.stx_mode.into(),
st_nlink: x.stx_nlink.into(),
st_uid: x.stx_uid.into(),
st_gid: x.stx_gid.into(),
st_rdev: crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor).into(),
st_size: x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
st_blksize: x.stx_blksize.into(),
st_blocks: x.stx_blocks.into(),
st_atime: bitcast!(i64::from(x.stx_atime.tv_sec)),
st_atime_nsec: x.stx_atime.tv_nsec as _,
st_mtime: bitcast!(i64::from(x.stx_mtime.tv_sec)),
st_mtime_nsec: x.stx_mtime.tv_nsec as _,
st_ctime: bitcast!(i64::from(x.stx_ctime.tv_sec)),
st_ctime_nsec: x.stx_ctime.tv_nsec as _,
st_ino: x.stx_ino.into(),
})
}
/// Convert from a Linux `statx` value to rustix's `Stat`. /// /// mips64' `struct stat64` in libc has private fields, and `stx_blocks` #[cfg(all(linux_kernel, any(target_arch = "mips64", target_arch = "mips64r6")))] fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> { letmut result: Stat = unsafe { core::mem::zeroed() };
/// Convert from a Linux `stat64` value to rustix's `Stat`. /// /// mips64' `struct stat64` in libc has private fields, and `st_blocks` has /// type `i64`. #[cfg(all(linux_kernel, any(target_arch = "mips64", target_arch = "mips64r6")))] fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> { letmut result: Stat = unsafe { core::mem::zeroed() };
#[cfg(linux_kernel)] #[allow(non_upper_case_globals)] pub(crate) fn statx(
dirfd: BorrowedFd<'_>,
path: &CStr,
flags: AtFlags,
mask: StatxFlags,
) -> io::Result<Statx> { // If a future Linux kernel adds more fields to `struct statx` and users // passing flags unknown to rustix in `StatxFlags`, we could end up // writing outside of the buffer. To prevent this possibility, we mask off // any flags that we don't know about. // // This includes `STATX__RESERVED`, which has a value that we know, but // which could take on arbitrary new meaning in the future. Linux currently // rejects this flag with `EINVAL`, so we do the same. // // This doesn't rely on `STATX_ALL` because [it's deprecated] and already // doesn't represent all the known flags. // // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/ #[cfg(not(any(target_os = "android", target_env = "musl")))] const STATX__RESERVED: u32 = c::STATX__RESERVED as u32; #[cfg(any(target_os = "android", target_env = "musl"))] const STATX__RESERVED: u32 = linux_raw_sys::general::STATX__RESERVED; if (mask.bits() & STATX__RESERVED) == STATX__RESERVED { return Err(io::Errno::INVAL);
} let mask = mask & StatxFlags::all();
#[cfg(linux_kernel)] #[inline] pub(crate) fn is_statx_available() -> bool { unsafe { // Call `statx` with null pointers so that if it fails for any reason // other than `EFAULT`, we know it's not supported.
matches!(
ret(sys::statx(CWD, null(), 0, 0, null_mut())),
Err(io::Errno::FAULT)
)
}
}
#[cfg(all(apple, feature = "alloc"))] pub(crate) fn getpath(fd: BorrowedFd<'_>) -> io::Result<CString> { // The use of `PATH_MAX` is generally not encouraged, but it // is inevitable in this case because macOS defines `fcntl` with // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no // alternatives. If a better method is invented, it should be used // instead. letmut buf = vec![0; c::PATH_MAX as usize];
let l = buf.iter().position(|&c| c == 0).unwrap();
buf.truncate(l);
buf.shrink_to_fit();
Ok(CString::new(buf).unwrap())
}
#[cfg(apple)] pub(crate) fn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::Result<()> { // From the [macOS `fcntl` manual page]: // `F_RDADVISE` - Issue an advisory read async with no copy to user. // // The `F_RDADVISE` command operates on the following structure which holds // information passed from the user to the system: // // ```c // struct radvisory { // off_t ra_offset; /* offset into the file */ // int ra_count; /* size of the read */ // }; // ``` // // [macOS `fcntl` manual page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html let ra_offset = match offset.try_into() {
Ok(len) => len, // If this conversion fails, the user is providing an offset outside // any possible file extent, so just ignore it.
Err(_) => return Ok(()),
}; let ra_count = match len.try_into() {
Ok(len) => len, // If this conversion fails, the user is providing a dubiously large // hint which is unlikely to improve performance.
Err(_) => return Ok(()),
}; unsafe { let radvisory = c::radvisory {
ra_offset,
ra_count,
};
ret(c::fcntl(borrowed_fd(fd), c::F_RDADVISE, &radvisory))
}
}
/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist` /// arguments. #[cfg(apple)] fn times_to_attrlist(times: &Timestamps) -> io::Result<(c::size_t, [c::timespec; 2], Attrlist)> { // ABI details. const ATTR_CMN_MODTIME: u32 = 0x0000_0400; const ATTR_CMN_ACCTIME: u32 = 0x0000_1000; const ATTR_BIT_MAP_COUNT: u16 = 5;
letmut times = times.clone();
// If we have any `UTIME_NOW` elements, replace them with the current time. if times.last_access.tv_nsec == c::UTIME_NOW.into()
|| times.last_modification.tv_nsec == c::UTIME_NOW.into()
{ let now = { letmut tv = c::timeval {
tv_sec: 0,
tv_usec: 0,
}; unsafe { let r = c::gettimeofday(&mut tv, null_mut());
assert_eq!(r, 0);
}
c::timespec {
tv_sec: tv.tv_sec,
tv_nsec: (tv.tv_usec * 1000) as _,
}
}; if times.last_access.tv_nsec == c::UTIME_NOW.into() {
times.last_access = crate::timespec::Timespec {
tv_sec: now.tv_sec.into(),
tv_nsec: now.tv_nsec as _,
};
} if times.last_modification.tv_nsec == c::UTIME_NOW.into() {
times.last_modification = crate::timespec::Timespec {
tv_sec: now.tv_sec.into(),
tv_nsec: now.tv_nsec as _,
};
}
}
#[cfg(apple)]
{ // Passing an empty to slice to getxattr leads to ERANGE on macOS. Pass null // instead. let ptr = if value.is_empty() {
core::ptr::null_mut()
} else {
value_ptr.cast::<c::c_void>()
};
// Assert that `Timestamps` has the expected layout. If we're not fixing // y2038, libc's type should match ours. If we are, it's smaller. #[cfg(not(fix_y2038))]
assert_eq_size!([c::timespec; 2], Timestamps); #[cfg(fix_y2038)]
assert!(core::mem::size_of::<[c::timespec; 2]>() < core::mem::size_of::<Timestamps>());
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.18 Sekunden
(vorverarbeitet am 2026-06-20)
¤