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