Skip to content

Commit d9d447d

Browse files
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>
2 parents 865c748 + 1a23312 commit d9d447d

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1212
(#[1456](https://github.com/nix-rust/nix/pull/1456))
1313
- Added `TcpUserTimeout` socket option (sockopt) on Linux and Fuchsia.
1414
(#[1457](https://github.com/nix-rust/nix/pull/1457))
15+
- Added `renameat2` for Linux
16+
(#[1458](https://github.com/nix-rust/nix/pull/1458))
1517

1618
### Changed
1719
- `ptsname_r` now returns a lossily-converted string in the event of bad UTF,

src/fcntl.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,43 @@ pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
210210
Errno::result(res).map(drop)
211211
}
212212

213+
#[cfg(all(
214+
target_os = "linux",
215+
target_env = "gnu",
216+
))]
217+
libc_bitflags! {
218+
pub struct RenameFlags: u32 {
219+
RENAME_EXCHANGE;
220+
RENAME_NOREPLACE;
221+
RENAME_WHITEOUT;
222+
}
223+
}
224+
225+
#[cfg(all(
226+
target_os = "linux",
227+
target_env = "gnu",
228+
))]
229+
pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
230+
old_dirfd: Option<RawFd>,
231+
old_path: &P1,
232+
new_dirfd: Option<RawFd>,
233+
new_path: &P2,
234+
flags: RenameFlags,
235+
) -> Result<()> {
236+
let res = old_path.with_nix_path(|old_cstr| {
237+
new_path.with_nix_path(|new_cstr| unsafe {
238+
libc::renameat2(
239+
at_rawfd(old_dirfd),
240+
old_cstr.as_ptr(),
241+
at_rawfd(new_dirfd),
242+
new_cstr.as_ptr(),
243+
flags.bits(),
244+
)
245+
})
246+
})??;
247+
Errno::result(res).map(drop)
248+
}
249+
213250
fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
214251
unsafe { v.set_len(len as usize) }
215252
v.shrink_to_fit();

test/test_fcntl.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ use nix::errno::*;
44
use nix::fcntl::{open, OFlag, readlink};
55
#[cfg(not(target_os = "redox"))]
66
use nix::fcntl::{openat, readlinkat, renameat};
7+
#[cfg(all(
8+
target_os = "linux",
9+
target_env = "gnu",
10+
any(
11+
target_arch = "x86_64",
12+
target_arch = "x32",
13+
target_arch = "powerpc",
14+
target_arch = "s390x"
15+
)
16+
))]
17+
use nix::fcntl::{RenameFlags, renameat2};
718
#[cfg(not(target_os = "redox"))]
819
use nix::sys::stat::Mode;
920
#[cfg(not(target_os = "redox"))]
@@ -59,6 +70,132 @@ fn test_renameat() {
5970
assert!(new_dir.path().join("new").exists());
6071
}
6172

73+
#[test]
74+
#[cfg(all(
75+
target_os = "linux",
76+
target_env = "gnu",
77+
any(
78+
target_arch = "x86_64",
79+
target_arch = "x32",
80+
target_arch = "powerpc",
81+
target_arch = "s390x"
82+
)
83+
))]
84+
fn test_renameat2_behaves_like_renameat_with_no_flags() {
85+
let old_dir = tempfile::tempdir().unwrap();
86+
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
87+
let old_path = old_dir.path().join("old");
88+
File::create(&old_path).unwrap();
89+
let new_dir = tempfile::tempdir().unwrap();
90+
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
91+
renameat2(
92+
Some(old_dirfd),
93+
"old",
94+
Some(new_dirfd),
95+
"new",
96+
RenameFlags::empty(),
97+
)
98+
.unwrap();
99+
assert_eq!(
100+
renameat2(
101+
Some(old_dirfd),
102+
"old",
103+
Some(new_dirfd),
104+
"new",
105+
RenameFlags::empty()
106+
)
107+
.unwrap_err(),
108+
Errno::ENOENT
109+
);
110+
close(old_dirfd).unwrap();
111+
close(new_dirfd).unwrap();
112+
assert!(new_dir.path().join("new").exists());
113+
}
114+
115+
#[test]
116+
#[cfg(all(
117+
target_os = "linux",
118+
target_env = "gnu",
119+
any(
120+
target_arch = "x86_64",
121+
target_arch = "x32",
122+
target_arch = "powerpc",
123+
target_arch = "s390x"
124+
)
125+
))]
126+
fn test_renameat2_exchange() {
127+
let old_dir = tempfile::tempdir().unwrap();
128+
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
129+
let old_path = old_dir.path().join("old");
130+
{
131+
let mut old_f = File::create(&old_path).unwrap();
132+
old_f.write(b"old").unwrap();
133+
}
134+
let new_dir = tempfile::tempdir().unwrap();
135+
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
136+
let new_path = new_dir.path().join("new");
137+
{
138+
let mut new_f = File::create(&new_path).unwrap();
139+
new_f.write(b"new").unwrap();
140+
}
141+
renameat2(
142+
Some(old_dirfd),
143+
"old",
144+
Some(new_dirfd),
145+
"new",
146+
RenameFlags::RENAME_EXCHANGE,
147+
)
148+
.unwrap();
149+
let mut buf = String::new();
150+
let mut new_f = File::open(&new_path).unwrap();
151+
new_f.read_to_string(&mut buf).unwrap();
152+
assert_eq!(buf, "old");
153+
buf = "".to_string();
154+
let mut old_f = File::open(&old_path).unwrap();
155+
old_f.read_to_string(&mut buf).unwrap();
156+
assert_eq!(buf, "new");
157+
close(old_dirfd).unwrap();
158+
close(new_dirfd).unwrap();
159+
}
160+
161+
#[test]
162+
#[cfg(all(
163+
target_os = "linux",
164+
target_env = "gnu",
165+
any(
166+
target_arch = "x86_64",
167+
target_arch = "x32",
168+
target_arch = "powerpc",
169+
target_arch = "s390x"
170+
)
171+
))]
172+
fn test_renameat2_noreplace() {
173+
let old_dir = tempfile::tempdir().unwrap();
174+
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
175+
let old_path = old_dir.path().join("old");
176+
File::create(&old_path).unwrap();
177+
let new_dir = tempfile::tempdir().unwrap();
178+
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
179+
let new_path = new_dir.path().join("new");
180+
File::create(&new_path).unwrap();
181+
assert_eq!(
182+
renameat2(
183+
Some(old_dirfd),
184+
"old",
185+
Some(new_dirfd),
186+
"new",
187+
RenameFlags::RENAME_NOREPLACE
188+
)
189+
.unwrap_err(),
190+
Errno::EEXIST
191+
);
192+
close(old_dirfd).unwrap();
193+
close(new_dirfd).unwrap();
194+
assert!(new_dir.path().join("new").exists());
195+
assert!(old_dir.path().join("old").exists());
196+
}
197+
198+
62199
#[test]
63200
#[cfg(not(target_os = "redox"))]
64201
fn test_readlink() {

0 commit comments

Comments
 (0)