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