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