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