stat-w32.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* Core of implementation of fstat and stat for native Windows.
  2. Copyright (C) 2017-2021 Free Software Foundation, Inc.
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 3 of the License, or
  6. (at your option) any later version.
  7. This program 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 General Public License for more details.
  11. You should have received a copy of the GNU 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