Skip to content

Commit 209ce59

Browse files
committed
Implement trivial file operations - opening and closing handles. Just enough to get file metadata.
1 parent 92e6f08 commit 209ce59

File tree

18 files changed

+838
-160
lines changed

18 files changed

+838
-160
lines changed

Diff for: src/tools/miri/Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ name = "miri"
538538
version = "0.1.0"
539539
dependencies = [
540540
"aes",
541+
"bitflags",
541542
"chrono",
542543
"chrono-tz",
543544
"colored",

Diff for: src/tools/miri/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ measureme = "12"
2626
chrono = { version = "0.4.38", default-features = false }
2727
chrono-tz = "0.10"
2828
directories = "6"
29+
bitflags = "2.6"
2930

3031
# Copied from `compiler/rustc/Cargo.toml`.
3132
# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't

Diff for: src/tools/miri/src/shims/files.rs

+105-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::any::Any;
22
use std::collections::BTreeMap;
3-
use std::io::{IsTerminal, SeekFrom, Write};
3+
use std::fs::{File, Metadata};
4+
use std::io::{IsTerminal, Seek, SeekFrom, Write};
45
use std::marker::CoercePointee;
56
use std::ops::Deref;
67
use std::rc::{Rc, Weak};
@@ -192,7 +193,7 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
192193
false
193194
}
194195

195-
fn as_unix(&self) -> &dyn UnixFileDescription {
196+
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
196197
panic!("Not a unix file descriptor: {}", self.name());
197198
}
198199
}
@@ -278,6 +279,97 @@ impl FileDescription for io::Stderr {
278279
}
279280
}
280281

282+
#[derive(Debug)]
283+
pub struct FileHandle {
284+
pub(crate) file: File,
285+
pub(crate) writable: bool,
286+
}
287+
288+
impl FileDescription for FileHandle {
289+
fn name(&self) -> &'static str {
290+
"file"
291+
}
292+
293+
fn read<'tcx>(
294+
self: FileDescriptionRef<Self>,
295+
communicate_allowed: bool,
296+
ptr: Pointer,
297+
len: usize,
298+
ecx: &mut MiriInterpCx<'tcx>,
299+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
300+
) -> InterpResult<'tcx> {
301+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
302+
303+
let result = ecx.read_from_host(&self.file, len, ptr)?;
304+
finish.call(ecx, result)
305+
}
306+
307+
fn write<'tcx>(
308+
self: FileDescriptionRef<Self>,
309+
communicate_allowed: bool,
310+
ptr: Pointer,
311+
len: usize,
312+
ecx: &mut MiriInterpCx<'tcx>,
313+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
314+
) -> InterpResult<'tcx> {
315+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
316+
317+
let result = ecx.write_to_host(&self.file, len, ptr)?;
318+
finish.call(ecx, result)
319+
}
320+
321+
fn seek<'tcx>(
322+
&self,
323+
communicate_allowed: bool,
324+
offset: SeekFrom,
325+
) -> InterpResult<'tcx, io::Result<u64>> {
326+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
327+
interp_ok((&mut &self.file).seek(offset))
328+
}
329+
330+
fn close<'tcx>(
331+
self,
332+
communicate_allowed: bool,
333+
_ecx: &mut MiriInterpCx<'tcx>,
334+
) -> InterpResult<'tcx, io::Result<()>> {
335+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
336+
// We sync the file if it was opened in a mode different than read-only.
337+
if self.writable {
338+
// `File::sync_all` does the checks that are done when closing a file. We do this to
339+
// to handle possible errors correctly.
340+
let result = self.file.sync_all();
341+
// Now we actually close the file and return the result.
342+
drop(self.file);
343+
interp_ok(result)
344+
} else {
345+
// We drop the file, this closes it but ignores any errors
346+
// produced when closing it. This is done because
347+
// `File::sync_all` cannot be done over files like
348+
// `/dev/urandom` which are read-only. Check
349+
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
350+
// for a deeper discussion.
351+
drop(self.file);
352+
interp_ok(Ok(()))
353+
}
354+
}
355+
356+
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
357+
interp_ok(self.file.metadata())
358+
}
359+
360+
fn is_tty(&self, communicate_allowed: bool) -> bool {
361+
communicate_allowed && self.file.is_terminal()
362+
}
363+
364+
fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
365+
assert!(
366+
ecx.target_os_is_unix(),
367+
"unix file operations are only available for unix targets"
368+
);
369+
self
370+
}
371+
}
372+
281373
/// Like /dev/null
282374
#[derive(Debug)]
283375
pub struct NullOutput;
@@ -300,10 +392,13 @@ impl FileDescription for NullOutput {
300392
}
301393
}
302394

