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