Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make linkat, unlinkat, and renameat weak on macos. #649

Merged
merged 6 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,8 @@ jobs:
cargo test --verbose --features=all-impls,all-apis,cc --release --workspace -- --nocapture
env:
RUST_BACKTRACE: full
MACOSX_DEPLOYMENT_TARGET: 10.7
MACOSX_SDK_VERSION: 10.7
if: matrix.rust != '1.48'

- run: |
Expand All @@ -545,13 +547,17 @@ jobs:
cargo test --verbose --features=fs-err,all-apis,cc --release --workspace -- --nocapture
env:
RUST_BACKTRACE: full
MACOSX_DEPLOYMENT_TARGET: 10.7
MACOSX_SDK_VERSION: 10.7
if: matrix.rust == '1.48'

- run: |
# Check the prebuilt debug libraries too.
cargo check --features=all-impls,all-apis,cc
env:
RUST_BACKTRACE: full
MACOSX_DEPLOYMENT_TARGET: 10.7
MACOSX_SDK_VERSION: 10.7
if: matrix.rust != '1.48'

- run: |
Expand All @@ -561,6 +567,8 @@ jobs:
cargo check --features=fs-err,all-apis,cc
env:
RUST_BACKTRACE: full
MACOSX_DEPLOYMENT_TARGET: 10.7
MACOSX_SDK_VERSION: 10.7
if: matrix.rust == '1.48'

