stat.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /* Work around platform bugs in stat.
  2. Copyright (C) 2009-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 Eric Blake and Bruno Haible. */
  14. /* If the user's config.h happens to include <sys/stat.h>, let it include only
  15. the system's <sys/stat.h> here, so that orig_stat doesn't recurse to
  16. rpl_stat. */
  17. #define __need_system_sys_stat_h
  18. #include <config.h>
  19. /* Get the original definition of stat. It might be defined as a macro. */
  20. #include <sys/types.h>
  21. #include <sys/stat.h>
  22. #undef __need_system_sys_stat_h
  23. #if defined _WIN32 && ! defined __CYGWIN__
  24. # define WINDOWS_NATIVE
  25. #endif
  26. #if !defined WINDOWS_NATIVE
  27. static int
  28. orig_stat (const char *filename, struct stat *buf)
  29. {
  30. return stat (filename, buf);
  31. }
  32. #endif
  33. /* Specification. */
  34. #ifdef __osf__
  35. /* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
  36. eliminates this include because of the preliminary #include <sys/stat.h>
  37. above. */
  38. # include "sys/stat.h"
  39. #else
  40. # include <sys/stat.h>
  41. #endif
  42. #include "stat-time.h"
  43. #include <errno.h>
  44. #include <limits.h>
  45. #include <stdbool.h>
  46. #include <string.h>
  47. #include "filename.h"
  48. #include "malloca.h"
  49. #include "verify.h"
  50. #ifdef WINDOWS_NATIVE
  51. # define WIN32_LEAN_AND_MEAN
  52. # include <windows.h>
  53. # include "stat-w32.h"
  54. /* Don't assume that UNICODE is not defined. */
  55. # undef WIN32_FIND_DATA
  56. # define WIN32_FIND_DATA WIN32_FIND_DATAA
  57. # undef CreateFile
  58. # define CreateFile CreateFileA
  59. # undef FindFirstFile
  60. # define FindFirstFile FindFirstFileA
  61. #endif
  62. #ifdef WINDOWS_NATIVE
  63. /* Return TRUE if the given file name denotes an UNC root. */
  64. static BOOL
  65. is_unc_root (const char *rname)
  66. {
  67. /* Test whether it has the syntax '\\server\share'. */
  68. if (ISSLASH (rname[0]) && ISSLASH (rname[1]))
  69. {
  70. /* It starts with two slashes. Find the next slash. */
  71. const char *p = rname + 2;
  72. const char *q = p;
  73. while (*q != '\0' && !ISSLASH (*q))
  74. q++;
  75. if (q > p && *q != '\0')
  76. {
  77. /* Found the next slash at q. */
  78. q++;
  79. const char *r = q;
  80. while (*r != '\0' && !ISSLASH (*r))
  81. r++;
  82. if (r > q && *r == '\0')
  83. return TRUE;
  84. }
  85. }
  86. return FALSE;
  87. }
  88. #endif
  89. /* Store information about NAME into ST. Work around bugs with
  90. trailing slashes. Mingw has other bugs (such as st_ino always
  91. being 0 on success) which this wrapper does not work around. But
  92. at least this implementation provides the ability to emulate fchdir
  93. correctly. */
  94. int
  95. rpl_stat (char const *name, struct stat *buf)
  96. {
  97. #ifdef WINDOWS_NATIVE
  98. /* Fill the fields ourselves, because the original stat function returns
  99. values for st_atime, st_mtime, st_ctime that depend on the current time
  100. zone. See
  101. <https://lists.gnu.org/r/bug-gnulib/2017-04/msg00134.html> */
  102. /* XXX Should we convert to wchar_t* and prepend '\\?\', in order to work
  103. around length limitations
  104. <https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file> ? */
  105. /* POSIX <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
  106. specifies: "More than two leading <slash> characters shall be treated as
  107. a single <slash> character." */
  108. if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2]))
  109. {
  110. name += 2;
  111. while (ISSLASH (name[1]))
  112. name++;
  113. }
  114. size_t len = strlen (name);
  115. size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0);
  116. /* Remove trailing slashes (except the very first one, at position
  117. drive_prefix_len), but remember their presence. */
  118. size_t rlen;
  119. bool check_dir = false;
  120. rlen = len;
  121. while (rlen > drive_prefix_len && ISSLASH (name[rlen-1]))
  122. {
  123. check_dir = true;
  124. if (rlen == drive_prefix_len + 1)
  125. break;
  126. rlen--;
  127. }
  128. /* Handle '' and 'C:'. */
  129. if (!check_dir && rlen == drive_prefix_len)
  130. {
  131. errno = ENOENT;
  132. return -1;
  133. }
  134. /* Handle '\\'. */
  135. if (rlen == 1 && ISSLASH (name[0]) && len >= 2)
  136. {
  137. errno = ENOENT;
  138. return -1;
  139. }
  140. const char *rname;
  141. char *malloca_rname;
  142. if (rlen == len)
  143. {
  144. rname = name;
  145. malloca_rname = NULL;
  146. }
  147. else
  148. {
  149. malloca_rname = malloca (rlen + 1);
  150. if (malloca_rname == NULL)
  151. {
  152. errno = ENOMEM;
  153. return -1;
  154. }
  155. memcpy (malloca_rname, name, rlen);
  156. malloca_rname[rlen] = '\0';
  157. rname = malloca_rname;
  158. }
  159. /* There are two ways to get at the requested information:
  160. - by scanning the parent directory and examining the relevant
  161. directory entry,
  162. - by opening the file directly.
  163. The first approach fails for root directories (e.g. 'C:\') and
  164. UNC root directories (e.g. '\\server\share').
  165. The second approach fails for some system files (e.g. 'C:\pagefile.sys'
  166. and 'C:\hiberfil.sys'): ERROR_SHARING_VIOLATION.
  167. The second approach gives more information (in particular, correct
  168. st_dev, st_ino, st_nlink fields).
  169. So we use the second approach and, as a fallback except for root and
  170. UNC root directories, also the first approach. */
  171. {
  172. int ret;
  173. {
  174. /* Approach based on the file. */
  175. /* Open a handle to the file.
  176. CreateFile
  177. <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea>
  178. <https://docs.microsoft.com/en-us/windows/desktop/FileIO/creating-and-opening-files> */
  179. HANDLE h =
  180. CreateFile (rname,
  181. FILE_READ_ATTRIBUTES,
  182. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  183. NULL,
  184. OPEN_EXISTING,
  185. /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
  186. in case as different) makes sense only when applied to *all*
  187. filesystem operations. */
  188. FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
  189. NULL);
  190. if (h != INVALID_HANDLE_VALUE)
  191. {
  192. ret = _gl_fstat_by_handle (h, rname, buf);
  193. CloseHandle (h);
  194. goto done;
  195. }
  196. }
  197. /* Test for root and UNC root directories. */
  198. if ((rlen == drive_prefix_len + 1 && ISSLASH (rname[drive_prefix_len]))
  199. || is_unc_root (rname))
  200. goto failed;
  201. /* Fallback. */
  202. {
  203. /* Approach based on the directory entry. */
  204. if (strchr (rname, '?') != NULL || strchr (rname, '*') != NULL)
  205. {
  206. /* Other Windows API functions would fail with error
  207. ERROR_INVALID_NAME. */
  208. if (malloca_rname != NULL)
  209. freea (malloca_rname);
  210. errno = ENOENT;
  211. return -1;
  212. }
  213. /* Get the details about the directory entry. This can be done through
  214. FindFirstFile
  215. <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-findfirstfilea>
  216. <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-_win32_find_dataa>
  217. or through
  218. FindFirstFileEx with argument FindExInfoBasic
  219. <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-findfirstfileexa>
  220. <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ne-minwinbase-findex_info_levels>
  221. <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-_win32_find_dataa> */
  222. WIN32_FIND_DATA info;
  223. HANDLE h = FindFirstFile (rname, &info);
  224. if (h == INVALID_HANDLE_VALUE)
  225. goto failed;
  226. /* Test for error conditions before starting to fill *buf. */
  227. if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
  228. {
  229. FindClose (h);
  230. if (malloca_rname != NULL)
  231. freea (malloca_rname);
  232. errno = EOVERFLOW;
  233. return -1;
  234. }
  235. # if _GL_WINDOWS_STAT_INODES
  236. buf->st_dev = 0;
  237. # if _GL_WINDOWS_STAT_INODES == 2
  238. buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
  239. # else /* _GL_WINDOWS_STAT_INODES == 1 */
  240. buf->st_ino = 0;
  241. # endif
  242. # else
  243. /* st_ino is not wide enough for identifying a file on a device.
  244. Without st_ino, st_dev is pointless. */
  245. buf->st_dev = 0;
  246. buf->st_ino = 0;
  247. # endif
  248. /* st_mode. */
  249. unsigned int mode =
  250. /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ? */
  251. ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR | S_IEXEC_UGO : _S_IFREG)
  252. | S_IREAD_UGO
  253. | ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? 0 : S_IWRITE_UGO);
  254. if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  255. {
  256. /* Determine whether the file is executable by looking at the file
  257. name suffix. */
  258. if (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
  259. {
  260. const char *last_dot = NULL;
  261. const char *p;
  262. for (p = info.cFileName; *p != '\0'; p++)
  263. if (*p == '.')
  264. last_dot = p;
  265. if (last_dot != NULL)
  266. {
  267. const char *suffix = last_dot + 1;
  268. if (_stricmp (suffix, "exe") == 0
  269. || _stricmp (suffix, "bat") == 0
  270. || _stricmp (suffix, "cmd") == 0
  271. || _stricmp (suffix, "com") == 0)
  272. mode |= S_IEXEC_UGO;
  273. }
  274. }
  275. }
  276. buf->st_mode = mode;
  277. /* st_nlink. Ignore hard links here. */
  278. buf->st_nlink = 1;
  279. /* There's no easy way to map the Windows SID concept to an integer. */
  280. buf->st_uid = 0;
  281. buf->st_gid = 0;
  282. /* st_rdev is irrelevant for normal files and directories. */
  283. buf->st_rdev = 0;
  284. /* st_size. */
  285. if (sizeof (buf->st_size) <= 4)
  286. /* Range check already done above. */
  287. buf->st_size = info.nFileSizeLow;
  288. else
  289. buf->st_size = ((long long) info.nFileSizeHigh << 32) | (long long) info.nFileSizeLow;
  290. /* st_atime, st_mtime, st_ctime. */
  291. # if _GL_WINDOWS_STAT_TIMESPEC
  292. buf->st_atim = _gl_convert_FILETIME_to_timespec (&info.ftLastAccessTime);
  293. buf->st_mtim = _gl_convert_FILETIME_to_timespec (&info.ftLastWriteTime);
  294. buf->st_ctim = _gl_convert_FILETIME_to_timespec (&info.ftCreationTime);
  295. # else
  296. buf->st_atime = _gl_convert_FILETIME_to_POSIX (&info.ftLastAccessTime);
  297. buf->st_mtime = _gl_convert_FILETIME_to_POSIX (&info.ftLastWriteTime);
  298. buf->st_ctime = _gl_convert_FILETIME_to_POSIX (&info.ftCreationTime);
  299. # endif
  300. FindClose (h);
  301. ret = 0;
  302. }
  303. done:
  304. if (ret >= 0 && check_dir && !S_ISDIR (buf->st_mode))
  305. {
  306. errno = ENOTDIR;
  307. ret = -1;
  308. }
  309. if (malloca_rname != NULL)
  310. {
  311. int saved_errno = errno;
  312. freea (malloca_rname);
  313. errno = saved_errno;
  314. }
  315. return ret;
  316. }
  317. failed:
  318. {
  319. DWORD error = GetLastError ();
  320. #if 0
  321. fprintf (stderr, "rpl_stat error 0x%x\n", (unsigned int) error);
  322. #endif
  323. if (malloca_rname != NULL)
  324. freea (malloca_rname);
  325. switch (error)
  326. {
  327. /* Some of these errors probably cannot happen with the specific flags
  328. that we pass to CreateFile. But who knows... */
  329. case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist. */
  330. case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist. */
  331. case ERROR_BAD_PATHNAME: /* rname is such as '\\server'. */
  332. case ERROR_BAD_NET_NAME: /* rname is such as '\\server\nonexistentshare'. */
  333. case ERROR_INVALID_NAME: /* rname contains wildcards, misplaced colon, etc. */
  334. case ERROR_DIRECTORY:
  335. errno = ENOENT;
  336. break;
  337. case ERROR_ACCESS_DENIED: /* rname is such as 'C:\System Volume Information\foo'. */
  338. case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys' (second approach only). */
  339. /* XXX map to EACCES or EPERM? */
  340. errno = EACCES;
  341. break;
  342. case ERROR_OUTOFMEMORY:
  343. errno = ENOMEM;
  344. break;
  345. case ERROR_WRITE_PROTECT:
  346. errno = EROFS;
  347. break;
  348. case ERROR_WRITE_FAULT:
  349. case ERROR_READ_FAULT:
  350. case ERROR_GEN_FAILURE:
  351. errno = EIO;
  352. break;
  353. case ERROR_BUFFER_OVERFLOW:
  354. case ERROR_FILENAME_EXCED_RANGE:
  355. errno = ENAMETOOLONG;
  356. break;
  357. case ERROR_DELETE_PENDING: /* XXX map to EACCES or EPERM? */
  358. errno = EPERM;
  359. break;
  360. default:
  361. errno = EINVAL;
  362. break;
  363. }
  364. return -1;
  365. }
  366. #else
  367. int result = orig_stat (name, buf);
  368. if (result == 0)
  369. {
  370. # if REPLACE_FUNC_STAT_FILE
  371. /* Solaris 9 mistakenly succeeds when given a non-directory with a
  372. trailing slash. */
  373. if (!S_ISDIR (buf->st_mode))
  374. {
  375. size_t len = strlen (name);
  376. if (ISSLASH (name[len - 1]))
  377. {
  378. errno = ENOTDIR;
  379. return -1;
  380. }
  381. }
  382. # endif /* REPLACE_FUNC_STAT_FILE */
  383. result = stat_time_normalize (result, buf);
  384. }
  385. return result;
  386. #endif
  387. }