395+
/// Internal type of a file-descriptor - this is what [`FdTable`] expects
396+
pub type FdNum = i32;
397+
303398
/// The file descriptor table
304399
#[derive(Debug)]
305400
pub struct FdTable {
306-
pub fds: BTreeMap<i32, DynFileDescriptionRef>,
401+
pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
307402
/// Unique identifier for file description, used to differentiate between various file description.
308403
next_file_description_id: FdId,
309404
}
@@ -339,21 +434,21 @@ impl FdTable {
339434
}
340435

341436
/// Insert a new file description to the FdTable.
342-
pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
437+
pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
343438
let fd_ref = self.new_ref(fd);
344439
self.insert(fd_ref)
345440
}
346441

347-
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
442+
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
348443
self.insert_with_min_num(fd_ref, 0)
349444
}
350445

351446
/// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
352447
pub fn insert_with_min_num(
353448
&mut self,
354449
file_handle: DynFileDescriptionRef,
355-
min_fd_num: i32,
356-
) -> i32 {
450+
min_fd_num: FdNum,
451+
) -> FdNum {
357452
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
358453
// between used FDs, the find_map combinator will return it. If the first such unused FD
359454
// is after all other used FDs, the find_map combinator will return None, and we will use
@@ -379,16 +474,16 @@ impl FdTable {
379474
new_fd_num
380475
}
381476

382-
pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
477+
pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
383478
let fd = self.fds.get(&fd_num)?;
384479
Some(fd.clone())
385480
}
386481

387-
pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
482+
pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
388483
self.fds.remove(&fd_num)
389484
}
390485

391-
pub fn is_fd_num(&self, fd_num: i32) -> bool {
486+
pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
392487
self.fds.contains_key(&fd_num)
393488
}
394489
}

Diff for: src/tools/miri/src/shims/time.rs

+26-10
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
219219

220220
let filetime = this.deref_pointer_as(LPFILETIME_op, this.windows_ty_layout("FILETIME"))?;
221221

222-
let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
223-
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
224-
let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
225-
let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
226-
let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
227-
228-
let duration = system_time_to_duration(&SystemTime::now())?
229-
+ Duration::from_secs(SECONDS_TO_UNIX_EPOCH);
230-
let duration_ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
231-
.map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
222+
let duration = this.system_time_since_windows_epoch(&SystemTime::now())?;
223+
let duration_ticks = this.windows_ticks_for(duration)?;
232224

233225
let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap();
234226
let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap();
@@ -281,6 +273,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
281273
interp_ok(Scalar::from_i32(-1)) // Return non-zero on success
282274
}
283275

276+
#[allow(non_snake_case, clippy::arithmetic_side_effects)]
277+
fn system_time_since_windows_epoch(&self, time: &SystemTime) -> InterpResult<'tcx, Duration> {
278+
let this = self.eval_context_ref();
279+
280+
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
281+
let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
282+
let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
283+
284+
interp_ok(system_time_to_duration(time)? + Duration::from_secs(SECONDS_TO_UNIX_EPOCH))
285+
}
286+
287+
#[allow(non_snake_case, clippy::arithmetic_side_effects)]
288+
fn windows_ticks_for(&self, duration: Duration) -> InterpResult<'tcx, u64> {
289+
let this = self.eval_context_ref();
290+
291+
let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
292+
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
293+
let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
294+
295+
let ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
296+
.map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
297+
interp_ok(ticks)
298+
}
299+
284300
fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar> {
285301
let this = self.eval_context_ref();
286302

Diff for: src/tools/miri/src/shims/unix/fd.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
121121
throw_unsup_format!("unsupported flags {:#x}", op);
122122
};
123123

124-
let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?;
124+
let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
125125
// return `0` if flock is successful
126126
let result = result.map(|()| 0i32);
127127
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
@@ -273,7 +273,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
273273
let Ok(offset) = u64::try_from(offset) else {
274274
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
275275
};
276-
fd.as_unix().pread(communicate, offset, buf, count, this, finish)?
276+
fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
277277
}
278278
};
279279
interp_ok(())
@@ -333,7 +333,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
333333
let Ok(offset) = u64::try_from(offset) else {
334334
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
335335
};
336-
fd.as_unix().pwrite(communicate, buf, count, offset, this, finish)?
336+
fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
337337
}
338338
};
339339
interp_ok(())

