Home | History | Annotate | Line # | Download | only in import
      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.  */
     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
     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
    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
    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
    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