stat.c 13 KB

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