Skip to content

Commit bf0a982

Browse files
authored
Add support for renameat_with on Apple platforms (#1340)
Apple platforms (both iOS and macOS) added a `renameat2` equivalent in 10.12 (and the iOS equivalent). This patch uses that syscall to implement `renameat2`, and subsequently `renameat_with`. Unlike Linux, the `RenameFlags::WHITEOUT` flag isn't supported. At the moment, any programs trying to use that flag will fail to compile on Apple operating systems.
1 parent 6565ebb commit bf0a982

4 files changed

Lines changed: 85 additions & 32 deletions

File tree

src/backend/libc/fs/syscalls.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use crate::fs::FallocateFlags;
2525
use crate::fs::FlockOperation;
2626
#[cfg(any(linux_kernel, target_os = "freebsd"))]
2727
use crate::fs::MemfdFlags;
28+
#[cfg(any(linux_kernel, apple))]
29+
use crate::fs::RenameFlags;
2830
#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
2931
use crate::fs::SealFlags;
3032
#[cfg(not(any(
@@ -79,7 +81,7 @@ use {crate::fs::Advice, core::num::NonZeroU64};
7981
use {crate::fs::XattrFlags, core::mem::size_of, core::ptr::null_mut};
8082
#[cfg(linux_kernel)]
8183
use {
82-
crate::fs::{RenameFlags, ResolveFlags, Statx, StatxFlags, CWD},
84+
crate::fs::{ResolveFlags, Statx, StatxFlags, CWD},
8385
core::ptr::null,
8486
};
8587

@@ -563,6 +565,47 @@ pub(crate) fn renameat2(
563565
}
564566
}
565567

568+
#[cfg(apple)]
569+
pub(crate) fn renameat2(
570+
old_dirfd: BorrowedFd<'_>,
571+
old_path: &CStr,
572+
new_dirfd: BorrowedFd<'_>,
573+
new_path: &CStr,
574+
flags: RenameFlags,
575+
) -> io::Result<()> {
576+
unsafe {
577+
// macOS < 10.12 lacks `renameatx_np`.
578+
weak! {
579+
fn renameatx_np(
580+
c::c_int,
581+
*const ffi::c_char,
582+
c::c_int,
583+
*const ffi::c_char,
584+
c::c_uint
585+
) -> c::c_int
586+
}
587+
// If we have `renameatx_np`, use it.
588+
if let Some(libc_renameatx_np) = renameatx_np.get() {
589+
return ret(libc_renameatx_np(
590+
borrowed_fd(old_dirfd),
591+
c_str(old_path),
592+
borrowed_fd(new_dirfd),
593+
c_str(new_path),
594+
flags.bits(),
595+
));
596+
}
597+
// Otherwise, see if we can use rename. There's no point in trying `renamex_np`
598+
// because it was added in the same macOS release as `renameatx_np`.
599+
if !flags.is_empty()
600+
|| borrowed_fd(old_dirfd) != c::AT_FDCWD
601+
|| borrowed_fd(new_dirfd) != c::AT_FDCWD
602+
{
603+
return Err(io::Errno::NOSYS);
604+
}
605+
ret(c::rename(c_str(old_path), c_str(new_path)))
606+
}
607+
}
608+
566609
pub(crate) fn symlink(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
567610
unsafe { ret(c::symlink(c_str(old_path), c_str(new_path))) }
568611
}

src/backend/libc/fs/types.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,25 @@ bitflags! {
475475
}
476476
}
477477

478+
#[cfg(apple)]
479+
bitflags! {
480+
/// `RENAME_*` constants for use with [`renameat_with`].
481+
///
482+
/// [`renameat_with`]: crate::fs::renameat_with
483+
#[repr(transparent)]
484+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
485+
pub struct RenameFlags: ffi::c_uint {
486+
/// `RENAME_SWAP`
487+
const EXCHANGE = bitcast!(c::RENAME_SWAP);
488+
489+
/// `RENAME_EXCL`
490+
const NOREPLACE = bitcast!(c::RENAME_EXCL);
491+
492+
/// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
493+
const _ = !0;
494+
}
495+
}
496+
478497
/// `S_IF*` constants for use with [`mknodat`] and [`Stat`]'s `st_mode` field.
479498
///
480499
/// [`mknodat`]: crate::fs::mknodat

src/fs/at.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::fs::Access;
1414
use crate::fs::AtFlags;
1515
#[cfg(apple)]
1616
use crate::fs::CloneFlags;
17-
#[cfg(linux_kernel)]
17+
#[cfg(any(linux_kernel, apple))]
1818
use crate::fs::RenameFlags;
1919
#[cfg(not(target_os = "espidf"))]
2020
use crate::fs::Stat;
@@ -277,9 +277,10 @@ pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
277277
/// - [Linux]
278278
///
279279
/// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html
280-
#[cfg(linux_kernel)]
280+
#[cfg(any(apple, linux_kernel))]
281281
#[inline]
282282
#[doc(alias = "renameat2")]
283+
#[doc(alias = "renameatx_np")]
283284
pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
284285
old_dirfd: PFd,
285286
old_path: P,

