stat-w32.c 18 KB

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