Skip to content

Commit ad1d8a8

Browse files
committed
Auto merge of rust-lang#3707 - adwinwhite:dup, r=RalfJung
Add syscall `dup()` for unix target Add support for `dup()` and `dup2()`. Fixes rust-lang#3454
2 parents 4a7d29f + c96bec1 commit ad1d8a8

File tree

3 files changed

+72
-8
lines changed

3 files changed

+72
-8
lines changed

src/tools/miri/src/shims/unix/fd.rs

+33-8
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,32 @@ impl FdTable {
273273

274274
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
275275
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
276+
fn dup(&mut self, old_fd: i32) -> InterpResult<'tcx, i32> {
277+
let this = self.eval_context_mut();
278+
279+
let Some(dup_fd) = this.machine.fds.dup(old_fd) else {
280+
return this.fd_not_found();
281+
};
282+
Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, 0))
283+
}
284+
285+
fn dup2(&mut self, old_fd: i32, new_fd: i32) -> InterpResult<'tcx, i32> {
286+
let this = self.eval_context_mut();
287+
288+
let Some(dup_fd) = this.machine.fds.dup(old_fd) else {
289+
return this.fd_not_found();
290+
};
291+
if new_fd != old_fd {
292+
// Close new_fd if it is previously opened.
293+
// If old_fd and new_fd point to the same description, then `dup_fd` ensures we keep the underlying file description alive.
294+
if let Some(file_descriptor) = this.machine.fds.fds.insert(new_fd, dup_fd) {
295+
// Ignore close error (not interpreter's) according to dup2() doc.
296+
file_descriptor.close(this.machine.communicate())?.ok();
297+
}
298+
}
299+
Ok(new_fd)
300+
}
301+
276302
fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, i32> {
277303
let this = self.eval_context_mut();
278304

@@ -334,14 +360,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
334360

335361
let fd = this.read_scalar(fd_op)?.to_i32()?;
336362

337-
Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.remove(fd) {
338-
let result = file_descriptor.close(this.machine.communicate())?;
339-
// return `0` if close is successful
340-
let result = result.map(|()| 0i32);
341-
this.try_unwrap_io_result(result)?
342-
} else {
343-
this.fd_not_found()?
344-
}))
363+
let Some(file_descriptor) = this.machine.fds.remove(fd) else {
364+
return Ok(Scalar::from_i32(this.fd_not_found()?));
365+
};
366+
let result = file_descriptor.close(this.machine.communicate())?;
367+
// return `0` if close is successful
368+
let result = result.map(|()| 0i32);
369+
Ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
345370
}
346371

347372
/// Function used when a file descriptor does not exist. It returns `Ok(-1)`and sets

src/tools/miri/src/shims/unix/foreign_items.rs

+13
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
115115
let result = this.fcntl(args)?;
116116
this.write_scalar(Scalar::from_i32(result), dest)?;
117117
}
118+
"dup" => {
119+
let [old_fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
120+
let old_fd = this.read_scalar(old_fd)?.to_i32()?;
121+
let new_fd = this.dup(old_fd)?;
122+
this.write_scalar(Scalar::from_i32(new_fd), dest)?;
123+
}
124+
"dup2" => {
125+
let [old_fd, new_fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
126+
let old_fd = this.read_scalar(old_fd)?.to_i32()?;
127+
let new_fd = this.read_scalar(new_fd)?.to_i32()?;
128+
let result = this.dup2(old_fd, new_fd)?;
129+
this.write_scalar(Scalar::from_i32(result), dest)?;
130+
}
118131

119132
// File and file system access
120133
"open" | "open64" => {

src/tools/miri/tests/pass-dep/libc/libc-fs.rs

+26
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::path::PathBuf;
1515
mod utils;
1616

1717
fn main() {
18+
test_dup();
1819
test_dup_stdout_stderr();
1920
test_canonicalize_too_long();
2021
test_rename();
@@ -74,6 +75,31 @@ fn test_dup_stdout_stderr() {
7475
}
7576
}
7677

78+
fn test_dup() {
79+
let bytes = b"dup and dup2";
80+
let path = utils::prepare_with_content("miri_test_libc_dup.txt", bytes);
81+
82+
let mut name = path.into_os_string();
83+
name.push("\0");
84+
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
85+
unsafe {
86+
let fd = libc::open(name_ptr, libc::O_RDONLY);
87+
let mut first_buf = [0u8; 4];
88+
libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
89+
assert_eq!(&first_buf, b"dup ");
90+
91+
let new_fd = libc::dup(fd);
92+
let mut second_buf = [0u8; 4];
93+
libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
94+
assert_eq!(&second_buf, b"and ");
95+
96+
let new_fd2 = libc::dup2(fd, 8);
97+
let mut third_buf = [0u8; 4];
98+
libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
99+
assert_eq!(&third_buf, b"dup2");
100+
}
101+
}
102+
77103
fn test_canonicalize_too_long() {
78104
// Make sure we get an error for long paths.
79105
let too_long = "x/".repeat(libc::PATH_MAX.try_into().unwrap());

0 commit comments

Comments
 (0)