Skip to content

Commit dc0a54c

Browse files
committed
Auto merge of #1511 - samrat:more-fd-trait-ops, r=RalfJung
Implement dup and close for stdin/stdout/stderr Implements some of the operations for stdin/out/err, towards #1499
2 parents 604a674 + 563fb8e commit dc0a54c

File tree

5 files changed

+109
-42
lines changed

5 files changed

+109
-42
lines changed

src/shims/posix/fs.rs

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ trait FileDescriptor : std::fmt::Debug {
2828
fn read<'tcx>(&mut self, communicate_allowed: bool, bytes: &mut [u8]) -> InterpResult<'tcx, io::Result<usize>>;
2929
fn write<'tcx>(&mut self, communicate_allowed: bool, bytes: &[u8]) -> InterpResult<'tcx, io::Result<usize>>;
3030
fn seek<'tcx>(&mut self, communicate_allowed: bool, offset: SeekFrom) -> InterpResult<'tcx, io::Result<u64>>;
31+
fn close<'tcx>(self: Box<Self>, _communicate_allowed: bool) -> InterpResult<'tcx, io::Result<i32>>;
32+
33+
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
3134
}
3235

3336
impl FileDescriptor for FileHandle {
@@ -49,6 +52,34 @@ impl FileDescriptor for FileHandle {
4952
assert!(communicate_allowed, "isolation should have prevented even opening a file");
5053
Ok(self.file.seek(offset))
5154
}
55+
56+
fn close<'tcx>(self: Box<Self>, communicate_allowed: bool) -> InterpResult<'tcx, io::Result<i32>> {
57+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
58+
// We sync the file if it was opened in a mode different than read-only.
59+
if self.writable {
60+
// `File::sync_all` does the checks that are done when closing a file. We do this to
61+
// to handle possible errors correctly.
62+
let result = self.file.sync_all().map(|_| 0i32);
63+
// Now we actually close the file.
64+
drop(self);
65+
// And return the result.
66+
Ok(result)
67+
} else {
68+
// We drop the file, this closes it but ignores any errors
69+
// produced when closing it. This is done because
70+
// `File::sync_all` cannot be done over files like
71+
// `/dev/urandom` which are read-only. Check
72+
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
73+
// for a deeper discussion.
74+
drop(self);
75+
Ok(Ok(0))
76+
}
77+
}
78+
79+
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
80+
let duplicated = self.file.try_clone()?;
81+
Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
82+
}
5283
}
5384

5485
impl FileDescriptor for io::Stdin {
@@ -71,6 +102,14 @@ impl FileDescriptor for io::Stdin {
71102
fn seek<'tcx>(&mut self, _communicate_allowed: bool, _offset: SeekFrom) -> InterpResult<'tcx, io::Result<u64>> {
72103
throw_unsup_format!("cannot seek on stdin");
73104
}
105+
106+
fn close<'tcx>(self: Box<Self>, _communicate_allowed: bool) -> InterpResult<'tcx, io::Result<i32>> {
107+
throw_unsup_format!("stdin cannot be closed");
108+
}
109+
110+
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
111+
Ok(Box::new(io::stdin()))
112+
}
74113
}
75114

76115
impl FileDescriptor for io::Stdout {
@@ -98,6 +137,14 @@ impl FileDescriptor for io::Stdout {
98137
fn seek<'tcx>(&mut self, _communicate_allowed: bool, _offset: SeekFrom) -> InterpResult<'tcx, io::Result<u64>> {
99138
throw_unsup_format!("cannot seek on stdout");
100139
}
140+
141+
fn close<'tcx>(self: Box<Self>, _communicate_allowed: bool) -> InterpResult<'tcx, io::Result<i32>> {
142+
throw_unsup_format!("stdout cannot be closed");
143+
}
144+
145+
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
146+
Ok(Box::new(io::stdout()))
147+
}
101148
}
102149

103150
impl FileDescriptor for io::Stderr {
@@ -118,6 +165,14 @@ impl FileDescriptor for io::Stderr {
118165
fn seek<'tcx>(&mut self, _communicate_allowed: bool, _offset: SeekFrom) -> InterpResult<'tcx, io::Result<u64>> {
119166
throw_unsup_format!("cannot seek on stderr");
120167
}
168+
169+
fn close<'tcx>(self: Box<Self>, _communicate_allowed: bool) -> InterpResult<'tcx, io::Result<i32>> {
170+
throw_unsup_format!("stderr cannot be closed");
171+
}
172+
173+
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
174+
Ok(Box::new(io::stderr()))
175+
}
121176
}
122177

123178
#[derive(Debug)]
@@ -137,18 +192,12 @@ impl<'tcx> Default for FileHandler {
137192
}
138193
}
139194

140-
141-
// fd numbers 0, 1, and 2 are reserved for stdin, stdout, and stderr
142-
const MIN_NORMAL_FILE_FD: i32 = 3;
143-
144195
impl<'tcx> FileHandler {
145-
fn insert_fd(&mut self, file_handle: FileHandle) -> i32 {
196+
fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
146197
self.insert_fd_with_min_fd(file_handle, 0)
147198
}
148199

