summaryrefslogtreecommitdiffstats
path: root/gl/stat-w32.c
blob: ddd6f5985664399443660746fca2b7c7733015d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
/* Core of implementation of fstat and stat for native Windows.
   Copyright (C) 2017-2024 Free Software Foundation, Inc.

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible.  */

#include <config.h>

#if defined _WIN32 && ! defined __CYGWIN__

/* Attempt to make <windows.h> define FILE_ID_INFO.
   But ensure that the redefinition of _WIN32_WINNT does not make us assume
   Windows Vista or newer when building for an older version of Windows.  */
#if HAVE_SDKDDKVER_H
# include <sdkddkver.h>
# if _WIN32_WINNT >= _WIN32_WINNT_VISTA
#  define WIN32_ASSUME_VISTA 1
# else
#  define WIN32_ASSUME_VISTA 0
# endif
# if !defined _WIN32_WINNT || (_WIN32_WINNT < _WIN32_WINNT_WIN8)
#  undef _WIN32_WINNT
#  define _WIN32_WINNT _WIN32_WINNT_WIN8
# endif
#else
# define WIN32_ASSUME_VISTA (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <windows.h>

/* Specification.  */
#include "stat-w32.h"

#include "pathmax.h"

/* Don't assume that UNICODE is not defined.  */
#undef LoadLibrary
#define LoadLibrary LoadLibraryA
#undef GetFinalPathNameByHandle
#define GetFinalPathNameByHandle GetFinalPathNameByHandleA

/* Older mingw headers do not define VOLUME_NAME_NONE.  */
#ifndef VOLUME_NAME_NONE
# define VOLUME_NAME_NONE 4
#endif

#if !WIN32_ASSUME_VISTA

/* Avoid warnings from gcc -Wcast-function-type.  */
# define GetProcAddress \
   (void *) GetProcAddress

# if _GL_WINDOWS_STAT_INODES == 2
/* GetFileInformationByHandleEx was introduced only in Windows Vista.  */
typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
                                                               FILE_INFO_BY_HANDLE_CLASS fiClass,
                                                               LPVOID lpBuffer,
                                                               DWORD dwBufferSize);
static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL;
# endif
/* GetFinalPathNameByHandle was introduced only in Windows Vista.  */
typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
                                                           LPSTR lpFilePath,
                                                           DWORD lenFilePath,
                                                           DWORD dwFlags);
static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
static BOOL initialized = FALSE;

static void
initialize (void)
{
  HMODULE kernel32 = LoadLibrary ("kernel32.dll");
  if (kernel32 != NULL)
    {
# if _GL_WINDOWS_STAT_INODES == 2
      GetFileInformationByHandleExFunc =
        (GetFileInformationByHandleExFuncType) GetProcAddress (kernel32, "GetFileInformationByHandleEx");
# endif
      GetFinalPathNameByHandleFunc =
        (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, "GetFinalPathNameByHandleA");
    }
  initialized = TRUE;
}

#else

# define GetFileInformationByHandleExFunc GetFileInformationByHandleEx
# define GetFinalPathNameByHandleFunc GetFinalPathNameByHandle

#endif

/* Converts a FILETIME to GMT time since 1970-01-01 00:00:00.  */
#if _GL_WINDOWS_STAT_TIMESPEC
struct timespec
_gl_convert_FILETIME_to_timespec (const FILETIME *ft)
{
  struct timespec result;
  /* FILETIME: <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
  unsigned long long since_1601 =
    ((unsigned long long) ft->dwHighDateTime << 32)
    | (unsigned long long) ft->dwLowDateTime;
  if (since_1601 == 0)
    {
      result.tv_sec = 0;
      result.tv_nsec = 0;
    }
  else
    {
      /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89
         leap years, in total 134774 days.  */
      unsigned long long since_1970 =
        since_1601 - (unsigned long long) 134774 * (unsigned long long) 86400 * (unsigned long long) 10000000;
      result.tv_sec = since_1970 / (unsigned long long) 10000000;
      result.tv_nsec = (unsigned long) (since_1970 % (unsigned long long) 10000000) * 100;
    }
  return result;
}
#else
time_t
_gl_convert_FILETIME_to_POSIX (const FILETIME *ft)
{
  /* FILETIME: <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
  unsigned long long since_1601 =
    ((unsigned long long) ft->dwHighDateTime << 32)
    | (unsigned long long) ft->dwLowDateTime;
  if (since_1601 == 0)
    return 0;
  else
    {
      /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89
         leap years, in total 134774 days.  */
      unsigned long long since_1970 =
        since_1601 - (unsigned long long) 134774 * (unsigned long long) 86400 * (unsigned long long) 10000000;
      return since_1970 / (unsigned long long) 10000000;
    }
}
#endif

