Skip to content

Commit 69d050f

Browse files
committed
Auto merge of #1495 - samrat:fd-trait, r=oli-obk
Add FileDescriptor trait to abstract fn's on File's and Stdin,Stdout,Stderr Related issue: #1486 Instead of mapping FDs to `FileHandle`, map them to a `FileDescriptor` trait object. The goal is to eventually have both `FileHandle` as well as `Stdin`, `Stdout` and `Stderr` implement this trait so that syscalls involving FDs can handle both `File`s as well as the standard IO streams. This PR adds the `FileDescriptor` trait and an `impl` for `FileHandle`. I'll open a separate PR for implementing the trait for the standard IO streams.
2 parents 2d2820d + 79e066f commit 69d050f

File tree

2 files changed

+129
-61
lines changed

2 files changed

+129
-61
lines changed

src/machine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ pub struct Evaluator<'mir, 'tcx> {
250250
/// Whether to enforce the validity invariant.
251251
pub(crate) validate: bool,
252252

253-
pub(crate) file_handler: shims::posix::FileHandler,
253+
pub(crate) file_handler: shims::posix::FileHandler<'tcx>,
254254
pub(crate) dir_handler: shims::posix::DirHandler,
255255

256256
/// The temporary used for storing the argument of

src/shims/posix/fs.rs

Lines changed: 128 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::BTreeMap;
22
use std::convert::{TryFrom, TryInto};
33
use std::fs::{read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir};
4-
use std::io::{Read, Seek, SeekFrom, Write};
4+
use std::io::{self, Read, Seek, SeekFrom, Write};
55
use std::path::Path;
66
use std::time::SystemTime;
77

@@ -22,15 +22,42 @@ struct FileHandle {
2222
writable: bool,
2323
}
2424

25+
trait FileDescriptor<'tcx> : std::fmt::Debug {
26+
fn as_file_handle(&self) -> InterpResult<'tcx, &FileHandle>;
27+
28+
fn read(&mut self, bytes: &mut [u8]) -> InterpResult<'tcx, io::Result<usize>>;
29+
fn write(&mut self, bytes: &[u8]) -> InterpResult<'tcx, io::Result<usize>>;
30+
fn seek(&mut self, offset: SeekFrom) -> InterpResult<'tcx, io::Result<u64>>;
31+
}
32+
33+
impl<'tcx> FileDescriptor<'tcx> for FileHandle {
34+
fn as_file_handle(&self) -> InterpResult<'tcx, &FileHandle> {
35+
Ok(&self)
36+
}
37+
38+
fn read(&mut self, bytes: &mut [u8]) -> InterpResult<'tcx, io::Result<usize>> {
39+
Ok(self.file.read(bytes))
40+
}
41+
42+
fn write(&mut self, bytes: &[u8]) -> InterpResult<'tcx, io::Result<usize>> {
43+
Ok(self.file.write(bytes))
44+
}
45+
46+
fn seek(&mut self, offset: SeekFrom) -> InterpResult<'tcx, io::Result<u64>> {
47+
Ok(self.file.seek(offset))
48+
}
49+
}
50+
2551
#[derive(Debug, Default)]
26-
pub struct FileHandler {
27-
handles: BTreeMap<i32, FileHandle>,
52+
pub struct FileHandler<'tcx> {
53+
handles: BTreeMap<i32, Box<dyn FileDescriptor<'tcx>>>,
2854
}
2955

56+
3057
// fd numbers 0, 1, and 2 are reserved for stdin, stdout, and stderr
3158
const MIN_NORMAL_FILE_FD: i32 = 3;
3259