149-
fn insert_fd_with_min_fd(&mut self, file_handle: FileHandle, min_fd: i32) -> i32 {
150-
let min_fd = std::cmp::max(min_fd, MIN_NORMAL_FILE_FD);
151-
200+
fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
152201
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
153202
// between used FDs, the find_map combinator will return it. If the first such unused FD
154203
// is after all other used FDs, the find_map combinator will return None, and we will use
@@ -173,7 +222,7 @@ impl<'tcx> FileHandler {
173222
self.handles.last_key_value().map(|(fd, _)| fd.checked_add(1).unwrap()).unwrap_or(min_fd)
174223
});
175224

176-
self.handles.insert(new_fd, Box::new(file_handle)).unwrap_none();
225+
self.handles.insert(new_fd, file_handle).unwrap_none();
177226
new_fd
178227
}
179228
}
@@ -449,7 +498,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
449498

450499
let fd = options.open(&path).map(|file| {
451500
let fh = &mut this.machine.file_handler;
452-
fh.insert_fd(FileHandle { file, writable })
501+
fh.insert_fd(Box::new(FileHandle { file, writable }))
453502
});
454503

455504
this.try_unwrap_io_result(fd)
@@ -489,22 +538,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
489538
// thus they can share the same implementation here.
490539
let &[_, _, start] = check_arg_count(args)?;
491540
let start = this.read_scalar(start)?.to_i32()?;
492-
if fd < MIN_NORMAL_FILE_FD {
493-
throw_unsup_format!("duplicating file descriptors for stdin, stdout, or stderr is not supported")
494-
}
541+
495542
let fh = &mut this.machine.file_handler;
496-
let (file_result, writable) = match fh.handles.get(&fd) {
543+
544+
match fh.handles.get_mut(&fd) {
497545
Some(file_descriptor) => {
498-
// FIXME: Support "dup" for all FDs(stdin, etc)
499-
let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
500-
(file.try_clone(), *writable)
546+
let dup_result = file_descriptor.dup();
547+
match dup_result {
548+
Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
549+
Err(e) => {
550+
this.set_last_error_from_io_error(e)?;
551+
Ok(-1)
552+
}
553+
}
501554
},
502555
None => return this.handle_not_found(),
503-
};
504-
let fd_result = file_result.map(|duplicated| {
505-
fh.insert_fd_with_min_fd(FileHandle { file: duplicated, writable }, start)
506-
});
507-
this.try_unwrap_io_result(fd_result)
556+
}
508557
} else if this.tcx.sess.target.target.target_os == "macos"
509558
&& cmd == this.eval_libc_i32("F_FULLFSYNC")?
510559
{
@@ -530,26 +579,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
530579
let fd = this.read_scalar(fd_op)?.to_i32()?;
531580

532581
if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
533-
// FIXME: Support `close` for all FDs(stdin, etc)
534-
let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
535-
// We sync the file if it was opened in a mode different than read-only.
536-
if *writable {
537-
// `File::sync_all` does the checks that are done when closing a file. We do this to
538-
// to handle possible errors correctly.
539-
let result = this.try_unwrap_io_result(file.sync_all().map(|_| 0i32));
540-
// Now we actually close the file.
541-
drop(file);
542-
// And return the result.
543-
result
544-
} else {
545-
// We drop the file, this closes it but ignores any errors produced when closing
546-
// it. This is done because `File::sync_all` cannot be done over files like
547-
// `/dev/urandom` which are read-only. Check
548-
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
549-
// discussion.
550-
drop(file);
551-
Ok(0)
552-
}
582+
let result = file_descriptor.close(this.machine.communicate)?;
583+
this.try_unwrap_io_result(result)
553584
} else {
554585
this.handle_not_found()
555586
}

tests/compile-fail/fs/close_stdout.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// ignore-windows: No libc on Windows
2+
// compile-flags: -Zmiri-disable-isolation
3+
4+
// FIXME: standard handles cannot be closed (https://github.com/rust-lang/rust/issues/40032)
5+
6+
#![feature(rustc_private)]
7+
8+
extern crate libc;
9+
10+
fn main() {
11+
unsafe {
12+
libc::close(1); //~ ERROR stdout cannot be closed
13+
}
14+
}

tests/run-pass/fs_libc.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// ignore-windows
2+
// compile-flags: -Zmiri-disable-isolation
3+
4+
#![feature(rustc_private)]
5+
6+
extern crate libc;
7+
8+
fn main() {
9+
dup_stdout_stderr_test();
10+
}
11+
12+
fn dup_stdout_stderr_test() {
13+
let bytes = b"hello dup fd\n";
14+
unsafe {
15+
let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
16+
let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
17+
libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
18+
libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
19+
}
20+
}

tests/run-pass/fs_libc.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello dup fd

tests/run-pass/fs_libc.stdout

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello dup fd

0 commit comments

Comments
 (0)