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