1 /* Core of implementation of fstat and stat for native Windows.
2 Copyright (C) 2017-2023 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Bruno Haible. */
21 #if defined _WIN32 && ! defined __CYGWIN__
23 /* Attempt to make <windows.h> define FILE_ID_INFO.
24 But ensure that the redefinition of _WIN32_WINNT does not make us assume
25 Windows Vista or newer when building for an older version of Windows. */
27 # include <sdkddkver.h>
28 # if _WIN32_WINNT >= _WIN32_WINNT_VISTA
29 # define WIN32_ASSUME_VISTA 1
31 # define WIN32_ASSUME_VISTA 0
33 # if !defined _WIN32_WINNT || (_WIN32_WINNT < _WIN32_WINNT_WIN8)
35 # define _WIN32_WINNT _WIN32_WINNT_WIN8
38 # define WIN32_ASSUME_VISTA (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
41 #include <sys/types.h>
54 /* Don't assume that UNICODE is not defined. */
56 #define LoadLibrary LoadLibraryA
57 #undef GetFinalPathNameByHandle
58 #define GetFinalPathNameByHandle GetFinalPathNameByHandleA
60 /* Older mingw headers do not define VOLUME_NAME_NONE. */
61 #ifndef VOLUME_NAME_NONE
62 # define VOLUME_NAME_NONE 4
65 #if !WIN32_ASSUME_VISTA
67 /* Avoid warnings from gcc -Wcast-function-type. */
68 # define GetProcAddress \
69 (void *) GetProcAddress
71 # if _GL_WINDOWS_STAT_INODES == 2
72 /* GetFileInformationByHandleEx was introduced only in Windows Vista. */
73 typedef DWORD (WINAPI
* GetFileInformationByHandleExFuncType
) (HANDLE hFile
,
74 FILE_INFO_BY_HANDLE_CLASS fiClass
,
77 static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc
= NULL
;
79 /* GetFinalPathNameByHandle was introduced only in Windows Vista. */
80 typedef DWORD (WINAPI
* GetFinalPathNameByHandleFuncType
) (HANDLE hFile
,
84 static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc
= NULL
;
85 static BOOL initialized
= FALSE
;
90 HMODULE kernel32
= LoadLibrary ("kernel32.dll");
93 # if _GL_WINDOWS_STAT_INODES == 2
94 GetFileInformationByHandleExFunc
=
95 (GetFileInformationByHandleExFuncType
) GetProcAddress (kernel32
, "GetFileInformationByHandleEx");
97 GetFinalPathNameByHandleFunc
=
98 (GetFinalPathNameByHandleFuncType
) GetProcAddress (kernel32
, "GetFinalPathNameByHandleA");
105 # define GetFileInformationByHandleExFunc GetFileInformationByHandleEx
106 # define GetFinalPathNameByHandleFunc GetFinalPathNameByHandle
110 /* Converts a FILETIME to GMT time since 1970-01-01 00:00:00. */
111 #if _GL_WINDOWS_STAT_TIMESPEC
113 _gl_convert_FILETIME_to_timespec (const FILETIME
*ft
)
115 struct timespec result
;
116 /* FILETIME: <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
117 unsigned long long since_1601
=
118 ((unsigned long long) ft
->dwHighDateTime
<< 32)
119 | (unsigned long long) ft
->dwLowDateTime
;
127 /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89
128 leap years, in total 134774 days. */
129 unsigned long long since_1970
=
130 since_1601
- (unsigned long long) 134774 * (unsigned long long) 86400 * (unsigned long long) 10000000;
131 result
.tv_sec
= since_1970
/ (unsigned long long) 10000000;
132 result
.tv_nsec
= (unsigned long) (since_1970
% (unsigned long long) 10000000) * 100;
138 _gl_convert_FILETIME_to_POSIX (const FILETIME
*ft
)
140 /* FILETIME: <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
141 unsigned long long since_1601
=
142 ((unsigned long long) ft
->dwHighDateTime
<< 32)
143 | (unsigned long long) ft
->dwLowDateTime
;
148 /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89
149 leap years, in total 134774 days. */
150 unsigned long long since_1970
=
151 since_1601
- (unsigned long long) 134774 * (unsigned long long) 86400 * (unsigned long long) 10000000;
152 return since_1970
/ (unsigned long long) 10000000;
157 /* Fill *BUF with information about the file designated by H.
158 PATH is the file name, if known, otherwise NULL.
159 Return 0 if successful, or -1 with errno set upon failure. */
161 _gl_fstat_by_handle (HANDLE h
, const char *path
, struct stat
*buf
)
164 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletype> */
165 DWORD type
= GetFileType (h
);
166 if (type
== FILE_TYPE_DISK
)
168 #if !WIN32_ASSUME_VISTA
173 /* st_mode can be determined through
175 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
176 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
178 GetFileInformationByHandle
179 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
180 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
182 GetFileInformationByHandleEx with argument FileBasicInfo
183 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
184 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_basic_info>
185 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
186 BY_HANDLE_FILE_INFORMATION info
;
187 if (! GetFileInformationByHandle (h
, &info
))
190 /* Test for error conditions before starting to fill *buf. */
191 if (sizeof (buf
->st_size
) <= 4 && info
.nFileSizeHigh
> 0)
197 #if _GL_WINDOWS_STAT_INODES
198 /* st_ino can be determined through
199 GetFileInformationByHandle
200 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
201 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
202 as 64 bits, or through
203 GetFileInformationByHandleEx with argument FileIdInfo
204 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
205 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_id_info>
207 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_WIN8 or higher. */
208 /* Experiments show that GetFileInformationByHandleEx does not provide
209 much more information than GetFileInformationByHandle:
210 * The dwVolumeSerialNumber from GetFileInformationByHandle is equal
211 to the low 32 bits of the 64-bit VolumeSerialNumber from
212 GetFileInformationByHandleEx, and is apparently sufficient for
213 identifying the device.
214 * The nFileIndex from GetFileInformationByHandle is equal to the low
215 64 bits of the 128-bit FileId from GetFileInformationByHandleEx,
216 and the high 64 bits of this 128-bit FileId are zero.
217 * On a FAT file system, GetFileInformationByHandleEx fails with error
218 ERROR_INVALID_PARAMETER, whereas GetFileInformationByHandle
220 * On a CIFS/SMB file system, GetFileInformationByHandleEx fails with
221 error ERROR_INVALID_LEVEL, whereas GetFileInformationByHandle
223 # if _GL_WINDOWS_STAT_INODES == 2
224 if (GetFileInformationByHandleExFunc
!= NULL
)
227 if (GetFileInformationByHandleExFunc (h
, FileIdInfo
, &id
, sizeof (id
)))
229 buf
->st_dev
= id
.VolumeSerialNumber
;
230 static_assert (sizeof (ino_t
) == sizeof (id
.FileId
));
231 memcpy (&buf
->st_ino
, &id
.FileId
, sizeof (ino_t
));
236 switch (GetLastError ())
238 case ERROR_INVALID_PARAMETER
: /* older Windows version, or FAT */
239 case ERROR_INVALID_LEVEL
: /* CIFS/SMB file system */
247 /* Fallback for older Windows versions. */
248 buf
->st_dev
= info
.dwVolumeSerialNumber
;
249 buf
->st_ino
._gl_ino
[0] = ((ULONGLONG
) info
.nFileIndexHigh
<< 32) | (ULONGLONG
) info
.nFileIndexLow
;
250 buf
->st_ino
._gl_ino
[1] = 0;
252 # else /* _GL_WINDOWS_STAT_INODES == 1 */
253 buf
->st_dev
= info
.dwVolumeSerialNumber
;
254 buf
->st_ino
= ((ULONGLONG
) info
.nFileIndexHigh
<< 32) | (ULONGLONG
) info
.nFileIndexLow
;
257 /* st_ino is not wide enough for identifying a file on a device.
258 Without st_ino, st_dev is pointless. */
265 /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ? */
266 ((info
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) ? _S_IFDIR
| S_IEXEC_UGO
: _S_IFREG
)
268 | ((info
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) ? 0 : S_IWRITE_UGO
);
269 if (!(info
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
271 /* Determine whether the file is executable by looking at the file
273 If the file name is already known, use it. Otherwise, for
274 non-empty files, it can be determined through
275 GetFinalPathNameByHandle
276 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea>
278 GetFileInformationByHandleEx with argument FileNameInfo
279 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
280 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_name_info>
281 Both require -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
282 if (info
.nFileSizeHigh
> 0 || info
.nFileSizeLow
> 0)
284 char fpath
[PATH_MAX
];
286 || (GetFinalPathNameByHandleFunc
!= NULL
287 && GetFinalPathNameByHandleFunc (h
, fpath
, sizeof (fpath
), VOLUME_NAME_NONE
)
289 && (path
= fpath
, 1)))
291 const char *last_dot
= NULL
;
293 for (p
= path
; *p
!= '\0'; p
++)
296 if (last_dot
!= NULL
)
298 const char *suffix
= last_dot
+ 1;
299 if (_stricmp (suffix
, "exe") == 0
300 || _stricmp (suffix
, "bat") == 0
301 || _stricmp (suffix
, "cmd") == 0
302 || _stricmp (suffix
, "com") == 0)
307 /* Cannot determine file name. Pretend that it is executable. */
313 /* st_nlink can be determined through
314 GetFileInformationByHandle
315 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
316 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
318 GetFileInformationByHandleEx with argument FileStandardInfo
319 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
320 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_standard_info>
321 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
322 buf
->st_nlink
= (info
.nNumberOfLinks
> SHRT_MAX
? SHRT_MAX
: info
.nNumberOfLinks
);
324 /* There's no easy way to map the Windows SID concept to an integer. */
328 /* st_rdev is irrelevant for normal files and directories. */
331 /* st_size can be determined through
333 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfilesizeex>
336 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
337 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
339 GetFileInformationByHandle
340 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
341 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
343 GetFileInformationByHandleEx with argument FileStandardInfo
344 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
345 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_standard_info>
346 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
347 if (sizeof (buf
->st_size
) <= 4)
348 /* Range check already done above. */
349 buf
->st_size
= info
.nFileSizeLow
;
351 buf
->st_size
= ((long long) info
.nFileSizeHigh
<< 32) | (long long) info
.nFileSizeLow
;
353 /* st_atime, st_mtime, st_ctime can be determined through
355 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletime>
358 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
359 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
361 GetFileInformationByHandle
362 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
363 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
365 GetFileInformationByHandleEx with argument FileBasicInfo
366 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
367 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_basic_info>
368 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
369 #if _GL_WINDOWS_STAT_TIMESPEC
370 buf
->st_atim
= _gl_convert_FILETIME_to_timespec (&info
.ftLastAccessTime
);
371 buf
->st_mtim
= _gl_convert_FILETIME_to_timespec (&info
.ftLastWriteTime
);
372 buf
->st_ctim
= _gl_convert_FILETIME_to_timespec (&info
.ftCreationTime
);
374 buf
->st_atime
= _gl_convert_FILETIME_to_POSIX (&info
.ftLastAccessTime
);
375 buf
->st_mtime
= _gl_convert_FILETIME_to_POSIX (&info
.ftLastWriteTime
);
376 buf
->st_ctime
= _gl_convert_FILETIME_to_POSIX (&info
.ftCreationTime
);
381 else if (type
== FILE_TYPE_CHAR
|| type
== FILE_TYPE_PIPE
)
384 #if _GL_WINDOWS_STAT_INODES == 2
385 buf
->st_ino
._gl_ino
[0] = buf
->st_ino
._gl_ino
[1] = 0;
389 buf
->st_mode
= (type
== FILE_TYPE_PIPE
? _S_IFIFO
: _S_IFCHR
);
394 if (type
== FILE_TYPE_PIPE
)
397 <https://msdn.microsoft.com/en-us/library/aa365779.aspx> */
398 DWORD bytes_available
;
399 if (PeekNamedPipe (h
, NULL
, 0, NULL
, &bytes_available
, NULL
))
400 buf
->st_size
= bytes_available
;
406 #if _GL_WINDOWS_STAT_TIMESPEC
407 buf
->st_atim
.tv_sec
= 0; buf
->st_atim
.tv_nsec
= 0;
408 buf
->st_mtim
.tv_sec
= 0; buf
->st_mtim
.tv_nsec
= 0;
409 buf
->st_ctim
.tv_sec
= 0; buf
->st_ctim
.tv_nsec
= 0;
425 DWORD error
= GetLastError ();
427 fprintf (stderr
, "_gl_fstat_by_handle error 0x%x\n", (unsigned int) error
);
431 case ERROR_ACCESS_DENIED
:
432 case ERROR_SHARING_VIOLATION
:
436 case ERROR_OUTOFMEMORY
:
440 case ERROR_WRITE_FAULT
:
441 case ERROR_READ_FAULT
:
442 case ERROR_GEN_FAILURE
:
456 /* This declaration is solely to ensure that after preprocessing
457 this file is never empty. */