tests/fs/renameat.rs

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ fn same(a: &Stat, b: &Stat) -> bool {
44
a.st_ino == b.st_ino && a.st_dev == b.st_dev
55
}
66

7+
#[cfg(test)]
8+
use rustix::fs::OFlags;
9+
10+
#[cfg(all(test, linux_kernel))]
11+
const DIR_OPEN_FLAGS: OFlags = OFlags::RDONLY.union(OFlags::PATH);
12+
#[cfg(all(test, apple))]
13+
const DIR_OPEN_FLAGS: OFlags = OFlags::RDONLY;
14+
715
#[test]
816
fn test_rename() {
9-
use rustix::fs::{access, open, rename, stat, Access, Mode, OFlags};
17+
use rustix::fs::{access, open, rename, stat, Access, Mode};
1018

1119
let tmp = tempfile::tempdir().unwrap();
1220

@@ -29,19 +37,13 @@ fn test_rename() {
2937
access(tmp.path().join("bar"), Access::EXISTS).unwrap();
3038
}
3139

32-
#[cfg(linux_kernel)]
40+
#[cfg(any(linux_kernel, apple))]
3341
#[test]
3442
fn test_renameat() {
35-
use rustix::fs::{accessat, openat, renameat, statat, Access, AtFlags, Mode, OFlags, CWD};
43+
use rustix::fs::{accessat, openat, renameat, statat, Access, AtFlags, Mode, CWD};
3644

3745
let tmp = tempfile::tempdir().unwrap();
38-
let dir = openat(
39-
CWD,
40-
tmp.path(),
41-
OFlags::RDONLY | OFlags::PATH,
42-
Mode::empty(),
43-
)
44-
.unwrap();
46+
let dir = openat(CWD, tmp.path(), DIR_OPEN_FLAGS, Mode::empty()).unwrap();
4547

4648
let _ = openat(&dir, "file", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
4749
let before = statat(&dir, "file", AtFlags::empty()).unwrap();
@@ -59,19 +61,13 @@ fn test_renameat() {
5961

6062
/// Like `test_renameat` but the file already exists, so `renameat`
6163
/// overwrites it.
62-
#[cfg(linux_kernel)]
64+
#[cfg(any(linux_kernel, apple))]
6365
#[test]
6466
fn test_renameat_overwrite() {
65-
use rustix::fs::{openat, renameat, statat, AtFlags, Mode, OFlags, CWD};
67+
use rustix::fs::{openat, renameat, statat, AtFlags, Mode, CWD};
6668

6769
let tmp = tempfile::tempdir().unwrap();
68-
let dir = openat(
69-
CWD,
70-
tmp.path(),
71-
OFlags::RDONLY | OFlags::PATH,
72-
Mode::empty(),
73-
)
74-
.unwrap();
70+
let dir = openat(CWD, tmp.path(), DIR_OPEN_FLAGS, Mode::empty()).unwrap();
7571

7672
let _ = openat(&dir, "file", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
7773
let _ = openat(&dir, "bar", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
@@ -81,19 +77,13 @@ fn test_renameat_overwrite() {
8177
assert!(same(&before, &renamed));
8278
}
8379

84-
#[cfg(linux_kernel)]
80+
#[cfg(any(linux_kernel, apple))]
8581
#[test]
8682
fn test_renameat_with() {
87-
use rustix::fs::{openat, renameat_with, statat, AtFlags, Mode, OFlags, RenameFlags, CWD};
83+
use rustix::fs::{openat, renameat_with, statat, AtFlags, Mode, RenameFlags, CWD};
8884

8985
let tmp = tempfile::tempdir().unwrap();
90-
let dir = openat(
91-
CWD,
92-
tmp.path(),
93-
OFlags::RDONLY | OFlags::PATH,
94-
Mode::empty(),
95-
)
96-
.unwrap();
86+
let dir = openat(CWD, tmp.path(), DIR_OPEN_FLAGS, Mode::empty()).unwrap();
9787

9888
let _ = openat(&dir, "file", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
9989
let before = statat(&dir, "file", AtFlags::empty()).unwrap();
@@ -115,7 +105,7 @@ fn test_renameat_with() {
115105
)
116106
.unwrap();
117107

118-
#[cfg(all(target_os = "linux", target_env = "gnu"))]
108+
#[cfg(any(apple, all(target_os = "linux", target_env = "gnu")))]
119109
{
120110
let green = statat(&dir, "green", AtFlags::empty()).unwrap();
121111

0 commit comments

Comments
 (0)