rustpython_vm/
windows.rs

1use crate::common::fileutils::{
2    windows::{get_file_information_by_name, FILE_INFO_BY_NAME_CLASS},
3    StatStruct,
4};
5use crate::{
6    convert::{ToPyObject, ToPyResult},
7    stdlib::os::errno_err,
8    PyObjectRef, PyResult, TryFromObject, VirtualMachine,
9};
10use std::{ffi::OsStr, time::SystemTime};
11use windows::Win32::Foundation::HANDLE;
12use windows_sys::Win32::Foundation::{BOOL, HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE};
13
14pub(crate) trait WindowsSysResultValue {
15    type Ok: ToPyObject;
16    fn is_err(&self) -> bool;
17    fn into_ok(self) -> Self::Ok;
18}
19
20impl WindowsSysResultValue for RAW_HANDLE {
21    type Ok = HANDLE;
22    fn is_err(&self) -> bool {
23        *self == INVALID_HANDLE_VALUE
24    }
25    fn into_ok(self) -> Self::Ok {
26        HANDLE(self)
27    }
28}
29
30impl WindowsSysResultValue for BOOL {
31    type Ok = ();
32    fn is_err(&self) -> bool {
33        *self == 0
34    }
35    fn into_ok(self) -> Self::Ok {}
36}
37
38pub(crate) struct WindowsSysResult<T>(pub T);
39
40impl<T: WindowsSysResultValue> WindowsSysResult<T> {
41    pub fn is_err(&self) -> bool {
42        self.0.is_err()
43    }
44    pub fn into_pyresult(self, vm: &VirtualMachine) -> PyResult<T::Ok> {
45        if self.is_err() {
46            Err(errno_err(vm))
47        } else {
48            Ok(self.0.into_ok())
49        }
50    }
51}
52
53impl<T: WindowsSysResultValue> ToPyResult for WindowsSysResult<T> {
54    fn to_pyresult(self, vm: &VirtualMachine) -> PyResult {
55        let ok = self.into_pyresult(vm)?;
56        Ok(ok.to_pyobject(vm))
57    }
58}
59
60type HandleInt = usize; // TODO: change to isize when fully ported to windows-rs
61
62impl TryFromObject for HANDLE {
63    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
64        let handle = HandleInt::try_from_object(vm, obj)?;
65        Ok(HANDLE(handle as isize))
66    }
67}
68
69impl ToPyObject for HANDLE {
70    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
71        (self.0 as HandleInt).to_pyobject(vm)
72    }
73}
74
75pub fn init_winsock() {
76    static WSA_INIT: parking_lot::Once = parking_lot::Once::new();
77    WSA_INIT.call_once(|| unsafe {
78        let mut wsa_data = std::mem::MaybeUninit::uninit();
79        let _ = windows_sys::Win32::Networking::WinSock::WSAStartup(0x0101, wsa_data.as_mut_ptr());
80    })
81}
82
83// win32_xstat in cpython
84pub fn win32_xstat(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> {
85    let mut result = win32_xstat_impl(path, traverse)?;
86    // ctime is only deprecated from 3.12, so we copy birthtime across
87    result.st_ctime = result.st_birthtime;
88    result.st_ctime_nsec = result.st_birthtime_nsec;
89    Ok(result)
90}
91
92fn is_reparse_tag_name_surrogate(tag: u32) -> bool {
93    (tag & 0x20000000) > 0
94}
95
96fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> {
97    use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT};
98
99    let stat_info =
100        get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo);
101    match stat_info {
102        Ok(stat_info) => {
103            if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == 0)
104                || (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag))
105            {
106                let mut result =
107                    crate::common::fileutils::windows::stat_basic_info_to_stat(&stat_info);
108                result.update_st_mode_from_path(path, stat_info.FileAttributes);
109                return Ok(result);
110            }
111        }
112        Err(e) => {
113            if let Some(errno) = e.raw_os_error() {
114                if matches!(
115                    errno as u32,
116                    Foundation::ERROR_FILE_NOT_FOUND
117                        | Foundation::ERROR_PATH_NOT_FOUND
118                        | Foundation::ERROR_NOT_READY
119                        | Foundation::ERROR_BAD_NET_NAME
120                ) {
121                    return Err(e);
122                }
123            }
124        }
125    }
126
127    // TODO: check if win32_xstat_slow_impl(&path, result, traverse) is required
128    meta_to_stat(
129        &crate::stdlib::os::fs_metadata(path, traverse)?,
130        file_id(path)?,
131    )
132}
133
134// Ported from zed: https://github.com/zed-industries/zed/blob/v0.131.6/crates/fs/src/fs.rs#L1532-L1562
135// can we get file id not open the file twice?
136// https://github.com/rust-lang/rust/issues/63010
137fn file_id(path: &OsStr) -> std::io::Result<u64> {
138    use std::os::windows::{fs::OpenOptionsExt, io::AsRawHandle};
139    use windows_sys::Win32::{
140        Foundation::HANDLE,
141        Storage::FileSystem::{
142            GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS,
143        },
144    };
145
146    let file = std::fs::OpenOptions::new()
147        .read(true)
148        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
149        .open(path)?;
150
151    let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
152    // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
153    // This function supports Windows XP+
154    let ret = unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info) };
155    if ret == 0 {
156        return Err(std::io::Error::last_os_error());
157    };
158
159    Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64))
160}
161
162fn meta_to_stat(meta: &std::fs::Metadata, file_id: u64) -> std::io::Result<StatStruct> {
163    let st_mode = {
164        // Based on CPython fileutils.c' attributes_to_mode
165        let mut m = 0;
166        if meta.is_dir() {
167            m |= libc::S_IFDIR | 0o111; /* IFEXEC for user,group,other */
168        } else {
169            m |= libc::S_IFREG;
170        }
171        if meta.is_symlink() {
172            m |= 0o100000;
173        }
174        if meta.permissions().readonly() {
175            m |= 0o444;
176        } else {
177            m |= 0o666;
178        }
179        m as _
180    };
181    let (atime, mtime, ctime) = (meta.accessed()?, meta.modified()?, meta.created()?);
182    let sec = |systime: SystemTime| match systime.duration_since(SystemTime::UNIX_EPOCH) {
183        Ok(d) => d.as_secs() as libc::time_t,
184        Err(e) => -(e.duration().as_secs() as libc::time_t),
185    };
186    let nsec = |systime: SystemTime| match systime.duration_since(SystemTime::UNIX_EPOCH) {
187        Ok(d) => d.subsec_nanos() as i32,
188        Err(e) => -(e.duration().subsec_nanos() as i32),
189    };
190    Ok(StatStruct {
191        st_dev: 0,
192        st_ino: file_id,
193        st_mode,
194        st_nlink: 0,
195        st_uid: 0,
196        st_gid: 0,
197        st_size: meta.len(),
198        st_atime: sec(atime),
199        st_mtime: sec(mtime),
200        st_birthtime: sec(ctime),
201        st_atime_nsec: nsec(atime),
202        st_mtime_nsec: nsec(mtime),
203        st_birthtime_nsec: nsec(ctime),
204        ..Default::default()
205    })
206}