stat.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /* Work around platform bugs in stat.
  2. Copyright (C) 2009-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 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. }