rustpython_host_env/
io.rs1#[cfg(any(unix, target_os = "wasi"))]
2use core::ffi::CStr;
3use std::io;
4
5#[cfg(any(unix, target_os = "wasi"))]
6use rustix::{fs::FileType, io::Errno};
7
8#[cfg(any(unix, target_os = "wasi"))]
9use crate::fileutils;
10use crate::{crt_fd, os};
11
12bitflagset::bitflag! {
13 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
14 #[repr(u8)]
15 pub enum FileModeFlag {
16 Created = 0,
17 Readable = 1,
18 Writable = 2,
19 Appending = 3,
20 }
21}
22
23bitflagset::bitflagset! {
24 #[derive(Copy, Clone, PartialEq, Eq)]
25 pub struct FileMode(u8): FileModeFlag
26}
27
28impl FileMode {
29 pub const CREATED: Self = Self::from_element(FileModeFlag::Created);
30 pub const READABLE: Self = Self::from_element(FileModeFlag::Readable);
31 pub const WRITABLE: Self = Self::from_element(FileModeFlag::Writable);
32 pub const APPENDING: Self = Self::from_element(FileModeFlag::Appending);
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum FileModeError {
37 Invalid,
38 BadRwa,
39}
40
41impl FileModeError {
42 pub fn error_msg(self, mode_str: &str) -> String {
43 match self {
44 Self::Invalid => format!("invalid mode: {mode_str}"),
45 Self::BadRwa => {
46 "Must have exactly one of create/read/write/append mode and at most one plus"
47 .to_owned()
48 }
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
54pub struct ParsedFileMode {
55 pub mode: FileMode,
56 pub flags: i32,
57}
58
59impl FileMode {
60 pub const fn raw_mode(self) -> &'static str {
61 if self.contains(&FileModeFlag::Created) {
62 if self.contains(&FileModeFlag::Readable) {
63 "xb+"
64 } else {
65 "xb"
66 }
67 } else if self.contains(&FileModeFlag::Appending) {
68 if self.contains(&FileModeFlag::Readable) {
69 "ab+"
70 } else {
71 "ab"
72 }
73 } else if self.contains(&FileModeFlag::Readable) {
74 if self.contains(&FileModeFlag::Writable) {
75 "rb+"
76 } else {
77 "rb"
78 }
79 } else {
80 "wb"
81 }
82 }
83}
84
85pub fn parse_fileio_mode(mode_str: &str) -> Result<ParsedFileMode, FileModeError> {
86 let mut flags = 0;
87 let mut plus = false;
88 let mut rwa = false;
89 let mut mode = FileMode::empty();
90 for c in mode_str.bytes() {
91 match c {
92 b'x' => {
93 if rwa {
94 return Err(FileModeError::BadRwa);
95 }
96 rwa = true;
97 mode |= FileMode::WRITABLE | FileMode::CREATED;
98 flags |= os::O_EXCL | os::O_CREAT;
99 }
100 b'r' => {
101 if rwa {
102 return Err(FileModeError::BadRwa);
103 }
104 rwa = true;
105 mode |= FileMode::READABLE;
106 }
107 b'w' => {
108 if rwa {
109 return Err(FileModeError::BadRwa);
110 }
111 rwa = true;
112 mode |= FileMode::WRITABLE;
113 flags |= os::O_CREAT | os::O_TRUNC;
114 }
115 b'a' => {
116 if rwa {
117 return Err(FileModeError::BadRwa);
118 }
119 rwa = true;
120 mode |= FileMode::WRITABLE | FileMode::APPENDING;
121 flags |= os::O_APPEND | os::O_CREAT;
122 }
123 b'+' => {
124 if plus {
125 return Err(FileModeError::BadRwa);
126 }
127 plus = true;
128 mode |= FileMode::READABLE | FileMode::WRITABLE;
129 }
130 b'b' => {}
131 _ => return Err(FileModeError::Invalid),
132 }
133 }
134
135 if !rwa {
136 return Err(FileModeError::BadRwa);
137 }
138
139 if mode.is_superset(&(FileMode::READABLE | FileMode::WRITABLE)) {
140 flags |= os::O_RDWR;
141 } else if mode.contains(&FileModeFlag::Readable) {
142 flags |= os::O_RDONLY;
143 } else {
144 flags |= os::O_WRONLY;
145 }
146
147 #[cfg(windows)]
148 {
149 flags |= os::O_BINARY | os::O_NOINHERIT;
150 }
151 #[cfg(unix)]
152 {
153 flags |= os::O_CLOEXEC;
154 }
155
156 Ok(ParsedFileMode { mode, flags })
157}
158
159#[derive(Clone, Copy, Debug)]
160pub struct FileTargetInfo {
161 pub blksize: Option<i64>,
162}
163
164#[cfg(any(unix, target_os = "wasi"))]
165pub fn inspect_file_target(fd: crt_fd::Borrowed<'_>) -> io::Result<FileTargetInfo> {
166 let status = fileutils::fstat(fd)?;
167 if FileType::from_raw_mode(status.st_mode).is_dir() {
168 return Err(io::Error::from(Errno::ISDIR));
169 }
170 #[allow(clippy::useless_conversion, reason = "needed for 32-bit platforms")]
171 let blksize = (status.st_blksize > 1).then(|| i64::from(status.st_blksize));
172 Ok(FileTargetInfo { blksize })
173}
174
175#[cfg(windows)]
176pub fn inspect_file_target(fd: crt_fd::Borrowed<'_>) -> io::Result<FileTargetInfo> {
177 if !crate::nt::fd_exists(fd) {
178 return Err(io::Error::from_raw_os_error(
179 crate::nt::ERROR_INVALID_HANDLE_I32,
180 ));
181 }
182 Ok(FileTargetInfo { blksize: None })
183}
184
185#[cfg(any(unix, target_os = "wasi"))]
186pub fn open_path(path: &CStr, flags: i32, mode: i32) -> io::Result<crt_fd::Owned> {
187 crt_fd::open(path, flags, mode)
188}
189
190#[cfg(windows)]
191pub fn open_path(path: &widestring::WideCStr, flags: i32, mode: i32) -> io::Result<crt_fd::Owned> {
192 crt_fd::wopen(path, flags, mode)
193}
194
195#[cfg(windows)]
196pub fn should_forget_fd_after_inspect_error(err: &io::Error, _fd_is_own: bool) -> bool {
197 err.raw_os_error() == Some(crate::nt::ERROR_INVALID_HANDLE_I32)
198}
199
200#[cfg(any(unix, target_os = "wasi"))]
201pub fn should_forget_fd_after_inspect_error(err: &io::Error, fd_is_own: bool) -> bool {
202 let errno = err.raw_os_error();
203 (errno == Some(libc::EISDIR) || errno == Some(libc::EBADF))
204 && (!fd_is_own || errno == Some(libc::EBADF))
205}
206
207pub fn seek_to_end(fd: crt_fd::Borrowed<'_>) -> io::Result<crt_fd::Offset> {
208 os::seek_fd(fd, 0, libc::SEEK_END)
209}
210
211pub fn is_seekable(fd: crt_fd::Borrowed<'_>) -> bool {
212 os::seek_fd(fd, 0, libc::SEEK_CUR).is_ok()
213}
214
215#[cfg(not(windows))]
223pub fn reads_without_waiting(fd: crt_fd::Borrowed<'_>) -> bool {
224 is_seekable(fd)
225}
226
227#[cfg(windows)]
228pub fn reads_without_waiting(fd: crt_fd::Borrowed<'_>) -> bool {
229 use std::os::windows::io::AsRawHandle;
230 use windows_sys::Win32::Storage::FileSystem::{FILE_TYPE_DISK, GetFileType};
231
232 let Ok(handle) = crt_fd::as_handle(fd) else {
233 return false;
234 };
235 unsafe { GetFileType(handle.as_raw_handle() as _) == FILE_TYPE_DISK }
236}
237
238pub fn validate_whence(whence: i32) -> bool {
239 let standard = (0..=2).contains(&whence);
240 #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))]
241 {
242 standard || matches!(whence, libc::SEEK_DATA | libc::SEEK_HOLE)
243 }
244 #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux")))]
245 {
246 standard
247 }
248}
249
250pub fn is_interrupted_errno(errno: i32) -> bool {
251 errno == libc::EINTR
252}
253
254pub fn is_interrupted_error(err: &io::Error) -> bool {
255 err.raw_os_error() == Some(libc::EINTR)
256}
257
258pub fn is_would_block_error(err: &io::Error) -> bool {
259 err.kind() == io::ErrorKind::WouldBlock || err.raw_os_error() == Some(libc::EAGAIN)
260}
261
262pub fn seek(
263 fd: crt_fd::Borrowed<'_>,
264 offset: crt_fd::Offset,
265 how: i32,
266) -> io::Result<crt_fd::Offset> {
267 os::seek_fd(fd, offset, how)
268}
269
270pub fn tell(fd: crt_fd::Borrowed<'_>) -> io::Result<crt_fd::Offset> {
271 os::seek_fd(fd, 0, libc::SEEK_CUR)
272}
273
274pub fn isatty(fd: i32) -> bool {
275 os::isatty(fd)
276}
277
278pub fn read_once(fd: crt_fd::Borrowed<'_>, buf: &mut [u8]) -> io::Result<usize> {
279 crt_fd::read(fd, buf)
280}
281
282pub fn read_all(fd: crt_fd::Borrowed<'_>, out: &mut Vec<u8>) -> io::Result<()> {
283 let mut fd = fd;
284 std::io::Read::read_to_end(&mut fd, out).map(|_| ())
285}
286
287pub fn write_once(fd: crt_fd::Borrowed<'_>, buf: &[u8]) -> io::Result<usize> {
288 crt_fd::write(fd, buf)
289}
290
291pub fn close_owned_fd(fd: crt_fd::Owned) -> io::Result<()> {
292 crt_fd::close(fd)
293}
294
295#[cfg(unix)]
299pub fn write_stderr_raw(buf: &[u8]) {
300 unsafe {
301 let _ = libc::write(libc::STDERR_FILENO, buf.as_ptr().cast(), buf.len());
302 }
303}