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