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