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 #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
initialize(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
_gl_convert_FILETIME_to_timespec(const FILETIME * ft)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
_gl_convert_FILETIME_to_POSIX(const FILETIME * ft)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
_gl_fstat_by_handle(HANDLE h,const char * path,struct stat * buf)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
426