mirror of
https://github.com/openharmony/third_party_rust_rustix.git
synced 2026-07-21 02:05:28 -04:00
Implement utimensat and futimens on macOS 10.12 and earlier.
macOS 10.12 lacks `utimensat` and `futimens`, so emulate them using `setattrlist` and `fsetattrlist`. Fixes #157.
This commit is contained in:
+251
-2
@@ -99,12 +99,22 @@ use crate::io::{self, OwnedFd, SeekFrom};
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
use crate::process::{Gid, Uid};
|
||||
use core::convert::TryInto;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
target_os = "linux",
|
||||
target_os = "macos"
|
||||
))]
|
||||
use core::mem::size_of;
|
||||
#[cfg(target_os = "linux")]
|
||||
use core::mem::transmute;
|
||||
use core::mem::MaybeUninit;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
target_os = "linux",
|
||||
target_os = "macos"
|
||||
))]
|
||||
use core::ptr::null_mut;
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
use {
|
||||
@@ -350,6 +360,7 @@ pub(crate) fn utimensat(
|
||||
times: &Timestamps,
|
||||
flags: AtFlags,
|
||||
) -> io::Result<()> {
|
||||
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
|
||||
unsafe {
|
||||
// Assert that `Timestamps` has the expected layout.
|
||||
let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
|
||||
@@ -361,6 +372,120 @@ pub(crate) fn utimensat(
|
||||
flags.bits(),
|
||||
))
|
||||
}
|
||||
|
||||
// `utimensat` was introduced in macOS 10.13.
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
unsafe {
|
||||
// ABI details
|
||||
weak! {
|
||||
fn utimensat(
|
||||
c::c_int,
|
||||
*const c::c_char,
|
||||
*const c::timespec,
|
||||
c::c_int
|
||||
) -> c::c_int
|
||||
}
|
||||
extern "C" {
|
||||
fn setattrlist(
|
||||
path: *const c::c_char,
|
||||
attr_list: *const Attrlist,
|
||||
attr_buf: *const c::c_void,
|
||||
attr_buf_size: c::size_t,
|
||||
options: c::c_ulong,
|
||||
) -> c::c_int;
|
||||
}
|
||||
const FSOPT_NOFOLLOW: c::c_ulong = 0x00000001;
|
||||
|
||||
// If we have `utimensat`, use it.
|
||||
if let Some(have_utimensat) = utimensat.get() {
|
||||
// Assert that `Timestamps` has the expected layout.
|
||||
let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
|
||||
|
||||
return ret(have_utimensat(
|
||||
borrowed_fd(dirfd),
|
||||
c_str(path),
|
||||
as_ptr(times).cast(),
|
||||
flags.bits(),
|
||||
));
|
||||
}
|
||||
|
||||
// `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::Error::IO),
|
||||
0 => {
|
||||
if c::fchdir(borrowed_fd(dirfd)) != 0 {
|
||||
let code = match errno::errno().0 {
|
||||
libc::EACCES => 2,
|
||||
libc::ENOTDIR => 3,
|
||||
_ => 1,
|
||||
};
|
||||
c::_exit(code);
|
||||
}
|
||||
|
||||
let mut flags_arg = 0;
|
||||
if flags.contains(AtFlags::SYMLINK_NOFOLLOW) {
|
||||
flags_arg |= FSOPT_NOFOLLOW;
|
||||
}
|
||||
|
||||
let (attrbuf_size, times, attrs) = times_to_attrlist(times);
|
||||
|
||||
if setattrlist(
|
||||
c_str(path),
|
||||
&attrs,
|
||||
as_ptr(×).cast(),
|
||||
attrbuf_size,
|
||||
flags_arg,
|
||||
) != 0
|
||||
{
|
||||
// Translate expected errno codes into ad-hoc integer
|
||||
// values suitable for exit statuses.
|
||||
let code = match errno::errno().0 {
|
||||
libc::EACCES => 2,
|
||||
libc::ENOTDIR => 3,
|
||||
libc::EPERM => 4,
|
||||
libc::EROFS => 5,
|
||||
libc::ELOOP => 6,
|
||||
libc::ENOENT => 7,
|
||||
libc::ENAMETOOLONG => 8,
|
||||
libc::EINVAL => 9,
|
||||
libc::ESRCH => 10,
|
||||
libc::ENOTSUP => 11,
|
||||
_ => 1,
|
||||
};
|
||||
c::_exit(code);
|
||||
}
|
||||
|
||||
c::_exit(0);
|
||||
}
|
||||
child_pid => {
|
||||
let mut wstatus = 0;
|
||||
let _ = ret_c_int(c::waitpid(child_pid, &mut wstatus, 0))?;
|
||||
if c::WIFEXITED(wstatus) {
|
||||
// Translate our ad-hoc exit statuses back to errno codes.
|
||||
match c::WEXITSTATUS(wstatus) {
|
||||
0 => Ok(()),
|
||||
2 => Err(io::Error::ACCESS),
|
||||
3 => Err(io::Error::NOTDIR),
|
||||
4 => Err(io::Error::PERM),
|
||||
5 => Err(io::Error::ROFS),
|
||||
6 => Err(io::Error::LOOP),
|
||||
7 => Err(io::Error::NOENT),
|
||||
8 => Err(io::Error::NAMETOOLONG),
|
||||
9 => Err(io::Error::INVAL),
|
||||
10 => Err(io::Error::SRCH),
|
||||
11 => Err(io::Error::NOTSUP),
|
||||
_ => Err(io::Error::IO),
|
||||
}
|
||||
} else {
|
||||
Err(io::Error::IO)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
@@ -663,12 +788,50 @@ pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
|
||||
}
|
||||
|
||||
pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
|
||||
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
|
||||
unsafe {
|
||||
// Assert that `Timestamps` has the expected layout.
|
||||
let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
|
||||
|
||||
ret(c::futimens(borrowed_fd(fd), as_ptr(times).cast()))
|
||||
}
|
||||
|
||||
// `futimens` was introduced in macOS 10.13.
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
unsafe {
|
||||
// ABI details.
|
||||
weak! {
|
||||
fn futimens(c::c_int, *const c::timespec) -> c::c_int
|
||||
}
|
||||
extern "C" {
|
||||
fn fsetattrlist(
|
||||
fd: c::c_int,
|
||||
attr_list: *const Attrlist,
|
||||
attr_buf: *const c::c_void,
|
||||
attr_buf_size: c::size_t,
|
||||
options: c::c_ulong,
|
||||
) -> c::c_int;
|
||||
}
|
||||
|
||||
// If we have `futimens`, use it.
|
||||
if let Some(have_futimens) = futimens.get() {
|
||||
// Assert that `Timestamps` has the expected layout.
|
||||
let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
|
||||
|
||||
return ret(have_futimens(borrowed_fd(fd), as_ptr(times).cast()));
|
||||
}
|
||||
|
||||
// Otherwise use `fsetattrlist`.
|
||||
let (attrbuf_size, times, attrs) = times_to_attrlist(times);
|
||||
|
||||
ret(fsetattrlist(
|
||||
borrowed_fd(fd),
|
||||
&attrs,
|
||||
as_ptr(×).cast(),
|
||||
attrbuf_size,
|
||||
0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
@@ -1023,3 +1186,89 @@ pub(crate) fn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::R
|
||||
pub(crate) fn fcntl_fullfsync(fd: BorrowedFd<'_>) -> io::Result<()> {
|
||||
unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_FULLFSYNC)) }
|
||||
}
|
||||
|
||||
/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist`
|
||||
/// arguments.
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
fn times_to_attrlist(times: &Timestamps) -> (c::size_t, [c::timespec; 2], Attrlist) {
|
||||
// ABI details.
|
||||
const ATTR_CMN_MODTIME: u32 = 0x00000400;
|
||||
const ATTR_CMN_ACCTIME: u32 = 0x00001000;
|
||||
const ATTR_BIT_MAP_COUNT: u16 = 5;
|
||||
|
||||
let mut 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 || times.last_modification.tv_nsec == c::UTIME_NOW
|
||||
{
|
||||
let now = {
|
||||
let mut 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 {
|
||||
times.last_access = now;
|
||||
}
|
||||
if times.last_modification.tv_nsec == c::UTIME_NOW {
|
||||
times.last_modification = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Pack the return values following the rules for [`getattrlist`].
|
||||
//
|
||||
// [`getattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getattrlist.2.html
|
||||
let mut times_size = 0;
|
||||
let mut attrs = Attrlist {
|
||||
bitmapcount: ATTR_BIT_MAP_COUNT,
|
||||
reserved: 0,
|
||||
commonattr: 0,
|
||||
volattr: 0,
|
||||
dirattr: 0,
|
||||
fileattr: 0,
|
||||
forkattr: 0,
|
||||
};
|
||||
let mut return_times = [c::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
}; 2];
|
||||
let mut times_index = 0;
|
||||
if times.last_modification.tv_nsec != c::UTIME_OMIT {
|
||||
attrs.commonattr |= ATTR_CMN_MODTIME;
|
||||
return_times[times_index] = times.last_modification;
|
||||
times_index += 1;
|
||||
times_size += size_of::<c::timespec>();
|
||||
}
|
||||
if times.last_access.tv_nsec != c::UTIME_OMIT {
|
||||
attrs.commonattr |= ATTR_CMN_ACCTIME;
|
||||
return_times[times_index] = times.last_access;
|
||||
times_size += size_of::<c::timespec>();
|
||||
}
|
||||
|
||||
(times_size, return_times, attrs)
|
||||
}
|
||||
|
||||
/// Support type for `Attrlist`.
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
type Attrgroup = u32;
|
||||
|
||||
/// Attribute list for use with `setattrlist`.
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
#[repr(C)]
|
||||
struct Attrlist {
|
||||
bitmapcount: u16,
|
||||
reserved: u16,
|
||||
commonattr: Attrgroup,
|
||||
volattr: Attrgroup,
|
||||
dirattr: Attrgroup,
|
||||
fileattr: Attrgroup,
|
||||
forkattr: Attrgroup,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
|
||||
#[test]
|
||||
fn test_futimens() {
|
||||
use rustix::fs::{cwd, fstat, futimens, openat, Mode, OFlags, Timestamps};
|
||||
use rustix::time::Timespec;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = openat(&cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
|
||||
|
||||
let foo = openat(
|
||||
&dir,
|
||||
"foo",
|
||||
OFlags::CREATE | OFlags::WRONLY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let times = Timestamps {
|
||||
last_access: Timespec {
|
||||
tv_sec: 44000,
|
||||
tv_nsec: 45000,
|
||||
},
|
||||
last_modification: Timespec {
|
||||
tv_sec: 46000,
|
||||
tv_nsec: 47000,
|
||||
},
|
||||
};
|
||||
futimens(&foo, ×).unwrap();
|
||||
|
||||
let after = fstat(&foo).unwrap();
|
||||
|
||||
assert_eq!(times.last_modification.tv_sec as u64, after.st_mtime as u64);
|
||||
assert_eq!(
|
||||
times.last_modification.tv_nsec as u64,
|
||||
after.st_mtime_nsec as u64
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ mod fcntl;
|
||||
mod file;
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
mod flock;
|
||||
mod futimens;
|
||||
mod invalid_offset;
|
||||
mod long_paths;
|
||||
#[cfg(not(any(
|
||||
@@ -31,3 +32,4 @@ mod renameat;
|
||||
#[cfg(not(any(target_os = "netbsd", target_os = "redox", target_os = "wasi")))]
|
||||
// not implemented in libc for netbsd yet
|
||||
mod statfs;
|
||||
mod utimensat;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
|
||||
#[test]
|
||||
fn test_utimensat() {
|
||||
use rustix::fs::{cwd, openat, statat, utimensat, AtFlags, Mode, OFlags, Timestamps};
|
||||
use rustix::time::Timespec;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = openat(
|
||||
&cwd(),
|
||||
tmp.path(),
|
||||
OFlags::RDONLY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = openat(
|
||||
&dir,
|
||||
"foo",
|
||||
OFlags::CREATE | OFlags::WRONLY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let times = Timestamps {
|
||||
last_access: Timespec {
|
||||
tv_sec: 44000,
|
||||
tv_nsec: 45000,
|
||||
},
|
||||
last_modification: Timespec {
|
||||
tv_sec: 46000,
|
||||
tv_nsec: 47000,
|
||||
},
|
||||
};
|
||||
utimensat(&dir, "foo", ×, AtFlags::empty()).unwrap();
|
||||
|
||||
let after = statat(&dir, "foo", AtFlags::empty()).unwrap();
|
||||
|
||||
assert_eq!(times.last_modification.tv_sec as u64, after.st_mtime as u64);
|
||||
assert_eq!(
|
||||
times.last_modification.tv_nsec as u64,
|
||||
after.st_mtime_nsec as u64
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
|
||||
#[test]
|
||||
fn test_utimensat_noent() {
|
||||
use rustix::fs::{cwd, openat, utimensat, AtFlags, Mode, OFlags, Timestamps};
|
||||
use rustix::time::Timespec;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = openat(
|
||||
&cwd(),
|
||||
tmp.path(),
|
||||
OFlags::RDONLY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let times = Timestamps {
|
||||
last_access: Timespec {
|
||||
tv_sec: 44000,
|
||||
tv_nsec: 45000,
|
||||
},
|
||||
last_modification: Timespec {
|
||||
tv_sec: 46000,
|
||||
tv_nsec: 47000,
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
utimensat(&dir, "foo", ×, AtFlags::empty()).unwrap_err(),
|
||||
rustix::io::Error::NOENT
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
|
||||
#[test]
|
||||
fn test_utimensat_notdir() {
|
||||
use rustix::fs::{cwd, openat, utimensat, AtFlags, Mode, OFlags, Timestamps};
|
||||
use rustix::time::Timespec;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = openat(
|
||||
&cwd(),
|
||||
tmp.path(),
|
||||
OFlags::RDONLY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let foo = openat(
|
||||
&dir,
|
||||
"foo",
|
||||
OFlags::CREATE | OFlags::WRONLY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let times = Timestamps {
|
||||
last_access: Timespec {
|
||||
tv_sec: 44000,
|
||||
tv_nsec: 45000,
|
||||
},
|
||||
last_modification: Timespec {
|
||||
tv_sec: 46000,
|
||||
tv_nsec: 47000,
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
utimensat(&foo, "bar", ×, AtFlags::empty()).unwrap_err(),
|
||||
rustix::io::Error::NOTDIR
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user