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