1#![allow(non_snake_case)]
3
4use alloc::ffi::CString;
5
6#[cfg(not(windows))]
7pub use rustix::fs::Stat as StatStruct;
8
9#[cfg(windows)]
10pub use windows::{StatStruct, fstat};
11
12#[cfg(not(windows))]
13pub fn fstat(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result<StatStruct> {
14 rustix::fs::fstat(fd).map_err(Into::into)
15}
16
17#[cfg(windows)]
18pub mod windows {
19 use crate::crt_fd;
20 use libc::{S_IFCHR, S_IFDIR, S_IFMT};
21 use std::ffi::OsStr;
22 use std::os::windows::io::AsRawHandle;
23 use std::path::Path;
24 use std::sync::OnceLock;
25 use windows_sys::Win32::Foundation::{
26 ERROR_INVALID_HANDLE, ERROR_NOT_SUPPORTED, FILETIME, FreeLibrary, SetLastError,
27 };
28 use windows_sys::Win32::Storage::FileSystem::{
29 BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY,
30 FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, FILE_ID_INFO, FILE_TYPE_CHAR,
31 FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN, FileBasicInfo, FileIdInfo,
32 GetFileInformationByHandle, GetFileInformationByHandleEx, GetFileType,
33 };
34 use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
35 use windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_SYMLINK;
36 use windows_sys::core::PCWSTR;
37 use windows_sys::w;
38
39 pub const S_IFIFO: libc::c_int = 0o010000;
40 pub const S_IFLNK: libc::c_int = 0o120000;
41
42 pub const SECS_BETWEEN_EPOCHS: i64 = 11644473600; #[derive(Clone, Copy, Default)]
45 pub struct StatStruct {
46 pub st_dev: libc::c_ulong,
47 pub st_ino: u64,
48 pub st_mode: libc::c_ushort,
49 pub st_nlink: i32,
50 pub st_uid: i32,
51 pub st_gid: i32,
52 pub st_rdev: libc::c_ulong,
53 pub st_size: u64,
54 pub st_atime: libc::time_t,
55 pub st_atime_nsec: i32,
56 pub st_mtime: libc::time_t,
57 pub st_mtime_nsec: i32,
58 pub st_ctime: libc::time_t,
59 pub st_ctime_nsec: i32,
60 pub st_birthtime: libc::time_t,
61 pub st_birthtime_nsec: i32,
62 pub st_file_attributes: libc::c_ulong,
63 pub st_reparse_tag: u32,
64 pub st_ino_high: u64,
65 }
66
67 impl StatStruct {
68 pub fn update_st_mode_from_path(&mut self, path: &OsStr, attr: u32) {
70 if attr & FILE_ATTRIBUTE_DIRECTORY == 0
71 && let Some(file_extension) =
72 Path::new(path).extension().and_then(|ext| ext.to_str())
73 && (file_extension.eq_ignore_ascii_case("exe")
74 || file_extension.eq_ignore_ascii_case("bat")
75 || file_extension.eq_ignore_ascii_case("cmd")
76 || file_extension.eq_ignore_ascii_case("com"))
77 {
78 self.st_mode |= 0o111;
79 }
80 }
81 }
82
83 pub fn fstat(fd: crt_fd::Borrowed<'_>) -> std::io::Result<StatStruct> {
85 let h = match crt_fd::as_handle(fd) {
86 Ok(h) => h,
87 Err(_) => {
88 unsafe { SetLastError(ERROR_INVALID_HANDLE) };
91 return Err(std::io::Error::last_os_error());
92 }
93 };
94 let h = h.as_raw_handle();
95 let file_type = unsafe { GetFileType(h as _) };
98 if file_type == FILE_TYPE_UNKNOWN {
99 return Err(std::io::Error::last_os_error());
100 }
101 if file_type != FILE_TYPE_DISK {
102 let st_mode = if file_type == FILE_TYPE_CHAR {
103 S_IFCHR
104 } else if file_type == FILE_TYPE_PIPE {
105 S_IFIFO
106 } else {
107 0
108 } as u16;
109 return Ok(StatStruct {
110 st_mode,
111 ..Default::default()
112 });
113 }
114
115 let mut info = unsafe { core::mem::zeroed() };
116 let mut basic_info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() };
117 let mut id_info: FILE_ID_INFO = unsafe { core::mem::zeroed() };
118
119 if unsafe { GetFileInformationByHandle(h as _, &mut info) } == 0
120 || unsafe {
121 GetFileInformationByHandleEx(
122 h as _,
123 FileBasicInfo,
124 &mut basic_info as *mut _ as *mut _,
125 core::mem::size_of_val(&basic_info) as u32,
126 )
127 } == 0
128 {
129 return Err(std::io::Error::last_os_error());
130 }
131
132 let p_id_info = if unsafe {
133 GetFileInformationByHandleEx(
134 h as _,
135 FileIdInfo,
136 &mut id_info as *mut _ as *mut _,
137 core::mem::size_of_val(&id_info) as u32,
138 )
139 } == 0
140 {
141 None
142 } else {
143 Some(&id_info)
144 };
145
146 Ok(attribute_data_to_stat(
147 &info,
148 0,
149 Some(&basic_info),
150 p_id_info,
151 ))
152 }
153
154 fn large_integer_to_time_t_nsec(input: i64) -> (libc::time_t, libc::c_int) {
155 let nsec_out = (input % 10_000_000) * 100; let time_out = ((input / 10_000_000) - SECS_BETWEEN_EPOCHS) as libc::time_t;
157 (time_out, nsec_out as _)
158 }
159
160 fn file_time_to_time_t_nsec(in_ptr: FILETIME) -> (libc::time_t, libc::c_int) {
161 let in_val: i64 = unsafe { core::mem::transmute_copy(&in_ptr) };
162 let nsec_out = (in_val % 10_000_000) * 100; let time_out = (in_val / 10_000_000) - SECS_BETWEEN_EPOCHS;
164 (time_out, nsec_out as _)
165 }
166
167 fn attribute_data_to_stat(
168 info: &BY_HANDLE_FILE_INFORMATION,
169 reparse_tag: u32,
170 basic_info: Option<&FILE_BASIC_INFO>,
171 id_info: Option<&FILE_ID_INFO>,
172 ) -> StatStruct {
173 let mut st_mode = attributes_to_mode(info.dwFileAttributes);
174 let st_size = ((info.nFileSizeHigh as u64) << 32) + info.nFileSizeLow as u64;
175 let st_dev: libc::c_ulong = if let Some(id_info) = id_info {
176 id_info.VolumeSerialNumber as _
177 } else {
178 info.dwVolumeSerialNumber
179 };
180 let st_rdev = 0;
181
182 let (st_birthtime, st_ctime, st_mtime, st_atime) = if let Some(basic_info) = basic_info {
183 (
184 large_integer_to_time_t_nsec(basic_info.CreationTime),
185 large_integer_to_time_t_nsec(basic_info.ChangeTime),
186 large_integer_to_time_t_nsec(basic_info.LastWriteTime),
187 large_integer_to_time_t_nsec(basic_info.LastAccessTime),
188 )
189 } else {
190 (
191 file_time_to_time_t_nsec(info.ftCreationTime),
192 (0, 0),
193 file_time_to_time_t_nsec(info.ftLastWriteTime),
194 file_time_to_time_t_nsec(info.ftLastAccessTime),
195 )
196 };
197 let st_nlink = info.nNumberOfLinks as i32;
198
199 let st_ino = if let Some(id_info) = id_info {
200 let file_id: [u64; 2] = unsafe { core::mem::transmute_copy(&id_info.FileId) };
201 file_id
202 } else {
203 let ino = ((info.nFileIndexHigh as u64) << 32) + info.nFileIndexLow as u64;
204 [ino, 0]
205 };
206
207 if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
208 && reparse_tag == IO_REPARSE_TAG_SYMLINK
209 {
210 st_mode = (st_mode & !(S_IFMT as u16)) | (S_IFLNK as u16);
211 }
212 let st_file_attributes = info.dwFileAttributes;
213
214 StatStruct {
215 st_dev,
216 st_ino: st_ino[0],
217 st_mode,
218 st_nlink,
219 st_uid: 0,
220 st_gid: 0,
221 st_rdev,
222 st_size,
223 st_atime: st_atime.0,
224 st_atime_nsec: st_atime.1,
225 st_mtime: st_mtime.0,
226 st_mtime_nsec: st_mtime.1,
227 st_ctime: st_ctime.0,
228 st_ctime_nsec: st_ctime.1,
229 st_birthtime: st_birthtime.0,
230 st_birthtime_nsec: st_birthtime.1,
231 st_file_attributes,
232 st_reparse_tag: reparse_tag,
233 st_ino_high: st_ino[1],
234 }
235 }
236
237 const fn attributes_to_mode(attr: u32) -> u16 {
238 let mut m = 0;
239 if attr & FILE_ATTRIBUTE_DIRECTORY != 0 {
240 m |= libc::S_IFDIR | 0o111; } else {
242 m |= libc::S_IFREG;
243 }
244 if attr & FILE_ATTRIBUTE_READONLY != 0 {
245 m |= 0o444;
246 } else {
247 m |= 0o666;
248 }
249 m as _
250 }
251
252 #[derive(Clone, Copy)]
253 #[repr(C)]
254 pub struct FILE_STAT_BASIC_INFORMATION {
255 pub FileId: i64,
256 pub CreationTime: i64,
257 pub LastAccessTime: i64,
258 pub LastWriteTime: i64,
259 pub ChangeTime: i64,
260 pub AllocationSize: i64,
261 pub EndOfFile: i64,
262 pub FileAttributes: u32,
263 pub ReparseTag: u32,
264 pub NumberOfLinks: u32,
265 pub DeviceType: u32,
266 pub DeviceCharacteristics: u32,
267 pub Reserved: u32,
268 pub VolumeSerialNumber: i64,
269 pub FileId128: [u64; 2],
270 }
271
272 #[allow(dead_code)]
273 #[derive(Clone, Copy)]
274 #[repr(C)]
275 pub enum FILE_INFO_BY_NAME_CLASS {
276 FileStatByNameInfo,
277 FileStatLxByNameInfo,
278 FileCaseSensitiveByNameInfo,
279 FileStatBasicByNameInfo,
280 MaximumFileInfoByNameClass,
281 }
282
283 pub fn get_file_information_by_name(
285 file_name: &widestring::WideCStr,
286 file_information_class: FILE_INFO_BY_NAME_CLASS,
287 ) -> std::io::Result<FILE_STAT_BASIC_INFORMATION> {
288 static GET_FILE_INFORMATION_BY_NAME: OnceLock<
289 Option<
290 unsafe extern "system" fn(
291 PCWSTR,
292 FILE_INFO_BY_NAME_CLASS,
293 *mut libc::c_void,
294 u32,
295 ) -> i32,
296 >,
297 > = OnceLock::new();
298
299 let GetFileInformationByName = GET_FILE_INFORMATION_BY_NAME
300 .get_or_init(|| {
301 let library_name = w!("api-ms-win-core-file-l2-1-4.dll");
302 let module = unsafe { LoadLibraryW(library_name) };
303 if module.is_null() {
304 return None;
305 }
306 let name = c"GetFileInformationByName";
307 if let Some(proc) = unsafe { GetProcAddress(module, name.as_ptr().cast()) } {
308 Some(unsafe {
309 core::mem::transmute::<
310 unsafe extern "system" fn() -> isize,
311 unsafe extern "system" fn(
312 *const u16,
313 FILE_INFO_BY_NAME_CLASS,
314 *mut libc::c_void,
315 u32,
316 ) -> i32,
317 >(proc)
318 })
319 } else {
320 unsafe { FreeLibrary(module) };
321 None
322 }
323 })
324 .ok_or_else(|| std::io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as _))?;
325
326 let file_info_buffer_size = core::mem::size_of::<FILE_STAT_BASIC_INFORMATION>() as u32;
327 let mut file_info_buffer = core::mem::MaybeUninit::<FILE_STAT_BASIC_INFORMATION>::uninit();
328 unsafe {
329 if GetFileInformationByName(
330 file_name.as_ptr(),
331 file_information_class as _,
332 file_info_buffer.as_mut_ptr() as _,
333 file_info_buffer_size,
334 ) == 0
335 {
336 Err(std::io::Error::last_os_error())
337 } else {
338 Ok(file_info_buffer.assume_init())
339 }
340 }
341 }
342
343 #[must_use]
344 pub fn stat_basic_info_to_stat(info: &FILE_STAT_BASIC_INFORMATION) -> StatStruct {
345 use windows_sys::Win32::Storage::FileSystem;
346 use windows_sys::Win32::System::Ioctl;
347
348 const S_IFMT: u16 = self::S_IFMT as _;
349 const S_IFDIR: u16 = self::S_IFDIR as _;
350 const S_IFCHR: u16 = self::S_IFCHR as _;
351 const S_IFIFO: u16 = self::S_IFIFO as _;
352 const S_IFLNK: u16 = self::S_IFLNK as _;
353
354 let mut st_mode = attributes_to_mode(info.FileAttributes);
355 let st_size = info.EndOfFile as u64;
356 let st_birthtime = large_integer_to_time_t_nsec(info.CreationTime);
357 let st_ctime = large_integer_to_time_t_nsec(info.ChangeTime);
358 let st_mtime = large_integer_to_time_t_nsec(info.LastWriteTime);
359 let st_atime = large_integer_to_time_t_nsec(info.LastAccessTime);
360 let st_nlink = info.NumberOfLinks as _;
361 let st_dev = info.VolumeSerialNumber as u32;
362 let st_ino = info.FileId128;
364 let st_reparse_tag = info.ReparseTag;
368 if info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
369 && info.ReparseTag == IO_REPARSE_TAG_SYMLINK
370 {
371 st_mode = (st_mode & !S_IFMT) | S_IFLNK;
373 }
374 let st_file_attributes = info.FileAttributes;
375 match info.DeviceType {
376 FileSystem::FILE_DEVICE_DISK
377 | Ioctl::FILE_DEVICE_VIRTUAL_DISK
378 | Ioctl::FILE_DEVICE_DFS
379 | FileSystem::FILE_DEVICE_CD_ROM
380 | Ioctl::FILE_DEVICE_CONTROLLER
381 | Ioctl::FILE_DEVICE_DATALINK => {}
382 Ioctl::FILE_DEVICE_DISK_FILE_SYSTEM
383 | Ioctl::FILE_DEVICE_CD_ROM_FILE_SYSTEM
384 | Ioctl::FILE_DEVICE_NETWORK_FILE_SYSTEM => {
385 st_mode = (st_mode & !S_IFMT) | 0x6000; }
387 Ioctl::FILE_DEVICE_CONSOLE
388 | Ioctl::FILE_DEVICE_NULL
389 | Ioctl::FILE_DEVICE_KEYBOARD
390 | Ioctl::FILE_DEVICE_MODEM
391 | Ioctl::FILE_DEVICE_MOUSE
392 | Ioctl::FILE_DEVICE_PARALLEL_PORT
393 | Ioctl::FILE_DEVICE_PRINTER
394 | Ioctl::FILE_DEVICE_SCREEN
395 | Ioctl::FILE_DEVICE_SERIAL_PORT
396 | Ioctl::FILE_DEVICE_SOUND => {
397 st_mode = (st_mode & !S_IFMT) | S_IFCHR;
398 }
399 Ioctl::FILE_DEVICE_NAMED_PIPE => {
400 st_mode = (st_mode & !S_IFMT) | S_IFIFO;
401 }
402 _ => {
403 if info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
404 st_mode = (st_mode & !S_IFMT) | S_IFDIR;
405 }
406 }
407 }
408
409 StatStruct {
410 st_dev,
411 st_ino: st_ino[0],
412 st_mode,
413 st_nlink,
414 st_uid: 0,
415 st_gid: 0,
416 st_rdev: 0,
417 st_size,
418 st_atime: st_atime.0,
419 st_atime_nsec: st_atime.1,
420 st_mtime: st_mtime.0,
421 st_mtime_nsec: st_mtime.1,
422 st_ctime: st_ctime.0,
423 st_ctime_nsec: st_ctime.1,
424 st_birthtime: st_birthtime.0,
425 st_birthtime_nsec: st_birthtime.1,
426 st_file_attributes,
427 st_reparse_tag,
428 st_ino_high: st_ino[1],
429 }
430 }
431}
432
433pub type CFile = libc::FILE;
435
436pub unsafe fn fclose(fp: *mut CFile) -> core::ffi::c_int {
442 unsafe { libc::fclose(fp) }
443}
444
445pub fn fopen(path: &std::path::Path, mode: &str) -> std::io::Result<*mut CFile> {
449 use std::fs::File;
450
451 if mode != "rb" {
454 return Err(std::io::Error::new(
455 std::io::ErrorKind::InvalidInput,
456 format!("unsupported mode: {mode}"),
457 ));
458 }
459
460 let file = File::open(path)?;
462
463 #[cfg(windows)]
464 {
465 use std::os::windows::io::IntoRawHandle;
466
467 let handle = file.into_raw_handle();
469 let fd = unsafe { libc::open_osfhandle(handle as isize, crate::os::O_RDONLY) };
470 if fd == -1 {
471 return Err(std::io::Error::last_os_error());
472 }
473
474 let mode_cstr = CString::new(mode).unwrap();
476 let fp = unsafe { libc::fdopen(fd, mode_cstr.as_ptr()) };
477 if fp.is_null() {
478 unsafe { libc::close(fd) };
479 return Err(std::io::Error::last_os_error());
480 }
481
482 if let Err(e) = set_inheritable(fd, false) {
484 unsafe { libc::fclose(fp) };
485 return Err(e);
486 }
487
488 Ok(fp)
489 }
490
491 #[cfg(not(windows))]
492 {
493 use std::os::fd::IntoRawFd;
494
495 let fd = file.into_raw_fd();
497
498 let mode_cstr = CString::new(mode).unwrap();
500 let fp = unsafe { libc::fdopen(fd, mode_cstr.as_ptr()) };
501 if fp.is_null() {
502 unsafe { libc::close(fd) };
503 return Err(std::io::Error::last_os_error());
504 }
505
506 Ok(fp)
508 }
509}
510
511#[cfg(windows)]
515fn set_inheritable(fd: libc::c_int, inheritable: bool) -> std::io::Result<()> {
516 use windows_sys::Win32::Foundation::{
517 HANDLE, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, SetHandleInformation,
518 };
519
520 let handle = unsafe { libc::get_osfhandle(fd) };
521 if handle == INVALID_HANDLE_VALUE as isize {
522 return Err(std::io::Error::last_os_error());
523 }
524
525 let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 };
526 let result = unsafe { SetHandleInformation(handle as HANDLE, HANDLE_FLAG_INHERIT, flags) };
527 if result == 0 {
528 return Err(std::io::Error::last_os_error());
529 }
530
531 Ok(())
532}