|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::fs::File; |
| 3 | +use std::io::Read; |
| 4 | + |
| 5 | +use rustc::ty::layout::Size; |
| 6 | + |
| 7 | +use crate::stacked_borrows::Tag; |
| 8 | +use crate::*; |
| 9 | + |
| 10 | +pub struct FileHandle { |
| 11 | + file: File, |
| 12 | + flag: i32, |
| 13 | +} |
| 14 | + |
| 15 | +pub struct FileHandler { |
| 16 | + handles: HashMap<i32, FileHandle>, |
| 17 | + low: i32, |
| 18 | +} |
| 19 | + |
| 20 | +impl Default for FileHandler { |
| 21 | + fn default() -> Self { |
| 22 | + FileHandler { |
| 23 | + handles: Default::default(), |
| 24 | + // 0, 1 and 2 are reserved for stdin, stdout and stderr |
| 25 | + low: 3, |
| 26 | + } |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} |
| 31 | +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> { |
| 32 | + fn open( |
| 33 | + &mut self, |
| 34 | + path_op: OpTy<'tcx, Tag>, |
| 35 | + flag_op: OpTy<'tcx, Tag>, |
| 36 | + ) -> InterpResult<'tcx, i32> { |
| 37 | + let this = self.eval_context_mut(); |
| 38 | + |
| 39 | + if !this.machine.communicate { |
| 40 | + throw_unsup_format!("`open` not available when isolation is enabled") |
| 41 | + } |
| 42 | + |
| 43 | + let flag = this.read_scalar(flag_op)?.to_i32()?; |
| 44 | + |
| 45 | + if flag != this.eval_libc_i32("O_RDONLY")? && flag != this.eval_libc_i32("O_CLOEXEC")? { |
| 46 | + throw_unsup_format!("Unsupported flag {:#x}", flag); |
| 47 | + } |
| 48 | + |
| 49 | + let path_bytes = this |
| 50 | + .memory() |
| 51 | + .read_c_str(this.read_scalar(path_op)?.not_undef()?)?; |
| 52 | + let path = std::str::from_utf8(path_bytes) |
| 53 | + .map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", path_bytes))?; |
| 54 | + let fd = File::open(path).map(|file| { |
| 55 | + let mut fh = &mut this.machine.file_handler; |
| 56 | + fh.low += 1; |
| 57 | + fh.handles.insert(fh.low, FileHandle { file, flag }); |
| 58 | + fh.low |
| 59 | + }); |
| 60 | + |
| 61 | + this.consume_result(fd) |
| 62 | + } |
| 63 | + |
| 64 | + fn fcntl( |
| 65 | + &mut self, |
| 66 | + fd_op: OpTy<'tcx, Tag>, |
| 67 | + cmd_op: OpTy<'tcx, Tag>, |
| 68 | + arg_op: Option<OpTy<'tcx, Tag>>, |
| 69 | + ) -> InterpResult<'tcx, i32> { |
| 70 | + let this = self.eval_context_mut(); |
| 71 | + |
| 72 | + if !this.machine.communicate { |
| 73 | + throw_unsup_format!("`open` not available when isolation is enabled") |
| 74 | + } |
| 75 | + |
| 76 | + let fd = this.read_scalar(fd_op)?.to_i32()?; |
| 77 | + let cmd = this.read_scalar(cmd_op)?.to_i32()?; |
| 78 | + |
| 79 | + if cmd == this.eval_libc_i32("F_SETFD")? { |
| 80 | + // This does not affect the file itself. Certain flags might require changing the file |
| 81 | + // or the way it is accessed somehow. |
| 82 | + let flag = this.read_scalar(arg_op.unwrap())?.to_i32()?; |
| 83 | + // The only usage of this in stdlib at the moment is to enable the `FD_CLOEXEC` flag. |
| 84 | + let fd_cloexec = this.eval_libc_i32("FD_CLOEXEC")?; |
| 85 | + if let Some(FileHandle { flag: old_flag, .. }) = |
| 86 | + this.machine.file_handler.handles.get_mut(&fd) |
| 87 | + { |
| 88 | + if flag ^ *old_flag == fd_cloexec { |
| 89 | + *old_flag = flag; |
| 90 | + } else { |
| 91 | + throw_unsup_format!("Unsupported arg {:#x} for `F_SETFD`", flag); |
| 92 | + } |
| 93 | + } |
| 94 | + Ok(0) |
| 95 | + } else if cmd == this.eval_libc_i32("F_GETFD")? { |
| 96 | + this.get_handle_and(fd, |handle| Ok(handle.flag)) |
| 97 | + } else { |
| 98 | + throw_unsup_format!("Unsupported command {:#x}", cmd); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + fn close(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { |
| 103 | + let this = self.eval_context_mut(); |
| 104 | + |
| 105 | + if !this.machine.communicate { |
| 106 | + throw_unsup_format!("`open` not available when isolation is enabled") |
| 107 | + } |
| 108 | + |
| 109 | + let fd = this.read_scalar(fd_op)?.to_i32()?; |
| 110 | + |
| 111 | + this.remove_handle_and( |
| 112 | + fd, |
| 113 | + |handle, this| this.consume_result(handle.file.sync_all().map(|_| 0i32)), |
| 114 | + ) |
| 115 | + } |
| 116 | + |
| 117 | + fn read( |
| 118 | + &mut self, |
| 119 | + fd_op: OpTy<'tcx, Tag>, |
| 120 | + buf_op: OpTy<'tcx, Tag>, |
| 121 | + count_op: OpTy<'tcx, Tag>, |
| 122 | + ) -> InterpResult<'tcx, i64> { |
| 123 | + let this = self.eval_context_mut(); |
| 124 | + |
| 125 | + if !this.machine.communicate { |
| 126 | + throw_unsup_format!("`open` not available when isolation is enabled") |
| 127 | + } |
| 128 | + |
| 129 | + let tcx = &{ this.tcx.tcx }; |
| 130 | + |
| 131 | + let fd = this.read_scalar(fd_op)?.to_i32()?; |
| 132 | + let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?; |
| 133 | + let count = this.read_scalar(count_op)?.to_usize(&*this.tcx)?; |
| 134 | + |
| 135 | + // Remove the file handle to avoid borrowing issues |
| 136 | + this.remove_handle_and( |
| 137 | + fd, |
| 138 | + |mut handle, this| { |
| 139 | + let bytes = handle |
| 140 | + .file |
| 141 | + .read(this.memory_mut().get_mut(buf.alloc_id)?.get_bytes_mut( |
| 142 | + tcx, |
| 143 | + buf, |
| 144 | + Size::from_bytes(count), |
| 145 | + )?) |
| 146 | + .map(|bytes| bytes as i64); |
| 147 | + // Reinsert the file handle |
| 148 | + this.machine.file_handler.handles.insert(fd, handle); |
| 149 | + this.consume_result(bytes) |
| 150 | + }, |
| 151 | + ) |
| 152 | + } |
| 153 | + |
| 154 | + /// Helper function that gets a `FileHandle` immutable reference and allows to manipulate it |
| 155 | + /// using `f`. |
| 156 | + /// |
| 157 | + /// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)` |
| 158 | + /// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor). |
| 159 | + /// |
| 160 | + /// This function uses `T: From<i32>` instead of `i32` directly because some IO related |
| 161 | + /// functions return different integer types (like `read`, that returns an `i64`) |
| 162 | + fn get_handle_and<F, T: From<i32>>(&mut self, fd: i32, f: F) -> InterpResult<'tcx, T> |
| 163 | + where |
| 164 | + F: Fn(&FileHandle) -> InterpResult<'tcx, T>, |
| 165 | + { |
| 166 | + let this = self.eval_context_mut(); |
| 167 | + if let Some(handle) = this.machine.file_handler.handles.get(&fd) { |
| 168 | + f(handle) |
| 169 | + } else { |
| 170 | + this.machine.last_error = this.eval_libc_i32("EBADF")? as u32; |
| 171 | + Ok((-1).into()) |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + /// Helper function that removes a `FileHandle` and allows to manipulate it using the `f` |
| 176 | + /// closure. This function is quite useful when you need to modify a `FileHandle` but you need |
| 177 | + /// to modify `MiriEvalContext` at the same time, so you can modify the handle and reinsert it |
| 178 | + /// using `f`. |
| 179 | + /// |
| 180 | + /// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)` |
| 181 | + /// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor). |
| 182 | + /// |
| 183 | + /// This function uses `T: From<i32>` instead of `i32` directly because some IO related |
| 184 | + /// functions return different integer types (like `read`, that returns an `i64`) |
| 185 | + fn remove_handle_and<F, T: From<i32>>(&mut self, fd: i32, mut f: F) -> InterpResult<'tcx, T> |
| 186 | + where |
| 187 | + F: FnMut(FileHandle, &mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx, T>, |
| 188 | + { |
| 189 | + let this = self.eval_context_mut(); |
| 190 | + if let Some(handle) = this.machine.file_handler.handles.remove(&fd) { |
| 191 | + f(handle, this) |
| 192 | + } else { |
| 193 | + this.machine.last_error = this.eval_libc_i32("EBADF")? as u32; |
| 194 | + Ok((-1).into()) |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + /// Helper function that consumes an `std::io::Result<T>` and returns an |
| 199 | + /// `InterpResult<'tcx,T>::Ok` instead. It is expected that the result can be converted to an |
| 200 | + /// OS error using `std::io::Error::raw_os_error`. |
| 201 | + /// |
| 202 | + /// This function uses `T: From<i32>` instead of `i32` directly because some IO related |
| 203 | + /// functions return different integer types (like `read`, that returns an `i64`) |
| 204 | + fn consume_result<T: From<i32>>(&mut self, result: std::io::Result<T>) -> InterpResult<'tcx, T> { |
| 205 | + match result { |
| 206 | + Ok(ok) => Ok(ok), |
| 207 | + Err(e) => { |
| 208 | + self.eval_context_mut().machine.last_error = e.raw_os_error().unwrap() as u32; |
| 209 | + Ok((-1).into()) |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | +} |
0 commit comments