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