diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index 84abe6260..2eda05519 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -611,9 +611,15 @@ pub(crate) fn stat(path: &CStr) -> io::Result { ) )))] unsafe { + #[cfg(test)] + assert_eq_size!(Stat, c::stat); + let mut stat = MaybeUninit::::uninit(); - ret(c::stat(c_str(path), stat.as_mut_ptr()))?; - Ok(stat.assume_init()) + ret(c::stat(c_str(path), stat.as_mut_ptr().cast()))?; + let stat = stat.assume_init(); + #[cfg(apple)] + let stat = fix_negative_stat_nsecs(stat); + Ok(stat) } } @@ -651,9 +657,15 @@ pub(crate) fn lstat(path: &CStr) -> io::Result { ) )))] unsafe { + #[cfg(test)] + assert_eq_size!(Stat, c::stat); + let mut stat = MaybeUninit::::uninit(); - ret(c::lstat(c_str(path), stat.as_mut_ptr()))?; - Ok(stat.assume_init()) + ret(c::lstat(c_str(path), stat.as_mut_ptr().cast()))?; + let stat = stat.assume_init(); + #[cfg(apple)] + let stat = fix_negative_stat_nsecs(stat); + Ok(stat) } } @@ -687,14 +699,20 @@ pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io:: ) )))] unsafe { + #[cfg(test)] + assert_eq_size!(Stat, c::stat); + let mut stat = MaybeUninit::::uninit(); ret(c::fstatat( borrowed_fd(dirfd), c_str(path), - stat.as_mut_ptr(), + stat.as_mut_ptr().cast(), bitflags_bits!(flags), ))?; - Ok(stat.assume_init()) + let stat = stat.assume_init(); + #[cfg(apple)] + let stat = fix_negative_stat_nsecs(stat); + Ok(stat) } } @@ -1443,9 +1461,15 @@ pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result { ) )))] unsafe { + #[cfg(test)] + assert_eq_size!(Stat, c::stat); + let mut stat = MaybeUninit::::uninit(); - ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr()))?; - Ok(stat.assume_init()) + ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr().cast()))?; + let stat = stat.assume_init(); + #[cfg(apple)] + let stat = fix_negative_stat_nsecs(stat); + Ok(stat) } } @@ -1850,17 +1874,17 @@ fn stat64_to_stat(s64: c::stat64) -> io::Result { st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?, st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?, - st_atime: bitcast!(i64::from(s64.st_atime)), + st_atime: i64::from(s64.st_atime), st_atime_nsec: s64 .st_atime_nsec .try_into() .map_err(|_| io::Errno::OVERFLOW)?, - st_mtime: bitcast!(i64::from(s64.st_mtime)), + st_mtime: i64::from(s64.st_mtime), st_mtime_nsec: s64 .st_mtime_nsec .try_into() .map_err(|_| io::Errno::OVERFLOW)?, - st_ctime: bitcast!(i64::from(s64.st_ctime)), + st_ctime: i64::from(s64.st_ctime), st_ctime_nsec: s64 .st_ctime_nsec .try_into() @@ -2553,6 +2577,18 @@ pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> { } } +/// See [`crate::timespec::fix_negative_nsec`] for details. +#[cfg(apple)] +fn fix_negative_stat_nsecs(mut stat: Stat) -> Stat { + stat.st_atime_nsec = + crate::timespec::fix_negative_nsecs(&mut stat.st_atime, stat.st_atime_nsec as _) as _; + stat.st_mtime_nsec = + crate::timespec::fix_negative_nsecs(&mut stat.st_mtime, stat.st_mtime_nsec as _) as _; + stat.st_ctime_nsec = + crate::timespec::fix_negative_nsecs(&mut stat.st_ctime, stat.st_ctime_nsec as _) as _; + stat +} + #[test] fn test_sizes() { #[cfg(linux_kernel)] diff --git a/src/backend/libc/fs/types.rs b/src/backend/libc/fs/types.rs index 0da1717b3..c92250475 100644 --- a/src/backend/libc/fs/types.rs +++ b/src/backend/libc/fs/types.rs @@ -877,7 +877,7 @@ pub enum FlockOperation { /// /// [`statat`]: crate::fs::statat /// [`fstat`]: crate::fs::fstat -#[cfg(not(any(linux_like, target_os = "hurd")))] +#[cfg(not(any(linux_like, target_os = "hurd", target_os = "netbsd")))] pub type Stat = c::stat; /// `struct stat` for use with [`statat`] and [`fstat`]. @@ -921,6 +921,40 @@ pub struct Stat { pub st_ino: u64, } +/// `struct stat` for use with [`statat`] and [`fstat`]. +/// +/// [`statat`]: crate::fs::statat +/// [`fstat`]: crate::fs::fstat +// NetBSD's `st_mtime_nsec` is named `st_mtimensec` so we declare our own +// `Stat` so that we can be consistent with other platforms. +#[cfg(target_os = "netbsd")] +#[derive(Debug, Copy, Clone)] +#[allow(missing_docs)] +#[repr(C)] +pub struct Stat { + pub st_dev: c::dev_t, + pub st_mode: c::mode_t, + pub st_ino: c::ino_t, + pub st_nlink: c::nlink_t, + pub st_uid: c::uid_t, + pub st_gid: c::gid_t, + pub st_rdev: c::dev_t, + pub st_atime: c::time_t, + pub st_atime_nsec: c::c_long, + pub st_mtime: c::time_t, + pub st_mtime_nsec: c::c_long, + pub st_ctime: c::time_t, + pub st_ctime_nsec: c::c_long, + pub st_birthtime: c::time_t, + pub st_birthtime_nsec: c::c_long, + pub st_size: c::off_t, + pub st_blocks: c::blkcnt_t, + pub st_blksize: c::blksize_t, + pub st_flags: u32, + pub st_gen: u32, + pub st_spare: [u32; 2], +} + /// `struct statfs` for use with [`statfs`] and [`fstatfs`]. /// /// [`statfs`]: crate::fs::statfs diff --git a/src/backend/libc/time/syscalls.rs b/src/backend/libc/time/syscalls.rs index ed1e745c8..06fe4a823 100644 --- a/src/backend/libc/time/syscalls.rs +++ b/src/backend/libc/time/syscalls.rs @@ -130,7 +130,10 @@ pub(crate) fn clock_gettime(id: ClockId) -> Timespec { as_libc_timespec_mut_ptr(&mut timespec), )) .unwrap(); - timespec.assume_init() + let timespec = timespec.assume_init(); + #[cfg(apple)] + let timespec = fix_negative_timespec_nsecs(timespec); + timespec } } @@ -220,8 +223,10 @@ pub(crate) fn clock_gettime_dynamic(id: DynamicClockId<'_>) -> io::Result) -> io::Result { }, }) } + +/// See [`crate::timespec::fix_negative_nsecs`] for details. +#[cfg(apple)] +fn fix_negative_timespec_nsecs(mut ts: Timespec) -> Timespec { + ts.tv_nsec = crate::timespec::fix_negative_nsecs(&mut ts.tv_sec, ts.tv_nsec as _) as _; + ts +} diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs index 67a0a236e..d098ed5ad 100644 --- a/src/backend/linux_raw/fs/syscalls.rs +++ b/src/backend/linux_raw/fs/syscalls.rs @@ -730,11 +730,11 @@ fn statx_to_stat(x: crate::fs::Statx) -> io::Result { 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: i64::from(x.stx_atime.tv_sec), st_atime_nsec: x.stx_atime.tv_nsec.into(), - st_mtime: bitcast!(i64::from(x.stx_mtime.tv_sec)), + st_mtime: i64::from(x.stx_mtime.tv_sec), st_mtime_nsec: x.stx_mtime.tv_nsec.into(), - st_ctime: bitcast!(i64::from(x.stx_ctime.tv_sec)), + st_ctime: i64::from(x.stx_ctime.tv_sec), st_ctime_nsec: x.stx_ctime.tv_nsec.into(), st_ino: x.stx_ino.into(), }) @@ -754,17 +754,17 @@ fn stat_to_stat(s64: linux_raw_sys::general::stat64) -> io::Result { st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?, st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?, - st_atime: bitcast!(i64::from(s64.st_atime)), + st_atime: i64::from(s64.st_atime), st_atime_nsec: s64 .st_atime_nsec .try_into() .map_err(|_| io::Errno::OVERFLOW)?, - st_mtime: bitcast!(i64::from(s64.st_mtime)), + st_mtime: i64::from(s64.st_mtime), st_mtime_nsec: s64 .st_mtime_nsec .try_into() .map_err(|_| io::Errno::OVERFLOW)?, - st_ctime: bitcast!(i64::from(s64.st_ctime)), + st_ctime: i64::from(s64.st_ctime), st_ctime_nsec: s64 .st_ctime_nsec .try_into() @@ -786,17 +786,17 @@ fn stat_to_stat(s: linux_raw_sys::general::stat) -> io::Result { st_size: s.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, st_blksize: s.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?, st_blocks: s.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?, - st_atime: bitcast!(i64::from(s.st_atime)), + st_atime: i64::from(s.st_atime), st_atime_nsec: s .st_atime_nsec .try_into() .map_err(|_| io::Errno::OVERFLOW)?, - st_mtime: bitcast!(i64::from(s.st_mtime)), + st_mtime: i64::from(s.st_mtime), st_mtime_nsec: s .st_mtime_nsec .try_into() .map_err(|_| io::Errno::OVERFLOW)?, - st_ctime: bitcast!(i64::from(s.st_ctime)), + st_ctime: i64::from(s.st_ctime), st_ctime_nsec: s .st_ctime_nsec .try_into() diff --git a/src/backend/linux_raw/fs/types.rs b/src/backend/linux_raw/fs/types.rs index 7c621bedd..629ca4a22 100644 --- a/src/backend/linux_raw/fs/types.rs +++ b/src/backend/linux_raw/fs/types.rs @@ -584,15 +584,14 @@ pub enum FlockOperation { /// /// [`statat`]: crate::fs::statat /// [`fstat`]: crate::fs::fstat -// On 32-bit, and mips64, Linux's `struct stat64` has a 32-bit `st_mtime` and -// friends, so we use our own struct, populated from `statx` where possible, to -// avoid the y2038 bug. +// On 32-bit with `struct stat64` and mips64 with `struct stat`, Linux's +// `st_mtime` and friends are 32-bit, so we use our own struct, populated from +// `statx` where possible, to avoid the y2038 bug. #[cfg(any( target_pointer_width = "32", target_arch = "mips64", target_arch = "mips64r6" ))] -#[repr(C)] #[derive(Debug, Copy, Clone)] #[allow(missing_docs)] #[non_exhaustive] @@ -636,11 +635,11 @@ pub struct Stat { pub st_size: ffi::c_long, pub st_blksize: ffi::c_long, pub st_blocks: ffi::c_long, - pub st_atime: ffi::c_ulong, + pub st_atime: ffi::c_long, pub st_atime_nsec: ffi::c_ulong, - pub st_mtime: ffi::c_ulong, + pub st_mtime: ffi::c_long, pub st_mtime_nsec: ffi::c_ulong, - pub st_ctime: ffi::c_ulong, + pub st_ctime: ffi::c_long, pub st_ctime_nsec: ffi::c_ulong, pub(crate) __unused: [ffi::c_long; 3], } @@ -698,7 +697,7 @@ pub struct Stat { pub(crate) __unused4: ffi::c_uint, pub(crate) __unused5: ffi::c_uint, } -// This follows `stat64`. +// This follows `stat`. powerpc64 defines a `stat64` but it's not used. #[repr(C)] #[derive(Debug, Copy, Clone)] #[allow(missing_docs)] @@ -715,11 +714,11 @@ pub struct Stat { pub st_size: ffi::c_long, pub st_blksize: ffi::c_ulong, pub st_blocks: ffi::c_ulong, - pub st_atime: ffi::c_ulong, + pub st_atime: ffi::c_long, pub st_atime_nsec: ffi::c_ulong, - pub st_mtime: ffi::c_ulong, + pub st_mtime: ffi::c_long, pub st_mtime_nsec: ffi::c_ulong, - pub st_ctime: ffi::c_ulong, + pub st_ctime: ffi::c_long, pub st_ctime_nsec: ffi::c_ulong, pub(crate) __unused4: ffi::c_ulong, pub(crate) __unused5: ffi::c_ulong, @@ -739,12 +738,12 @@ pub struct Stat { pub st_gid: ffi::c_uint, pub(crate) __pad1: ffi::c_uint, pub st_rdev: ffi::c_ulong, - pub st_size: ffi::c_ulong, - pub st_atime: ffi::c_ulong, + pub st_size: ffi::c_long, // Linux has `c_ulong` but we make it signed. + pub st_atime: ffi::c_long, pub st_atime_nsec: ffi::c_ulong, - pub st_mtime: ffi::c_ulong, + pub st_mtime: ffi::c_long, pub st_mtime_nsec: ffi::c_ulong, - pub st_ctime: ffi::c_ulong, + pub st_ctime: ffi::c_long, pub st_ctime_nsec: ffi::c_ulong, pub st_blksize: ffi::c_ulong, pub st_blocks: ffi::c_long, diff --git a/src/fs/constants.rs b/src/fs/constants.rs index fced70632..09dd8e46e 100644 --- a/src/fs/constants.rs +++ b/src/fs/constants.rs @@ -8,6 +8,7 @@ pub use backend::fs::types::*; #[cfg(test)] #[allow(unused_imports)] +#[allow(unsafe_code)] mod tests { use super::*; use crate::backend::c; @@ -39,74 +40,192 @@ mod tests { ))] assert_eq_size!(u16, linux_raw_sys::general::__kernel_mode_t); - #[cfg(all( - linux_raw, - target_pointer_width = "64", - not(target_arch = "mips64"), - not(target_arch = "mips64r6") - ))] - { - check_renamed_type!(Stat, stat); - check_renamed_struct_field!(Stat, stat, st_dev); - check_renamed_struct_field!(Stat, stat, st_ino); - check_renamed_struct_field!(Stat, stat, st_nlink); - check_renamed_struct_field!(Stat, stat, st_mode); - check_renamed_struct_field!(Stat, stat, st_uid); - check_renamed_struct_field!(Stat, stat, st_gid); - #[cfg(all( - linux_raw, - not(any( - target_arch = "aarch64", - target_arch = "powerpc64", - target_arch = "riscv64", - target_arch = "s390x" - )) - ))] - check_renamed_struct_field!(Stat, stat, __pad0); - check_renamed_struct_field!(Stat, stat, st_rdev); - #[cfg(all(linux_raw, not(any(target_arch = "powerpc64", target_arch = "x86_64"))))] - check_renamed_struct_field!(Stat, stat, __pad1); - check_renamed_struct_field!(Stat, stat, st_size); - check_renamed_struct_field!(Stat, stat, st_blksize); - #[cfg(all( - linux_raw, - not(any( - target_arch = "powerpc64", - target_arch = "s390x", - target_arch = "x86_64" - )) - ))] - check_renamed_struct_field!(Stat, stat, __pad2); - check_renamed_struct_field!(Stat, stat, st_blocks); - check_renamed_struct_field!(Stat, stat, st_atime); - check_renamed_struct_field!(Stat, stat, st_atime_nsec); - check_renamed_struct_field!(Stat, stat, st_mtime); - check_renamed_struct_field!(Stat, stat, st_mtime_nsec); - check_renamed_struct_field!(Stat, stat, st_ctime); - check_renamed_struct_field!(Stat, stat, st_ctime_nsec); - #[cfg(all( + // Ensure that seconds fields are 64-bit. + let some_stat: Stat = unsafe { core::mem::zeroed() }; + assert_eq!(some_stat.st_atime, 0_i64); + assert_eq!(some_stat.st_mtime, 0_i64); + assert_eq!(some_stat.st_ctime, 0_i64); + + // Ensure that file offsets are 64-bit. + assert_eq!(some_stat.st_size, 0_i64); + + // Check that various fields match expected types. + assert_eq!(some_stat.st_mode, 0 as RawMode); + assert_eq!(some_stat.st_dev, 0 as Dev); + assert_eq!(some_stat.st_rdev, 0 as Dev); + assert_eq!(some_stat.st_uid, 0 as crate::ugid::RawUid); + assert_eq!(some_stat.st_gid, 0 as crate::ugid::RawGid); + + // `Stat` should match `c::stat` or `c::stat64` unless we need y2038 + // fixes and are using a different layout. + #[cfg(not(any( + all(libc, linux_kernel, target_pointer_width = "32"), + all( linux_raw, - not(any( - target_arch = "aarch64", - target_arch = "powerpc64", - target_arch = "riscv64" - )) - ))] - check_renamed_struct_field!(Stat, stat, __unused); - #[cfg(all(linux_raw, not(any(target_arch = "s390x", target_arch = "x86_64"))))] - check_renamed_struct_field!(Stat, stat, __unused4); - #[cfg(all(linux_raw, not(any(target_arch = "s390x", target_arch = "x86_64"))))] - check_renamed_struct_field!(Stat, stat, __unused5); + any( + target_pointer_width = "32", + target_arch = "mips64", + target_arch = "mips64r6" + ) + ) + )))] + { + // Check that `Stat` matches `c::stat`. + #[cfg(not(all( + libc, + any( + all(linux_kernel, target_pointer_width = "64"), + target_os = "hurd", + target_os = "emscripten", + target_os = "l4re", + ) + )))] + { + check_renamed_type!(Stat, stat); + check_renamed_struct_field!(Stat, stat, st_dev); + check_renamed_struct_field!(Stat, stat, st_ino); + check_renamed_struct_field!(Stat, stat, st_nlink); + check_renamed_struct_field!(Stat, stat, st_mode); + check_renamed_struct_field!(Stat, stat, st_uid); + check_renamed_struct_field!(Stat, stat, st_gid); + #[cfg(all( + linux_raw, + not(any( + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "riscv64", + target_arch = "s390x" + )) + ))] + check_renamed_struct_field!(Stat, stat, __pad0); + check_renamed_struct_field!(Stat, stat, st_rdev); + #[cfg(all(linux_raw, not(any(target_arch = "powerpc64", target_arch = "x86_64"))))] + check_renamed_struct_field!(Stat, stat, __pad1); + check_renamed_struct_field!(Stat, stat, st_size); + check_renamed_struct_field!(Stat, stat, st_blksize); + #[cfg(all( + linux_raw, + not(any( + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86_64" + )) + ))] + check_renamed_struct_field!(Stat, stat, __pad2); + check_renamed_struct_field!(Stat, stat, st_blocks); + check_renamed_struct_field!(Stat, stat, st_atime); + #[cfg(not(target_os = "netbsd"))] + check_renamed_struct_field!(Stat, stat, st_atime_nsec); + #[cfg(target_os = "netbsd")] + check_renamed_struct_renamed_field!(Stat, stat, st_atime_nsec, st_atimensec); + check_renamed_struct_field!(Stat, stat, st_mtime); + #[cfg(not(target_os = "netbsd"))] + check_renamed_struct_field!(Stat, stat, st_mtime_nsec); + #[cfg(target_os = "netbsd")] + check_renamed_struct_renamed_field!(Stat, stat, st_mtime_nsec, st_mtimensec); + check_renamed_struct_field!(Stat, stat, st_ctime); + #[cfg(not(target_os = "netbsd"))] + check_renamed_struct_field!(Stat, stat, st_ctime_nsec); + #[cfg(target_os = "netbsd")] + check_renamed_struct_renamed_field!(Stat, stat, st_ctime_nsec, st_ctimensec); + #[cfg(all( + linux_raw, + not(any( + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "riscv64" + )) + ))] + check_renamed_struct_field!(Stat, stat, __unused); + #[cfg(all(linux_raw, not(any(target_arch = "s390x", target_arch = "x86_64"))))] + check_renamed_struct_field!(Stat, stat, __unused4); + #[cfg(all(linux_raw, not(any(target_arch = "s390x", target_arch = "x86_64"))))] + check_renamed_struct_field!(Stat, stat, __unused5); + #[cfg(all( + linux_raw, + not(any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "x86_64" + )) + ))] + check_renamed_struct_field!(Stat, stat, __unused6); + } + + // Check that `Stat` matches `c::stat64`. #[cfg(all( - linux_raw, - not(any( - target_arch = "aarch64", - target_arch = "riscv64", - target_arch = "s390x", - target_arch = "x86_64" - )) + libc, + any( + all(linux_kernel, target_pointer_width = "64"), + target_os = "hurd", + target_os = "emscripten", + target_os = "l4re", + ) ))] - check_renamed_struct_field!(Stat, stat, __unused6); + { + check_renamed_type!(Stat, stat64); + check_renamed_struct_field!(Stat, stat64, st_dev); + check_renamed_struct_field!(Stat, stat64, st_ino); + check_renamed_struct_field!(Stat, stat64, st_nlink); + check_renamed_struct_field!(Stat, stat64, st_mode); + check_renamed_struct_field!(Stat, stat64, st_uid); + check_renamed_struct_field!(Stat, stat64, st_gid); + #[cfg(all( + linux_raw, + not(any( + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "riscv64", + target_arch = "s390x" + )) + ))] + check_renamed_struct_field!(Stat, stat64, __pad0); + check_renamed_struct_field!(Stat, stat64, st_rdev); + #[cfg(all(linux_raw, not(any(target_arch = "powerpc64", target_arch = "x86_64"))))] + check_renamed_struct_field!(Stat, stat64, __pad1); + check_renamed_struct_field!(Stat, stat64, st_size); + check_renamed_struct_field!(Stat, stat64, st_blksize); + #[cfg(all( + linux_raw, + not(any( + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86_64" + )) + ))] + check_renamed_struct_field!(Stat, stat64, __pad2); + check_renamed_struct_field!(Stat, stat64, st_blocks); + check_renamed_struct_field!(Stat, stat64, st_atime); + check_renamed_struct_field!(Stat, stat64, st_atime_nsec); + check_renamed_struct_field!(Stat, stat64, st_mtime); + check_renamed_struct_field!(Stat, stat64, st_mtime_nsec); + check_renamed_struct_field!(Stat, stat64, st_ctime); + check_renamed_struct_field!(Stat, stat64, st_ctime_nsec); + #[cfg(all( + linux_raw, + not(any( + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "riscv64" + )) + ))] + check_renamed_struct_field!(Stat, stat64, __unused); + #[cfg(all(linux_raw, not(any(target_arch = "s390x", target_arch = "x86_64"))))] + check_renamed_struct_field!(Stat, stat64, __unused4); + #[cfg(all(linux_raw, not(any(target_arch = "s390x", target_arch = "x86_64"))))] + check_renamed_struct_field!(Stat, stat64, __unused5); + #[cfg(all( + linux_raw, + not(any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "x86_64" + )) + ))] + check_renamed_struct_field!(Stat, stat64, __unused6); + } } #[cfg(not(any( diff --git a/src/timespec.rs b/src/timespec.rs index 080e85cbe..961159b2c 100644 --- a/src/timespec.rs +++ b/src/timespec.rs @@ -109,6 +109,42 @@ pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const } } +/// As described [here], Apple platforms may return a negative nanoseconds +/// value in some cases; adjust it so that nanoseconds is always in +/// `0..1_000_000_000`. +/// +/// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158 +#[cfg(apple)] +#[inline] +pub(crate) fn fix_negative_nsecs(secs: &mut i64, mut nsecs: i32) -> i32 { + #[cold] + fn adjust(secs: &mut i64, nsecs: i32) -> i32 { + assert!(nsecs >= -1_000_000_000); + assert!(*secs < 0); + assert!(*secs > i64::MIN); + *secs -= 1; + nsecs + 1_000_000_000 + } + + if nsecs < 0 { + nsecs = adjust(secs, nsecs); + } + nsecs +} + +#[cfg(apple)] +#[test] +fn test_negative_timestamps() { + let mut secs = -59; + let mut nsecs = -900_000_000; + nsecs = fix_negative_nsecs(&mut secs, nsecs); + assert_eq!(secs, -60); + assert_eq!(nsecs, 100_000_000); + nsecs = fix_negative_nsecs(&mut secs, nsecs); + assert_eq!(secs, -60); + assert_eq!(nsecs, 100_000_000); +} + #[test] fn test_sizes() { assert_eq_size!(Secs, u64); diff --git a/tests/fs/futimens.rs b/tests/fs/futimens.rs index 616638f13..963b57f39 100644 --- a/tests/fs/futimens.rs +++ b/tests/fs/futimens.rs @@ -29,14 +29,8 @@ fn test_futimens() { let after = fstat(&file).unwrap(); assert_eq!(times.last_modification.tv_sec as u64, after.st_mtime as u64); - #[cfg(not(target_os = "netbsd"))] assert_eq!( times.last_modification.tv_nsec as u64, after.st_mtime_nsec as u64 ); - #[cfg(target_os = "netbsd")] - assert_eq!( - times.last_modification.tv_nsec as u64, - after.st_mtimensec as u64 - ); } diff --git a/tests/fs/main.rs b/tests/fs/main.rs index 54e41cb51..e2f810824 100644 --- a/tests/fs/main.rs +++ b/tests/fs/main.rs @@ -31,6 +31,7 @@ mod long_paths; mod makedev; mod mkdirat; mod mknodat; +mod negative_timestamp; #[cfg(linux_kernel)] mod openat; #[cfg(linux_kernel)] diff --git a/tests/fs/negative_timestamp.rs b/tests/fs/negative_timestamp.rs new file mode 100644 index 000000000..84d74c1db --- /dev/null +++ b/tests/fs/negative_timestamp.rs @@ -0,0 +1,52 @@ +#[test] +fn negative_file_timetamp() { + use rustix::fs::{ + fstat, futimens, lstat, open, stat, statat, AtFlags, Mode, OFlags, Timespec, Timestamps, + CWD, + }; + + let tmp = tempfile::tempdir().unwrap(); + + let file = open( + tmp.path().join("foo"), + OFlags::CREATE | OFlags::WRONLY, + Mode::RWXU, + ) + .unwrap(); + + let stamps = Timestamps { + last_modification: Timespec { + tv_sec: -20, + tv_nsec: 12, + }, + last_access: Timespec { + tv_sec: -23, + tv_nsec: 14, + }, + }; + futimens(&file, &stamps).unwrap(); + + let st = fstat(file).unwrap(); + assert_eq!(st.st_mtime, -20); + assert_eq!(st.st_mtime_nsec, 12); + assert_eq!(st.st_atime, -23); + assert_eq!(st.st_atime_nsec, 14); + + let st = stat(tmp.path().join("foo")).unwrap(); + assert_eq!(st.st_mtime, -20); + assert_eq!(st.st_mtime_nsec, 12); + assert_eq!(st.st_atime, -23); + assert_eq!(st.st_atime_nsec, 14); + + let st = lstat(tmp.path().join("foo")).unwrap(); + assert_eq!(st.st_mtime, -20); + assert_eq!(st.st_mtime_nsec, 12); + assert_eq!(st.st_atime, -23); + assert_eq!(st.st_atime_nsec, 14); + + let st = statat(CWD, tmp.path().join("foo"), AtFlags::empty()).unwrap(); + assert_eq!(st.st_mtime, -20); + assert_eq!(st.st_mtime_nsec, 12); + assert_eq!(st.st_atime, -23); + assert_eq!(st.st_atime_nsec, 14); +} diff --git a/tests/fs/utimensat.rs b/tests/fs/utimensat.rs index ec65f466d..2da161286 100644 --- a/tests/fs/utimensat.rs +++ b/tests/fs/utimensat.rs @@ -35,27 +35,15 @@ fn test_utimensat() { let after = statat(&dir, "file", AtFlags::empty()).unwrap(); assert_eq!(times.last_modification.tv_sec as u64, after.st_mtime as u64); - #[cfg(not(target_os = "netbsd"))] assert_eq!( times.last_modification.tv_nsec as u64, after.st_mtime_nsec as u64 ); - #[cfg(target_os = "netbsd")] - assert_eq!( - times.last_modification.tv_nsec as u64, - after.st_mtimensec as u64 - ); assert!(times.last_access.tv_sec as u64 >= after.st_atime as u64); - #[cfg(not(target_os = "netbsd"))] assert!( times.last_access.tv_sec as u64 > after.st_atime as u64 || times.last_access.tv_nsec as u64 >= after.st_atime_nsec as u64 ); - #[cfg(target_os = "netbsd")] - assert!( - times.last_access.tv_sec as u64 > after.st_atime as u64 - || times.last_access.tv_nsec as u64 >= after.st_atimensec as u64 - ); } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] diff --git a/tests/fs/y2038.rs b/tests/fs/y2038.rs index eba3a16d5..6b49a823c 100644 --- a/tests/fs/y2038.rs +++ b/tests/fs/y2038.rs @@ -48,23 +48,14 @@ fn test_y2038_with_utimensat() { assert_eq!(TryInto::::try_into(stat.st_mtime).unwrap(), m_sec); - #[cfg(not(target_os = "netbsd"))] assert_eq!(stat.st_mtime_nsec as u32, m_nsec); - #[cfg(target_os = "netbsd")] - assert_eq!(stat.st_mtimensec as u32, m_nsec); assert!(TryInto::::try_into(stat.st_atime).unwrap() >= a_sec); - #[cfg(not(target_os = "netbsd"))] assert!( TryInto::::try_into(stat.st_atime).unwrap() > a_sec || stat.st_atime_nsec as u32 >= a_nsec ); - #[cfg(target_os = "netbsd")] - assert!( - TryInto::::try_into(stat.st_atime).unwrap() > a_sec - || stat.st_atimensec as u32 >= a_nsec - ); // Now test the same thing, but with `fstat`. let file = openat(&dir, "file", OFlags::RDONLY, Mode::empty()).unwrap(); @@ -72,23 +63,14 @@ fn test_y2038_with_utimensat() { assert_eq!(TryInto::::try_into(stat.st_mtime).unwrap(), m_sec); - #[cfg(not(target_os = "netbsd"))] assert_eq!(stat.st_mtime_nsec as u32, m_nsec); - #[cfg(target_os = "netbsd")] - assert_eq!(stat.st_mtimensec as u32, m_nsec); assert!(TryInto::::try_into(stat.st_atime).unwrap() >= a_sec); - #[cfg(not(target_os = "netbsd"))] assert!( TryInto::::try_into(stat.st_atime).unwrap() > a_sec || stat.st_atime_nsec as u32 >= a_nsec ); - #[cfg(target_os = "netbsd")] - assert!( - TryInto::::try_into(stat.st_atime).unwrap() > a_sec - || stat.st_atimensec as u32 >= a_nsec - ); } /// Test that we can set a file timestamp to a date past the year 2038 with @@ -141,23 +123,14 @@ fn test_y2038_with_futimens() { assert_eq!(TryInto::::try_into(stat.st_mtime).unwrap(), m_sec); - #[cfg(not(target_os = "netbsd"))] assert_eq!(stat.st_mtime_nsec as u32, m_nsec); - #[cfg(target_os = "netbsd")] - assert_eq!(stat.st_mtimensec as u32, m_nsec); assert!(TryInto::::try_into(stat.st_atime).unwrap() >= a_sec); - #[cfg(not(target_os = "netbsd"))] assert!( TryInto::::try_into(stat.st_atime).unwrap() > a_sec || stat.st_atime_nsec as u32 >= a_nsec ); - #[cfg(target_os = "netbsd")] - assert!( - TryInto::::try_into(stat.st_atime).unwrap() > a_sec - || stat.st_atimensec as u32 >= a_nsec - ); // Now test the same thing, but with `fstat`. let file = openat(&dir, "file", OFlags::RDONLY, Mode::empty()).unwrap(); @@ -165,23 +138,14 @@ fn test_y2038_with_futimens() { assert_eq!(TryInto::::try_into(stat.st_mtime).unwrap(), m_sec); - #[cfg(not(target_os = "netbsd"))] assert_eq!(stat.st_mtime_nsec as u32, m_nsec); - #[cfg(target_os = "netbsd")] - assert_eq!(stat.st_mtimensec as u32, m_nsec); assert!(TryInto::::try_into(stat.st_atime).unwrap() >= a_sec); - #[cfg(not(target_os = "netbsd"))] assert!( TryInto::::try_into(stat.st_atime).unwrap() > a_sec || stat.st_atime_nsec as u32 >= a_nsec ); - #[cfg(target_os = "netbsd")] - assert!( - TryInto::::try_into(stat.st_atime).unwrap() > a_sec - || stat.st_atimensec as u32 >= a_nsec - ); } /// Like `test_y2038_with_futimens`, but using `stat` instead of `statat`. @@ -235,23 +199,14 @@ fn test_y2038_with_futimens_and_stat() { assert_eq!(TryInto::::try_into(stat.st_mtime).unwrap(), m_sec); - #[cfg(not(target_os = "netbsd"))] assert_eq!(stat.st_mtime_nsec as u32, m_nsec); - #[cfg(target_os = "netbsd")] - assert_eq!(stat.st_mtimensec as u32, m_nsec); assert!(TryInto::::try_into(stat.st_atime).unwrap() >= a_sec); - #[cfg(not(target_os = "netbsd"))] assert!( TryInto::::try_into(stat.st_atime).unwrap() > a_sec || stat.st_atime_nsec as u32 >= a_nsec ); - #[cfg(target_os = "netbsd")] - assert!( - TryInto::::try_into(stat.st_atime).unwrap() > a_sec - || stat.st_atimensec as u32 >= a_nsec - ); // Now test the same thing, but with `fstat`. let file = open(tmp.path().join("file"), OFlags::RDONLY, Mode::empty()).unwrap(); @@ -259,21 +214,12 @@ fn test_y2038_with_futimens_and_stat() { assert_eq!(TryInto::::try_into(stat.st_mtime).unwrap(), m_sec); - #[cfg(not(target_os = "netbsd"))] assert_eq!(stat.st_mtime_nsec as u32, m_nsec); - #[cfg(target_os = "netbsd")] - assert_eq!(stat.st_mtimensec as u32, m_nsec); assert!(TryInto::::try_into(stat.st_atime).unwrap() >= a_sec); - #[cfg(not(target_os = "netbsd"))] assert!( TryInto::::try_into(stat.st_atime).unwrap() > a_sec || stat.st_atime_nsec as u32 >= a_nsec ); - #[cfg(target_os = "netbsd")] - assert!( - TryInto::::try_into(stat.st_atime).unwrap() > a_sec - || stat.st_atimensec as u32 >= a_nsec - ); }