(root)/
findutils-4.9.0/
gl/
lib/
stat-w32.c
       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