33-
impl FileHandler {
60+
impl<'tcx> FileHandler<'tcx> {
3461
fn insert_fd(&mut self, file_handle: FileHandle) -> i32 {
3562
self.insert_fd_with_min_fd(file_handle, 0)
3663
}
@@ -62,7 +89,7 @@ impl FileHandler {
6289
self.handles.last_key_value().map(|(fd, _)| fd.checked_add(1).unwrap()).unwrap_or(min_fd)
6390
});
6491

65-
self.handles.insert(new_fd, file_handle).unwrap_none();
92+
self.handles.insert(new_fd, Box::new(file_handle)).unwrap_none();
6693
new_fd
6794
}
6895
}
@@ -383,7 +410,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
383410
}
384411
let fh = &mut this.machine.file_handler;
385412
let (file_result, writable) = match fh.handles.get(&fd) {
386-
Some(FileHandle { file, writable }) => (file.try_clone(), *writable),
413+
Some(file_descriptor) => match file_descriptor.as_file_handle() {
414+
Ok(FileHandle { file, writable }) => (file.try_clone(), *writable),
415+
Err(_) => return this.handle_not_found(),
416+
},
387417
None => return this.handle_not_found(),
388418
};
389419
let fd_result = file_result.map(|duplicated| {
@@ -394,9 +424,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
394424
&& cmd == this.eval_libc_i32("F_FULLFSYNC")?
395425
{
396426
let &[_, _] = check_arg_count(args)?;
397-
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
398-
let io_result = maybe_sync_file(file, *writable, File::sync_all);
399-
this.try_unwrap_io_result(io_result)
427+
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
428+
match file_descriptor.as_file_handle() {
429+
Ok(FileHandle { file, writable }) => {
430+
let io_result = maybe_sync_file(&file, *writable, File::sync_all);
431+
this.try_unwrap_io_result(io_result)
432+
},
433+
Err(_) => this.handle_not_found(),
434+
}
400435
} else {
401436
this.handle_not_found()
402437
}
@@ -412,24 +447,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
412447

413448
let fd = this.read_scalar(fd_op)?.to_i32()?;
414449

415-
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.remove(&fd) {
416-
// We sync the file if it was opened in a mode different than read-only.
417-
if writable {
418-
// `File::sync_all` does the checks that are done when closing a file. We do this to
419-
// to handle possible errors correctly.
420-
let result = this.try_unwrap_io_result(file.sync_all().map(|_| 0i32));
421-
// Now we actually close the file.
422-
drop(file);
423-
// And return the result.
424-
result
425-
} else {
426-
// We drop the file, this closes it but ignores any errors produced when closing
427-
// it. This is done because `File::sync_all` cannot be done over files like
428-
// `/dev/urandom` which are read-only. Check
429-
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
430-
// discussion.
431-
drop(file);
432-
Ok(0)
450+
if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
451+
match file_descriptor.as_file_handle() {
452+
Ok(FileHandle { file, writable }) => {
453+
// We sync the file if it was opened in a mode different than read-only.
454+
if *writable {
455+
// `File::sync_all` does the checks that are done when closing a file. We do this to
456+
// to handle possible errors correctly.
457+
let result = this.try_unwrap_io_result(file.sync_all().map(|_| 0i32));
458+
// Now we actually close the file.
459+
drop(file);
460+
// And return the result.
461+
result
462+
} else {
463+
// We drop the file, this closes it but ignores any errors produced when closing
464+
// it. This is done because `File::sync_all` cannot be done over files like
465+
// `/dev/urandom` which are read-only. Check
466+
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
467+
// discussion.
468+
drop(file);
469+
Ok(0)
470+
}
471+
},
472+
Err(_) => this.handle_not_found()
433473
}
434474
} else {
435475
this.handle_not_found()
@@ -460,15 +500,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
460500
// host's and target's `isize`. This saves us from having to handle overflows later.
461501
let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
462502

463-
if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
464-
trace!("read: FD mapped to {:?}", file);
503+
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
504+
trace!("read: FD mapped to {:?}", file_descriptor);
465505
// We want to read at most `count` bytes. We are sure that `count` is not negative
466506
// because it was a target's `usize`. Also we are sure that its smaller than
467507
// `usize::MAX` because it is a host's `isize`.
468508
let mut bytes = vec![0; count as usize];
469-
let result = file
470-
.read(&mut bytes)
471-
// `File::read` never returns a value larger than `count`, so this cannot fail.
509+
// `File::read` never returns a value larger than `count`,
510+
// so this cannot fail.
511+
let result = file_descriptor
512+
.read(&mut bytes)?
472513
.map(|c| i64::try_from(c).unwrap());
473514

474515
match result {
@@ -510,9 +551,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
510551
// host's and target's `isize`. This saves us from having to handle overflows later.
511552
let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
512553

513-
if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
554+
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
514555
let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
515-
let result = file.write(&bytes).map(|c| i64::try_from(c).unwrap());
556+
let result = file_descriptor
557+
.write(&bytes)?
558+
.map(|c| i64::try_from(c).unwrap());
516559
this.try_unwrap_io_result(result)
517560
} else {
518561
this.handle_not_found()
@@ -545,8 +588,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
545588
return Ok(-1);
546589
};
547590

548-
if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
549-
let result = file.seek(seek_from).map(|offset| i64::try_from(offset).unwrap());
591+
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
592+
let result = file_descriptor
593+
.seek(seek_from)?
594+
.map(|offset| i64::try_from(offset).unwrap());
550595
this.try_unwrap_io_result(result)
551596
} else {
552597
this.handle_not_found()
@@ -1103,21 +1148,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
11031148

11041149
let fd = this.read_scalar(fd_op)?.to_i32()?;
11051150
let length = this.read_scalar(length_op)?.to_i64()?;
1106-
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get_mut(&fd) {
1107-
if *writable {
1108-
if let Ok(length) = length.try_into() {
1109-
let result = file.set_len(length);
1110-
this.try_unwrap_io_result(result.map(|_| 0i32))
1111-
} else {
1112-
let einval = this.eval_libc("EINVAL")?;
1113-
this.set_last_error(einval)?;
1114-
Ok(-1)
1151+
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1152+
match file_descriptor.as_file_handle() {
1153+
Ok(FileHandle { file, writable }) => {
1154+
if *writable {
1155+
if let Ok(length) = length.try_into() {
1156+
let result = file.set_len(length);
1157+
this.try_unwrap_io_result(result.map(|_| 0i32))
1158+
} else {
1159+
let einval = this.eval_libc("EINVAL")?;
1160+
this.set_last_error(einval)?;
1161+
Ok(-1)
1162+
}
1163+
} else {
1164+
// The file is not writable
1165+
let einval = this.eval_libc("EINVAL")?;
1166+
this.set_last_error(einval)?;
1167+
Ok(-1)
1168+
}
11151169
}
1116-
} else {
1117-
// The file is not writable
1118-
let einval = this.eval_libc("EINVAL")?;
1119-
this.set_last_error(einval)?;
1120-
Ok(-1)
1170+
Err(_) => this.handle_not_found()
11211171
}
11221172
} else {
11231173
this.handle_not_found()
@@ -1135,9 +1185,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
11351185
this.check_no_isolation("fsync")?;
11361186

11371187
let fd = this.read_scalar(fd_op)?.to_i32()?;
1138-
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
1139-
let io_result = maybe_sync_file(file, *writable, File::sync_all);
1140-
this.try_unwrap_io_result(io_result)
1188+
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1189+
match file_descriptor.as_file_handle() {
1190+
Ok(FileHandle { file, writable }) => {
1191+
let io_result = maybe_sync_file(&file, *writable, File::sync_all);
1192+
this.try_unwrap_io_result(io_result)
1193+
}
1194+
Err(_) => this.handle_not_found()
1195+
}
11411196
} else {
11421197
this.handle_not_found()
11431198
}
@@ -1149,9 +1204,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
11491204
this.check_no_isolation("fdatasync")?;
11501205

11511206
let fd = this.read_scalar(fd_op)?.to_i32()?;
1152-
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
1153-
let io_result = maybe_sync_file(file, *writable, File::sync_data);
1154-
this.try_unwrap_io_result(io_result)
1207+
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1208+
match file_descriptor.as_file_handle() {
1209+
Ok(FileHandle { file, writable }) => {
1210+
let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1211+
this.try_unwrap_io_result(io_result)
1212+
}
1213+
Err(_) => this.handle_not_found()
1214+
}
11551215
} else {
11561216
this.handle_not_found()
11571217
}
@@ -1187,9 +1247,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
11871247
return Ok(-1);
11881248
}
11891249

1190-
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
1191-
let io_result = maybe_sync_file(file, *writable, File::sync_data);
1192-
this.try_unwrap_io_result(io_result)
1250+
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1251+
match file_descriptor.as_file_handle() {
1252+
Ok(FileHandle { file, writable }) => {
1253+
let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1254+
this.try_unwrap_io_result(io_result)
1255+
},
1256+
Err(_) => this.handle_not_found()
1257+
}
11931258
} else {
11941259
this.handle_not_found()
11951260
}
@@ -1239,7 +1304,10 @@ impl FileMetadata {
12391304
) -> InterpResult<'tcx, Option<FileMetadata>> {
12401305
let option = ecx.machine.file_handler.handles.get(&fd);
12411306
let file = match option {
1242-
Some(FileHandle { file, writable: _ }) => file,
1307+
Some(file_descriptor) => match file_descriptor.as_file_handle() {
1308+
Ok(FileHandle { file, writable: _ }) => file,
1309+
Err(_) => return ecx.handle_not_found().map(|_: i32| None),
1310+
},
12431311
None => return ecx.handle_not_found().map(|_: i32| None),
12441312
};
12451313
let metadata = file.metadata();

0 commit comments

Comments
 (0)