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