/* Fill *BUF with information about the file designated by H.
   PATH is the file name, if known, otherwise NULL.
   Return 0 if successful, or -1 with errno set upon failure.  */
int
_gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf)
{
  /* GetFileType
     <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletype> */
  DWORD type = GetFileType (h);
  if (type == FILE_TYPE_DISK)
    {
#if !WIN32_ASSUME_VISTA
      if (!initialized)
        initialize ();
#endif

      /* st_mode can be determined through
         GetFileAttributesEx
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
         or through
         GetFileInformationByHandle
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
         or through
         GetFileInformationByHandleEx with argument FileBasicInfo
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_basic_info>
         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
      BY_HANDLE_FILE_INFORMATION info;
      if (! GetFileInformationByHandle (h, &info))
        goto failed;

      /* Test for error conditions before starting to fill *buf.  */
      if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
        {
          errno = EOVERFLOW;
          return -1;
        }

#if _GL_WINDOWS_STAT_INODES
      /* st_ino can be determined through
         GetFileInformationByHandle
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
         as 64 bits, or through
         GetFileInformationByHandleEx with argument FileIdInfo
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_id_info>
         as 128 bits.
         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_WIN8 or higher.  */
      /* Experiments show that GetFileInformationByHandleEx does not provide
         much more information than GetFileInformationByHandle:
           * The dwVolumeSerialNumber from GetFileInformationByHandle is equal
             to the low 32 bits of the 64-bit VolumeSerialNumber from
             GetFileInformationByHandleEx, and is apparently sufficient for
             identifying the device.
           * The nFileIndex from GetFileInformationByHandle is equal to the low
             64 bits of the 128-bit FileId from GetFileInformationByHandleEx,
             and the high 64 bits of this 128-bit FileId are zero.
           * On a FAT file system, GetFileInformationByHandleEx fails with error
             ERROR_INVALID_PARAMETER, whereas GetFileInformationByHandle
             succeeds.
           * On a CIFS/SMB file system, GetFileInformationByHandleEx fails with
             error ERROR_INVALID_LEVEL, whereas GetFileInformationByHandle
             succeeds.  */
# if _GL_WINDOWS_STAT_INODES == 2
      if (GetFileInformationByHandleExFunc != NULL)
        {
          FILE_ID_INFO id;
          if (GetFileInformationByHandleExFunc (h, FileIdInfo, &id, sizeof (id)))
            {
              buf->st_dev = id.VolumeSerialNumber;
              static_assert (sizeof (ino_t) == sizeof (id.FileId));
              memcpy (&buf->st_ino, &id.FileId, sizeof (ino_t));
              goto ino_done;
            }
          else
            {
              switch (GetLastError ())
                {
                case ERROR_INVALID_PARAMETER: /* older Windows version, or FAT */
                case ERROR_INVALID_LEVEL: /* CIFS/SMB file system */
                  goto fallback;
                default:
                  goto failed;
                }
            }
        }
     fallback: ;
      /* Fallback for older Windows versions.  */
      buf->st_dev = info.dwVolumeSerialNumber;
      buf->st_ino._gl_ino[0] = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
      buf->st_ino._gl_ino[1] = 0;
     ino_done: ;
# else /* _GL_WINDOWS_STAT_INODES == 1 */
      buf->st_dev = info.dwVolumeSerialNumber;
      buf->st_ino = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
# endif
#else
      /* st_ino is not wide enough for identifying a file on a device.
         Without st_ino, st_dev is pointless.  */
      buf->st_dev = 0;
      buf->st_ino = 0;
#endif

      /* st_mode.  */
      unsigned int mode =
        /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ?  */
        ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR | S_IEXEC_UGO : _S_IFREG)
        | S_IREAD_UGO
        | ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? 0 : S_IWRITE_UGO);
      if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {
          /* Determine whether the file is executable by looking at the file
             name suffix.
             If the file name is already known, use it. Otherwise, for
             non-empty files, it can be determined through
             GetFinalPathNameByHandle
             <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea>
             or through
             GetFileInformationByHandleEx with argument FileNameInfo
             <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
             <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_name_info>
             Both require -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
          if (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
            {
              char fpath[PATH_MAX];
              if (path != NULL
                  || (GetFinalPathNameByHandleFunc != NULL
                      && GetFinalPathNameByHandleFunc (h, fpath, sizeof (fpath), VOLUME_NAME_NONE)
                         < sizeof (fpath)
                      && (path = fpath, 1)))
                {
                  const char *last_dot = NULL;
                  const char *p;
                  for (p = path; *p != '\0'; p++)
                    if (*p == '.')
                      last_dot = p;
                  if (last_dot != NULL)
                    {
                      const char *suffix = last_dot + 1;
                      if (_stricmp (suffix, "exe") == 0
                          || _stricmp (suffix, "bat") == 0
                          || _stricmp (suffix, "cmd") == 0
                          || _stricmp (suffix, "com") == 0)
                        mode |= S_IEXEC_UGO;
                    }
                }
              else
                /* Cannot determine file name.  Pretend that it is executable.  */
                mode |= S_IEXEC_UGO;
            }
        }
      buf->st_mode = mode;

      /* st_nlink can be determined through
         GetFileInformationByHandle
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
         or through
         GetFileInformationByHandleEx with argument FileStandardInfo
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_standard_info>
         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
      buf->st_nlink = (info.nNumberOfLinks > SHRT_MAX ? SHRT_MAX : info.nNumberOfLinks);

      /* There's no easy way to map the Windows SID concept to an integer.  */
      buf->st_uid = 0;
      buf->st_gid = 0;

      /* st_rdev is irrelevant for normal files and directories.  */
      buf->st_rdev = 0;

      /* st_size can be determined through
         GetFileSizeEx
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfilesizeex>
         or through
         GetFileAttributesEx
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
         or through
         GetFileInformationByHandle
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
         or through
         GetFileInformationByHandleEx with argument FileStandardInfo
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_standard_info>
         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
      if (sizeof (buf->st_size) <= 4)
        /* Range check already done above.  */
        buf->st_size = info.nFileSizeLow;
      else
        buf->st_size = ((long long) info.nFileSizeHigh << 32) | (long long) info.nFileSizeLow;

      /* st_atime, st_mtime, st_ctime can be determined through
         GetFileTime
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfiletime>
         or through
         GetFileAttributesEx
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesexa>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_win32_file_attribute_data>
         or through
         GetFileInformationByHandle
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileinformationbyhandle>
         <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information>
         or through
         GetFileInformationByHandleEx with argument FileBasicInfo
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
         <https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_basic_info>
         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
#if _GL_WINDOWS_STAT_TIMESPEC
      buf->st_atim = _gl_convert_FILETIME_to_timespec (&info.ftLastAccessTime);
      buf->st_mtim = _gl_convert_FILETIME_to_timespec (&info.ftLastWriteTime);
      buf->st_ctim = _gl_convert_FILETIME_to_timespec (&info.ftCreationTime);
#else
      buf->st_atime = _gl_convert_FILETIME_to_POSIX (&info.ftLastAccessTime);
      buf->st_mtime = _gl_convert_FILETIME_to_POSIX (&info.ftLastWriteTime);
      buf->st_ctime = _gl_convert_FILETIME_to_POSIX (&info.ftCreationTime);
#endif

      return 0;
    }
  else if (type == FILE_TYPE_CHAR || type == FILE_TYPE_PIPE)
    {
      buf->st_dev = 0;
#if _GL_WINDOWS_STAT_INODES == 2
      buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
#else
      buf->st_ino = 0;
#endif
      buf->st_mode = (type == FILE_TYPE_PIPE ? _S_IFIFO : _S_IFCHR);
      buf->st_nlink = 1;
      buf->st_uid = 0;
      buf->st_gid = 0;
      buf->st_rdev = 0;
      if (type == FILE_TYPE_PIPE)
        {
          /* PeekNamedPipe
             <https://msdn.microsoft.com/en-us/library/aa365779.aspx> */
          DWORD bytes_available;
          if (PeekNamedPipe (h, NULL, 0, NULL, &bytes_available, NULL))
            buf->st_size = bytes_available;
          else
            buf->st_size = 0;
        }
      else
        buf->st_size = 0;