test_use_libc:
Expand Down
131 changes: 130 additions & 1 deletion src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,42 @@ pub(crate) fn linkat(
new_path: &CStr,
flags: AtFlags,
) -> io::Result<()> {
// macOS <= 10.9 lacks `linkat`.
#[cfg(target_os = "macos")]
unsafe {
weak! {
fn linkat(
c::c_int,
*const c::c_char,
c::c_int,
*const c::c_char,
c::c_int
) -> c::c_int
}
// If we have `linkat`, use it.
if let Some(libc_linkat) = linkat.get() {
return ret(libc_linkat(
borrowed_fd(old_dirfd),
c_str(old_path),
borrowed_fd(new_dirfd),
c_str(new_path),
flags.bits(),
));
}
// Otherwise, see if we can emulate the `AT_FDCWD` case.
if borrowed_fd(old_dirfd) != c::AT_FDCWD || borrowed_fd(new_dirfd) != c::AT_FDCWD {
return Err(io::Errno::NOSYS);
}
if flags.intersects(!AtFlags::SYMLINK_FOLLOW) {
return Err(io::Errno::INVAL);
}
if !flags.is_empty() {
return Err(io::Errno::OPNOTSUPP);
}
ret(c::link(c_str(old_path), c_str(new_path)))
}

#[cfg(not(target_os = "macos"))]
unsafe {
ret(c::linkat(
borrowed_fd(old_dirfd),
Expand All @@ -264,7 +300,38 @@ pub(crate) fn linkat(

#[cfg(not(target_os = "redox"))]
pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
unsafe { ret(c::unlinkat(borrowed_fd(dirfd), c_str(path), flags.bits())) }
// macOS <= 10.9 lacks `unlinkat`.
#[cfg(target_os = "macos")]
unsafe {
weak! {
fn unlinkat(
c::c_int,
*const c::c_char,
c::c_int
) -> c::c_int
}
// If we have `unlinkat`, use it.
if let Some(libc_unlinkat) = unlinkat.get() {
return ret(libc_unlinkat(borrowed_fd(dirfd), c_str(path), flags.bits()));
}
// Otherwise, see if we can emulate the `AT_FDCWD` case.
if borrowed_fd(dirfd) != c::AT_FDCWD {
return Err(io::Errno::NOSYS);
}
if flags.intersects(!AtFlags::REMOVEDIR) {
return Err(io::Errno::INVAL);
}
if flags.contains(AtFlags::REMOVEDIR) {
ret(c::rmdir(c_str(path)))
} else {
ret(c::unlink(c_str(path)))
}
}

#[cfg(not(target_os = "macos"))]
unsafe {
ret(c::unlinkat(borrowed_fd(dirfd), c_str(path), flags.bits()))
}
}

#[cfg(not(target_os = "redox"))]
Expand All @@ -274,6 +341,34 @@ pub(crate) fn renameat(
new_dirfd: BorrowedFd<'_>,
new_path: &CStr,
) -> io::Result<()> {
// macOS <= 10.9 lacks `renameat`.
#[cfg(target_os = "macos")]
unsafe {
weak! {
fn renameat(
c::c_int,
*const c::c_char,
c::c_int,
*const c::c_char
) -> c::c_int
}
// If we have `renameat`, use it.
if let Some(libc_renameat) = renameat.get() {
return ret(libc_renameat(
borrowed_fd(old_dirfd),
c_str(old_path),
borrowed_fd(new_dirfd),
c_str(new_path),
));
}
// Otherwise, see if we can emulate the `AT_FDCWD` case.
if borrowed_fd(old_dirfd) != c::AT_FDCWD || borrowed_fd(new_dirfd) != c::AT_FDCWD {
return Err(io::Errno::NOSYS);
}
ret(c::rename(c_str(old_path), c_str(new_path)))
}

#[cfg(not(target_os = "macos"))]
unsafe {
ret(c::renameat(
borrowed_fd(old_dirfd),
Expand Down Expand Up @@ -405,6 +500,40 @@ pub(crate) fn accessat(
access: Access,
flags: AtFlags,
) -> io::Result<()> {
// macOS <= 10.9 lacks `faccessat`.
#[cfg(target_os = "macos")]
unsafe {
weak! {
fn faccessat(
c::c_int,
*const c::c_char,
c::c_int,
c::c_int
) -> c::c_int
}
// If we have `faccessat`, use it.
if let Some(libc_faccessat) = faccessat.get() {
return ret(libc_faccessat(
borrowed_fd(dirfd),
c_str(path),
access.bits(),
flags.bits(),
));
}
// Otherwise, see if we can emulate the `AT_FDCWD` case.
if borrowed_fd(dirfd) != c::AT_FDCWD {
return Err(io::Errno::NOSYS);
}
if flags.intersects(!(AtFlags::EACCESS | AtFlags::SYMLINK_NOFOLLOW)) {
return Err(io::Errno::INVAL);
}
if !flags.is_empty() {
return Err(io::Errno::OPNOTSUPP);
}
ret(c::access(c_str(path), access.bits()))
}

#[cfg(not(target_os = "macos"))]
unsafe {
ret(c::faccessat(
borrowed_fd(dirfd),
Expand Down
54 changes: 54 additions & 0 deletions tests/fs/chmodat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#[cfg(not(target_os = "wasi"))]
#[test]
fn test_chmodat() {
use rustix::fs::{chmodat, cwd, openat, statat, AtFlags, Mode, OFlags};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::RWXU).unwrap();

let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::RWXU).unwrap();

let before = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_ne!(before.st_mode as u64 & libc::S_IRWXU as u64, 0);

chmodat(&dir, "foo", Mode::empty()).unwrap();

let after = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_eq!(after.st_mode as u64 & libc::S_IRWXU as u64, 0);

chmodat(&dir, "foo", Mode::RWXU).unwrap();

let reverted = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_ne!(reverted.st_mode as u64 & libc::S_IRWXU as u64, 0);
}

#[cfg(not(target_os = "wasi"))]
#[test]
fn test_chmodat_with() {
use rustix::fs::{chmodat_with, cwd, openat, statat, symlinkat, AtFlags, Mode, OFlags};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::RWXU).unwrap();

let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::RWXU).unwrap();
symlinkat("foo", &dir, "link").unwrap();

match chmodat_with(&dir, "link", Mode::empty(), AtFlags::SYMLINK_NOFOLLOW) {
Ok(()) => (),
Err(rustix::io::Errno::OPNOTSUPP) => return,
Err(e) => Err(e).unwrap(),
}

let before = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_ne!(before.st_mode as u64 & libc::S_IRWXU as u64, 0);

chmodat_with(&dir, "foo", Mode::empty(), AtFlags::empty()).unwrap();

let after = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_eq!(after.st_mode as u64 & libc::S_IRWXU as u64, 0);

chmodat_with(&dir, "foo", Mode::RWXU, AtFlags::empty()).unwrap();

let reverted = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_ne!(reverted.st_mode as u64 & libc::S_IRWXU as u64, 0);
}
26 changes: 26 additions & 0 deletions tests/fs/linkat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#[test]
fn test_linkat() {
use rustix::fs::{cwd, linkat, openat, readlinkat, statat, AtFlags, Mode, OFlags};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();

let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::RUSR).unwrap();

linkat(&dir, "foo", &dir, "link", AtFlags::empty()).unwrap();

readlinkat(&dir, "foo", Vec::new()).unwrap_err();
readlinkat(&dir, "link", Vec::new()).unwrap_err();

assert_eq!(
statat(&dir, "foo", AtFlags::empty()).unwrap().st_ino,
statat(&dir, "link", AtFlags::empty()).unwrap().st_ino
);

linkat(&dir, "link", &dir, "another", AtFlags::empty()).unwrap();

assert_eq!(
statat(&dir, "foo", AtFlags::empty()).unwrap().st_ino,
statat(&dir, "another", AtFlags::empty()).unwrap().st_ino
);
}
4 changes: 4 additions & 0 deletions tests/fs/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![cfg_attr(io_lifetimes_use_std, feature(io_safety))]
#![cfg_attr(core_c_str, feature(core_c_str))]

mod chmodat;
mod cwd;
mod dir;
mod fcntl;
Expand All @@ -20,6 +21,7 @@ mod file;
mod flock;
mod futimens;
mod invalid_offset;
mod linkat;
mod long_paths;
#[cfg(not(any(solarish, target_os = "haiku", target_os = "redox", target_os = "wasi")))]
mod makedev;
Expand All @@ -31,11 +33,13 @@ mod openat;
mod openat2;
#[cfg(not(target_os = "redox"))]
mod readdir;
mod readlinkat;
mod renameat;
#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
mod statfs;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod statx;
mod symlinkat;
#[cfg(not(any(solarish, target_os = "redox", target_os = "wasi")))]
mod sync;
mod utimensat;
Expand Down
13 changes: 11 additions & 2 deletions tests/fs/mkdirat.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
#[test]
fn test_mkdirat() {
use rustix::fs::{cwd, mkdirat, openat, statat, unlinkat, AtFlags, FileType, Mode, OFlags};
use rustix::fs::{
accessat, cwd, mkdirat, openat, statat, unlinkat, Access, AtFlags, FileType, Mode, OFlags,
};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();

mkdirat(&dir, "foo", Mode::RWXU).unwrap();
let stat = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
accessat(&dir, "foo", Access::READ_OK, AtFlags::empty()).unwrap();
accessat(&dir, "foo", Access::WRITE_OK, AtFlags::empty()).unwrap();
accessat(&dir, "foo", Access::EXEC_OK, AtFlags::empty()).unwrap();
accessat(&dir, "foo", Access::EXISTS, AtFlags::empty()).unwrap();
unlinkat(&dir, "foo", AtFlags::REMOVEDIR).unwrap();
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[test]
fn test_mkdirat_with_o_path() {
use rustix::fs::{cwd, mkdirat, openat, statat, unlinkat, AtFlags, FileType, Mode, OFlags};
use rustix::fs::{
accessat, cwd, mkdirat, openat, statat, unlinkat, Access, AtFlags, FileType, Mode, OFlags,
};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(
Expand All @@ -29,5 +37,6 @@ fn test_mkdirat_with_o_path() {
mkdirat(&dir, "foo", Mode::RWXU).unwrap();
let stat = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
accessat(&dir, "foo", Access::EXISTS, AtFlags::empty()).unwrap();
unlinkat(&dir, "foo", AtFlags::REMOVEDIR).unwrap();
}
5 changes: 4 additions & 1 deletion tests/fs/mknodat.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#[cfg(not(any(apple, target_os = "redox", target_os = "wasi")))]
#[test]
fn test_mknodat() {
use rustix::fs::{cwd, mknodat, openat, statat, unlinkat, AtFlags, FileType, Mode, OFlags};
use rustix::fs::{
accessat, cwd, mknodat, openat, statat, unlinkat, Access, AtFlags, FileType, Mode, OFlags,
};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
Expand All @@ -18,5 +20,6 @@ fn test_mknodat() {
mknodat(&dir, "foo", FileType::Fifo, Mode::empty(), 0).unwrap();
let stat = statat(&dir, "foo", AtFlags::empty()).unwrap();
assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Fifo);
accessat(&dir, "foo", Access::EXISTS, AtFlags::empty()).unwrap();
unlinkat(&dir, "foo", AtFlags::empty()).unwrap();
}
23 changes: 23 additions & 0 deletions tests/fs/readlinkat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[test]
fn test_readlinkat() {
use rustix::fs::{cwd, openat, readlinkat, symlinkat, Mode, OFlags};

let tmp = tempfile::tempdir().unwrap();
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();

let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::RUSR).unwrap();
symlinkat("foo", &dir, "link").unwrap();

readlinkat(&dir, "absent", Vec::new()).unwrap_err();
readlinkat(&dir, "foo", Vec::new()).unwrap_err();

let target = readlinkat(&dir, "link", Vec::new()).unwrap();
assert_eq!(target.to_string_lossy(), "foo");

symlinkat("link", &dir, "another").unwrap();

let target = readlinkat(&dir, "link", Vec::new()).unwrap();
assert_eq!(target.to_string_lossy(), "foo");
let target = readlinkat(&dir, "another", Vec::new()).unwrap();
assert_eq!(target.to_string_lossy(), "link");
}