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        ("EMLINK", TooManyLinks),
83        ("ENAMETOOLONG", InvalidFilename),
84        ("ENETDOWN", NetworkDown),
85        ("ENETUNREACH", NetworkUnreachable),
86        ("ENOTCONN", NotConnected),
87        ("ENOTDIR", NotADirectory),
88        ("ENOTEMPTY", DirectoryNotEmpty),
89        ("EPIPE", BrokenPipe),
90        ("EROFS", ReadOnlyFilesystem),
91        ("ESPIPE", NotSeekable),
92        ("ESTALE", StaleNetworkFileHandle),
93        ("ETIMEDOUT", TimedOut),
94        ("ETXTBSY", ExecutableFileBusy),
95        ("EXDEV", CrossesDevices),
96        ("EINPROGRESS", InProgress),
97        ("EIO", InputOutputError),
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        ("ENOSYS", Unsupported),
105        ("EOPNOTSUPP", Unsupported),
106        ("ENOTSUP", Unsupported),
107        ("EMFILE", TooManyOpenFiles),
108        ("ENFILE", TooManyOpenFiles),
109    ]
110};
111// On Unix hosts are can avoid round-tripping via `ErrorKind`, which can preserve more
112// details and leads to nicer output in `strerror_r`.
113#[cfg(unix)]
114const UNIX_ERRNO_TABLE: &[(&str, libc::c_int)] = &[
115    ("E2BIG", libc::E2BIG),
116    ("EACCES", libc::EACCES),
117    ("EADDRINUSE", libc::EADDRINUSE),
118    ("EADDRNOTAVAIL", libc::EADDRNOTAVAIL),
119    ("EAFNOSUPPORT", libc::EAFNOSUPPORT),
120    ("EAGAIN", libc::EAGAIN),
121    ("EALREADY", libc::EALREADY),
122    ("EBADF", libc::EBADF),
123    ("EBADMSG", libc::EBADMSG),
124    ("EBUSY", libc::EBUSY),
125    ("ECANCELED", libc::ECANCELED),
126    ("ECHILD", libc::ECHILD),
127    ("ECONNABORTED", libc::ECONNABORTED),
128    ("ECONNREFUSED", libc::ECONNREFUSED),
129    ("ECONNRESET", libc::ECONNRESET),
130    ("EDEADLK", libc::EDEADLK),
131    ("EDESTADDRREQ", libc::EDESTADDRREQ),
132    ("EDOM", libc::EDOM),
133    ("EDQUOT", libc::EDQUOT),
134    ("EEXIST", libc::EEXIST),
135    ("EFAULT", libc::EFAULT),
136    ("EFBIG", libc::EFBIG),
137    ("EHOSTUNREACH", libc::EHOSTUNREACH),
138    ("EIDRM", libc::EIDRM),
139    ("EILSEQ", libc::EILSEQ),
140    ("EINPROGRESS", libc::EINPROGRESS),
141    ("EINTR", libc::EINTR),
142    ("EINVAL", libc::EINVAL),
143    ("EIO", libc::EIO),
144    ("EISCONN", libc::EISCONN),
145    ("EISDIR", libc::EISDIR),
146    ("ELOOP", libc::ELOOP),
147    ("EMFILE", libc::EMFILE),
148    ("EMLINK", libc::EMLINK),
149    ("EMSGSIZE", libc::EMSGSIZE),
150    ("EMULTIHOP", libc::EMULTIHOP),
151    ("ENAMETOOLONG", libc::ENAMETOOLONG),
152    ("ENETDOWN", libc::ENETDOWN),
153    ("ENETRESET", libc::ENETRESET),
154    ("ENETUNREACH", libc::ENETUNREACH),
155    ("ENFILE", libc::ENFILE),
156    ("ENOBUFS", libc::ENOBUFS),
157    ("ENODEV", libc::ENODEV),
158    ("ENOENT", libc::ENOENT),
159    ("ENOEXEC", libc::ENOEXEC),
160    ("ENOLCK", libc::ENOLCK),
161    ("ENOLINK", libc::ENOLINK),
162    ("ENOMEM", libc::ENOMEM),
163    ("ENOMSG", libc::ENOMSG),
164    ("ENOPROTOOPT", libc::ENOPROTOOPT),
165    ("ENOSPC", libc::ENOSPC),
166    ("ENOSYS", libc::ENOSYS),
167    ("ENOTCONN", libc::ENOTCONN),
168    ("ENOTDIR", libc::ENOTDIR),
169    ("ENOTEMPTY", libc::ENOTEMPTY),
170    ("ENOTRECOVERABLE", libc::ENOTRECOVERABLE),
171    ("ENOTSOCK", libc::ENOTSOCK),
172    ("ENOTSUP", libc::ENOTSUP),
173    ("ENOTTY", libc::ENOTTY),
174    ("ENXIO", libc::ENXIO),
175    ("EOPNOTSUPP", libc::EOPNOTSUPP),
176    ("EOVERFLOW", libc::EOVERFLOW),
177    ("EOWNERDEAD", libc::EOWNERDEAD),
178    ("EPERM", libc::EPERM),
179    ("EPIPE", libc::EPIPE),
180    ("EPROTO", libc::EPROTO),
181    ("EPROTONOSUPPORT", libc::EPROTONOSUPPORT),
182    ("EPROTOTYPE", libc::EPROTOTYPE),
183    ("ERANGE", libc::ERANGE),
184    ("EROFS", libc::EROFS),
185    ("ESOCKTNOSUPPORT", libc::ESOCKTNOSUPPORT),
186    ("ESPIPE", libc::ESPIPE),
187    ("ESRCH", libc::ESRCH),
188    ("ESTALE", libc::ESTALE),
189    ("ETIMEDOUT", libc::ETIMEDOUT),
190    ("ETXTBSY", libc::ETXTBSY),
191    ("EWOULDBLOCK", libc::EWOULDBLOCK),
192    ("EXDEV", libc::EXDEV),
193];
194// This mapping should match `decode_error_kind` in
195// <https://github.com/rust-lang/rust/blob/HEAD/library/std/src/sys/io/error/windows.rs>.
196const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
197    use std::io::ErrorKind::*;
198    // It's common for multiple error codes to map to the same io::ErrorKind. We have all for the
199    // forwards mapping; only the first one will be used for the backwards mapping.
200    // Slightly arbitrarily, we prefer non-WSA and the most generic sounding variant for backwards
201    // mapping.
202    &[
203        ("WSAEADDRINUSE", AddrInUse),
204        ("WSAEADDRNOTAVAIL", AddrNotAvailable),
205        ("ERROR_ALREADY_EXISTS", AlreadyExists),
206        ("ERROR_FILE_EXISTS", AlreadyExists),
207        ("ERROR_NO_DATA", BrokenPipe),
208        ("WSAECONNABORTED", ConnectionAborted),
209        ("WSAECONNREFUSED", ConnectionRefused),
210        ("WSAECONNRESET", ConnectionReset),
211        ("ERROR_NOT_SAME_DEVICE", CrossesDevices),
212        ("ERROR_POSSIBLE_DEADLOCK", Deadlock),
213        ("ERROR_DIR_NOT_EMPTY", DirectoryNotEmpty),
214        ("ERROR_CANT_RESOLVE_FILENAME", FilesystemLoop),
215        ("ERROR_DISK_QUOTA_EXCEEDED", QuotaExceeded),
216        ("WSAEDQUOT", QuotaExceeded),
217        ("ERROR_FILE_TOO_LARGE", FileTooLarge),
218        ("ERROR_HOST_UNREACHABLE", HostUnreachable),
219        ("WSAEHOSTUNREACH", HostUnreachable),
220        ("ERROR_INVALID_NAME", InvalidFilename),
221        ("ERROR_BAD_PATHNAME", InvalidFilename),
222        ("ERROR_FILENAME_EXCED_RANGE", InvalidFilename),
223        ("ERROR_INVALID_PARAMETER", InvalidInput),
224        ("WSAEINVAL", InvalidInput),
225        ("ERROR_DIRECTORY_NOT_SUPPORTED", IsADirectory),
226        ("WSAENETDOWN", NetworkDown),
227        ("ERROR_NETWORK_UNREACHABLE", NetworkUnreachable),
228        ("WSAENETUNREACH", NetworkUnreachable),
229        ("ERROR_DIRECTORY", NotADirectory),
230        ("WSAENOTCONN", NotConnected),
231        ("ERROR_FILE_NOT_FOUND", NotFound),
232        ("ERROR_PATH_NOT_FOUND", NotFound),
233        ("ERROR_INVALID_DRIVE", NotFound),
234        ("ERROR_BAD_NETPATH", NotFound),
235        ("ERROR_BAD_NET_NAME", NotFound),
236        ("ERROR_SEEK_ON_DEVICE", NotSeekable),
237        ("ERROR_NOT_ENOUGH_MEMORY", OutOfMemory),
238        ("ERROR_OUTOFMEMORY", OutOfMemory),
239        ("ERROR_ACCESS_DENIED", PermissionDenied),
240        ("WSAEACCES", PermissionDenied),
241        ("ERROR_WRITE_PROTECT", ReadOnlyFilesystem),
242        ("ERROR_BUSY", ResourceBusy),
243        ("ERROR_DISK_FULL", StorageFull),
244        ("ERROR_HANDLE_DISK_FULL", StorageFull),
245        ("WAIT_TIMEOUT", TimedOut),
246        ("WSAETIMEDOUT", TimedOut),
247        ("ERROR_DRIVER_CANCEL_TIMEOUT", TimedOut),
248        ("ERROR_OPERATION_ABORTED", TimedOut),
249        ("ERROR_SERVICE_REQUEST_TIMEOUT", TimedOut),
250        ("ERROR_COUNTER_TIMEOUT", TimedOut),
251        ("ERROR_TIMEOUT", TimedOut),
252        ("ERROR_RESOURCE_CALL_TIMED_OUT", TimedOut),
253        ("ERROR_CTX_MODEM_RESPONSE_TIMEOUT", TimedOut),
254        ("ERROR_CTX_CLIENT_QUERY_TIMEOUT", TimedOut),
255        ("FRS_ERR_SYSVOL_POPULATE_TIMEOUT", TimedOut),
256        ("ERROR_DS_TIMELIMIT_EXCEEDED", TimedOut),
257        ("DNS_ERROR_RECORD_TIMED_OUT", TimedOut),
258        ("ERROR_IPSEC_IKE_TIMED_OUT", TimedOut),
259        ("ERROR_RUNLEVEL_SWITCH_TIMEOUT", TimedOut),
260        ("ERROR_RUNLEVEL_SWITCH_AGENT_TIMEOUT", TimedOut),
261        ("ERROR_TOO_MANY_LINKS", TooManyLinks),
262        ("ERROR_CALL_NOT_IMPLEMENTED", Unsupported),
263        ("WSAEWOULDBLOCK", WouldBlock),
264        ("ERROR_TOO_MANY_OPEN_FILES", TooManyOpenFiles),
265        ("ERROR_IO_DEVICE", InputOutputError),
266    ]
267};
268
269impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
270pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
271    /// Get last error variable as a place, lazily allocating thread-local storage for it if
272    /// necessary.
273    fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
274        let this = self.eval_context_mut();
275        if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() {
276            interp_ok(errno_place.clone())
277        } else {
278            // Allocate new place, set initial value to 0.
279            let errno_layout = this.machine.layouts.u32;
280            let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?;
281            this.write_scalar(Scalar::from_u32(0), &errno_place)?;
282            this.active_thread_mut().last_error = Some(errno_place.clone());
283            interp_ok(errno_place)
284        }
285    }
286
287    fn io_error_to_errnum(&mut self, err: impl Into<IoError>) -> InterpResult<'tcx, Scalar> {
288        let this = self.eval_context_mut();
289        interp_ok(match err.into() {
290            HostError(err) => this.host_error_to_errnum(err)?,
291            LibcError(name) => this.eval_libc(name),
292            WindowsError(name) => this.eval_windows("c", name),
293            Raw(val) => val,
294        })
295    }
296
297    /// Sets the last error variable.
298    fn set_last_error(&mut self, err: impl Into<IoError>) -> InterpResult<'tcx> {
299        let this = self.eval_context_mut();
300        let errno = this.io_error_to_errnum(err)?;
301        let errno_place = this.last_error_place()?;
302        this.write_scalar(errno, &errno_place)
303    }
304
305    /// Sets the last OS error and writes -1 to dest place.
306    fn set_errno_and_return_neg1(
307        &mut self,
308        err: impl Into<IoError>,
309        dest: &MPlaceTy<'tcx>,
310    ) -> InterpResult<'tcx> {
311        let this = self.eval_context_mut();
312        this.set_last_error(err)?;
313        this.write_int(-1, dest)?;
314        interp_ok(())
315    }
316
317    /// Sets the last OS error and return `-1` as a `i32`-typed Scalar
318    fn set_errno_and_return_neg1_i32(
319        &mut self,
320        err: impl Into<IoError>,
321    ) -> InterpResult<'tcx, Scalar> {
322        let this = self.eval_context_mut();
323        this.set_last_error(err)?;
324        interp_ok(Scalar::from_i32(-1))
325    }
326
327    /// Sets the last OS error and return `-1` as a `i64`-typed Scalar
328    fn set_errno_and_return_neg1_i64(
329        &mut self,
330        err: impl Into<IoError>,
331    ) -> InterpResult<'tcx, Scalar> {
332        let this = self.eval_context_mut();
333        this.set_last_error(err)?;
334        interp_ok(Scalar::from_i64(-1))
335    }
336
337    /// Gets the last error variable.
338    fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> {
339        let this = self.eval_context_mut();
340        let errno_place = this.last_error_place()?;
341        this.read_scalar(&errno_place)
342    }
343
344    /// This function converts host errors to target errors. It tries to produce the most similar OS
345    /// error from the `std::io::ErrorKind` as a platform-specific errnum.
346    fn host_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> {
347        let this = self.eval_context_ref();
348        let target = &this.tcx.sess.target;
349
350        if target.families.iter().any(|f| f == "unix") {
351            // If the host is also Unix, we can use the raw OS error and avoid a potentially lossy
352            // trip through `ErrorKind`.
353            #[cfg(unix)]
354            if let Some(host_errno) = err.raw_os_error() {
355                for &(name, errno) in UNIX_ERRNO_TABLE {
356                    if host_errno == errno {
357                        return interp_ok(this.eval_libc(name));
358                    }
359                }
360            }
361            // For other hosts or other constants, we fall back to translating via `ErrorKind`.
362            for &(name, kind) in UNIX_IO_ERROR_TABLE {
363                if err.kind() == kind {
364                    return interp_ok(this.eval_libc(name));
365                }
366            }
367            throw_unsup_format!("unsupported io error: {err}")
368        } else if target.families.iter().any(|f| f == "windows") {
369            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
370                if err.kind() == kind {
371                    return interp_ok(this.eval_windows("c", name));
372                }
373            }
374            throw_unsup_format!("unsupported io error: {err}");
375        } else {
376            throw_unsup_format!(
377                "converting io::Error into errnum is unsupported for OS {}",
378                target.os
379            )
380        }
381    }
382
383    /// The inverse of `io_error_to_errnum`: it converts target errors to host errors.
384    /// This is used to render such errors as user-visible strings.
385    /// This is done in a best-effort way.
386    #[expect(clippy::needless_return)]
387    fn try_errnum_to_io_error(
388        &self,
389        target_errnum: Scalar,
390    ) -> InterpResult<'tcx, Option<io::Error>> {
391        let this = self.eval_context_ref();
392        let target = &this.tcx.sess.target;
393        if target.families.iter().any(|f| f == "unix") {
394            let target_errnum = target_errnum.to_i32()?;
395            // If the host is also unix, we try to translate the errno directly.
396            // That lets us use `Error::from_raw_os_error`, which has a much better `Display`
397            // impl than what we get by going through `ErrorKind`.
398            #[cfg(unix)]
399            for &(name, errno) in UNIX_ERRNO_TABLE {
400                if target_errnum == this.eval_libc_i32(name) {
401                    return interp_ok(Some(io::Error::from_raw_os_error(errno)));
402                }
403            }
404            // For other hosts or other constants, we fall back to translating via `ErrorKind`.
405            for &(name, kind) in UNIX_IO_ERROR_TABLE {
406                if target_errnum == this.eval_libc_i32(name) {
407                    return interp_ok(Some(kind.into()));
408                }
409            }
410            return interp_ok(None);
411        } else if target.families.iter().any(|f| f == "windows") {
412            let target_errnum = target_errnum.to_u32()?;
413            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
414                if target_errnum == this.eval_windows("c", name).to_u32()? {
415                    return interp_ok(Some(kind.into()));
416                }
417            }
418            return interp_ok(None);
419        } else {
420            throw_unsup_format!(
421                "converting errnum into io::Error is unsupported for OS {}",
422                target.os
423            )
424        }
425    }
426
427    /// Helper function that consumes an `std::io::Result<T>` and returns an
428    /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns
429    /// `Ok(-1)` and sets the last OS error accordingly.
430    ///
431    /// This function uses `T: From<i32>` instead of `i32` directly because some IO related
432    /// functions return different integer types (like `read`, that returns an `i64`).
433    fn try_unwrap_io_result<T: From<i32>>(
434        &mut self,
435        result: std::io::Result<T>,
436    ) -> InterpResult<'tcx, T> {
437        match result {
438            Ok(ok) => interp_ok(ok),
439            Err(e) => {
440                self.eval_context_mut().set_last_error(e)?;
441                interp_ok((-1).into())
442            }
443        }
444    }
445}