#if _GL_WINDOWS_STAT_TIMESPEC
      buf->st_atim.tv_sec = 0; buf->st_atim.tv_nsec = 0;
      buf->st_mtim.tv_sec = 0; buf->st_mtim.tv_nsec = 0;
      buf->st_ctim.tv_sec = 0; buf->st_ctim.tv_nsec = 0;
#else
      buf->st_atime = 0;
      buf->st_mtime = 0;
      buf->st_ctime = 0;
#endif
      return 0;
    }
  else
    {
      errno = ENOENT;
      return -1;
    }

 failed:
  {
    DWORD error = GetLastError ();
    #if 0
    fprintf (stderr, "_gl_fstat_by_handle error 0x%x\n", (unsigned int) error);
    #endif
    switch (error)
      {
      case ERROR_ACCESS_DENIED:
      case ERROR_SHARING_VIOLATION:
        errno = EACCES;
        break;

      case ERROR_OUTOFMEMORY:
        errno = ENOMEM;
        break;

      case ERROR_WRITE_FAULT:
      case ERROR_READ_FAULT:
      case ERROR_GEN_FAILURE:
        errno = EIO;
        break;

      default:
        errno = EINVAL;
        break;
      }
    return -1;
  }
}

#else

/* This declaration is solely to ensure that after preprocessing
   this file is never empty.  */
typedef int dummy;

#endif