1 /* Core of implementation of fstat and stat for native Windows.
2 Copyright (C) 2017-2021 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
8
9 This program 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 General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Written by Bruno Haible. */
18
19 #include <config.h>
20
21 #if defined _WIN32 && ! defined __CYGWIN__
22
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. */
26 #if HAVE_SDKDDKVER_H
27 # include <sdkddkver.h>
28 # if _WIN32_WINNT >= _WIN32_WINNT_VISTA
29 # define WIN32_ASSUME_VISTA 1
30 # else
31 # define WIN32_ASSUME_VISTA 0
32 # endif
33 # if !defined _WIN32_WINNT || (_WIN32_WINNT < _WIN32_WINNT_WIN8)
34 # undef _WIN32_WINNT
35 # define _WIN32_WINNT _WIN32_WINNT_WIN8
36 # endif
37 #else
38 # define WIN32_ASSUME_VISTA (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
39 #endif
40
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <errno.h>
44 #include <limits.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <windows.h>
48
49 /* Specification. */
50 #include "stat-w32.h"
51
52 #include "pathmax.h"
53 #include "verify.h"
54
55 /* Don't assume that UNICODE is not defined. */
56 #undef LoadLibrary
57 #define LoadLibrary LoadLibraryA
58 #undef GetFinalPathNameByHandle
59 #define GetFinalPathNameByHandle GetFinalPathNameByHandleA
60
61 /* Older mingw headers do not define VOLUME_NAME_NONE. */
62 #ifndef VOLUME_NAME_NONE
63 # define VOLUME_NAME_NONE 4
64 #endif
65
66 #if !WIN32_ASSUME_VISTA
67
68 /* Avoid warnings from gcc -Wcast-function-type. */
69 # define GetProcAddress \
70 (void *) GetProcAddress
71
72 # if _GL_WINDOWS_STAT_INODES == 2
73 /* GetFileInformationByHandleEx was introduced only in Windows Vista. */
74 typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
75 FILE_INFO_BY_HANDLE_CLASS fiClass,
76 LPVOID lpBuffer,
77 DWORD dwBufferSize);
78 static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL;
79 # endif
80 /* GetFinalPathNameByHandle was introduced only in Windows Vista. */
81 typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
82 LPSTR lpFilePath,
83 DWORD lenFilePath,
84 DWORD dwFlags);
85 static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
86 static BOOL initialized = FALSE;
87
88 static void
initialize(void)89 initialize (void)
90 {
91 HMODULE kernel32 = LoadLibrary ("kernel32.dll");
92 if (kernel32 != NULL)
93 {
94 # if _GL_WINDOWS_STAT_INODES == 2
95 GetFileInformationByHandleExFunc =
96 (GetFileInformationByHandleExFuncType) GetProcAddress (kernel32, "GetFileInformationByHandleEx");
97 # endif
98 GetFinalPathNameByHandleFunc =
99 (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, "GetFinalPathNameByHandleA");
100 }
101 initialized = TRUE;
102 }
103
104 #else
105
106 # define GetFileInformationByHandleExFunc GetFileInformationByHandleEx
107 # define GetFinalPathNameByHandleFunc GetFinalPathNameByHandle
108
109 #endif
110
111 /* Converts a FILETIME to GMT time since 1970-01-01 00:00:00. */
112 #if _GL_WINDOWS_STAT_TIMESPEC
113 struct timespec
_gl_convert_FILETIME_to_timespec(const FILETIME * ft)114 _gl_convert_FILETIME_to_timespec (const FILETIME *ft)
115 {
116 struct timespec result;
117 /* FILETIME: <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
118 unsigned long long since_1601 =
119 ((unsigned long long) ft->dwHighDateTime << 32)
120 | (unsigned long long) ft->dwLowDateTime;
121 if (since_1601 == 0)
122 {
123 result.tv_sec = 0;
124 result.tv_nsec = 0;
125 }
126 else
127 {
128 /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89
129 leap years, in total 134774 days. */
130 unsigned long long since_1970 =
131 since_1601 - (unsigned long long) 134774 * (unsigned long long) 86400 * (unsigned long long) 10000000;
132 result.tv_sec = since_1970 / (unsigned long long) 10000000;
133 result.tv_nsec = (unsigned long) (since_1970 % (unsigned long long) 10000000) * 100;
134 }
135 return result;
136 }
137 #else
138 time_t
_gl_convert_FILETIME_to_POSIX(const FILETIME * ft)139 _gl_convert_FILETIME_to_POSIX (const FILETIME *ft)
140 {
141 /* FILETIME: <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
142 unsigned long long since_1601 =
143 ((unsigned long long) ft->dwHighDateTime << 32)
144 | (unsigned long long) ft->dwLowDateTime;
145 if (since_1601 == 0)
146 return 0;
147 else
148 {
149 /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89
150 leap years, in total 134774 days. */
151 unsigned long long since_1970 =
152 since_1601 - (unsigned long long) 134774 * (unsigned long long) 86400 * (unsigned long long) 10000000;
153 return since_1970 / (unsigned long long) 10000000;
154 }
155 }
156 #endif
157
158 /* Fill *BUF with information about the file designated by H.
159 PATH is the file name, if known, otherwise NULL.
160 Return 0 if successful, or -1 with errno set upon failure. */
161 int
_gl_fstat_by_handle(HANDLE h,const char * path,struct stat * buf)162 _gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf)
163 {
164 /* GetFileType
165 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletype> */
166 DWORD type = GetFileType (h);
167 if (type == FILE_TYPE_DISK)
168 {
169 #if !WIN32_ASSUME_VISTA
170 if (!initialized)
171 initialize ();
172 #endif
173
174 /* st_mode can be determined through
175 GetFileAttributesEx
176 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
177 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
178 or through
179 GetFileInformationByHandle
180 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
181 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
182 or through
183 GetFileInformationByHandleEx with argument FileBasicInfo
184 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
185 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_basic_info>
186 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
187 BY_HANDLE_FILE_INFORMATION info;
188 if (! GetFileInformationByHandle (h, &info))
189 goto failed;
190
191 /* Test for error conditions before starting to fill *buf. */
192 if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
193 {
194 errno = EOVERFLOW;
195 return -1;
196 }
197
198 #if _GL_WINDOWS_STAT_INODES
199 /* st_ino can be determined through
200 GetFileInformationByHandle
201 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
202 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
203 as 64 bits, or through
204 GetFileInformationByHandleEx with argument FileIdInfo
205 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
206 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_id_info>
207 as 128 bits.
208 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_WIN8 or higher. */
209 /* Experiments show that GetFileInformationByHandleEx does not provide
210 much more information than GetFileInformationByHandle:
211 * The dwVolumeSerialNumber from GetFileInformationByHandle is equal
212 to the low 32 bits of the 64-bit VolumeSerialNumber from
213 GetFileInformationByHandleEx, and is apparently sufficient for
214 identifying the device.
215 * The nFileIndex from GetFileInformationByHandle is equal to the low
216 64 bits of the 128-bit FileId from GetFileInformationByHandleEx,
217 and the high 64 bits of this 128-bit FileId are zero.
218 * On a FAT file system, GetFileInformationByHandleEx fails with error
219 ERROR_INVALID_PARAMETER, whereas GetFileInformationByHandle
220 succeeds.
221 * On a CIFS/SMB file system, GetFileInformationByHandleEx fails with
222 error ERROR_INVALID_LEVEL, whereas GetFileInformationByHandle
223 succeeds. */
224 # if _GL_WINDOWS_STAT_INODES == 2
225 if (GetFileInformationByHandleExFunc != NULL)
226 {
227 FILE_ID_INFO id;
228 if (GetFileInformationByHandleExFunc (h, FileIdInfo, &id, sizeof (id)))
229 {
230 buf->st_dev = id.VolumeSerialNumber;
231 verify (sizeof (ino_t) == sizeof (id.FileId));
232 memcpy (&buf->st_ino, &id.FileId, sizeof (ino_t));
233 goto ino_done;
234 }
235 else
236 {
237 switch (GetLastError ())
238 {
239 case ERROR_INVALID_PARAMETER: /* older Windows version, or FAT */
240 case ERROR_INVALID_LEVEL: /* CIFS/SMB file system */
241 goto fallback;
242 default:
243 goto failed;
244 }
245 }
246 }
247 fallback: ;
248 /* Fallback for older Windows versions. */
249 buf->st_dev = info.dwVolumeSerialNumber;
250 buf->st_ino._gl_ino[0] = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
251 buf->st_ino._gl_ino[1] = 0;
252 ino_done: ;
253 # else /* _GL_WINDOWS_STAT_INODES == 1 */
254 buf->st_dev = info.dwVolumeSerialNumber;
255 buf->st_ino = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
256 # endif
257 #else
258 /* st_ino is not wide enough for identifying a file on a device.
259 Without st_ino, st_dev is pointless. */
260 buf->st_dev = 0;
261 buf->st_ino = 0;
262 #endif
263
264 /* st_mode. */
265 unsigned int mode =
266 /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ? */
267 ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR | S_IEXEC_UGO : _S_IFREG)
268 | S_IREAD_UGO
269 | ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? 0 : S_IWRITE_UGO);
270 if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
271 {
272 /* Determine whether the file is executable by looking at the file
273 name suffix.
274 If the file name is already known, use it. Otherwise, for
275 non-empty files, it can be determined through
276 GetFinalPathNameByHandle
277 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea>
278 or through
279 GetFileInformationByHandleEx with argument FileNameInfo
280 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
281 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_name_info>
282 Both require -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
283 if (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
284 {
285 char fpath[PATH_MAX];
286 if (path != NULL
287 || (GetFinalPathNameByHandleFunc != NULL
288 && GetFinalPathNameByHandleFunc (h, fpath, sizeof (fpath), VOLUME_NAME_NONE)
289 < sizeof (fpath)
290 && (path = fpath, 1)))
291 {
292 const char *last_dot = NULL;
293 const char *p;
294 for (p = path; *p != '\0'; p++)
295 if (*p == '.')
296 last_dot = p;
297 if (last_dot != NULL)
298 {
299 const char *suffix = last_dot + 1;
300 if (_stricmp (suffix, "exe") == 0
301 || _stricmp (suffix, "bat") == 0
302 || _stricmp (suffix, "cmd") == 0
303 || _stricmp (suffix, "com") == 0)
304 mode |= S_IEXEC_UGO;
305 }
306 }
307 else
308 /* Cannot determine file name. Pretend that it is executable. */
309 mode |= S_IEXEC_UGO;
310 }
311 }
312 buf->st_mode = mode;
313
314 /* st_nlink can be determined through
315 GetFileInformationByHandle
316 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
317 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
318 or through
319 GetFileInformationByHandleEx with argument FileStandardInfo
320 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
321 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_standard_info>
322 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
323 buf->st_nlink = (info.nNumberOfLinks > SHRT_MAX ? SHRT_MAX : info.nNumberOfLinks);
324
325 /* There's no easy way to map the Windows SID concept to an integer. */
326 buf->st_uid = 0;
327 buf->st_gid = 0;
328
329 /* st_rdev is irrelevant for normal files and directories. */
330 buf->st_rdev = 0;
331
332 /* st_size can be determined through
333 GetFileSizeEx
334 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfilesizeex>
335 or through
336 GetFileAttributesEx
337 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
338 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
339 or through
340 GetFileInformationByHandle
341 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
342 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
343 or through
344 GetFileInformationByHandleEx with argument FileStandardInfo
345 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
346 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_standard_info>
347 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
348 if (sizeof (buf->st_size) <= 4)
349 /* Range check already done above. */
350 buf->st_size = info.nFileSizeLow;
351 else
352 buf->st_size = ((long long) info.nFileSizeHigh << 32) | (long long) info.nFileSizeLow;
353
354 /* st_atime, st_mtime, st_ctime can be determined through
355 GetFileTime
356 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletime>
357 or through
358 GetFileAttributesEx
359 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
360 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
361 or through
362 GetFileInformationByHandle
363 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
364 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
365 or through
366 GetFileInformationByHandleEx with argument FileBasicInfo
367 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
368 <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_basic_info>
369 The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher. */
370 #if _GL_WINDOWS_STAT_TIMESPEC
371 buf->st_atim = _gl_convert_FILETIME_to_timespec (&info.ftLastAccessTime);
372 buf->st_mtim = _gl_convert_FILETIME_to_timespec (&info.ftLastWriteTime);
373 buf->st_ctim = _gl_convert_FILETIME_to_timespec (&info.ftCreationTime);
374 #else
375 buf->st_atime = _gl_convert_FILETIME_to_POSIX (&info.ftLastAccessTime);
376 buf->st_mtime = _gl_convert_FILETIME_to_POSIX (&info.ftLastWriteTime);
377 buf->st_ctime = _gl_convert_FILETIME_to_POSIX (&info.ftCreationTime);
378 #endif
379
380 return 0;
381 }
382 else if (type == FILE_TYPE_CHAR || type == FILE_TYPE_PIPE)
383 {
384 buf->st_dev = 0;
385 #if _GL_WINDOWS_STAT_INODES == 2
386 buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
387 #else
388 buf->st_ino = 0;
389 #endif
390 buf->st_mode = (type == FILE_TYPE_PIPE ? _S_IFIFO : _S_IFCHR);
391 buf->st_nlink = 1;
392 buf->st_uid = 0;
393 buf->st_gid = 0;
394 buf->st_rdev = 0;
395 if (type == FILE_TYPE_PIPE)
396 {
397 /* PeekNamedPipe
398 <https://msdn.microsoft.com/en-us/library/aa365779.aspx> */
399 DWORD bytes_available;
400 if (PeekNamedPipe (h, NULL, 0, NULL, &bytes_available, NULL))
401 buf->st_size = bytes_available;
402 else
403 buf->st_size = 0;
404 }
405 else
406 buf->st_size = 0;
407 #if _GL_WINDOWS_STAT_TIMESPEC
408 buf->st_atim.tv_sec = 0; buf->st_atim.tv_nsec = 0;
409 buf->st_mtim.tv_sec = 0; buf->st_mtim.tv_nsec = 0;
410 buf->st_ctim.tv_sec = 0; buf->st_ctim.tv_nsec = 0;
411 #else
412 buf->st_atime = 0;
413 buf->st_mtime = 0;
414 buf->st_ctime = 0;
415 #endif
416 return 0;
417 }
418 else
419 {
420 errno = ENOENT;
421 return -1;
422 }
423
424 failed:
425 {
426 DWORD error = GetLastError ();
427 #if 0
428 fprintf (stderr, "_gl_fstat_by_handle error 0x%x\n", (unsigned int) error);
429 #endif
430 switch (error)
431 {
432 case ERROR_ACCESS_DENIED:
433 case ERROR_SHARING_VIOLATION:
434 errno = EACCES;
435 break;
436
437 case ERROR_OUTOFMEMORY:
438 errno = ENOMEM;
439 break;
440
441 case ERROR_WRITE_FAULT:
442 case ERROR_READ_FAULT:
443 case ERROR_GEN_FAILURE:
444 errno = EIO;
445 break;
446
447 default:
448 errno = EINVAL;
449 break;
450 }
451 return -1;
452 }
453 }
454
455 #else
456
457 /* This declaration is solely to ensure that after preprocessing
458 this file is never empty. */
459 typedef int dummy;
460
461 #endif
462