Skip to main content

rustpython_common/
os.rs

1// spell-checker:disable
2// TODO: we can move more os-specific bindings/interfaces from stdlib::{os, posix, nt} to here
3
4use core::str::Utf8Error;
5use std::{io, process::ExitCode};
6
7/// Convert exit code to std::process::ExitCode
8///
9/// On Windows, this supports the full u32 range including STATUS_CONTROL_C_EXIT (0xC000013A).
10/// On other platforms, only the lower 8 bits are used.
11pub fn exit_code(code: u32) -> ExitCode {
12    #[cfg(windows)]
13    {
14        // For large exit codes like STATUS_CONTROL_C_EXIT (0xC000013A),
15        // we need to call std::process::exit() directly since ExitCode::from(u8)
16        // would truncate the value, and ExitCode::from_raw() is still unstable.
17        // FIXME: side effect in exit_code is not ideal.
18        if code > u8::MAX as u32 {
19            std::process::exit(code as i32)
20        }
21    }
22    ExitCode::from(code as u8)
23}
24
25pub trait ErrorExt {
26    fn posix_errno(&self) -> i32;
27}
28
29impl ErrorExt for io::Error {
30    #[cfg(not(windows))]
31    fn posix_errno(&self) -> i32 {
32        self.raw_os_error().unwrap_or(0)
33    }
34    #[cfg(windows)]
35    fn posix_errno(&self) -> i32 {
36        let winerror = self.raw_os_error().unwrap_or(0);
37        winerror_to_errno(winerror)
38    }
39}
40
41/// Get the last error from C runtime library functions (like _dup, _dup2, _fstat, etc.)
42/// CRT functions set errno, not GetLastError(), so we need to read errno directly.
43#[cfg(windows)]
44pub fn errno_io_error() -> io::Error {
45    let errno: i32 = get_errno();
46    let winerror = errno_to_winerror(errno);
47    io::Error::from_raw_os_error(winerror)
48}
49
50#[cfg(not(windows))]
51pub fn errno_io_error() -> io::Error {
52    std::io::Error::last_os_error()
53}
54
55#[cfg(windows)]
56pub fn get_errno() -> i32 {
57    unsafe extern "C" {
58        fn _get_errno(pValue: *mut i32) -> i32;
59    }
60    let mut errno = 0;
61    unsafe { suppress_iph!(_get_errno(&mut errno)) };
62    errno
63}
64
65#[cfg(not(windows))]
66pub fn get_errno() -> i32 {
67    std::io::Error::last_os_error().posix_errno()
68}
69
70/// Set errno to the specified value.
71#[cfg(windows)]
72pub fn set_errno(value: i32) {
73    unsafe extern "C" {
74        fn _set_errno(value: i32) -> i32;
75    }
76    unsafe { suppress_iph!(_set_errno(value)) };
77}
78
79#[cfg(unix)]
80pub fn set_errno(value: i32) {
81    nix::errno::Errno::from_raw(value).set();
82}
83
84#[cfg(unix)]
85pub fn bytes_as_os_str(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> {
86    use std::os::unix::ffi::OsStrExt;
87    Ok(std::ffi::OsStr::from_bytes(b))
88}
89
90#[cfg(not(unix))]
91pub fn bytes_as_os_str(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> {
92    Ok(core::str::from_utf8(b)?.as_ref())
93}
94
95#[cfg(unix)]
96pub use std::os::unix::ffi;
97
98// WASIp1 uses stable std::os::wasi::ffi
99#[cfg(all(target_os = "wasi", not(target_env = "p2")))]
100pub use std::os::wasi::ffi;
101
102// WASIp2: std::os::wasip2::ffi is unstable, so we provide a stable implementation
103// leveraging WASI's UTF-8 string guarantee
104#[cfg(all(target_os = "wasi", target_env = "p2"))]
105pub mod ffi {
106    use std::ffi::{OsStr, OsString};
107
108    pub trait OsStrExt: sealed::Sealed {
109        fn as_bytes(&self) -> &[u8];
110        fn from_bytes(slice: &[u8]) -> &Self;
111    }
112
113    impl OsStrExt for OsStr {
114        fn as_bytes(&self) -> &[u8] {
115            // WASI strings are guaranteed to be UTF-8
116            self.to_str().expect("wasip2 strings are UTF-8").as_bytes()
117        }
118
119        fn from_bytes(slice: &[u8]) -> &OsStr {
120            // WASI strings are guaranteed to be UTF-8
121            OsStr::new(core::str::from_utf8(slice).expect("wasip2 strings are UTF-8"))
122        }
123    }
124
125    pub trait OsStringExt: sealed::Sealed {
126        fn from_vec(vec: Vec<u8>) -> Self;
127        fn into_vec(self) -> Vec<u8>;
128    }
129
130    impl OsStringExt for OsString {
131        fn from_vec(vec: Vec<u8>) -> OsString {
132            // WASI strings are guaranteed to be UTF-8
133            OsString::from(String::from_utf8(vec).expect("wasip2 strings are UTF-8"))
134        }
135
136        fn into_vec(self) -> Vec<u8> {
137            // WASI strings are guaranteed to be UTF-8
138            self.to_str()
139                .expect("wasip2 strings are UTF-8")
140                .as_bytes()
141                .to_vec()
142        }
143    }
144
145    mod sealed {
146        pub trait Sealed {}
147        impl Sealed for std::ffi::OsStr {}
148        impl Sealed for std::ffi::OsString {}
149    }
150}
151
152#[cfg(windows)]
153pub fn errno_to_winerror(errno: i32) -> i32 {
154    use libc::*;
155    use windows_sys::Win32::Foundation::*;
156    let winerror = match errno {
157        ENOENT => ERROR_FILE_NOT_FOUND,
158        E2BIG => ERROR_BAD_ENVIRONMENT,
159        ENOEXEC => ERROR_BAD_FORMAT,
160        EBADF => ERROR_INVALID_HANDLE,
161        ECHILD => ERROR_WAIT_NO_CHILDREN,
162        EAGAIN => ERROR_NO_PROC_SLOTS,
163        ENOMEM => ERROR_NOT_ENOUGH_MEMORY,
164        EACCES => ERROR_ACCESS_DENIED,
165        EEXIST => ERROR_FILE_EXISTS,
166        EXDEV => ERROR_NOT_SAME_DEVICE,
167        ENOTDIR => ERROR_DIRECTORY,
168        EMFILE => ERROR_TOO_MANY_OPEN_FILES,
169        ENOSPC => ERROR_DISK_FULL,
170        EPIPE => ERROR_BROKEN_PIPE,
171        ENOTEMPTY => ERROR_DIR_NOT_EMPTY,
172        EILSEQ => ERROR_NO_UNICODE_TRANSLATION,
173        EINVAL => ERROR_INVALID_FUNCTION,
174        _ => ERROR_INVALID_FUNCTION,
175    };
176    winerror as _
177}
178
179// winerror: https://learn.microsoft.com/windows/win32/debug/system-error-codes--0-499-
180// errno: https://learn.microsoft.com/cpp/c-runtime-library/errno-constants?view=msvc-170
181#[cfg(windows)]
182pub fn winerror_to_errno(winerror: i32) -> i32 {
183    use libc::*;
184    use windows_sys::Win32::{
185        Foundation::*,
186        Networking::WinSock::{
187            WSAEACCES, WSAEBADF, WSAECONNABORTED, WSAECONNREFUSED, WSAECONNRESET, WSAEFAULT,
188            WSAEINTR, WSAEINVAL, WSAEMFILE,
189        },
190    };
191    // Unwrap FACILITY_WIN32 HRESULT errors.
192    // if ((winerror & 0xFFFF0000) == 0x80070000) {
193    //     winerror &= 0x0000FFFF;
194    // }
195
196    // Winsock error codes (10000-11999) are errno values.
197    if (10000..12000).contains(&winerror) {
198        match winerror {
199            WSAEINTR | WSAEBADF | WSAEACCES | WSAEFAULT | WSAEINVAL | WSAEMFILE => {
200                // Winsock definitions of errno values. See WinSock2.h
201                return winerror - 10000;
202            }
203            _ => return winerror as _,
204        }
205    }
206
207    #[allow(non_upper_case_globals)]
208    match winerror as u32 {
209        ERROR_FILE_NOT_FOUND
210        | ERROR_PATH_NOT_FOUND
211        | ERROR_INVALID_DRIVE
212        | ERROR_NO_MORE_FILES
213        | ERROR_BAD_NETPATH
214        | ERROR_BAD_NET_NAME
215        | ERROR_BAD_PATHNAME
216        | ERROR_FILENAME_EXCED_RANGE => ENOENT,
217        ERROR_BAD_ENVIRONMENT => E2BIG,
218        ERROR_BAD_FORMAT
219        | ERROR_INVALID_STARTING_CODESEG
220        | ERROR_INVALID_STACKSEG
221        | ERROR_INVALID_MODULETYPE
222        | ERROR_INVALID_EXE_SIGNATURE
223        | ERROR_EXE_MARKED_INVALID
224        | ERROR_BAD_EXE_FORMAT
225        | ERROR_ITERATED_DATA_EXCEEDS_64k
226        | ERROR_INVALID_MINALLOCSIZE
227        | ERROR_DYNLINK_FROM_INVALID_RING
228        | ERROR_IOPL_NOT_ENABLED
229        | ERROR_INVALID_SEGDPL
230        | ERROR_AUTODATASEG_EXCEEDS_64k
231        | ERROR_RING2SEG_MUST_BE_MOVABLE
232        | ERROR_RELOC_CHAIN_XEEDS_SEGLIM
233        | ERROR_INFLOOP_IN_RELOC_CHAIN => ENOEXEC,
234        ERROR_INVALID_HANDLE | ERROR_INVALID_TARGET_HANDLE | ERROR_DIRECT_ACCESS_HANDLE => EBADF,
235        ERROR_WAIT_NO_CHILDREN | ERROR_CHILD_NOT_COMPLETE => ECHILD,
236        ERROR_NO_PROC_SLOTS | ERROR_MAX_THRDS_REACHED | ERROR_NESTING_NOT_ALLOWED => EAGAIN,
237        ERROR_ARENA_TRASHED
238        | ERROR_NOT_ENOUGH_MEMORY
239        | ERROR_INVALID_BLOCK
240        | ERROR_NOT_ENOUGH_QUOTA => ENOMEM,
241        ERROR_ACCESS_DENIED
242        | ERROR_CURRENT_DIRECTORY
243        | ERROR_WRITE_PROTECT
244        | ERROR_BAD_UNIT
245        | ERROR_NOT_READY
246        | ERROR_BAD_COMMAND
247        | ERROR_CRC
248        | ERROR_BAD_LENGTH
249        | ERROR_SEEK
250        | ERROR_NOT_DOS_DISK
251        | ERROR_SECTOR_NOT_FOUND
252        | ERROR_OUT_OF_PAPER
253        | ERROR_WRITE_FAULT
254        | ERROR_READ_FAULT
255        | ERROR_GEN_FAILURE
256        | ERROR_SHARING_VIOLATION
257        | ERROR_LOCK_VIOLATION
258        | ERROR_WRONG_DISK
259        | ERROR_SHARING_BUFFER_EXCEEDED
260        | ERROR_NETWORK_ACCESS_DENIED
261        | ERROR_CANNOT_MAKE
262        | ERROR_FAIL_I24
263        | ERROR_DRIVE_LOCKED
264        | ERROR_SEEK_ON_DEVICE
265        | ERROR_NOT_LOCKED
266        | ERROR_LOCK_FAILED
267        | 35 => EACCES,
268        ERROR_FILE_EXISTS | ERROR_ALREADY_EXISTS => EEXIST,
269        ERROR_NOT_SAME_DEVICE => EXDEV,
270        ERROR_DIRECTORY => ENOTDIR,
271        ERROR_TOO_MANY_OPEN_FILES => EMFILE,
272        ERROR_DISK_FULL => ENOSPC,
273        ERROR_BROKEN_PIPE | ERROR_NO_DATA => EPIPE,
274        ERROR_DIR_NOT_EMPTY => ENOTEMPTY,
275        ERROR_NO_UNICODE_TRANSLATION => EILSEQ,
276        // Connection-related Windows error codes - map to Winsock error codes
277        // which Python uses on Windows (errno.ECONNREFUSED = 10061, etc.)
278        ERROR_CONNECTION_REFUSED => WSAECONNREFUSED,
279        ERROR_CONNECTION_ABORTED => WSAECONNABORTED,
280        ERROR_NETNAME_DELETED => WSAECONNRESET,
281        ERROR_INVALID_FUNCTION
282        | ERROR_INVALID_ACCESS
283        | ERROR_INVALID_DATA
284        | ERROR_INVALID_PARAMETER
285        | ERROR_NEGATIVE_SEEK => EINVAL,
286        _ => EINVAL,
287    }
288}