1#![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; #[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 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 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 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; 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; 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; } 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 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 let st_ino = info.FileId128;
385 let st_reparse_tag = info.ReparseTag;
389 if info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
390 && info.ReparseTag == IO_REPARSE_TAG_SYMLINK
391 {
392 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; }
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}