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