xref: /reactos/sdk/lib/ucrt/filesystem/stat.cpp (revision 4de43491)
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