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_FILE_EXISTS", AlreadyExists),
206        ("ERROR_ALREADY_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(&self, err: impl Into<IoError>) -> InterpResult<'tcx, Scalar> {
288        let this = self.eval_context_ref();
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    fn return_io_result_via_errno<T: Into<i128>, E: Into<IoError>>(
318        &mut self,
319        res: Result<T, E>,
320        dest: &MPlaceTy<'tcx>,
321    ) -> InterpResult<'tcx> {
322        let ecx = self.eval_context_mut();
323        match res {
324            Ok(ret) => ecx.write_int(ret, dest),
325            Err(err) => ecx.set_errno_and_return_neg1(err, dest),
326        }
327    }
328
329    /// Sets the last OS error and return `-1` as a `i32`-typed Scalar
330    fn set_errno_and_return_neg1_i32(
331        &mut self,
332        err: impl Into<IoError>,
333    ) -> InterpResult<'tcx, Scalar> {
334        let this = self.eval_context_mut();
335        this.set_last_error(err)?;
336        interp_ok(Scalar::from_i32(-1))
337    }
338
339    /// Sets the last OS error and return `-1` as a `i64`-typed Scalar
340    fn set_errno_and_return_neg1_i64(
341        &mut self,
342        err: impl Into<IoError>,
343    ) -> InterpResult<'tcx, Scalar> {
344        let this = self.eval_context_mut();
345        this.set_last_error(err)?;
346        interp_ok(Scalar::from_i64(-1))
347    }
348
349    /// Gets the last error variable.
350    fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> {
351        let this = self.eval_context_mut();
352        let errno_place = this.last_error_place()?;
353        this.read_scalar(&errno_place)
354    }
355
356    /// This function converts host errors to target errors. It tries to produce the most similar OS
357    /// error from the `std::io::ErrorKind` as a platform-specific errnum.
358    fn host_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> {
359        let this = self.eval_context_ref();
360        let target = &this.tcx.sess.target;
361
362        if target.families.iter().any(|f| f == "unix") {
363            // If the host is also Unix, we can use the raw OS error and avoid a potentially lossy
364            // trip through `ErrorKind`.
365            #[cfg(unix)]
366            if let Some(host_errno) = err.raw_os_error() {
367                for &(name, errno) in UNIX_ERRNO_TABLE {
368                    if host_errno == errno {
369                        return interp_ok(this.eval_libc(name));
370                    }
371                }
372            }
373            // For other hosts or other constants, we fall back to translating via `ErrorKind`.
374            for &(name, kind) in UNIX_IO_ERROR_TABLE {
375                if err.kind() == kind {
376                    return interp_ok(this.eval_libc(name));
377                }
378            }
379            throw_unsup_format!("unsupported io error: {err}")
380        } else if target.families.iter().any(|f| f == "windows") {
381            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
382                if err.kind() == kind {
383                    return interp_ok(this.eval_windows("c", name));
384                }
385            }
386            throw_unsup_format!("unsupported io error: {err}");
387        } else {
388            throw_unsup_format!(
389                "converting io::Error into errnum is unsupported for OS {}",
390                target.os
391            )
392        }
393    }
394
395    /// The inverse of `io_error_to_errnum`: it converts target errors to host errors.
396    /// This is used to render such errors as user-visible strings.
397    /// This is done in a best-effort way.
398    #[expect(clippy::needless_return)]
399    fn try_errnum_to_io_error(
400        &self,
401        target_errnum: Scalar,
402    ) -> InterpResult<'tcx, Option<io::Error>> {
403        let this = self.eval_context_ref();
404        let target = &this.tcx.sess.target;
405        if target.families.iter().any(|f| f == "unix") {
406            let target_errnum = target_errnum.to_i32()?;
407            // If the host is also unix, we try to translate the errno directly.
408            // That lets us use `Error::from_raw_os_error`, which has a much better `Display`
409            // impl than what we get by going through `ErrorKind`.
410            #[cfg(unix)]
411            for &(name, errno) in UNIX_ERRNO_TABLE {
412                if target_errnum == this.eval_libc_i32(name) {
413                    return interp_ok(Some(io::Error::from_raw_os_error(errno)));
414                }
415            }
416            // For other hosts or other constants, we fall back to translating via `ErrorKind`.
417            for &(name, kind) in UNIX_IO_ERROR_TABLE {
418                if target_errnum == this.eval_libc_i32(name) {
419                    return interp_ok(Some(kind.into()));
420                }
421            }
422            return interp_ok(None);
423        } else if target.families.iter().any(|f| f == "windows") {
424            let target_errnum = target_errnum.to_u32()?;
425            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
426                if target_errnum == this.eval_windows("c", name).to_u32()? {
427                    return interp_ok(Some(kind.into()));
428                }
429            }
430            return interp_ok(None);
431        } else {
432            throw_unsup_format!(
433                "converting errnum into io::Error is unsupported for OS {}",
434                target.os
435            )
436        }
437    }
438
439    /// Helper function that consumes an `std::io::Result<T>` and returns an
440    /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns
441    /// `Ok(-1)` and sets the last OS error accordingly.
442    ///
443    /// This function uses `T: From<i32>` instead of `i32` directly because some IO related
444    /// functions return different integer types (like `read`, that returns an `i64`).
445    fn try_unwrap_io_result<T: From<i32>>(
446        &mut self,
447        result: std::io::Result<T>,
448    ) -> InterpResult<'tcx, T> {
449        match result {
450            Ok(ok) => interp_ok(ok),
451            Err(e) => {
452                self.eval_context_mut().set_last_error(e)?;
453                interp_ok((-1).into())
454            }
455        }
456    }
457}