1/* Core of implementation of fstat and stat for native Windows.
2   Copyright (C) 2017-2022 Free Software Foundation, Inc.
3
4   This file is free software: you can redistribute it and/or modify
5   it under the terms of the GNU Lesser General Public License as
6   published by the Free Software Foundation; either version 2.1 of the
7   License, or (at your option) any later version.
8
9   This file 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 Lesser General Public License for more details.
13
14   You should have received a copy of the GNU Lesser 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.  */
74typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
75                                                               FILE_INFO_BY_HANDLE_CLASS fiClass,
76                                                               LPVOID lpBuffer,
77                                                               DWORD dwBufferSize);
78static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL;
79# endif
80/* GetFinalPathNameByHandle was introduced only in Windows Vista.  */
81typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
82                                                           LPSTR lpFilePath,
83                                                           DWORD lenFilePath,
84                                                           DWORD dwFlags);
85static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
86static BOOL initialized = FALSE;
87
88static void
89initialize (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
113struct timespec
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
138time_t
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.  */
161int
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.  */
459typedef int dummy;
460
461#endif
462