Skip to content

Commit

Permalink
Adding linux specific renameat2()
Browse files Browse the repository at this point in the history
  • Loading branch information
tombardier committed Jul 8, 2021
1 parent 865c748 commit 1a23312
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1456](https://github.com/nix-rust/nix/pull/1456))
- Added `TcpUserTimeout` socket option (sockopt) on Linux and Fuchsia.
(#[1457](https://github.com/nix-rust/nix/pull/1457))
- Added `renameat2` for Linux
(#[1458](https://github.com/nix-rust/nix/pull/1458))

### Changed
- `ptsname_r` now returns a lossily-converted string in the event of bad UTF,
Expand Down
37 changes: 37 additions & 0 deletions src/fcntl.rs
Expand Up @@ -210,6 +210,43 @@ pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
Errno::result(res).map(drop)
}

#[cfg(all(
target_os = "linux",
target_env = "gnu",
))]
libc_bitflags! {
pub struct RenameFlags: u32 {
RENAME_EXCHANGE;
RENAME_NOREPLACE;
RENAME_WHITEOUT;
}
}

#[cfg(all(
target_os = "linux",
target_env = "gnu",
))]
pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
old_dirfd: Option<RawFd>,
old_path: &P1,
new_dirfd: Option<RawFd>,
new_path: &P2,
flags: RenameFlags,
) -> Result<()> {
let res = old_path.with_nix_path(|old_cstr| {
new_path.with_nix_path(|new_cstr| unsafe {
libc::renameat2(
at_rawfd(old_dirfd),
old_cstr.as_ptr(),
at_rawfd(new_dirfd),
new_cstr.as_ptr(),
flags.bits(),
)
})
})??;
Errno::result(res).map(drop)
}

fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
unsafe { v.set_len(len as usize) }
v.shrink_to_fit();
Expand Down
137 changes: 137 additions & 0 deletions test/test_fcntl.rs
Expand Up @@ -4,6 +4,17 @@ use nix::errno::*;
use nix::fcntl::{open, OFlag, readlink};
#[cfg(not(target_os = "redox"))]
use nix::fcntl::{openat, readlinkat, renameat};
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
use nix::fcntl::{RenameFlags, renameat2};
#[cfg(not(target_os = "redox"))]
use nix::sys::stat::Mode;
#[cfg(not(target_os = "redox"))]
Expand Down Expand Up @@ -59,6 +70,132 @@ fn test_renameat() {
assert!(new_dir.path().join("new").exists());
}

#[test]
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
fn test_renameat2_behaves_like_renameat_with_no_flags() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
File::create(&old_path).unwrap();
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::empty(),
)
.unwrap();
assert_eq!(
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::empty()
)
.unwrap_err(),
Errno::ENOENT
);
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").exists());
}

#[test]
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
fn test_renameat2_exchange() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
{
let mut old_f = File::create(&old_path).unwrap();
old_f.write(b"old").unwrap();
}
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let new_path = new_dir.path().join("new");
{
let mut new_f = File::create(&new_path).unwrap();
new_f.write(b"new").unwrap();
}
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::RENAME_EXCHANGE,
)
.unwrap();
let mut buf = String::new();
let mut new_f = File::open(&new_path).unwrap();
new_f.read_to_string(&mut buf).unwrap();
assert_eq!(buf, "old");
buf = "".to_string();
let mut old_f = File::open(&old_path).unwrap();
old_f.read_to_string(&mut buf).unwrap();
assert_eq!(buf, "new");
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
}

#[test]
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
fn test_renameat2_noreplace() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
File::create(&old_path).unwrap();
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let new_path = new_dir.path().join("new");
File::create(&new_path).unwrap();
assert_eq!(
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::RENAME_NOREPLACE
)
.unwrap_err(),
Errno::EEXIST
);
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").exists());
assert!(old_dir.path().join("old").exists());
}


#[test]
#[cfg(not(target_os = "redox"))]
fn test_readlink() {
Expand Down

0 comments on commit 1a23312

Please sign in to comment.