summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Boland <tom@t0mb.net>2021-07-08 22:08:48 +0100
committerTom Boland <tom@t0mb.net>2021-07-08 22:08:48 +0100
commit1a23312c77b74e0e896700733a189f8ecfdcbc1f (patch)
tree2a8b1952cf03ca0dec9235d6640dfb29875ad3e4
parent865c7488b8d9d0909b03d8312073976ee6514757 (diff)
downloadnix-1a23312c77b74e0e896700733a189f8ecfdcbc1f.zip
Adding linux specific renameat2()
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/fcntl.rs37
-rw-r--r--test/test_fcntl.rs137
3 files changed, 176 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fe700d8..43d1fdea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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,
diff --git a/src/fcntl.rs b/src/fcntl.rs
index ec6db00a..f8f1372a 100644
--- a/src/fcntl.rs
+++ b/src/fcntl.rs
@@ -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();
diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs
index 18b73481..ae6756ec 100644
--- a/test/test_fcntl.rs
+++ b/test/test_fcntl.rs
@@ -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"))]
@@ -60,6 +71,132 @@ fn test_renameat() {
}
#[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() {
let tempdir = tempfile::tempdir().unwrap();