summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-07-08 22:11:11 +0000
committerGitHub <noreply@github.com>2021-07-08 22:11:11 +0000
commitd9d447db42a16d7f946177e093b5990fba52561e (patch)
tree2a8b1952cf03ca0dec9235d6640dfb29875ad3e4
parent865c7488b8d9d0909b03d8312073976ee6514757 (diff)
parent1a23312c77b74e0e896700733a189f8ecfdcbc1f (diff)
downloadnix-d9d447db42a16d7f946177e093b5990fba52561e.zip
Merge #1458
1458: Added support for renameat2 on linux r=asomers a=tomboland Hi, please find my PR for adding the linux-specific `renameat2` syscall. It's largely similar to `renameat`, with an additional flags parameter: The flags are: * RENAME_REPLACE - performs an atomic swap. * RENAME_NOREPLACE - returns EEXIST if the target already exists. * RENAME_WHITEOUT - specific to overly/union filesystems, and I haven't added a test-case for this one. PLEASE NOTE: It looks like my formatter has made numerous changes. If you have a preferred formatting config then please let me know, and I can push up changes consistent with the accepted style. I'm not all that experienced with rust, and this is my first time looking at the nix project, so I'm more than happy to receive guidance on improving my submission. Cheers! Co-authored-by: Tom Boland <tom@t0mb.net>
-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();