summaryrefslogtreecommitdiffstats
path: root/gl/stat-w32.c
diff options
context:
space:
mode:
Diffstat (limited to 'gl/stat-w32.c')
-rw-r--r--gl/stat-w32.c460
1 files changed, 460 insertions, 0 deletions
diff --git a/gl/stat-w32.c b/gl/stat-w32.c
new file mode 100644
index 00000000..2f011975
--- /dev/null
+++ b/gl/stat-w32.c
@@ -0,0 +1,460 @@
1/* Core of implementation of fstat and stat for native Windows.
2 Copyright (C) 2017-2023 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
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
54/* Don't assume that UNICODE is not defined. */
55#undef LoadLibrary
56#define LoadLibrary LoadLibraryA
57#undef GetFinalPathNameByHandle
58#define GetFinalPathNameByHandle GetFinalPathNameByHandleA
59
60/* Older mingw headers do not define VOLUME_NAME_NONE. */
61#ifndef VOLUME_NAME_NONE
62# define VOLUME_NAME_NONE 4
63#endif
64
65#if !WIN32_ASSUME_VISTA
66
67/* Avoid warnings from gcc -Wcast-function-type. */
68# define GetProcAddress \
69 (void *) GetProcAddress
70
71# if _GL_WINDOWS_STAT_INODES == 2
72/* GetFileInformationByHandleEx was introduced only in Windows Vista. */
73typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
74 FILE_INFO_BY_HANDLE_CLASS fiClass,
75 LPVOID lpBuffer,
76 DWORD dwBufferSize);
77static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL;
78# endif
79/* GetFinalPathNameByHandle was introduced only in Windows Vista. */
80typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
81 LPSTR lpFilePath,
82 DWORD lenFilePath,
83 DWORD dwFlags);
84static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
85static BOOL initialized = FALSE;
86
87static void
88initialize (void)
89{
90 HMODULE kernel32 = LoadLibrary ("kernel32.dll");
91 if (kernel32 != NULL)
92 {
93# if _GL_WINDOWS_STAT_INODES == 2
94 GetFileInformationByHandleExFunc =
95 (GetFileInformationByHandleExFuncType) GetProcAddress (kernel32, "GetFileInformationByHandleEx");
96# endif
97 GetFinalPathNameByHandleFunc =
98 (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, "GetFinalPathNameByHandleA");
99 }
100 initialized = TRUE;
101}
102
103#else
104
105# define GetFileInformationByHandleExFunc GetFileInformationByHandleEx
106# define GetFinalPathNameByHandleFunc GetFinalPathNameByHandle
107
108#endif
109
110/* Converts a FILETIME to GMT time since 1970-01-01 00:00:00. */
111#if _GL_WINDOWS_STAT_TIMESPEC
112struct timespec
113_gl_convert_FILETIME_to_timespec (const FILETIME *ft)
114{
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;
120 if (since_1601 == 0)
121 {
122 result.tv_sec = 0;
123 result.tv_nsec = 0;
124 }
125 else
126 {
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;
133 }
134 return result;
135}
136#else
137time_t
138_gl_convert_FILETIME_to_POSIX (const FILETIME *ft)
139{
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;
144 if (since_1601 == 0)
145 return 0;
146 else
147 {
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;
153 }
154}
155#endif
156
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. */
160int
161_gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf)
162{
163 /* GetFileType
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)
167 {
168#if !WIN32_ASSUME_VISTA
169 if (!initialized)
170 initialize ();
171#endif
172
173 /* st_mode can be determined through
174 GetFileAttributesEx
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>
177 or through
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>
181 or through
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))
188 goto failed;
189
190 /* Test for error conditions before starting to fill *buf. */
191 if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
192 {
193 errno = EOVERFLOW;
194 return -1;
195 }
196
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>
206 as 128 bits.
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
219 succeeds.
220 * On a CIFS/SMB file system, GetFileInformationByHandleEx fails with
221 error ERROR_INVALID_LEVEL, whereas GetFileInformationByHandle
222 succeeds. */
223# if _GL_WINDOWS_STAT_INODES == 2
224 if (GetFileInformationByHandleExFunc != NULL)
225 {
226 FILE_ID_INFO id;
227 if (GetFileInformationByHandleExFunc (h, FileIdInfo, &id, sizeof (id)))
228 {
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));
232 goto ino_done;
233 }
234 else
235 {
236 switch (GetLastError ())
237 {
238 case ERROR_INVALID_PARAMETER: /* older Windows version, or FAT */
239 case ERROR_INVALID_LEVEL: /* CIFS/SMB file system */
240 goto fallback;
241 default:
242 goto failed;
243 }
244 }
245 }
246 fallback: ;
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;
251 ino_done: ;
252# else /* _GL_WINDOWS_STAT_INODES == 1 */
253 buf->st_dev = info.dwVolumeSerialNumber;
254 buf->st_ino = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
255# endif
256#else
257 /* st_ino is not wide enough for identifying a file on a device.
258 Without st_ino, st_dev is pointless. */
259 buf->st_dev = 0;
260 buf->st_ino = 0;
261#endif
262
263 /* st_mode. */
264 unsigned int mode =
265 /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ? */
266 ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR | S_IEXEC_UGO : _S_IFREG)
267 | S_IREAD_UGO
268 | ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? 0 : S_IWRITE_UGO);
269 if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
270 {
271 /* Determine whether the file is executable by looking at the file
272 name suffix.
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>
277 or through
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)
283 {
284 char fpath[PATH_MAX];
285 if (path != NULL
286 || (GetFinalPathNameByHandleFunc != NULL
287 && GetFinalPathNameByHandleFunc (h, fpath, sizeof (fpath), VOLUME_NAME_NONE)
288 < sizeof (fpath)
289 && (path = fpath, 1)))
290 {
291 const char *last_dot = NULL;
292 const char *p;
293 for (p = path; *p != '\0'; p++)
294 if (*p == '.')
295 last_dot = p;
296 if (last_dot != NULL)
297 {
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)
303 mode |= S_IEXEC_UGO;
304 }
305 }
306 else
307 /* Cannot determine file name. Pretend that it is executable. */
308 mode |= S_IEXEC_UGO;
309 }
310 }
311 buf->st_mode = mode;
312
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>
317 or through
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);
323
324 /* There's no easy way to map the Windows SID concept to an integer. */
325 buf->st_uid = 0;
326 buf->st_gid = 0;
327
328 /* st_rdev is irrelevant for normal files and directories. */
329 buf->st_rdev = 0;
330
331 /* st_size can be determined through
332 GetFileSizeEx
333 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfilesizeex>
334 or through
335 GetFileAttributesEx
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>
338 or through
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>
342 or through
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;
350 else
351 buf->st_size = ((long long) info.nFileSizeHigh << 32) | (long long) info.nFileSizeLow;
352
353 /* st_atime, st_mtime, st_ctime can be determined through
354 GetFileTime
355 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletime>
356 or through
357 GetFileAttributesEx
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>
360 or through
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>
364 or through
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);
373#else
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);
377#endif
378
379 return 0;
380 }
381 else if (type == FILE_TYPE_CHAR || type == FILE_TYPE_PIPE)
382 {
383 buf->st_dev = 0;
384#if _GL_WINDOWS_STAT_INODES == 2
385 buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
386#else
387 buf->st_ino = 0;
388#endif
389 buf->st_mode = (type == FILE_TYPE_PIPE ? _S_IFIFO : _S_IFCHR);
390 buf->st_nlink = 1;
391 buf->st_uid = 0;
392 buf->st_gid = 0;
393 buf->st_rdev = 0;
394 if (type == FILE_TYPE_PIPE)
395 {
396 /* PeekNamedPipe
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;
401 else
402 buf->st_size = 0;
403 }
404 else
405 buf->st_size = 0;
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;
410#else
411 buf->st_atime = 0;
412 buf->st_mtime = 0;
413 buf->st_ctime = 0;
414#endif
415 return 0;
416 }
417 else
418 {
419 errno = ENOENT;
420 return -1;
421 }
422
423 failed:
424 {
425 DWORD error = GetLastError ();
426 #if 0
427 fprintf (stderr, "_gl_fstat_by_handle error 0x%x\n", (unsigned int) error);
428 #endif
429 switch (error)
430 {
431 case ERROR_ACCESS_DENIED:
432 case ERROR_SHARING_VIOLATION:
433 errno = EACCES;
434 break;
435
436 case ERROR_OUTOFMEMORY:
437 errno = ENOMEM;
438 break;
439
440 case ERROR_WRITE_FAULT:
441 case ERROR_READ_FAULT:
442 case ERROR_GEN_FAILURE:
443 errno = EIO;
444 break;
445
446 default:
447 errno = EINVAL;
448 break;
449 }
450 return -1;
451 }
452}
453
454#else
455
456/* This declaration is solely to ensure that after preprocessing
457 this file is never empty. */
458typedef int dummy;
459
460#endif