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