Skip to content

Commit 1a23312

Browse files
committed
Adding linux specific renameat2()
1 parent 865c748 commit 1a23312

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)