miri/shims/
io_error.rs

1use std::io;
2
3use crate::*;
4
5/// A representation of an IO error: either a libc error name,
6/// or a host error.
7#[derive(Debug)]
8pub enum IoError {
9    LibcError(&'static str),
10    WindowsError(&'static str),
11    HostError(io::Error),
12    Raw(Scalar),
13}
14pub use self::IoError::*;
15
16impl From<io::Error> for IoError {
17    fn from(value: io::Error) -> Self {
18        IoError::HostError(value)
19    }
20}
21
22impl From<io::ErrorKind> for IoError {
23    fn from(value: io::ErrorKind) -> Self {
24        IoError::HostError(value.into())
25    }
26}
27
28impl From<Scalar> for IoError {
29    fn from(value: Scalar) -> Self {
30        IoError::Raw(value)
31    }
32}
33
34// This mapping should match `decode_error_kind` in
35// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/unix/mod.rs>.
36const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
37    use std::io::ErrorKind::*;
38    &[
39        ("E2BIG", ArgumentListTooLong),
40        ("EADDRINUSE", AddrInUse),
41        ("EADDRNOTAVAIL", AddrNotAvailable),
42        ("EBUSY", ResourceBusy),
43        ("ECONNABORTED", ConnectionAborted),
44        ("ECONNREFUSED", ConnectionRefused),
45        ("ECONNRESET", ConnectionReset),
46        ("EDEADLK", Deadlock),
47        ("EDQUOT", QuotaExceeded),
48        ("EEXIST", AlreadyExists),
49        ("EFBIG", FileTooLarge),
50        ("EHOSTUNREACH", HostUnreachable),
51        ("EINTR", Interrupted),
52        ("EINVAL", InvalidInput),
53        ("EISDIR", IsADirectory),
54        ("ELOOP", FilesystemLoop),
55        ("ENOENT", NotFound),
56        ("ENOMEM", OutOfMemory),
57        ("ENOSPC", StorageFull),
58        ("ENOSYS", Unsupported),
59        ("EMLINK", TooManyLinks),
60        ("ENAMETOOLONG", InvalidFilename),
61        ("ENETDOWN", NetworkDown),
62        ("ENETUNREACH", NetworkUnreachable),
63        ("ENOTCONN", NotConnected),
64        ("ENOTDIR", NotADirectory),
65        ("ENOTEMPTY", DirectoryNotEmpty),
66        ("EPIPE", BrokenPipe),
67        ("EROFS", ReadOnlyFilesystem),
68        ("ESPIPE", NotSeekable),
69        ("ESTALE", StaleNetworkFileHandle),
70        ("ETIMEDOUT", TimedOut),
71        ("ETXTBSY", ExecutableFileBusy),
72        ("EXDEV", CrossesDevices),
73        // The following have two valid options. We have both for the forwards mapping; only the
74        // first one will be used for the backwards mapping.
75        ("EPERM", PermissionDenied),
76        ("EACCES", PermissionDenied),
77        ("EWOULDBLOCK", WouldBlock),
78        ("EAGAIN", WouldBlock),
79    ]
80};
81// This mapping should match `decode_error_kind` in
82// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/mod.rs>.
83const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
84    use std::io::ErrorKind::*;
85    // It's common for multiple error codes to map to the same io::ErrorKind. We have all for the
86    // forwards mapping; only the first one will be used for the backwards mapping.
87    // Slightly arbitrarily, we prefer non-WSA and the most generic sounding variant for backwards
88    // mapping.
89    &[
90        ("WSAEADDRINUSE", AddrInUse),
91        ("WSAEADDRNOTAVAIL", AddrNotAvailable),
92        ("ERROR_ALREADY_EXISTS", AlreadyExists),
93        ("ERROR_FILE_EXISTS", AlreadyExists),
94        ("ERROR_NO_DATA", BrokenPipe),
95        ("WSAECONNABORTED", ConnectionAborted),
96        ("WSAECONNREFUSED", ConnectionRefused),
97        ("WSAECONNRESET", ConnectionReset),
98        ("ERROR_NOT_SAME_DEVICE", CrossesDevices),
99        ("ERROR_POSSIBLE_DEADLOCK", Deadlock),
100        ("ERROR_DIR_NOT_EMPTY", DirectoryNotEmpty),
101        ("ERROR_CANT_RESOLVE_FILENAME", FilesystemLoop),
102        ("ERROR_DISK_QUOTA_EXCEEDED", QuotaExceeded),
103        ("WSAEDQUOT", QuotaExceeded),
104        ("ERROR_FILE_TOO_LARGE", FileTooLarge),
105        ("ERROR_HOST_UNREACHABLE", HostUnreachable),
106        ("WSAEHOSTUNREACH", HostUnreachable),
107        ("ERROR_INVALID_NAME", InvalidFilename),
108        ("ERROR_BAD_PATHNAME", InvalidFilename),
109        ("ERROR_FILENAME_EXCED_RANGE", InvalidFilename),
110        ("ERROR_INVALID_PARAMETER", InvalidInput),
111        ("WSAEINVAL", InvalidInput),
112        ("ERROR_DIRECTORY_NOT_SUPPORTED", IsADirectory),
113        ("WSAENETDOWN", NetworkDown),
114        ("ERROR_NETWORK_UNREACHABLE", NetworkUnreachable),
115        ("WSAENETUNREACH", NetworkUnreachable),
116        ("ERROR_DIRECTORY", NotADirectory),
117        ("WSAENOTCONN", NotConnected),
118        ("ERROR_FILE_NOT_FOUND", NotFound),
119        ("ERROR_PATH_NOT_FOUND", NotFound),
120        ("ERROR_INVALID_DRIVE", NotFound),
121        ("ERROR_BAD_NETPATH", NotFound),
122        ("ERROR_BAD_NET_NAME", NotFound),
123        ("ERROR_SEEK_ON_DEVICE", NotSeekable),
124        ("ERROR_NOT_ENOUGH_MEMORY", OutOfMemory),
125        ("ERROR_OUTOFMEMORY", OutOfMemory),
126        ("ERROR_ACCESS_DENIED", PermissionDenied),
127        ("WSAEACCES", PermissionDenied),
128        ("ERROR_WRITE_PROTECT", ReadOnlyFilesystem),
129        ("ERROR_BUSY", ResourceBusy),
130        ("ERROR_DISK_FULL", StorageFull),
131        ("ERROR_HANDLE_DISK_FULL", StorageFull),
132        ("WAIT_TIMEOUT", TimedOut),
133        ("WSAETIMEDOUT", TimedOut),
134        ("ERROR_DRIVER_CANCEL_TIMEOUT", TimedOut),
135        ("ERROR_OPERATION_ABORTED", TimedOut),
136        ("ERROR_SERVICE_REQUEST_TIMEOUT", TimedOut),
137        ("ERROR_COUNTER_TIMEOUT", TimedOut),
138        ("ERROR_TIMEOUT", TimedOut),
139        ("ERROR_RESOURCE_CALL_TIMED_OUT", TimedOut),
140        ("ERROR_CTX_MODEM_RESPONSE_TIMEOUT", TimedOut),
141        ("ERROR_CTX_CLIENT_QUERY_TIMEOUT", TimedOut),
142        ("FRS_ERR_SYSVOL_POPULATE_TIMEOUT", TimedOut),
143        ("ERROR_DS_TIMELIMIT_EXCEEDED", TimedOut),
144        ("DNS_ERROR_RECORD_TIMED_OUT", TimedOut),
145        ("ERROR_IPSEC_IKE_TIMED_OUT", TimedOut),
146        ("ERROR_RUNLEVEL_SWITCH_TIMEOUT", TimedOut),
147        ("ERROR_RUNLEVEL_SWITCH_AGENT_TIMEOUT", TimedOut),
148        ("ERROR_TOO_MANY_LINKS", TooManyLinks),
149        ("ERROR_CALL_NOT_IMPLEMENTED", Unsupported),
150        ("WSAEWOULDBLOCK", WouldBlock),
151    ]
152};
153
154impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
155pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
156    /// Get last error variable as a place, lazily allocating thread-local storage for it if
157    /// necessary.
158    fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
159        let this = self.eval_context_mut();
160        if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() {
161            interp_ok(errno_place.clone())
162        } else {
163            // Allocate new place, set initial value to 0.
164            let errno_layout = this.machine.layouts.u32;
165            let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?;
166            this.write_scalar(Scalar::from_u32(0), &errno_place)?;
167            this.active_thread_mut().last_error = Some(errno_place.clone());
168            interp_ok(errno_place)
169        }
170    }
171
172    /// Sets the last error variable.
173    fn set_last_error(&mut self, err: impl Into<IoError>) -> InterpResult<'tcx> {
174        let this = self.eval_context_mut();
175        let errno = match err.into() {
176            HostError(err) => this.io_error_to_errnum(err)?,
177            LibcError(name) => this.eval_libc(name),
178            WindowsError(name) => this.eval_windows("c", name),
179            Raw(val) => val,
180        };
181        let errno_place = this.last_error_place()?;
182        this.write_scalar(errno, &errno_place)
183    }
184
185    /// Sets the last OS error and writes -1 to dest place.
186    fn set_last_error_and_return(
187        &mut self,
188        err: impl Into<IoError>,
189        dest: &MPlaceTy<'tcx>,
190    ) -> InterpResult<'tcx> {
191        let this = self.eval_context_mut();
192        this.set_last_error(err)?;
193        this.write_int(-1, dest)?;
194        interp_ok(())
195    }
196
197    /// Sets the last OS error and return `-1` as a `i32`-typed Scalar
198    fn set_last_error_and_return_i32(
199        &mut self,
200        err: impl Into<IoError>,
201    ) -> InterpResult<'tcx, Scalar> {
202        let this = self.eval_context_mut();
203        this.set_last_error(err)?;
204        interp_ok(Scalar::from_i32(-1))
205    }
206
207    /// Sets the last OS error and return `-1` as a `i64`-typed Scalar
208    fn set_last_error_and_return_i64(
209        &mut self,
210        err: impl Into<IoError>,
211    ) -> InterpResult<'tcx, Scalar> {
212        let this = self.eval_context_mut();
213        this.set_last_error(err)?;
214        interp_ok(Scalar::from_i64(-1))
215    }
216
217    /// Gets the last error variable.
218    fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> {
219        let this = self.eval_context_mut();
220        let errno_place = this.last_error_place()?;
221        this.read_scalar(&errno_place)
222    }
223
224    /// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
225    /// as a platform-specific errnum.
226    fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> {
227        let this = self.eval_context_ref();
228        let target = &this.tcx.sess.target;
229        if target.families.iter().any(|f| f == "unix") {
230            for &(name, kind) in UNIX_IO_ERROR_TABLE {
231                if err.kind() == kind {
232                    return interp_ok(this.eval_libc(name));
233                }
234            }
235            throw_unsup_format!("unsupported io error: {err}")
236        } else if target.families.iter().any(|f| f == "windows") {
237            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
238                if err.kind() == kind {
239                    return interp_ok(this.eval_windows("c", name));
240                }
241            }
242            throw_unsup_format!("unsupported io error: {err}");
243        } else {
244            throw_unsup_format!(
245                "converting io::Error into errnum is unsupported for OS {}",
246                target.os
247            )
248        }
249    }
250
251    /// The inverse of `io_error_to_errnum`.
252    #[expect(clippy::needless_return)]
253    fn try_errnum_to_io_error(
254        &self,
255        errnum: Scalar,
256    ) -> InterpResult<'tcx, Option<std::io::ErrorKind>> {
257        let this = self.eval_context_ref();
258        let target = &this.tcx.sess.target;
259        if target.families.iter().any(|f| f == "unix") {
260            let errnum = errnum.to_i32()?;
261            for &(name, kind) in UNIX_IO_ERROR_TABLE {
262                if errnum == this.eval_libc_i32(name) {
263                    return interp_ok(Some(kind));
264                }
265            }
266            return interp_ok(None);
267        } else if target.families.iter().any(|f| f == "windows") {
268            let errnum = errnum.to_u32()?;
269            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
270                if errnum == this.eval_windows("c", name).to_u32()? {
271                    return interp_ok(Some(kind));
272                }
273            }
274            return interp_ok(None);
275        } else {
276            throw_unsup_format!(
277                "converting errnum into io::Error is unsupported for OS {}",
278                target.os
279            )
280        }
281    }
282
283    /// Helper function that consumes an `std::io::Result<T>` and returns an
284    /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns
285    /// `Ok(-1)` and sets the last OS error accordingly.
286    ///
287    /// This function uses `T: From<i32>` instead of `i32` directly because some IO related
288    /// functions return different integer types (like `read`, that returns an `i64`).
289    fn try_unwrap_io_result<T: From<i32>>(
290        &mut self,
291        result: std::io::Result<T>,
292    ) -> InterpResult<'tcx, T> {
293        match result {
294            Ok(ok) => interp_ok(ok),
295            Err(e) => {
296                self.eval_context_mut().set_last_error(e)?;
297                interp_ok((-1).into())
298            }
299        }
300    }
301}