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. */ 74typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile, 75 FILE_INFO_BY_HANDLE_CLASS fiClass, 76 LPVOID lpBuffer, 77 DWORD dwBufferSize); 78static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL; 79# endif 80/* GetFinalPathNameByHandle was introduced only in Windows Vista. */ 81typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile, 82 LPSTR lpFilePath, 83 DWORD lenFilePath, 84 DWORD dwFlags); 85static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL; 86static BOOL initialized = FALSE; 87 88static void 89initialize (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 113struct 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 138time_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. */ 161int 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. */ 459typedef int dummy; 460 461#endif 462