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.  */
57typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
58                                                               FILE_INFO_BY_HANDLE_CLASS fiClass,
59                                                               LPVOID lpBuffer,
60                                                               DWORD dwBufferSize);
61static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL;
62# endif
63/* GetFinalPathNameByHandle was introduced only in Windows Vista.  */
64typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
65                                                           LPSTR lpFilePath,
66                                                           DWORD lenFilePath,
67                                                           DWORD dwFlags);
68static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
69static BOOL initialized = FALSE;
70
71static void
72initialize (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
96struct timespec
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
121time_t
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.  */
144int
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.  */
442typedef int dummy;
443
444#endif
445