Commit | Line | Data |
---|---|---|
9c9d63b1 | 1 | /* Copyright (C) 1991-2021 Free Software Foundation, Inc. |
6ec2e0f5 SDJ |
2 | This file is part of the GNU C Library. |
3 | ||
9c9d63b1 PM |
4 | The GNU C Library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU General Public | |
6 | License as published by the Free Software Foundation; either | |
7 | version 3 of the License, or (at your option) any later version. | |
6ec2e0f5 | 8 | |
9c9d63b1 | 9 | The GNU C Library is distributed in the hope that it will be useful, |
6ec2e0f5 | 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
9c9d63b1 PM |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | General Public License for more details. | |
6ec2e0f5 | 13 | |
9c9d63b1 PM |
14 | You should have received a copy of the GNU General Public |
15 | License along with the GNU C Library; if not, see | |
16 | <https://www.gnu.org/licenses/>. */ | |
6ec2e0f5 SDJ |
17 | |
18 | #if !_LIBC | |
19 | # include <config.h> | |
20 | # include <unistd.h> | |
9c9d63b1 PM |
21 | # include "pathmax.h" |
22 | #else | |
23 | # define HAVE_OPENAT 1 | |
24 | # define D_INO_IN_DIRENT 1 | |
25 | # define HAVE_MSVC_INVALID_PARAMETER_HANDLER 0 | |
26 | # define HAVE_MINIMALLY_WORKING_GETCWD 0 | |
6ec2e0f5 SDJ |
27 | #endif |
28 | ||
29 | #include <errno.h> | |
30 | #include <sys/types.h> | |
31 | #include <sys/stat.h> | |
32 | #include <stdbool.h> | |
33 | #include <stddef.h> | |
34 | ||
35 | #include <fcntl.h> /* For AT_FDCWD on Solaris 9. */ | |
36 | ||
37 | /* If this host provides the openat function or if we're using the | |
38 | gnulib replacement function with a native fdopendir, then enable | |
39 | code below to make getcwd more efficient and robust. */ | |
40 | #if defined HAVE_OPENAT || (defined GNULIB_OPENAT && defined HAVE_FDOPENDIR) | |
41 | # define HAVE_OPENAT_SUPPORT 1 | |
42 | #else | |
43 | # define HAVE_OPENAT_SUPPORT 0 | |
44 | #endif | |
45 | ||
46 | #ifndef __set_errno | |
47 | # define __set_errno(val) (errno = (val)) | |
48 | #endif | |
49 | ||
50 | #include <dirent.h> | |
51 | #ifndef _D_EXACT_NAMLEN | |
52 | # define _D_EXACT_NAMLEN(d) strlen ((d)->d_name) | |
53 | #endif | |
54 | #ifndef _D_ALLOC_NAMLEN | |
55 | # define _D_ALLOC_NAMLEN(d) (_D_EXACT_NAMLEN (d) + 1) | |
56 | #endif | |
57 | ||
58 | #include <unistd.h> | |
59 | #include <stdlib.h> | |
60 | #include <string.h> | |
61 | ||
62 | #if _LIBC | |
63 | # ifndef mempcpy | |
64 | # define mempcpy __mempcpy | |
65 | # endif | |
66 | #endif | |
67 | ||
68 | #ifndef MAX | |
69 | # define MAX(a, b) ((a) < (b) ? (b) : (a)) | |
70 | #endif | |
71 | #ifndef MIN | |
72 | # define MIN(a, b) ((a) < (b) ? (a) : (b)) | |
73 | #endif | |
74 | ||
6ec2e0f5 SDJ |
75 | /* In this file, PATH_MAX only serves as a threshold for choosing among two |
76 | algorithms. */ | |
77 | #ifndef PATH_MAX | |
78 | # define PATH_MAX 8192 | |
79 | #endif | |
80 | ||
81 | #if D_INO_IN_DIRENT | |
82 | # define MATCHING_INO(dp, ino) ((dp)->d_ino == (ino)) | |
83 | #else | |
84 | # define MATCHING_INO(dp, ino) true | |
85 | #endif | |
86 | ||
c0c3707f CB |
87 | #if HAVE_MSVC_INVALID_PARAMETER_HANDLER |
88 | # include "msvc-inval.h" | |
89 | #endif | |
90 | ||
6ec2e0f5 | 91 | #if !_LIBC |
9c9d63b1 PM |
92 | # define GETCWD_RETURN_TYPE char * |
93 | # define __close_nocancel_nostatus close | |
94 | # define __getcwd_generic rpl_getcwd | |
95 | # undef stat64 | |
96 | # define stat64 stat | |
97 | # define __fstat64 fstat | |
98 | # define __fstatat64 fstatat | |
99 | # define __lstat64 lstat | |
6ec2e0f5 SDJ |
100 | # define __closedir closedir |
101 | # define __opendir opendir | |
9c9d63b1 PM |
102 | # define __readdir64 readdir |
103 | # define __fdopendir fdopendir | |
104 | # define __openat openat | |
105 | # define __rewinddir rewinddir | |
106 | # define __openat64 openat | |
107 | # define dirent64 dirent | |
108 | #else | |
109 | # include <not-cancel.h> | |
6ec2e0f5 SDJ |
110 | #endif |
111 | ||
112 | /* The results of opendir() in this file are not used with dirfd and fchdir, | |
113 | and we do not leak fds to any single-threaded code that could use stdio, | |
114 | therefore save some unnecessary recursion in fchdir.c. | |
115 | FIXME - if the kernel ever adds support for multi-thread safety for | |
116 | avoiding standard fds, then we should use opendir_safer and | |
117 | openat_safer. */ | |
118 | #ifdef GNULIB_defined_opendir | |
119 | # undef opendir | |
120 | #endif | |
121 | #ifdef GNULIB_defined_closedir | |
122 | # undef closedir | |
123 | #endif | |
124 | \f | |
9c9d63b1 | 125 | #if defined _WIN32 && !defined __CYGWIN__ |
c0c3707f CB |
126 | # if HAVE_MSVC_INVALID_PARAMETER_HANDLER |
127 | static char * | |
128 | getcwd_nothrow (char *buf, size_t size) | |
129 | { | |
130 | char *result; | |
131 | ||
132 | TRY_MSVC_INVAL | |
133 | { | |
134 | result = _getcwd (buf, size); | |
135 | } | |
136 | CATCH_MSVC_INVAL | |
137 | { | |
138 | result = NULL; | |
139 | errno = ERANGE; | |
140 | } | |
141 | DONE_MSVC_INVAL; | |
142 | ||
143 | return result; | |
144 | } | |
145 | # else | |
146 | # define getcwd_nothrow _getcwd | |
147 | # endif | |
148 | # define getcwd_system getcwd_nothrow | |
149 | #else | |
150 | # define getcwd_system getcwd | |
151 | #endif | |
152 | ||
6ec2e0f5 | 153 | /* Get the name of the current working directory, and put it in SIZE |
698be2d8 CB |
154 | bytes of BUF. Returns NULL with errno set if the directory couldn't be |
155 | determined or SIZE was too small. If successful, returns BUF. In GNU, | |
156 | if BUF is NULL, an array is allocated with 'malloc'; the array is SIZE | |
157 | bytes long, unless SIZE == 0, in which case it is as big as necessary. */ | |
6ec2e0f5 | 158 | |
9c9d63b1 PM |
159 | GETCWD_RETURN_TYPE |
160 | __getcwd_generic (char *buf, size_t size) | |
6ec2e0f5 SDJ |
161 | { |
162 | /* Lengths of big file name components and entire file names, and a | |
163 | deep level of file name nesting. These numbers are not upper | |
164 | bounds; they are merely large values suitable for initial | |
165 | allocations, designed to be large enough for most real-world | |
166 | uses. */ | |
167 | enum | |
168 | { | |
169 | BIG_FILE_NAME_COMPONENT_LENGTH = 255, | |
170 | BIG_FILE_NAME_LENGTH = MIN (4095, PATH_MAX - 1), | |
171 | DEEP_NESTING = 100 | |
172 | }; | |
173 | ||
174 | #if HAVE_OPENAT_SUPPORT | |
175 | int fd = AT_FDCWD; | |
176 | bool fd_needs_closing = false; | |
177 | #else | |
178 | char dots[DEEP_NESTING * sizeof ".." + BIG_FILE_NAME_COMPONENT_LENGTH + 1]; | |
179 | char *dotlist = dots; | |
180 | size_t dotsize = sizeof dots; | |
181 | size_t dotlen = 0; | |
182 | #endif | |
183 | DIR *dirstream = NULL; | |
184 | dev_t rootdev, thisdev; | |
185 | ino_t rootino, thisino; | |
186 | char *dir; | |
187 | register char *dirp; | |
9c9d63b1 | 188 | struct stat64 st; |
6ec2e0f5 SDJ |
189 | size_t allocated = size; |
190 | size_t used; | |
191 | ||
192 | #if HAVE_MINIMALLY_WORKING_GETCWD | |
193 | /* If AT_FDCWD is not defined, the algorithm below is O(N**2) and | |
194 | this is much slower than the system getcwd (at least on | |
195 | GNU/Linux). So trust the system getcwd's results unless they | |
196 | look suspicious. | |
197 | ||
198 | Use the system getcwd even if we have openat support, since the | |
199 | system getcwd works even when a parent is unreadable, while the | |
200 | openat-based approach does not. | |
201 | ||
202 | But on AIX 5.1..7.1, the system getcwd is not even minimally | |
203 | working: If the current directory name is slightly longer than | |
204 | PATH_MAX, it omits the first directory component and returns | |
205 | this wrong result with errno = 0. */ | |
206 | ||
207 | # undef getcwd | |
c0c3707f | 208 | dir = getcwd_system (buf, size); |
6ec2e0f5 SDJ |
209 | if (dir || (size && errno == ERANGE)) |
210 | return dir; | |
211 | ||
212 | /* Solaris getcwd (NULL, 0) fails with errno == EINVAL, but it has | |
213 | internal magic that lets it work even if an ancestor directory is | |
214 | inaccessible, which is better in many cases. So in this case try | |
215 | again with a buffer that's almost always big enough. */ | |
216 | if (errno == EINVAL && buf == NULL && size == 0) | |
217 | { | |
218 | char big_buffer[BIG_FILE_NAME_LENGTH + 1]; | |
c0c3707f | 219 | dir = getcwd_system (big_buffer, sizeof big_buffer); |
6ec2e0f5 SDJ |
220 | if (dir) |
221 | return strdup (dir); | |
222 | } | |
223 | ||
224 | # if HAVE_PARTLY_WORKING_GETCWD | |
225 | /* The system getcwd works, except it sometimes fails when it | |
226 | shouldn't, setting errno to ERANGE, ENAMETOOLONG, or ENOENT. */ | |
227 | if (errno != ERANGE && errno != ENAMETOOLONG && errno != ENOENT) | |
228 | return NULL; | |
229 | # endif | |
230 | #endif | |
6ec2e0f5 SDJ |
231 | if (size == 0) |
232 | { | |
233 | if (buf != NULL) | |
234 | { | |
235 | __set_errno (EINVAL); | |
236 | return NULL; | |
237 | } | |
238 | ||
239 | allocated = BIG_FILE_NAME_LENGTH + 1; | |
240 | } | |
241 | ||
242 | if (buf == NULL) | |
243 | { | |
244 | dir = malloc (allocated); | |
245 | if (dir == NULL) | |
246 | return NULL; | |
247 | } | |
248 | else | |
249 | dir = buf; | |
250 | ||
251 | dirp = dir + allocated; | |
252 | *--dirp = '\0'; | |
253 | ||
9c9d63b1 | 254 | if (__lstat64 (".", &st) < 0) |
6ec2e0f5 SDJ |
255 | goto lose; |
256 | thisdev = st.st_dev; | |
257 | thisino = st.st_ino; | |
258 | ||
9c9d63b1 | 259 | if (__lstat64 ("/", &st) < 0) |
6ec2e0f5 SDJ |
260 | goto lose; |
261 | rootdev = st.st_dev; | |
262 | rootino = st.st_ino; | |
263 | ||
264 | while (!(thisdev == rootdev && thisino == rootino)) | |
265 | { | |
9c9d63b1 | 266 | struct dirent64 *d; |
6ec2e0f5 SDJ |
267 | dev_t dotdev; |
268 | ino_t dotino; | |
269 | bool mount_point; | |
270 | int parent_status; | |
271 | size_t dirroom; | |
272 | size_t namlen; | |
273 | bool use_d_ino = true; | |
274 | ||
275 | /* Look at the parent directory. */ | |
276 | #if HAVE_OPENAT_SUPPORT | |
9c9d63b1 | 277 | fd = __openat64 (fd, "..", O_RDONLY); |
6ec2e0f5 SDJ |
278 | if (fd < 0) |
279 | goto lose; | |
280 | fd_needs_closing = true; | |
9c9d63b1 | 281 | parent_status = __fstat64 (fd, &st); |
6ec2e0f5 SDJ |
282 | #else |
283 | dotlist[dotlen++] = '.'; | |
284 | dotlist[dotlen++] = '.'; | |
285 | dotlist[dotlen] = '\0'; | |
9c9d63b1 | 286 | parent_status = __lstat64 (dotlist, &st); |
6ec2e0f5 SDJ |
287 | #endif |
288 | if (parent_status != 0) | |
289 | goto lose; | |
290 | ||
291 | if (dirstream && __closedir (dirstream) != 0) | |
292 | { | |
293 | dirstream = NULL; | |
294 | goto lose; | |
295 | } | |
296 | ||
297 | /* Figure out if this directory is a mount point. */ | |
298 | dotdev = st.st_dev; | |
299 | dotino = st.st_ino; | |
300 | mount_point = dotdev != thisdev; | |
301 | ||
302 | /* Search for the last directory. */ | |
303 | #if HAVE_OPENAT_SUPPORT | |
9c9d63b1 | 304 | dirstream = __fdopendir (fd); |
6ec2e0f5 SDJ |
305 | if (dirstream == NULL) |
306 | goto lose; | |
307 | fd_needs_closing = false; | |
308 | #else | |
309 | dirstream = __opendir (dotlist); | |
310 | if (dirstream == NULL) | |
311 | goto lose; | |
312 | dotlist[dotlen++] = '/'; | |
313 | #endif | |
314 | for (;;) | |
315 | { | |
316 | /* Clear errno to distinguish EOF from error if readdir returns | |
317 | NULL. */ | |
318 | __set_errno (0); | |
9c9d63b1 | 319 | d = __readdir64 (dirstream); |
6ec2e0f5 SDJ |
320 | |
321 | /* When we've iterated through all directory entries without finding | |
322 | one with a matching d_ino, rewind the stream and consider each | |
323 | name again, but this time, using lstat. This is necessary in a | |
324 | chroot on at least one system (glibc-2.3.6 + linux 2.6.12), where | |
325 | .., ../.., ../../.., etc. all had the same device number, yet the | |
326 | d_ino values for entries in / did not match those obtained | |
327 | via lstat. */ | |
328 | if (d == NULL && errno == 0 && use_d_ino) | |
329 | { | |
330 | use_d_ino = false; | |
9c9d63b1 PM |
331 | __rewinddir (dirstream); |
332 | d = __readdir64 (dirstream); | |
6ec2e0f5 SDJ |
333 | } |
334 | ||
335 | if (d == NULL) | |
336 | { | |
337 | if (errno == 0) | |
338 | /* EOF on dirstream, which can mean e.g., that the current | |
339 | directory has been removed. */ | |
340 | __set_errno (ENOENT); | |
341 | goto lose; | |
342 | } | |
343 | if (d->d_name[0] == '.' && | |
344 | (d->d_name[1] == '\0' || | |
345 | (d->d_name[1] == '.' && d->d_name[2] == '\0'))) | |
346 | continue; | |
347 | ||
348 | if (use_d_ino) | |
349 | { | |
350 | bool match = (MATCHING_INO (d, thisino) || mount_point); | |
351 | if (! match) | |
352 | continue; | |
353 | } | |
354 | ||
355 | { | |
356 | int entry_status; | |
357 | #if HAVE_OPENAT_SUPPORT | |
9c9d63b1 | 358 | entry_status = __fstatat64 (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW); |
6ec2e0f5 SDJ |
359 | #else |
360 | /* Compute size needed for this file name, or for the file | |
361 | name ".." in the same directory, whichever is larger. | |
362 | Room for ".." might be needed the next time through | |
363 | the outer loop. */ | |
364 | size_t name_alloc = _D_ALLOC_NAMLEN (d); | |
365 | size_t filesize = dotlen + MAX (sizeof "..", name_alloc); | |
366 | ||
367 | if (filesize < dotlen) | |
368 | goto memory_exhausted; | |
369 | ||
370 | if (dotsize < filesize) | |
371 | { | |
372 | /* My, what a deep directory tree you have, Grandma. */ | |
373 | size_t newsize = MAX (filesize, dotsize * 2); | |
374 | size_t i; | |
375 | if (newsize < dotsize) | |
376 | goto memory_exhausted; | |
377 | if (dotlist != dots) | |
378 | free (dotlist); | |
379 | dotlist = malloc (newsize); | |
380 | if (dotlist == NULL) | |
381 | goto lose; | |
382 | dotsize = newsize; | |
383 | ||
384 | i = 0; | |
385 | do | |
386 | { | |
387 | dotlist[i++] = '.'; | |
388 | dotlist[i++] = '.'; | |
389 | dotlist[i++] = '/'; | |
390 | } | |
391 | while (i < dotlen); | |
392 | } | |
393 | ||
394 | memcpy (dotlist + dotlen, d->d_name, _D_ALLOC_NAMLEN (d)); | |
9c9d63b1 | 395 | entry_status = __lstat64 (dotlist, &st); |
6ec2e0f5 SDJ |
396 | #endif |
397 | /* We don't fail here if we cannot stat() a directory entry. | |
398 | This can happen when (network) file systems fail. If this | |
399 | entry is in fact the one we are looking for we will find | |
400 | out soon as we reach the end of the directory without | |
401 | having found anything. */ | |
402 | if (entry_status == 0 && S_ISDIR (st.st_mode) | |
403 | && st.st_dev == thisdev && st.st_ino == thisino) | |
404 | break; | |
405 | } | |
406 | } | |
407 | ||
408 | dirroom = dirp - dir; | |
409 | namlen = _D_EXACT_NAMLEN (d); | |
410 | ||
411 | if (dirroom <= namlen) | |
412 | { | |
413 | if (size != 0) | |
414 | { | |
415 | __set_errno (ERANGE); | |
416 | goto lose; | |
417 | } | |
418 | else | |
419 | { | |
420 | char *tmp; | |
421 | size_t oldsize = allocated; | |
422 | ||
423 | allocated += MAX (allocated, namlen); | |
424 | if (allocated < oldsize | |
425 | || ! (tmp = realloc (dir, allocated))) | |
426 | goto memory_exhausted; | |
427 | ||
428 | /* Move current contents up to the end of the buffer. | |
429 | This is guaranteed to be non-overlapping. */ | |
430 | dirp = memcpy (tmp + allocated - (oldsize - dirroom), | |
431 | tmp + dirroom, | |
432 | oldsize - dirroom); | |
433 | dir = tmp; | |
434 | } | |
435 | } | |
436 | dirp -= namlen; | |
437 | memcpy (dirp, d->d_name, namlen); | |
438 | *--dirp = '/'; | |
439 | ||
440 | thisdev = dotdev; | |
441 | thisino = dotino; | |
442 | } | |
443 | ||
444 | if (dirstream && __closedir (dirstream) != 0) | |
445 | { | |
446 | dirstream = NULL; | |
447 | goto lose; | |
448 | } | |
449 | ||
450 | if (dirp == &dir[allocated - 1]) | |
451 | *--dirp = '/'; | |
452 | ||
453 | #if ! HAVE_OPENAT_SUPPORT | |
454 | if (dotlist != dots) | |
455 | free (dotlist); | |
456 | #endif | |
457 | ||
458 | used = dir + allocated - dirp; | |
459 | memmove (dir, dirp, used); | |
460 | ||
461 | if (size == 0) | |
462 | /* Ensure that the buffer is only as large as necessary. */ | |
c0c3707f | 463 | buf = (used < allocated ? realloc (dir, used) : dir); |
6ec2e0f5 SDJ |
464 | |
465 | if (buf == NULL) | |
466 | /* Either buf was NULL all along, or 'realloc' failed but | |
467 | we still have the original string. */ | |
468 | buf = dir; | |
469 | ||
470 | return buf; | |
471 | ||
472 | memory_exhausted: | |
473 | __set_errno (ENOMEM); | |
474 | lose: | |
475 | { | |
476 | int save = errno; | |
477 | if (dirstream) | |
478 | __closedir (dirstream); | |
479 | #if HAVE_OPENAT_SUPPORT | |
480 | if (fd_needs_closing) | |
9c9d63b1 | 481 | __close_nocancel_nostatus (fd); |
6ec2e0f5 SDJ |
482 | #else |
483 | if (dotlist != dots) | |
484 | free (dotlist); | |
485 | #endif | |
486 | if (buf == NULL) | |
487 | free (dir); | |
488 | __set_errno (save); | |
489 | } | |
490 | return NULL; | |
491 | } | |
492 | ||
9c9d63b1 PM |
493 | #if defined _LIBC && !defined GETCWD_RETURN_TYPE |
494 | libc_hidden_def (__getcwd) | |
6ec2e0f5 SDJ |
495 | weak_alias (__getcwd, getcwd) |
496 | #endif |