Diff for: src/tools/miri/src/shims/unix/fs.rs

+3-91
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
33
use std::borrow::Cow;
44
use std::fs::{
5-
DirBuilder, File, FileType, Metadata, OpenOptions, ReadDir, read_dir, remove_dir, remove_file,
6-
rename,
5+
DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename,
76
};
8-
use std::io::{self, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
7+
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
98
use std::path::{Path, PathBuf};
109
use std::time::SystemTime;
1110

@@ -14,98 +13,11 @@ use rustc_data_structures::fx::FxHashMap;
1413

1514
use self::shims::time::system_time_to_duration;
1615
use crate::helpers::check_min_vararg_count;
17-
use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef};
16+
use crate::shims::files::FileHandle;
1817
use crate::shims::os_str::bytes_to_os_str;
1918
use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
2019
use crate::*;
2120

22-
#[derive(Debug)]
23-
struct FileHandle {
24-
file: File,
25-
writable: bool,
26-
}
27-
28-
impl FileDescription for FileHandle {
29-
fn name(&self) -> &'static str {
30-
"file"
31-
}
32-
33-
fn read<'tcx>(
34-
self: FileDescriptionRef<Self>,
35-
communicate_allowed: bool,
36-
ptr: Pointer,
37-
len: usize,
38-
ecx: &mut MiriInterpCx<'tcx>,
39-
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
40-
) -> InterpResult<'tcx> {
41-
assert!(communicate_allowed, "isolation should have prevented even opening a file");
42-
43-
let result = ecx.read_from_host(&self.file, len, ptr)?;
44-
finish.call(ecx, result)
45-
}
46-
47-
fn write<'tcx>(
48-
self: FileDescriptionRef<Self>,
49-
communicate_allowed: bool,
50-
ptr: Pointer,
51-
len: usize,
52-
ecx: &mut MiriInterpCx<'tcx>,
53-
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
54-
) -> InterpResult<'tcx> {
55-
assert!(communicate_allowed, "isolation should have prevented even opening a file");
56-
57-
let result = ecx.write_to_host(&self.file, len, ptr)?;
58-
finish.call(ecx, result)
59-
}
60-
61-
fn seek<'tcx>(
62-
&self,
63-
communicate_allowed: bool,
64-
offset: SeekFrom,
65-
) -> InterpResult<'tcx, io::Result<u64>> {
66-
assert!(communicate_allowed, "isolation should have prevented even opening a file");
67-
interp_ok((&mut &self.file).seek(offset))
68-
}
69-
70-
fn close<'tcx>(
71-
self,
72-
communicate_allowed: bool,
73-
_ecx: &mut MiriInterpCx<'tcx>,
74-
) -> InterpResult<'tcx, io::Result<()>> {
75-
assert!(communicate_allowed, "isolation should have prevented even opening a file");
76-
// We sync the file if it was opened in a mode different than read-only.
77-
if self.writable {
78-
// `File::sync_all` does the checks that are done when closing a file. We do this to
79-
// to handle possible errors correctly.
80-
let result = self.file.sync_all();
81-
// Now we actually close the file and return the result.
82-
drop(self.file);
83-
interp_ok(result)
84-
} else {
85-
// We drop the file, this closes it but ignores any errors
86-
// produced when closing it. This is done because
87-
// `File::sync_all` cannot be done over files like
88-
// `/dev/urandom` which are read-only. Check
89-
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
90-
// for a deeper discussion.
91-
drop(self.file);
92-
interp_ok(Ok(()))
93-
}
94-
}
95-
96-
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
97-
interp_ok(self.file.metadata())
98-
}
99-
100-
fn is_tty(&self, communicate_allowed: bool) -> bool {
101-
communicate_allowed && self.file.is_terminal()
102-
}
103-
104-
fn as_unix(&self) -> &dyn UnixFileDescription {
105-
self
106-
}
107-
}
108-
10921
impl UnixFileDescription for FileHandle {
11022
fn pread<'tcx>(
11123
&self,

0 commit comments

Comments
 (0)