rustpython_common/
fileutils.rs

1// Python/fileutils.c in CPython
2#![allow(non_snake_case)]
3
4#[cfg(not(windows))]
5pub use libc::stat as StatStruct;
6
7#[cfg(windows)]
8pub use windows::{fstat, StatStruct};
9
10#[cfg(not(windows))]
11pub fn fstat(fd: libc::c_int) -> std::io::Result<StatStruct> {
12    let mut stat = std::mem::MaybeUninit::uninit();
13    unsafe {
14        let ret = libc::fstat(fd, stat.as_mut_ptr());
15        if ret == -1 {
16            Err(crate::os::last_os_error())
17        } else {
18            Ok(stat.assume_init())
19        }
20    }
21}
22
23#[cfg(windows)]
24pub mod windows {
25    use crate::suppress_iph;
26    use crate::windows::ToWideString;
27    use libc::{S_IFCHR, S_IFDIR, S_IFMT};
28    use std::ffi::{CString, OsStr, OsString};
29    use std::os::windows::ffi::OsStrExt;
30    use std::sync::OnceLock;
31    use windows_sys::core::PCWSTR;
32    use windows_sys::Win32::Foundation::{
33        FreeLibrary, SetLastError, BOOL, ERROR_INVALID_HANDLE, ERROR_NOT_SUPPORTED, FILETIME,
34        HANDLE, INVALID_HANDLE_VALUE,
35    };
36    use windows_sys::Win32::Storage::FileSystem::{
37        FileBasicInfo, FileIdInfo, GetFileInformationByHandle, GetFileInformationByHandleEx,
38        GetFileType, BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY,
39        FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, FILE_ID_INFO, FILE_TYPE_CHAR,
40        FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN,
41    };
42    use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
43    use windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_SYMLINK;
44
45    pub const S_IFIFO: libc::c_int = 0o010000;
46    pub const S_IFLNK: libc::c_int = 0o120000;
47
48    pub const SECS_BETWEEN_EPOCHS: i64 = 11644473600; // Seconds between 1.1.1601 and 1.1.1970
49
50    #[derive(Default)]
51    pub struct StatStruct {
52        pub st_dev: libc::c_ulong,
53        pub st_ino: u64,
54        pub st_mode: libc::c_ushort,
55        pub st_nlink: i32,
56        pub st_uid: i32,
57        pub st_gid: i32,
58        pub st_rdev: libc::c_ulong,
59        pub st_size: u64,
60        pub st_atime: libc::time_t,
61        pub st_atime_nsec: i32,
62        pub st_mtime: libc::time_t,
63        pub st_mtime_nsec: i32,
64        pub st_ctime: libc::time_t,
65        pub st_ctime_nsec: i32,
66        pub st_birthtime: libc::time_t,
67        pub st_birthtime_nsec: i32,
68        pub st_file_attributes: libc::c_ulong,
69        pub st_reparse_tag: u32,
70        pub st_ino_high: u64,
71    }
72
73    impl StatStruct {
74        // update_st_mode_from_path in cpython
75        pub fn update_st_mode_from_path(&mut self, path: &OsStr, attr: u32) {
76            if attr & FILE_ATTRIBUTE_DIRECTORY == 0 {
77                let file_extension = path
78                    .encode_wide()
79                    .collect::<Vec<u16>>()
80                    .split(|&c| c == '.' as u16)
81                    .last()
82                    .and_then(|s| String::from_utf16(s).ok());
83
84                if let Some(file_extension) = file_extension {
85                    if file_extension.eq_ignore_ascii_case("exe")
86                        || file_extension.eq_ignore_ascii_case("bat")
87                        || file_extension.eq_ignore_ascii_case("cmd")
88                        || file_extension.eq_ignore_ascii_case("com")
89                    {
90                        self.st_mode |= 0o111;
91                    }
92                }
93            }
94        }
95    }
96
97    extern "C" {
98        fn _get_osfhandle(fd: i32) -> libc::intptr_t;
99    }
100
101    fn get_osfhandle(fd: i32) -> std::io::Result<isize> {
102        let ret = unsafe { suppress_iph!(_get_osfhandle(fd)) };
103        if ret as HANDLE == INVALID_HANDLE_VALUE {
104            Err(crate::os::last_os_error())
105        } else {
106            Ok(ret)
107        }
108    }
109
110    // _Py_fstat_noraise in cpython
111    pub fn fstat(fd: libc::c_int) -> std::io::Result<StatStruct> {
112        let h = get_osfhandle(fd);
113        if h.is_err() {
114            unsafe { SetLastError(ERROR_INVALID_HANDLE) };
115        }
116        let h = h?;
117        // reset stat?
118
119        let file_type = unsafe { GetFileType(h) };
120        if file_type == FILE_TYPE_UNKNOWN {
121            return Err(std::io::Error::last_os_error());
122        }
123        if file_type != FILE_TYPE_DISK {
124            let st_mode = if file_type == FILE_TYPE_CHAR {
125                S_IFCHR
126            } else if file_type == FILE_TYPE_PIPE {
127                S_IFIFO
128            } else {
129                0
130            } as u16;
131            return Ok(StatStruct {
132                st_mode,
133                ..Default::default()
134            });
135        }
136
137        let mut info = unsafe { std::mem::zeroed() };
138        let mut basic_info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() };
139        let mut id_info: FILE_ID_INFO = unsafe { std::mem::zeroed() };
140
141        if unsafe { GetFileInformationByHandle(h, &mut info) } == 0
142            || unsafe {
143                GetFileInformationByHandleEx(
144                    h,
145                    FileBasicInfo,
146                    &mut basic_info as *mut _ as *mut _,
147                    std::mem::size_of_val(&basic_info) as u32,
148                )
149            } == 0
150        {
151            return Err(std::io::Error::last_os_error());
152        }
153
154        let p_id_info = if unsafe {
155            GetFileInformationByHandleEx(
156                h,
157                FileIdInfo,
158                &mut id_info as *mut _ as *mut _,
159                std::mem::size_of_val(&id_info) as u32,
160            )
161        } == 0
162        {
163            None
164        } else {
165            Some(&id_info)
166        };
167
168        Ok(attribute_data_to_stat(
169            &info,
170            0,
171            Some(&basic_info),
172            p_id_info,
173        ))
174    }
175
176    fn large_integer_to_time_t_nsec(input: i64) -> (libc::time_t, libc::c_int) {
177        let nsec_out = (input % 10_000_000) * 100; // FILETIME is in units of 100 nsec.
178        let time_out = ((input / 10_000_000) - SECS_BETWEEN_EPOCHS) as libc::time_t;
179        (time_out, nsec_out as _)
180    }
181
182    fn file_time_to_time_t_nsec(in_ptr: &FILETIME) -> (libc::time_t, libc::c_int) {
183        let in_val: i64 = unsafe { std::mem::transmute_copy(in_ptr) };
184        let nsec_out = (in_val % 10_000_000) * 100; // FILETIME is in units of 100 nsec.
185        let time_out = (in_val / 10_000_000) - SECS_BETWEEN_EPOCHS;
186        (time_out, nsec_out as _)
187    }
188
189    fn attribute_data_to_stat(
190        info: &BY_HANDLE_FILE_INFORMATION,
191        reparse_tag: u32,
192        basic_info: Option<&FILE_BASIC_INFO>,
193        id_info: Option<&FILE_ID_INFO>,
194    ) -> StatStruct {
195        let mut st_mode = attributes_to_mode(info.dwFileAttributes);
196        let st_size = ((info.nFileSizeHigh as u64) << 32) + info.nFileSizeLow as u64;
197        let st_dev: libc::c_ulong = if let Some(id_info) = id_info {
198            id_info.VolumeSerialNumber as _
199        } else {
200            info.dwVolumeSerialNumber
201        };
202        let st_rdev = 0;
203
204        let (st_birthtime, st_ctime, st_mtime, st_atime) = if let Some(basic_info) = basic_info {
205            (
206                large_integer_to_time_t_nsec(basic_info.CreationTime),
207                large_integer_to_time_t_nsec(basic_info.ChangeTime),
208                large_integer_to_time_t_nsec(basic_info.LastWriteTime),
209                large_integer_to_time_t_nsec(basic_info.LastAccessTime),
210            )
211        } else {
212            (
213                file_time_to_time_t_nsec(&info.ftCreationTime),
214                (0, 0),
215                file_time_to_time_t_nsec(&info.ftLastWriteTime),
216                file_time_to_time_t_nsec(&info.ftLastAccessTime),
217            )
218        };
219        let st_nlink = info.nNumberOfLinks as i32;
220
221        let st_ino = if let Some(id_info) = id_info {
222            let file_id: [u64; 2] = unsafe { std::mem::transmute_copy(&id_info.FileId) };
223            file_id
224        } else {
225            let ino = ((info.nFileIndexHigh as u64) << 32) + info.nFileIndexLow as u64;
226            [ino, 0]
227        };
228
229        if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
230            && reparse_tag == IO_REPARSE_TAG_SYMLINK
231        {
232            st_mode = (st_mode & !(S_IFMT as u16)) | (S_IFLNK as u16);
233        }
234        let st_file_attributes = info.dwFileAttributes;
235
236        StatStruct {
237            st_dev,
238            st_ino: st_ino[0],
239            st_mode,
240            st_nlink,
241            st_uid: 0,
242            st_gid: 0,
243            st_rdev,
244            st_size,
245            st_atime: st_atime.0,
246            st_atime_nsec: st_atime.1,
247            st_mtime: st_mtime.0,
248            st_mtime_nsec: st_mtime.1,
249            st_ctime: st_ctime.0,
250            st_ctime_nsec: st_ctime.1,
251            st_birthtime: st_birthtime.0,
252            st_birthtime_nsec: st_birthtime.1,
253            st_file_attributes,
254            st_reparse_tag: reparse_tag,
255            st_ino_high: st_ino[1],
256        }
257    }
258
259    fn attributes_to_mode(attr: u32) -> u16 {
260        let mut m = 0;
261        if attr & FILE_ATTRIBUTE_DIRECTORY != 0 {
262            m |= libc::S_IFDIR | 0o111; // IFEXEC for user,group,other
263        } else {
264            m |= libc::S_IFREG;
265        }
266        if attr & FILE_ATTRIBUTE_READONLY != 0 {
267            m |= 0o444;
268        } else {
269            m |= 0o666;
270        }
271        m as _
272    }
273
274    #[repr(C)]
275    pub struct FILE_STAT_BASIC_INFORMATION {
276        pub FileId: i64,
277        pub CreationTime: i64,
278        pub LastAccessTime: i64,
279        pub LastWriteTime: i64,
280        pub ChangeTime: i64,
281        pub AllocationSize: i64,
282        pub EndOfFile: i64,
283        pub FileAttributes: u32,
284        pub ReparseTag: u32,
285        pub NumberOfLinks: u32,
286        pub DeviceType: u32,
287        pub DeviceCharacteristics: u32,
288        pub Reserved: u32,
289        pub VolumeSerialNumber: i64,
290        pub FileId128: [u64; 2],
291    }
292
293    #[repr(C)]
294    #[allow(dead_code)]
295    pub enum FILE_INFO_BY_NAME_CLASS {
296        FileStatByNameInfo,
297        FileStatLxByNameInfo,
298        FileCaseSensitiveByNameInfo,
299        FileStatBasicByNameInfo,
300        MaximumFileInfoByNameClass,
301    }
302
303    // _Py_GetFileInformationByName in cpython
304    pub fn get_file_information_by_name(
305        file_name: &OsStr,
306        file_information_class: FILE_INFO_BY_NAME_CLASS,
307    ) -> std::io::Result<FILE_STAT_BASIC_INFORMATION> {
308        static GET_FILE_INFORMATION_BY_NAME: OnceLock<
309            Option<
310                unsafe extern "system" fn(
311                    PCWSTR,
312                    FILE_INFO_BY_NAME_CLASS,
313                    *mut libc::c_void,
314                    u32,
315                ) -> BOOL,
316            >,
317        > = OnceLock::new();
318
319        let GetFileInformationByName = GET_FILE_INFORMATION_BY_NAME
320            .get_or_init(|| {
321                let library_name = OsString::from("api-ms-win-core-file-l2-1-4").to_wide_with_nul();
322                let module = unsafe { LoadLibraryW(library_name.as_ptr()) };
323                if module == 0 {
324                    return None;
325                }
326                let name = CString::new("GetFileInformationByName").unwrap();
327                if let Some(proc) =
328                    unsafe { GetProcAddress(module, name.as_bytes_with_nul().as_ptr()) }
329                {
330                    Some(unsafe {
331                        std::mem::transmute::<
332                            unsafe extern "system" fn() -> isize,
333                            unsafe extern "system" fn(
334                                *const u16,
335                                FILE_INFO_BY_NAME_CLASS,
336                                *mut libc::c_void,
337                                u32,
338                            ) -> i32,
339                        >(proc)
340                    })
341                } else {
342                    unsafe { FreeLibrary(module) };
343                    None
344                }
345            })
346            .ok_or_else(|| std::io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as _))?;
347
348        let file_name = file_name.to_wide_with_nul();
349        let file_info_buffer_size = std::mem::size_of::<FILE_STAT_BASIC_INFORMATION>() as u32;
350        let mut file_info_buffer = std::mem::MaybeUninit::<FILE_STAT_BASIC_INFORMATION>::uninit();
351        unsafe {
352            if GetFileInformationByName(
353                file_name.as_ptr(),
354                file_information_class as _,
355                file_info_buffer.as_mut_ptr() as _,
356                file_info_buffer_size,
357            ) == 0
358            {
359                Err(std::io::Error::last_os_error())
360            } else {
361                Ok(file_info_buffer.assume_init())
362            }
363        }
364    }
365    pub fn stat_basic_info_to_stat(info: &FILE_STAT_BASIC_INFORMATION) -> StatStruct {
366        use windows_sys::Win32::Storage::FileSystem;
367        use windows_sys::Win32::System::Ioctl;
368
369        const S_IFMT: u16 = self::S_IFMT as _;
370        const S_IFDIR: u16 = self::S_IFDIR as _;
371        const S_IFCHR: u16 = self::S_IFCHR as _;
372        const S_IFIFO: u16 = self::S_IFIFO as _;
373        const S_IFLNK: u16 = self::S_IFLNK as _;
374
375        let mut st_mode = attributes_to_mode(info.FileAttributes);
376        let st_size = info.EndOfFile as u64;
377        let st_birthtime = large_integer_to_time_t_nsec(info.CreationTime);
378        let st_ctime = large_integer_to_time_t_nsec(info.ChangeTime);
379        let st_mtime = large_integer_to_time_t_nsec(info.LastWriteTime);
380        let st_atime = large_integer_to_time_t_nsec(info.LastAccessTime);
381        let st_nlink = info.NumberOfLinks as _;
382        let st_dev = info.VolumeSerialNumber as u32;
383        // File systems with less than 128-bits zero pad into this field
384        let st_ino = info.FileId128;
385        // bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will
386        // open other name surrogate reparse points without traversing them. To
387        // detect/handle these, check st_file_attributes and st_reparse_tag.
388        let st_reparse_tag = info.ReparseTag;
389        if info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
390            && info.ReparseTag == IO_REPARSE_TAG_SYMLINK
391        {
392            // set the bits that make this a symlink
393            st_mode = (st_mode & !S_IFMT) | S_IFLNK;
394        }
395        let st_file_attributes = info.FileAttributes;
396        match info.DeviceType {
397            FileSystem::FILE_DEVICE_DISK
398            | Ioctl::FILE_DEVICE_VIRTUAL_DISK
399            | Ioctl::FILE_DEVICE_DFS
400            | FileSystem::FILE_DEVICE_CD_ROM
401            | Ioctl::FILE_DEVICE_CONTROLLER
402            | Ioctl::FILE_DEVICE_DATALINK => {}
403            Ioctl::FILE_DEVICE_DISK_FILE_SYSTEM
404            | Ioctl::FILE_DEVICE_CD_ROM_FILE_SYSTEM
405            | Ioctl::FILE_DEVICE_NETWORK_FILE_SYSTEM => {
406                st_mode = (st_mode & !S_IFMT) | 0x6000; // _S_IFBLK
407            }
408            Ioctl::FILE_DEVICE_CONSOLE
409            | Ioctl::FILE_DEVICE_NULL
410            | Ioctl::FILE_DEVICE_KEYBOARD
411            | Ioctl::FILE_DEVICE_MODEM
412            | Ioctl::FILE_DEVICE_MOUSE
413            | Ioctl::FILE_DEVICE_PARALLEL_PORT
414            | Ioctl::FILE_DEVICE_PRINTER
415            | Ioctl::FILE_DEVICE_SCREEN
416            | Ioctl::FILE_DEVICE_SERIAL_PORT
417            | Ioctl::FILE_DEVICE_SOUND => {
418                st_mode = (st_mode & !S_IFMT) | S_IFCHR;
419            }
420            Ioctl::FILE_DEVICE_NAMED_PIPE => {
421                st_mode = (st_mode & !S_IFMT) | S_IFIFO;
422            }
423            _ => {
424                if info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
425                    st_mode = (st_mode & !S_IFMT) | S_IFDIR;
426                }
427            }
428        }
429
430        StatStruct {
431            st_dev,
432            st_ino: st_ino[0],
433            st_mode,
434            st_nlink,
435            st_uid: 0,
436            st_gid: 0,
437            st_rdev: 0,
438            st_size,
439            st_atime: st_atime.0,
440            st_atime_nsec: st_atime.1,
441            st_mtime: st_mtime.0,
442            st_mtime_nsec: st_mtime.1,
443            st_ctime: st_ctime.0,
444            st_ctime_nsec: st_ctime.1,
445            st_birthtime: st_birthtime.0,
446            st_birthtime_nsec: st_birthtime.1,
447            st_file_attributes,
448            st_reparse_tag,
449            st_ino_high: st_ino[1],
450        }
451    }
452}