1 /*** 2 *stat64.c - get file status 3 * 4 * Copyright (c) Microsoft Corporation. All rights reserved. 5 * 6 *Purpose: 7 * defines _stat64() - get file status 8 * 9 *******************************************************************************/ 10 11 #include <ctype.h> 12 #include <direct.h> 13 #include <errno.h> 14 #include <fcntl.h> 15 #include <corecrt_internal_lowio.h> 16 #include <corecrt_internal_time.h> 17 #include <corecrt_internal_win32_buffer.h> 18 #include <io.h> 19 #include <share.h> 20 #include <stdlib.h> 21 #include <string.h> 22 #include <sys/stat.h> 23 #include <sys/types.h> 24 25 26 27 namespace 28 { 29 struct file_handle_traits 30 { 31 typedef int type; 32 33 inline static bool close(_In_ type const fh) throw() 34 { 35 _close(fh); 36 return true; 37 } 38 39 inline static type get_invalid_value() throw() 40 { 41 return -1; 42 } 43 }; 44 45 struct find_handle_traits 46 { 47 typedef HANDLE type; 48 49 static bool close(_In_ type const handle) throw() 50 { 51 return ::FindClose(handle) != FALSE; 52 } 53 54 static type get_invalid_value() throw() 55 { 56 return INVALID_HANDLE_VALUE; 57 } 58 }; 59 60 61 62 typedef __crt_unique_handle_t<file_handle_traits> scoped_file_handle; 63 typedef __crt_unique_handle_t<find_handle_traits> unique_find_handle; 64 } 65 66 static bool __cdecl is_slash(wchar_t const c) throw() 67 { 68 return c == L'\\' || c == L'/'; 69 } 70 71 static bool __cdecl compute_size(BY_HANDLE_FILE_INFORMATION const& file_info, long& size) throw() 72 { 73 size = 0; 74 _VALIDATE_RETURN_NOEXC(file_info.nFileSizeHigh == 0 && file_info.nFileSizeLow <= LONG_MAX, EOVERFLOW, false); 75 76 size = static_cast<long>(file_info.nFileSizeLow); 77 return true; 78 } 79 80 static bool __cdecl compute_size(BY_HANDLE_FILE_INFORMATION const& file_info, __int64& size) throw() 81 { 82 size = 0; 83 _VALIDATE_RETURN_NOEXC(file_info.nFileSizeHigh <= LONG_MAX, EOVERFLOW, false); 84 85 size = static_cast<__int64>( 86 static_cast<unsigned __int64>(file_info.nFileSizeHigh) * 0x100000000ll + 87 static_cast<unsigned __int64>(file_info.nFileSizeLow)); 88 return true; 89 } 90 91 _Success_(return != 0) 92 static wchar_t* __cdecl call_wfullpath( 93 _Out_writes_z_(buffer_size) wchar_t* const buffer, 94 wchar_t const* const path, 95 size_t const buffer_size, 96 _Inout_ wchar_t** const buffer_result 97 ) throw() 98 { 99 errno_t const saved_errno = errno; 100 errno = 0; 101 102 wchar_t* const result = _wfullpath(buffer, path, buffer_size); 103 if (result != nullptr) 104 { 105 errno = saved_errno; 106 return result; 107 } 108 109 if (errno != ERANGE) 110 return nullptr; 111 112 errno = saved_errno; 113 114 *buffer_result = _wfullpath(nullptr, path, 0); 115 return *buffer_result; 116 } 117 118 static bool __cdecl has_executable_extension(wchar_t const* const path) throw() 119 { 120 if (!path) 121 { 122 return false; 123 } 124 125 wchar_t const* const last_dot = wcsrchr(path, L'.'); 126 if (!last_dot) 127 { 128 return false; 129 } 130 131 if (_wcsicmp(last_dot, L".exe") != 0 && 132 _wcsicmp(last_dot, L".cmd") != 0 && 133 _wcsicmp(last_dot, L".bat") != 0 && 134 _wcsicmp(last_dot, L".com") != 0) 135 { 136 return false; 137 } 138 139 return true; 140 } 141 142 static bool __cdecl is_root_or_empty(wchar_t const* const path) throw() 143 { 144 if (!path) 145 { 146 return false; 147 } 148 149 bool const has_drive_letter_and_colon = __ascii_iswalpha(path[0]) && path[1] == L':'; 150 wchar_t const* const path_start = has_drive_letter_and_colon 151 ? path + 2 152 : path; 153 154 if (path_start[0] == L'\0') 155 { 156 return true; 157 } 158 159 if (is_slash(path_start[0]) && path_start[1] == L'\0') 160 { 161 return true; 162 } 163 164 return false; 165 } 166 167 static unsigned short __cdecl convert_to_stat_mode( 168 int const attributes, 169 wchar_t const* const path 170 ) throw() 171 { 172 unsigned const os_mode = attributes & 0xff; 173 174 // check to see if this is a directory - note we must make a special 175 // check for the root, which DOS thinks is not a directory: 176 bool const is_directory = (os_mode & FILE_ATTRIBUTE_DIRECTORY) != 0; 177 178 unsigned short stat_mode = is_directory || is_root_or_empty(path) 179 ? _S_IFDIR | _S_IEXEC 180 : _S_IFREG; 181 182 // If attribute byte does not have read-only bit, it is read-write: 183 stat_mode |= (os_mode & FILE_ATTRIBUTE_READONLY) != 0 184 ? _S_IREAD 185 : _S_IREAD | _S_IWRITE; 186 187 // See if file appears to be an executable by checking its extension: 188 if (has_executable_extension(path)) 189 stat_mode |= _S_IEXEC; 190 191 // propagate user read/write/execute bits to group/other fields: 192 stat_mode |= (stat_mode & 0700) >> 3; 193 stat_mode |= (stat_mode & 0700) >> 6; 194 195 return stat_mode; 196 } 197 198 // Returns false if and only if the path is invalid. 199 static bool __cdecl get_drive_number_from_path(wchar_t const* const path, int& drive_number) throw() 200 { 201 drive_number = 0; 202 203 // If path has a drive letter and a colon, return the value of that drive, 204 // as expected from _getdrive(). A = 1, B = 2, etc. 205 // If the path is relative, then use _getdrive() to get the current drive. 206 if (__ascii_iswalpha(path[0]) && path[1] == L':') 207 { 208 // If the path is just a drive letter followed by a colon, it is not a 209 // valid input to the stat functions: 210 if (path[2] == L'\0') 211 { 212 __acrt_errno_map_os_error(ERROR_FILE_NOT_FOUND); 213 return false; 214 } 215 216 drive_number = __ascii_towlower(path[0]) - L'a' + 1; 217 } 218 else 219 { 220 drive_number = _getdrive(); 221 } 222 223 return true; 224 } 225 226 static bool __cdecl is_root_unc_name(wchar_t const* const path) throw() 227 { 228 // The shortest allowed string is of the form //x/y: 229 if (wcslen(path) < 5) 230 return 0; 231 232 // The string must begin with exactly two consecutive slashes: 233 if (!is_slash(path[0]) || !is_slash(path[1]) || is_slash(path[2])) 234 return 0; 235 236 // Find the slash between the server name and share name: 237 wchar_t const* p = path + 2; // Account for the two slashes 238 while (*++p) 239 { 240 if (is_slash(*p)) 241 break; 242 } 243 244 // We reached the end before finding a slash, or the slash is at the end: 245 if (p[0] == L'\0' || p[1] == L'\0') 246 return 0; 247 248 // Is there a further slash? 249 while (*++p) 250 { 251 if (is_slash(*p)) 252 break; 253 } 254 255 // Just the final slash (or no final slash): 256 if (p[0] == L'\0' || p[1] == L'\0') 257 return 1; 258 259 return 0; 260 } 261 262 static bool __cdecl is_usable_drive_or_unc_root(wchar_t const* const path) throw() 263 { 264 if (wcspbrk(path, L"./\\") == nullptr) 265 return false; 266 267 wchar_t full_path_buffer[_MAX_PATH]; 268 __crt_unique_heap_ptr<wchar_t, __crt_public_free_policy> full_path_pointer; 269 wchar_t* const full_path = call_wfullpath( 270 full_path_buffer, 271 path, 272 _MAX_PATH, 273 full_path_pointer.get_address_of()); 274 275 if (full_path == nullptr) 276 return false; 277 278 // Check to see if the path is a root of a directory ("C:\") or a UNC root 279 // directory ("\\server\share\"): 280 if (wcslen(full_path) != 3 && !is_root_unc_name(full_path)) 281 return false; 282 283 if (GetDriveTypeW(path) <= 1) 284 return false; 285 286 return true; 287 } 288 289 template <typename TimeType> 290 static TimeType __cdecl convert_filetime_to_time_t( 291 FILETIME const file_time, 292 TimeType const fallback_time 293 ) throw() 294 { 295 using time_traits = __crt_time_time_t_traits<TimeType>; 296 297 if (file_time.dwLowDateTime == 0 && file_time.dwHighDateTime == 0) 298 { 299 return fallback_time; 300 } 301 302 SYSTEMTIME system_time; 303 SYSTEMTIME local_time; 304 if (!FileTimeToSystemTime(&file_time, &system_time) || 305 !SystemTimeToTzSpecificLocalTime(nullptr, &system_time, &local_time)) 306 { 307 // Ignore failures from these APIs, for consistency with the logic below 308 // that ignores failures in the conversion from SYSTEMTIME to time_t. 309 return -1; 310 } 311 312 // If the conversion to time_t fails, it will return -1. We'll use this as 313 // the time_t value instead of failing the entire stat call, to allow callers 314 // to get information about files whose time information is not representable. 315 // (Callers use this API to test for file existence or to get file sizes.) 316 return time_traits::loctotime( 317 local_time.wYear, 318 local_time.wMonth, 319 local_time.wDay, 320 local_time.wHour, 321 local_time.wMinute, 322 local_time.wSecond, 323 -1); 324 } 325 326 template <typename StatStruct> 327 static bool __cdecl common_stat_handle_file_not_opened( 328 wchar_t const* const path, 329 StatStruct& result 330 ) throw() 331 { 332 using time_traits = __crt_time_time_t_traits<decltype(result.st_mtime)>; 333 334 if (!is_usable_drive_or_unc_root(path)) 335 { 336 __acrt_errno_map_os_error(ERROR_FILE_NOT_FOUND); 337 return false; 338 } 339 340 // Root directories (such as C:\ or \\server\share\) are fabricated: 341 result.st_mode = convert_to_stat_mode(FILE_ATTRIBUTE_DIRECTORY, path); 342 result.st_nlink = 1; 343 344 // Try to get the disk from the name; if there is none, get the current disk: 345 int drive_number{}; 346 if (!get_drive_number_from_path(path, drive_number)) 347 { 348 return false; 349 } 350 351 result.st_rdev = static_cast<_dev_t>(drive_number - 1); 352 result.st_dev = static_cast<_dev_t>(drive_number - 1); // A=0, B=1, etc. 353 354 result.st_mtime = time_traits::loctotime(1980, 1, 1, 0, 0, 0, -1); 355 result.st_atime = result.st_mtime; 356 result.st_ctime = result.st_mtime; 357 return true; 358 } 359 360 template <typename StatStruct> 361 static bool __cdecl common_stat_handle_file_opened( 362 wchar_t const* const path, 363 int const fh, 364 HANDLE const handle, 365 StatStruct& result 366 ) throw() 367 { 368 using time_type = decltype(result.st_mtime); 369 370 // Figure out what kind of file underlies the file handle: 371 int const file_type = GetFileType(handle) & ~FILE_TYPE_REMOTE; 372 373 if (file_type == FILE_TYPE_DISK) 374 { 375 // Okay, it's a disk file; we'll do the normal logic below. 376 } 377 else if (file_type == FILE_TYPE_CHAR || file_type == FILE_TYPE_PIPE) 378 { 379 // We treat pipes and devices similarly: no further information is 380 // available from any API, so we set the fields as reasonably as 381 // possible and return. 382 result.st_mode = file_type == FILE_TYPE_CHAR 383 ? _S_IFCHR 384 : _S_IFIFO; 385 386 result.st_nlink = 1; 387 388 result.st_rdev = static_cast<unsigned>(fh); 389 result.st_dev = static_cast<unsigned>(fh); 390 391 if (file_type != FILE_TYPE_CHAR) 392 { 393 unsigned long available; 394 if (PeekNamedPipe(handle, nullptr, 0, nullptr, &available, nullptr)) 395 { 396 result.st_size = static_cast<_off_t>(available); 397 } 398 } 399 400 return true; 401 } 402 else if (file_type == FILE_TYPE_UNKNOWN) 403 { 404 errno = EBADF; 405 return false; 406 } 407 else 408 { 409 // Per the documentation we should not reach here, but we'll add 410 // this just to be safe: 411 __acrt_errno_map_os_error(GetLastError()); 412 return false; 413 } 414 415 // At this point, we know we have a file on disk. Set the common fields: 416 result.st_nlink = 1; 417 418 if (path) 419 { 420 // Try to get the disk from the name; if there is none, get the current disk: 421 int drive_number{}; 422 if (!get_drive_number_from_path(path, drive_number)) 423 { 424 return false; 425 } 426 427 result.st_rdev = static_cast<_dev_t>(drive_number - 1); 428 result.st_dev = static_cast<_dev_t>(drive_number - 1); // A=0, B=1, etc. 429 } 430 431 BY_HANDLE_FILE_INFORMATION file_info{}; 432 if (!GetFileInformationByHandle(handle, &file_info)) 433 { 434 __acrt_errno_map_os_error(GetLastError()); 435 return false; 436 } 437 438 result.st_mode = convert_to_stat_mode(file_info.dwFileAttributes, path); 439 440 result.st_mtime = convert_filetime_to_time_t(file_info.ftLastWriteTime, static_cast<time_type>(0)); 441 result.st_atime = convert_filetime_to_time_t(file_info.ftLastAccessTime, result.st_mtime); 442 result.st_ctime = convert_filetime_to_time_t(file_info.ftCreationTime, result.st_mtime); 443 444 if (!compute_size(file_info, result.st_size)) 445 { 446 return false; 447 } 448 449 return true; 450 } 451 452 template <typename StatStruct> 453 static int __cdecl common_stat( 454 wchar_t const* const path, 455 StatStruct* const result 456 ) throw() 457 { 458 _VALIDATE_CLEAR_OSSERR_RETURN(result != nullptr, EINVAL, -1); 459 *result = StatStruct{}; 460 461 _VALIDATE_CLEAR_OSSERR_RETURN(path != nullptr, EINVAL, -1); 462 463 __crt_unique_handle const file_handle(CreateFileW( 464 path, 465 FILE_READ_ATTRIBUTES, 466 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 467 nullptr, 468 OPEN_EXISTING, 469 FILE_FLAG_BACKUP_SEMANTICS, 470 nullptr)); 471 472 if (file_handle) 473 { 474 if (!common_stat_handle_file_opened(path, -1, file_handle.get(), *result)) 475 { 476 *result = StatStruct{}; 477 return -1; 478 } 479 } 480 else 481 { 482 if (!common_stat_handle_file_not_opened(path, *result)) 483 { 484 *result = StatStruct{}; 485 return -1; 486 } 487 } 488 489 return 0; 490 } 491 492 493 494 template <typename StatStruct> 495 static int __cdecl common_stat( 496 char const* const path, 497 StatStruct* const result 498 ) throw() 499 { 500 if (path == nullptr) { 501 return common_stat(static_cast<wchar_t const*>(nullptr), result); 502 } 503 504 __crt_internal_win32_buffer<wchar_t> wide_path; 505 506 errno_t const cvt = __acrt_mbs_to_wcs_cp(path, wide_path, __acrt_get_utf8_acp_compatibility_codepage()); 507 508 if (cvt != 0) { 509 return -1; 510 } 511 512 return common_stat(wide_path.data(), result); 513 } 514 515 516 517 extern "C" int __cdecl _stat32(char const* const path, struct _stat32* const result) 518 { 519 return common_stat(path, result); 520 } 521 522 extern "C" int __cdecl _stat32i64(char const* const path, struct _stat32i64* const result) 523 { 524 return common_stat(path, result); 525 } 526 527 extern "C" int __cdecl _stat64(char const* const path, struct _stat64* const result) 528 { 529 return common_stat(path, result); 530 } 531 532 extern "C" int __cdecl _stat64i32(char const* const path, struct _stat64i32* const result) 533 { 534 return common_stat(path, result); 535 } 536 537 extern "C" int __cdecl _wstat32(wchar_t const* const path, struct _stat32* const result) 538 { 539 return common_stat(path, result); 540 } 541 542 extern "C" int __cdecl _wstat32i64(wchar_t const* const path, struct _stat32i64* const result) 543 { 544 return common_stat(path, result); 545 } 546 547 extern "C" int __cdecl _wstat64(wchar_t const* const path, struct _stat64* const result) 548 { 549 return common_stat(path, result); 550 } 551 552 extern "C" int __cdecl _wstat64i32(wchar_t const* const path, struct _stat64i32* const result) 553 { 554 return common_stat(path, result); 555 } 556 557 558 559 template <typename StatStruct> 560 static int __cdecl common_fstat(int const fh, StatStruct* const result) throw() 561 { 562 _VALIDATE_CLEAR_OSSERR_RETURN(result != nullptr, EINVAL, -1); 563 *result = StatStruct{}; 564 565 _CHECK_FH_CLEAR_OSSERR_RETURN(fh, EBADF, -1); 566 _VALIDATE_CLEAR_OSSERR_RETURN(fh >= 0 && fh < _nhandle, EBADF, -1); 567 _VALIDATE_CLEAR_OSSERR_RETURN(_osfile(fh) & FOPEN, EBADF, -1); 568 569 return __acrt_lowio_lock_fh_and_call(fh, [&]() 570 { 571 if ((_osfile(fh) & FOPEN) == 0) 572 { 573 errno = EBADF; 574 _ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0)); 575 return -1; 576 } 577 578 if (!common_stat_handle_file_opened(nullptr, fh, reinterpret_cast<HANDLE>(_osfhnd(fh)), *result)) 579 { 580 *result = StatStruct{}; 581 return -1; 582 } 583 584 return 0; 585 }); 586 } 587 588 extern "C" int __cdecl _fstat32(int const fh, struct _stat32* const result) 589 { 590 return common_fstat(fh, result); 591 } 592 593 extern "C" int __cdecl _fstat32i64(int const fh, struct _stat32i64* const result) 594 { 595 return common_fstat(fh, result); 596 } 597 598 extern "C" int __cdecl _fstat64(int const fh, struct _stat64* const result) 599 { 600 return common_fstat(fh, result); 601 } 602 603 extern "C" int __cdecl _fstat64i32(int const fh, struct _stat64i32* const result) 604 { 605 return common_fstat(fh, result); 606 } 607