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