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
close__anon3d8585840111::file_handle_traits33 inline static bool close(_In_ type const fh) throw()
34 {
35 _close(fh);
36 return true;
37 }
38
get_invalid_value__anon3d8585840111::file_handle_traits39 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
close__anon3d8585840111::find_handle_traits49 static bool close(_In_ type const handle) throw()
50 {
51 return ::FindClose(handle) != FALSE;
52 }
53
get_invalid_value__anon3d8585840111::find_handle_traits54 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
is_slash(wchar_t const c)66 static bool __cdecl is_slash(wchar_t const c) throw()
67 {
68 return c == L'\\' || c == L'/';
69 }
70
compute_size(BY_HANDLE_FILE_INFORMATION const & file_info,long & size)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
compute_size(BY_HANDLE_FILE_INFORMATION const & file_info,__int64 & size)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)
call_wfullpath(_Out_writes_z_ (buffer_size)wchar_t * const buffer,wchar_t const * const path,size_t const buffer_size,_Inout_ wchar_t ** const buffer_result)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
has_executable_extension(wchar_t const * const path)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
is_root_or_empty(wchar_t const * const path)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
convert_to_stat_mode(int const attributes,wchar_t const * const path)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.
get_drive_number_from_path(wchar_t const * const path,int & drive_number)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
is_root_unc_name(wchar_t const * const path)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
is_usable_drive_or_unc_root(wchar_t const * const path)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>
convert_filetime_to_time_t(FILETIME const file_time,TimeType const fallback_time)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>
common_stat_handle_file_not_opened(wchar_t const * const path,StatStruct & result)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>
common_stat_handle_file_opened(wchar_t const * const path,int const fh,HANDLE const handle,StatStruct & result)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>
common_stat(wchar_t const * const path,StatStruct * const result)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>
common_stat(char const * const path,StatStruct * const result)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
_stat32(char const * const path,struct _stat32 * const result)517 extern "C" int __cdecl _stat32(char const* const path, struct _stat32* const result)
518 {
519 return common_stat(path, result);
520 }
521
_stat32i64(char const * const path,struct _stat32i64 * const result)522 extern "C" int __cdecl _stat32i64(char const* const path, struct _stat32i64* const result)
523 {
524 return common_stat(path, result);
525 }
526
_stat64(char const * const path,struct _stat64 * const result)527 extern "C" int __cdecl _stat64(char const* const path, struct _stat64* const result)
528 {
529 return common_stat(path, result);
530 }
531
_stat64i32(char const * const path,struct _stat64i32 * const result)532 extern "C" int __cdecl _stat64i32(char const* const path, struct _stat64i32* const result)
533 {
534 return common_stat(path, result);
535 }
536
_wstat32(wchar_t const * const path,struct _stat32 * const result)537 extern "C" int __cdecl _wstat32(wchar_t const* const path, struct _stat32* const result)
538 {
539 return common_stat(path, result);
540 }
541
_wstat32i64(wchar_t const * const path,struct _stat32i64 * const result)542 extern "C" int __cdecl _wstat32i64(wchar_t const* const path, struct _stat32i64* const result)
543 {
544 return common_stat(path, result);
545 }
546
_wstat64(wchar_t const * const path,struct _stat64 * const result)547 extern "C" int __cdecl _wstat64(wchar_t const* const path, struct _stat64* const result)
548 {
549 return common_stat(path, result);
550 }
551
_wstat64i32(wchar_t const * const path,struct _stat64i32 * const result)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>
common_fstat(int const fh,StatStruct * const result)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
_fstat32(int const fh,struct _stat32 * const result)588 extern "C" int __cdecl _fstat32(int const fh, struct _stat32* const result)
589 {
590 return common_fstat(fh, result);
591 }
592
_fstat32i64(int const fh,struct _stat32i64 * const result)593 extern "C" int __cdecl _fstat32i64(int const fh, struct _stat32i64* const result)
594 {
595 return common_fstat(fh, result);
596 }
597
_fstat64(int const fh,struct _stat64 * const result)598 extern "C" int __cdecl _fstat64(int const fh, struct _stat64* const result)
599 {
600 return common_fstat(fh, result);
601 }
602
_fstat64i32(int const fh,struct _stat64i32 * const result)603 extern "C" int __cdecl _fstat64i32(int const fh, struct _stat64i32* const result)
604 {
605 return common_fstat(fh, result);
606 }
607