1 use std::collections::HashMap;
2 use std::convert::TryFrom;
3 use std::fs::{remove_file, File, OpenOptions};
4 use std::io::{Read, Write};
6 use rustc::ty::layout::Size;
8 use crate::stacked_borrows::Tag;
12 pub struct FileHandle {
16 pub struct FileHandler {
17 handles: HashMap<i32, FileHandle>,
21 impl Default for FileHandler {
22 fn default() -> Self {
24 handles: Default::default(),
25 // 0, 1 and 2 are reserved for stdin, stdout and stderr.
31 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
32 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
35 path_op: OpTy<'tcx, Tag>,
36 flag_op: OpTy<'tcx, Tag>,
37 ) -> InterpResult<'tcx, i32> {
38 let this = self.eval_context_mut();
40 this.check_no_isolation("open")?;
42 let flag = this.read_scalar(flag_op)?.to_i32()?;
44 let mut options = OpenOptions::new();
46 let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
47 let o_wronly = this.eval_libc_i32("O_WRONLY")?;
48 let o_rdwr = this.eval_libc_i32("O_RDWR")?;
49 // The first two bits of the flag correspond to the access mode in linux, macOS and
50 // windows. We need to check that in fact the access mode flags for the current platform
51 // only use these two bits, otherwise we are in an unsupported platform and should error.
52 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
53 throw_unsup_format!("Access mode flags on this platform are unsupported");
55 // Now we check the access mode
56 let access_mode = flag & 0b11;
58 if access_mode == o_rdonly {
60 } else if access_mode == o_wronly {
62 } else if access_mode == o_rdwr {
63 options.read(true).write(true);
65 throw_unsup_format!("Unsupported access mode {:#x}", access_mode);
67 // We need to check that there aren't unsupported options in `flag`. For this we try to
68 // reproduce the content of `flag` in the `mirror` variable using only the supported
70 let mut mirror = access_mode;
72 let o_append = this.eval_libc_i32("O_APPEND")?;
73 if flag & o_append != 0 {
77 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
78 if flag & o_trunc != 0 {
79 options.truncate(true);
82 let o_creat = this.eval_libc_i32("O_CREAT")?;
83 if flag & o_creat != 0 {
87 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
88 if flag & o_cloexec != 0 {
89 // We do not need to do anything for this flag because `std` already sets it.
90 // (Technically we do not support *not* setting this flag, but we ignore that.)
93 // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
94 // then we throw an error.
96 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
99 let path = this.read_os_string_from_c_string(this.read_scalar(path_op)?.not_undef()?)?;
101 let fd = options.open(path).map(|file| {
102 let mut fh = &mut this.machine.file_handler;
104 fh.handles.insert(fh.low, FileHandle { file }).unwrap_none();
108 this.try_unwrap_io_result(fd)
113 fd_op: OpTy<'tcx, Tag>,
114 cmd_op: OpTy<'tcx, Tag>,
115 _arg1_op: Option<OpTy<'tcx, Tag>>,
116 ) -> InterpResult<'tcx, i32> {
117 let this = self.eval_context_mut();
119 this.check_no_isolation("fcntl")?;
121 let fd = this.read_scalar(fd_op)?.to_i32()?;
122 let cmd = this.read_scalar(cmd_op)?.to_i32()?;
123 // We only support getting the flags for a descriptor.
124 if cmd == this.eval_libc_i32("F_GETFD")? {
125 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
126 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
127 // always sets this flag when opening a file. However we still need to check that the
128 // file itself is open.
129 if this.machine.file_handler.handles.contains_key(&fd) {
130 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
132 this.handle_not_found()
135 throw_unsup_format!("The {:#x} command is not supported for `fcntl`)", cmd);
139 fn close(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
140 let this = self.eval_context_mut();
142 this.check_no_isolation("close")?;
144 let fd = this.read_scalar(fd_op)?.to_i32()?;
146 if let Some(handle) = this.machine.file_handler.handles.remove(&fd) {
147 // `File::sync_all` does the checks that are done when closing a file. We do this to
148 // to handle possible errors correctly.
149 let result = this.try_unwrap_io_result(handle.file.sync_all().map(|_| 0i32));
150 // Now we actually close the file.
152 // And return the result.
155 this.handle_not_found()
161 fd_op: OpTy<'tcx, Tag>,
162 buf_op: OpTy<'tcx, Tag>,
163 count_op: OpTy<'tcx, Tag>,
164 ) -> InterpResult<'tcx, i64> {
165 let this = self.eval_context_mut();
167 this.check_no_isolation("read")?;
169 let ptr_size = this.pointer_size().bits();
171 // We cap the number of read bytes to the largest value that we are able to fit in both the
172 // host's and target's `isize`.
174 .read_scalar(count_op)?
175 .to_machine_usize(&*this.tcx)?
176 .min((1 << (ptr_size - 1)) - 1) // max value of target `isize`
177 .min(isize::max_value() as u64);
179 let fd = this.read_scalar(fd_op)?.to_i32()?;
180 let buf = this.read_scalar(buf_op)?.not_undef()?;
182 if let Some(handle) = this.machine.file_handler.handles.get_mut(&fd) {
183 // This can never fail because `count` was capped to be smaller than
184 // `isize::max_value()`.
185 let count = isize::try_from(count).unwrap();
186 // We want to read at most `count` bytes. We are sure that `count` is not negative
187 // because it was a target's `usize`. Also we are sure that its smaller than
188 // `usize::max_value()` because it is a host's `isize`.
189 let mut bytes = vec![0; count as usize];
193 // `File::read` never returns a value larger than `count`, so this cannot fail.
194 .map(|c| i64::try_from(c).unwrap());
198 // If reading to `bytes` did not fail, we write those bytes to the buffer.
199 this.memory.write_bytes(buf, bytes)?;
203 this.set_last_error_from_io_error(e)?;
208 this.handle_not_found()
214 fd_op: OpTy<'tcx, Tag>,
215 buf_op: OpTy<'tcx, Tag>,
216 count_op: OpTy<'tcx, Tag>,
217 ) -> InterpResult<'tcx, i64> {
218 let this = self.eval_context_mut();
220 this.check_no_isolation("write")?;
222 let ptr_size = this.pointer_size().bits();
224 // We cap the number of read bytes to the largest value that we are able to fit in both the
225 // host's and target's `isize`.
227 .read_scalar(count_op)?
228 .to_machine_usize(&*this.tcx)?
229 .min((1 << (ptr_size - 1)) - 1) // max value of target `isize`
230 .min(isize::max_value() as u64);
232 let fd = this.read_scalar(fd_op)?.to_i32()?;
233 let buf = this.read_scalar(buf_op)?.not_undef()?;
235 if let Some(handle) = this.machine.file_handler.handles.get_mut(&fd) {
236 let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
237 let result = handle.file.write(&bytes).map(|c| i64::try_from(c).unwrap());
238 this.try_unwrap_io_result(result)
240 this.handle_not_found()
244 fn unlink(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
245 let this = self.eval_context_mut();
247 this.check_no_isolation("unlink")?;
249 let path = this.read_os_string_from_c_string(this.read_scalar(path_op)?.not_undef()?)?;
251 let result = remove_file(path).map(|_| 0);
253 this.try_unwrap_io_result(result)
256 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
257 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
258 /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
259 /// types (like `read`, that returns an `i64`).
260 fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
261 let this = self.eval_context_mut();
262 let ebadf = this.eval_libc("EBADF")?;
263 this.set_last_error(ebadf)?;