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