Skip to main content

rustpython_host_env/
crt_fd.rs

1//! A module implementing an io type backed by the C runtime's file descriptors, i.e. what's
2//! returned from libc::open, even on windows.
3
4use alloc::fmt;
5use core::cmp;
6use std::{ffi, io};
7
8#[cfg(any(unix, target_os = "wasi"))]
9use std::os::fd::AsFd;
10#[cfg(not(windows))]
11use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
12#[cfg(windows)]
13use std::os::windows::io::BorrowedHandle;
14
15mod c {
16    pub(super) use libc::*;
17
18    #[cfg(windows)]
19    pub(super) use libc::commit as fsync;
20    #[cfg(windows)]
21    unsafe extern "C" {
22        #[link_name = "_chsize_s"]
23        pub(super) fn ftruncate(fd: i32, len: i64) -> i32;
24    }
25}
26
27// this is basically what CPython has for Py_off_t; windows uses long long
28// for offsets, other platforms just use off_t
29pub type Offset = cfg_select! {
30    windows => c::c_longlong,
31    _ => c::off_t,
32};
33
34pub type Raw = cfg_select! {
35    windows => i32,
36    _ => RawFd,
37};
38
39#[inline]
40fn cvt<I: num_traits::PrimInt>(ret: I) -> io::Result<I> {
41    if ret < I::zero() {
42        // CRT functions set errno, not GetLastError(), so use errno_io_error
43        Err(crate::os::errno_io_error())
44    } else {
45        Ok(ret)
46    }
47}
48
49fn cvt_fd(ret: Raw) -> io::Result<Owned> {
50    cvt(ret).map(|fd| unsafe { Owned::from_raw(fd) })
51}
52
53const MAX_RW: usize = if cfg!(any(windows, target_vendor = "apple")) {
54    i32::MAX as usize
55} else {
56    isize::MAX as usize
57};
58
59#[cfg(not(windows))]
60type OwnedInner = OwnedFd;
61#[cfg(not(windows))]
62type BorrowedInner<'fd> = BorrowedFd<'fd>;
63
64#[cfg(windows)]
65mod win {
66    use super::*;
67    use core::marker::PhantomData;
68    use core::mem::ManuallyDrop;
69
70    #[repr(transparent)]
71    pub(super) struct OwnedInner(i32);
72
73    impl OwnedInner {
74        #[inline]
75        pub(super) unsafe fn from_raw_fd(fd: Raw) -> Self {
76            Self(fd)
77        }
78
79        #[inline]
80        pub(super) fn as_raw_fd(&self) -> Raw {
81            self.0
82        }
83
84        #[inline]
85        pub(super) fn into_raw_fd(self) -> Raw {
86            let me = ManuallyDrop::new(self);
87            me.0
88        }
89    }
90
91    impl Drop for OwnedInner {
92        #[inline]
93        fn drop(&mut self) {
94            let _ = _close(self.0);
95        }
96    }
97
98    #[derive(Copy, Clone)]
99    #[repr(transparent)]
100    pub(super) struct BorrowedInner<'fd> {
101        fd: Raw,
102        _marker: PhantomData<&'fd Owned>,
103    }
104
105    impl BorrowedInner<'_> {
106        #[inline]
107        pub(super) const unsafe fn borrow_raw(fd: Raw) -> Self {
108            Self {
109                fd,
110                _marker: PhantomData,
111            }
112        }
113
114        #[inline]
115        pub(super) fn as_raw_fd(self) -> Raw {
116            self.fd
117        }
118    }
119}
120
121#[cfg(windows)]
122use self::win::{BorrowedInner, OwnedInner};
123
124#[repr(transparent)]
125pub struct Owned {
126    inner: OwnedInner,
127}
128
129impl fmt::Debug for Owned {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        f.debug_tuple("crt_fd::Owned")
132            .field(&self.as_raw())
133            .finish()
134    }
135}
136
137#[derive(Copy, Clone)]
138#[repr(transparent)]
139pub struct Borrowed<'fd> {
140    inner: BorrowedInner<'fd>,
141}
142
143impl PartialEq for Borrowed<'_> {
144    fn eq(&self, other: &Self) -> bool {
145        self.as_raw() == other.as_raw()
146    }
147}
148
149impl Eq for Borrowed<'_> {}
150
151impl fmt::Debug for Borrowed<'_> {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        f.debug_tuple("crt_fd::Borrowed")
154            .field(&self.as_raw())
155            .finish()
156    }
157}
158
159impl Owned {
160    /// Create a `crt_fd::Owned` from a raw file descriptor.
161    ///
162    /// # Safety
163    ///
164    /// `fd` must be a valid file descriptor.
165    #[inline]
166    #[must_use]
167    pub unsafe fn from_raw(fd: Raw) -> Self {
168        let inner = unsafe { OwnedInner::from_raw_fd(fd) };
169        Self { inner }
170    }
171
172    /// Create a `crt_fd::Owned` from a raw file descriptor.
173    ///
174    /// Returns an error if `fd` is -1.
175    ///
176    /// # Safety
177    ///
178    /// `fd` must be a valid file descriptor.
179    #[inline]
180    pub unsafe fn try_from_raw(fd: Raw) -> io::Result<Self> {
181        if fd == -1 {
182            Err(ebadf())
183        } else {
184            Ok(unsafe { Self::from_raw(fd) })
185        }
186    }
187
188    #[inline]
189    #[must_use]
190    pub fn borrow(&self) -> Borrowed<'_> {
191        unsafe { Borrowed::borrow_raw(self.as_raw()) }
192    }
193
194    #[inline]
195    #[must_use]
196    pub fn as_raw(&self) -> Raw {
197        self.inner.as_raw_fd()
198    }
199
200    #[inline]
201    #[must_use]
202    pub fn into_raw(self) -> Raw {
203        self.inner.into_raw_fd()
204    }
205
206    #[must_use]
207    pub fn leak<'fd>(self) -> Borrowed<'fd> {
208        unsafe { Borrowed::borrow_raw(self.into_raw()) }
209    }
210}
211
212#[cfg(any(unix, target_os = "wasi"))]
213impl From<Owned> for OwnedFd {
214    fn from(fd: Owned) -> Self {
215        fd.inner
216    }
217}
218
219#[cfg(any(unix, target_os = "wasi"))]
220impl From<OwnedFd> for Owned {
221    fn from(fd: OwnedFd) -> Self {
222        Self { inner: fd }
223    }
224}
225
226#[cfg(any(unix, target_os = "wasi"))]
227impl AsFd for Owned {
228    fn as_fd(&self) -> BorrowedFd<'_> {
229        self.inner.as_fd()
230    }
231}
232
233#[cfg(any(unix, target_os = "wasi"))]
234impl AsRawFd for Owned {
235    fn as_raw_fd(&self) -> RawFd {
236        self.as_raw()
237    }
238}
239
240#[cfg(any(unix, target_os = "wasi"))]
241impl FromRawFd for Owned {
242    unsafe fn from_raw_fd(fd: RawFd) -> Self {
243        unsafe { Self::from_raw(fd) }
244    }
245}
246
247#[cfg(any(unix, target_os = "wasi"))]
248impl IntoRawFd for Owned {
249    fn into_raw_fd(self) -> RawFd {
250        self.into_raw()
251    }
252}
253
254impl Borrowed<'_> {
255    /// Create a `crt_fd::Borrowed` from a raw file descriptor.
256    ///
257    /// # Safety
258    ///
259    /// `fd` must be a valid file descriptor.
260    #[inline]
261    #[must_use]
262    pub const unsafe fn borrow_raw(fd: Raw) -> Self {
263        let inner = unsafe { BorrowedInner::borrow_raw(fd) };
264        Self { inner }
265    }
266
267    /// Create a `crt_fd::Borrowed` from a raw file descriptor.
268    ///
269    /// Returns an error if `fd` is -1.
270    ///
271    /// # Safety
272    ///
273    /// `fd` must be a valid file descriptor.
274    #[inline]
275    pub unsafe fn try_borrow_raw(fd: Raw) -> io::Result<Self> {
276        if fd == -1 {
277            Err(ebadf())
278        } else {
279            Ok(unsafe { Self::borrow_raw(fd) })
280        }
281    }
282
283    #[inline]
284    #[must_use]
285    pub fn as_raw(self) -> Raw {
286        self.inner.as_raw_fd()
287    }
288}
289
290#[cfg(any(unix, target_os = "wasi"))]
291impl<'fd> From<Borrowed<'fd>> for BorrowedFd<'fd> {
292    fn from(fd: Borrowed<'fd>) -> Self {
293        fd.inner
294    }
295}
296
297#[cfg(any(unix, target_os = "wasi"))]
298impl<'fd> From<BorrowedFd<'fd>> for Borrowed<'fd> {
299    fn from(fd: BorrowedFd<'fd>) -> Self {
300        Self { inner: fd }
301    }
302}
303
304#[cfg(any(unix, target_os = "wasi"))]
305impl AsFd for Borrowed<'_> {
306    fn as_fd(&self) -> BorrowedFd<'_> {
307        self.inner.as_fd()
308    }
309}
310
311#[cfg(any(unix, target_os = "wasi"))]
312impl AsRawFd for Borrowed<'_> {
313    fn as_raw_fd(&self) -> RawFd {
314        self.as_raw()
315    }
316}
317
318#[inline]
319fn ebadf() -> io::Error {
320    io::Error::from_raw_os_error(c::EBADF)
321}
322
323pub fn open(path: &ffi::CStr, flags: i32, mode: i32) -> io::Result<Owned> {
324    cvt_fd(unsafe { c::open(path.as_ptr(), flags, mode) })
325}
326
327#[cfg(windows)]
328pub fn wopen(path: &widestring::WideCStr, flags: i32, mode: i32) -> io::Result<Owned> {
329    cvt_fd(unsafe { suppress_iph!(c::wopen(path.as_ptr(), flags, mode)) })
330}
331
332#[cfg(all(any(unix, target_os = "wasi"), not(target_os = "redox")))]
333pub fn openat(dir: Borrowed<'_>, path: &ffi::CStr, flags: i32, mode: i32) -> io::Result<Owned> {
334    cvt_fd(unsafe { c::openat(dir.as_raw(), path.as_ptr(), flags, mode) })
335}
336
337pub fn fsync(fd: Borrowed<'_>) -> io::Result<()> {
338    cvt(unsafe { suppress_iph!(c::fsync(fd.as_raw())) })?;
339    Ok(())
340}
341
342fn _close(fd: Raw) -> io::Result<()> {
343    cvt(unsafe { suppress_iph!(c::close(fd)) })?;
344    Ok(())
345}
346
347pub fn close(fd: Owned) -> io::Result<()> {
348    _close(fd.into_raw())
349}
350
351pub fn ftruncate(fd: Borrowed<'_>, len: Offset) -> io::Result<()> {
352    let ret = unsafe { suppress_iph!(c::ftruncate(fd.as_raw(), len)) };
353    // On Windows, _chsize_s returns 0 on success, or a positive error code (errno value) on failure.
354    // On other platforms, ftruncate returns 0 on success, or -1 on failure with errno set.
355    cfg_select! {
356        windows => {
357            if ret != 0 {
358                // _chsize_s returns errno directly; preserve it exactly.
359                return Err(crate::os::io_error_from_errno(ret));
360            }
361        }
362        _ => cvt(ret)?,
363    };
364    Ok(())
365}
366
367#[cfg(windows)]
368pub fn as_handle(fd: Borrowed<'_>) -> io::Result<BorrowedHandle<'_>> {
369    use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
370    unsafe extern "C" {
371        fn _get_osfhandle(fd: Borrowed<'_>) -> c::intptr_t;
372    }
373    let handle = unsafe { suppress_iph!(_get_osfhandle(fd)) };
374    if handle as HANDLE == INVALID_HANDLE_VALUE {
375        // _get_osfhandle is a CRT function that sets errno, not GetLastError()
376        Err(crate::os::errno_io_error())
377    } else {
378        Ok(unsafe { BorrowedHandle::borrow_raw(handle as _) })
379    }
380}
381
382fn _write(fd: Raw, buf: &[u8]) -> io::Result<usize> {
383    let count = cmp::min(buf.len(), MAX_RW);
384    let n = cvt(unsafe { suppress_iph!(c::write(fd, buf.as_ptr() as _, count as _)) })?;
385    Ok(n as usize)
386}
387
388fn _read(fd: Raw, buf: &mut [u8]) -> io::Result<usize> {
389    let count = cmp::min(buf.len(), MAX_RW);
390    let n = cvt(unsafe { suppress_iph!(libc::read(fd, buf.as_mut_ptr() as _, count as _)) })?;
391    Ok(n as usize)
392}
393
394pub fn write(fd: Borrowed<'_>, buf: &[u8]) -> io::Result<usize> {
395    _write(fd.as_raw(), buf)
396}
397
398pub fn read(fd: Borrowed<'_>, buf: &mut [u8]) -> io::Result<usize> {
399    _read(fd.as_raw(), buf)
400}
401
402macro_rules! impl_rw {
403    ($t:ty) => {
404        impl io::Write for $t {
405            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
406                _write(self.as_raw(), buf)
407            }
408
409            #[inline]
410            fn flush(&mut self) -> io::Result<()> {
411                Ok(())
412            }
413        }
414
415        impl io::Read for $t {
416            fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
417                _read(self.as_raw(), buf)
418            }
419        }
420    };
421}
422
423impl_rw!(Owned);
424impl_rw!(Borrowed<'_>);