Skip to main content

miri/shims/
io_error.rs

1use std::io;
2use std::io::ErrorKind;
3
4use crate::*;
5
6/// A representation of an IO error: either a libc error name,
7/// or a host error.
8#[derive(Debug)]
9pub enum IoError {
10    LibcError(&'static str),
11    WindowsError(&'static str),
12    HostError(io::Error),
13    Raw(Scalar),
14}
15pub use self::IoError::*;
16
17impl IoError {
18    pub(crate) fn into_ntstatus(self) -> i32 {
19        let raw = match self {
20            HostError(e) =>
21                match e.kind() {
22                    // STATUS_MEDIA_WRITE_PROTECTED
23                    ErrorKind::ReadOnlyFilesystem => 0xC00000A2u32,
24                    // STATUS_FILE_INVALID
25                    ErrorKind::InvalidInput => 0xC0000098,
26                    // STATUS_DISK_FULL
27                    ErrorKind::QuotaExceeded => 0xC000007F,
28                    // STATUS_ACCESS_DENIED
29                    ErrorKind::PermissionDenied => 0xC0000022,
30                    // For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
31                    _ => 0xC0000185,
32                },
33            // For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
34            _ => 0xC0000185,
35        };
36        raw.cast_signed()
37    }
38}
39
40impl From<io::Error> for IoError {
41    fn from(value: io::Error) -> Self {
42        IoError::HostError(value)
43    }
44}
45
46impl From<io::ErrorKind> for IoError {
47    fn from(value: io::ErrorKind) -> Self {
48        IoError::HostError(value.into())
49    }
50}
51
52impl From<Scalar> for IoError {
53    fn from(value: Scalar) -> Self {
54        IoError::Raw(value)
55    }
56}
57
58// This mapping should match `decode_error_kind` in
59// <https://github.com/rust-lang/rust/blob/HEAD/library/std/src/sys/io/error/unix.rs>.
60const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
61    use std::io::ErrorKind::*;
62    &[
63        ("E2BIG", ArgumentListTooLong),
64        ("EADDRINUSE", AddrInUse),
65        ("EADDRNOTAVAIL", AddrNotAvailable),
66        ("EBUSY", ResourceBusy),
67        ("ECONNABORTED", ConnectionAborted),
68        ("ECONNREFUSED", ConnectionRefused),
69        ("ECONNRESET", ConnectionReset),
70        ("EDEADLK", Deadlock),
71        ("EDQUOT", QuotaExceeded),
72        ("EEXIST", AlreadyExists),
73        ("EFBIG", FileTooLarge),
74        ("EHOSTUNREACH", HostUnreachable),
75        ("EINTR", Interrupted),
76        ("EINVAL", InvalidInput),
77        ("EISDIR", IsADirectory),
78        ("ELOOP", FilesystemLoop),
79        ("ENOENT", NotFound),
80        ("ENOMEM", OutOfMemory),
81        ("ENOSPC", StorageFull),
82        ("ENOSYS", Unsupported),
83        ("EMLINK", TooManyLinks),
84        ("ENAMETOOLONG", InvalidFilename),
85        ("ENETDOWN", NetworkDown),
86        ("ENETUNREACH", NetworkUnreachable),
87        ("ENOTCONN", NotConnected),
88        ("ENOTDIR", NotADirectory),
89        ("ENOTEMPTY", DirectoryNotEmpty),
90        ("EPIPE", BrokenPipe),
91        ("EROFS", ReadOnlyFilesystem),
92        ("ESPIPE", NotSeekable),
93        ("ESTALE", StaleNetworkFileHandle),
94        ("ETIMEDOUT", TimedOut),
95        ("ETXTBSY", ExecutableFileBusy),
96        ("EXDEV", CrossesDevices),
97        ("EINPROGRESS", InProgress),
98        // The following have two valid options. We have both for the forwards mapping; only the
99        // first one will be used for the backwards mapping.
100        ("EPERM", PermissionDenied),
101        ("EACCES", PermissionDenied),
102        ("EWOULDBLOCK", WouldBlock),
103        ("EAGAIN", WouldBlock),
104    ]
105};
106// This mapping should match `decode_error_kind` in
107// <https://github.com/rust-lang/rust/blob/HEAD/library/std/src/sys/io/error/windows.rs>.
108const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
109    use std::io::ErrorKind::*;
110    // It's common for multiple error codes to map to the same io::ErrorKind. We have all for the
111    // forwards mapping; only the first one will be used for the backwards mapping.
112    // Slightly arbitrarily, we prefer non-WSA and the most generic sounding variant for backwards
113    // mapping.
114    &[
115        ("WSAEADDRINUSE", AddrInUse),
116        ("WSAEADDRNOTAVAIL", AddrNotAvailable),
117        ("ERROR_ALREADY_EXISTS", AlreadyExists),
118        ("ERROR_FILE_EXISTS", AlreadyExists),
119        ("ERROR_NO_DATA", BrokenPipe),
120        ("WSAECONNABORTED", ConnectionAborted),
121        ("WSAECONNREFUSED", ConnectionRefused),
122        ("WSAECONNRESET", ConnectionReset),
123        ("ERROR_NOT_SAME_DEVICE", CrossesDevices),
124        ("ERROR_POSSIBLE_DEADLOCK", Deadlock),
125        ("ERROR_DIR_NOT_EMPTY", DirectoryNotEmpty),
126        ("ERROR_CANT_RESOLVE_FILENAME", FilesystemLoop),
127        ("ERROR_DISK_QUOTA_EXCEEDED", QuotaExceeded),
128        ("WSAEDQUOT", QuotaExceeded),
129        ("ERROR_FILE_TOO_LARGE", FileTooLarge),
130        ("ERROR_HOST_UNREACHABLE", HostUnreachable),
131        ("WSAEHOSTUNREACH", HostUnreachable),
132        ("ERROR_INVALID_NAME", InvalidFilename),
133        ("ERROR_BAD_PATHNAME", InvalidFilename),
134        ("ERROR_FILENAME_EXCED_RANGE", InvalidFilename),
135        ("ERROR_INVALID_PARAMETER", InvalidInput),
136        ("WSAEINVAL", InvalidInput),
137        ("ERROR_DIRECTORY_NOT_SUPPORTED", IsADirectory),
138        ("WSAENETDOWN", NetworkDown),
139        ("ERROR_NETWORK_UNREACHABLE", NetworkUnreachable),
140        ("WSAENETUNREACH", NetworkUnreachable),
141        ("ERROR_DIRECTORY", NotADirectory),
142        ("WSAENOTCONN", NotConnected),
143        ("ERROR_FILE_NOT_FOUND", NotFound),
144        ("ERROR_PATH_NOT_FOUND", NotFound),
145        ("ERROR_INVALID_DRIVE", NotFound),
146        ("ERROR_BAD_NETPATH", NotFound),
147        ("ERROR_BAD_NET_NAME", NotFound),
148        ("ERROR_SEEK_ON_DEVICE", NotSeekable),
149        ("ERROR_NOT_ENOUGH_MEMORY", OutOfMemory),
150        ("ERROR_OUTOFMEMORY", OutOfMemory),
151        ("ERROR_ACCESS_DENIED", PermissionDenied),
152        ("WSAEACCES", PermissionDenied),
153        ("ERROR_WRITE_PROTECT", ReadOnlyFilesystem),
154        ("ERROR_BUSY", ResourceBusy),
155        ("ERROR_DISK_FULL", StorageFull),
156        ("ERROR_HANDLE_DISK_FULL", StorageFull),
157        ("WAIT_TIMEOUT", TimedOut),
158        ("WSAETIMEDOUT", TimedOut),
159        ("ERROR_DRIVER_CANCEL_TIMEOUT", TimedOut),
160        ("ERROR_OPERATION_ABORTED", TimedOut),
161        ("ERROR_SERVICE_REQUEST_TIMEOUT", TimedOut),
162        ("ERROR_COUNTER_TIMEOUT", TimedOut),
163        ("ERROR_TIMEOUT", TimedOut),
164        ("ERROR_RESOURCE_CALL_TIMED_OUT", TimedOut),
165        ("ERROR_CTX_MODEM_RESPONSE_TIMEOUT", TimedOut),
166        ("ERROR_CTX_CLIENT_QUERY_TIMEOUT", TimedOut),
167        ("FRS_ERR_SYSVOL_POPULATE_TIMEOUT", TimedOut),
168        ("ERROR_DS_TIMELIMIT_EXCEEDED", TimedOut),
169        ("DNS_ERROR_RECORD_TIMED_OUT", TimedOut),
170        ("ERROR_IPSEC_IKE_TIMED_OUT", TimedOut),
171        ("ERROR_RUNLEVEL_SWITCH_TIMEOUT", TimedOut),
172        ("ERROR_RUNLEVEL_SWITCH_AGENT_TIMEOUT", TimedOut),
173        ("ERROR_TOO_MANY_LINKS", TooManyLinks),
174        ("ERROR_CALL_NOT_IMPLEMENTED", Unsupported),
175        ("WSAEWOULDBLOCK", WouldBlock),
176    ]
177};
178
179impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
180pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
181    /// Get last error variable as a place, lazily allocating thread-local storage for it if
182    /// necessary.
183    fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
184        let this = self.eval_context_mut();
185        if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() {
186            interp_ok(errno_place.clone())
187        } else {
188            // Allocate new place, set initial value to 0.
189            let errno_layout = this.machine.layouts.u32;
190            let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?;
191            this.write_scalar(Scalar::from_u32(0), &errno_place)?;
192            this.active_thread_mut().last_error = Some(errno_place.clone());
193            interp_ok(errno_place)
194        }
195    }
196
197    /// Sets the last error variable.
198    fn set_last_error(&mut self, err: impl Into<IoError>) -> InterpResult<'tcx> {
199        let this = self.eval_context_mut();
200        let errno = match err.into() {
201            HostError(err) => this.io_error_to_errnum(err)?,
202            LibcError(name) => this.eval_libc(name),
203            WindowsError(name) => this.eval_windows("c", name),
204            Raw(val) => val,
205        };
206        let errno_place = this.last_error_place()?;
207        this.write_scalar(errno, &errno_place)
208    }
209
210    /// Sets the last OS error and writes -1 to dest place.
211    fn set_last_error_and_return(
212        &mut self,
213        err: impl Into<IoError>,
214        dest: &MPlaceTy<'tcx>,
215    ) -> InterpResult<'tcx> {
216        let this = self.eval_context_mut();
217        this.set_last_error(err)?;
218        this.write_int(-1, dest)?;
219        interp_ok(())
220    }
221
222    /// Sets the last OS error and return `-1` as a `i32`-typed Scalar
223    fn set_last_error_and_return_i32(
224        &mut self,
225        err: impl Into<IoError>,
226    ) -> InterpResult<'tcx, Scalar> {
227        let this = self.eval_context_mut();
228        this.set_last_error(err)?;
229        interp_ok(Scalar::from_i32(-1))
230    }
231
232    /// Sets the last OS error and return `-1` as a `i64`-typed Scalar
233    fn set_last_error_and_return_i64(
234        &mut self,
235        err: impl Into<IoError>,
236    ) -> InterpResult<'tcx, Scalar> {
237        let this = self.eval_context_mut();
238        this.set_last_error(err)?;
239        interp_ok(Scalar::from_i64(-1))
240    }
241
242    /// Gets the last error variable.
243    fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> {
244        let this = self.eval_context_mut();
245        let errno_place = this.last_error_place()?;
246        this.read_scalar(&errno_place)
247    }
248
249    /// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
250    /// as a platform-specific errnum.
251    fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> {
252        let this = self.eval_context_ref();
253        let target = &this.tcx.sess.target;
254        if target.families.iter().any(|f| f == "unix") {
255            for &(name, kind) in UNIX_IO_ERROR_TABLE {
256                if err.kind() == kind {
257                    return interp_ok(this.eval_libc(name));
258                }
259            }
260            throw_unsup_format!("unsupported io error: {err}")
261        } else if target.families.iter().any(|f| f == "windows") {
262            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
263                if err.kind() == kind {
264                    return interp_ok(this.eval_windows("c", name));
265                }
266            }
267            throw_unsup_format!("unsupported io error: {err}");
268        } else {
269            throw_unsup_format!(
270                "converting io::Error into errnum is unsupported for OS {}",
271                target.os
272            )
273        }
274    }
275
276    /// The inverse of `io_error_to_errnum`.
277    #[expect(clippy::needless_return)]
278    fn try_errnum_to_io_error(
279        &self,
280        errnum: Scalar,
281    ) -> InterpResult<'tcx, Option<std::io::ErrorKind>> {
282        let this = self.eval_context_ref();
283        let target = &this.tcx.sess.target;
284        if target.families.iter().any(|f| f == "unix") {
285            let errnum = errnum.to_i32()?;
286            for &(name, kind) in UNIX_IO_ERROR_TABLE {
287                if errnum == this.eval_libc_i32(name) {
288                    return interp_ok(Some(kind));
289                }
290            }
291            return interp_ok(None);
292        } else if target.families.iter().any(|f| f == "windows") {
293            let errnum = errnum.to_u32()?;
294            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
295                if errnum == this.eval_windows("c", name).to_u32()? {
296                    return interp_ok(Some(kind));
297                }
298            }
299            return interp_ok(None);
300        } else {
301            throw_unsup_format!(
302                "converting errnum into io::Error is unsupported for OS {}",
303                target.os
304            )
305        }
306    }
307
308    /// Helper function that consumes an `std::io::Result<T>` and returns an
309    /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns
310    /// `Ok(-1)` and sets the last OS error accordingly.
311    ///
312    /// This function uses `T: From<i32>` instead of `i32` directly because some IO related
313    /// functions return different integer types (like `read`, that returns an `i64`).
314    fn try_unwrap_io_result<T: From<i32>>(
315        &mut self,
316        result: std::io::Result<T>,
317    ) -> InterpResult<'tcx, T> {
318        match result {
319            Ok(ok) => interp_ok(ok),
320            Err(e) => {
321                self.eval_context_mut().set_last_error(e)?;
322                interp_ok((-1).into())
323            }
324        }
325    }
326}