X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fgetcwd.c;h=23b35de679d1e50f97d0b49cdd8932b4a9b2a3c4;hb=024eeefda59f214eeaac745db9f3e982b9a726c3;hp=94e5ba3b551aef0f1e0c6e2d4d6ac242bfd33bb7;hpb=359c0a71713e606f440d40f55a43c77349df2aaa;p=gnulib.git diff --git a/lib/getcwd.c b/lib/getcwd.c index 94e5ba3b5..23b35de67 100644 --- a/lib/getcwd.c +++ b/lib/getcwd.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1991,92,93,94,95,96,97,98,99,2004,2005 Free Software +/* Copyright (C) 1991,92,93,94,95,96,97,98,99,2004,2005,2006,2007 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -16,12 +16,10 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - #if !_LIBC -# include "getcwd.h" +# include +# include +# include "dirfd.h" #endif #include @@ -30,42 +28,21 @@ #include #include -#if HAVE_FCNTL_H -# include /* For AT_FDCWD on Solaris 9. */ -#endif +#include /* For AT_FDCWD on Solaris 9. */ #ifndef __set_errno # define __set_errno(val) (errno = (val)) #endif -#if HAVE_DIRENT_H || _LIBC -# include -# ifndef _D_EXACT_NAMLEN -# define _D_EXACT_NAMLEN(d) strlen ((d)->d_name) -# endif -#else -# define dirent direct -# if HAVE_SYS_NDIR_H -# include -# endif -# if HAVE_SYS_DIR_H -# include -# endif -# if HAVE_NDIR_H -# include -# endif -#endif +#include #ifndef _D_EXACT_NAMLEN -# define _D_EXACT_NAMLEN(d) ((d)->d_namlen) +# define _D_EXACT_NAMLEN(d) strlen ((d)->d_name) #endif #ifndef _D_ALLOC_NAMLEN # define _D_ALLOC_NAMLEN(d) (_D_EXACT_NAMLEN (d) + 1) #endif -#if HAVE_UNISTD_H || _LIBC -# include -#endif - +#include #include #include @@ -73,12 +50,18 @@ # ifndef mempcpy # define mempcpy __mempcpy # endif -#else -# include "mempcpy.h" #endif #include +/* Work around a bug in Solaris 9 and 10: AT_FDCWD is positive. Its + value exceeds INT_MAX, so its use as an int doesn't conform to the + C standard, and GCC and Sun C complain in some cases. */ +#if 0 < AT_FDCWD && AT_FDCWD == 0xffd19553 +# undef AT_FDCWD +# define AT_FDCWD (-3041965) +#endif + #ifdef ENAMETOOLONG # define is_ENAMETOOLONG(x) ((x) == ENAMETOOLONG) #else @@ -113,6 +96,11 @@ # define __opendir opendir # define __readdir readdir #endif + +/* The results of opendir() in this file are not used with dirfd and fchdir, + therefore save some unnecessary recursion in fchdir.c. */ +#undef opendir +#undef closedir /* Get the name of the current working directory, and put it in SIZE bytes of BUF. Returns NULL if the directory couldn't be determined or @@ -153,13 +141,18 @@ __getcwd (char *buf, size_t size) size_t allocated = size; size_t used; -#if HAVE_PARTLY_WORKING_GETCWD && !defined AT_FDCWD +#if HAVE_PARTLY_WORKING_GETCWD /* The system getcwd works, except it sometimes fails when it shouldn't, setting errno to ERANGE, ENAMETOOLONG, or ENOENT. If AT_FDCWD is not defined, the algorithm below is O(N**2) and this is much slower than the system getcwd (at least on GNU/Linux). So trust the system getcwd's results unless they look - suspicious. */ + suspicious. + + Use the system getcwd even if we have openat support, since the + system getcwd works even when a parent is unreadable, while the + openat-based approach does not. */ + # undef getcwd dir = getcwd (buf, size); if (dir || (errno != ERANGE && !is_ENAMETOOLONG (errno) && errno != ENOENT)) @@ -206,6 +199,9 @@ __getcwd (char *buf, size_t size) ino_t dotino; bool mount_point; int parent_status; + size_t dirroom; + size_t namlen; + bool use_d_ino = true; /* Look at the parent directory. */ #ifdef AT_FDCWD @@ -239,6 +235,8 @@ __getcwd (char *buf, size_t size) dirstream = fdopendir (fd); if (dirstream == NULL) goto lose; + /* Reset fd. It may have been closed by fdopendir. */ + fd = dirfd (dirstream); fd_needs_closing = false; #else dirstream = __opendir (dotlist); @@ -246,110 +244,131 @@ __getcwd (char *buf, size_t size) goto lose; dotlist[dotlen++] = '/'; #endif - /* Clear errno to distinguish EOF from error if readdir returns - NULL. */ - __set_errno (0); - while ((d = __readdir (dirstream)) != NULL) + for (;;) { + /* Clear errno to distinguish EOF from error if readdir returns + NULL. */ + __set_errno (0); + d = __readdir (dirstream); + + /* When we've iterated through all directory entries without finding + one with a matching d_ino, rewind the stream and consider each + name again, but this time, using lstat. This is necessary in a + chroot on at least one system (glibc-2.3.6 + linux 2.6.12), where + .., ../.., ../../.., etc. all had the same device number, yet the + d_ino values for entries in / did not match those obtained + via lstat. */ + if (d == NULL && errno == 0 && use_d_ino) + { + use_d_ino = false; + rewinddir (dirstream); + d = __readdir (dirstream); + } + + if (d == NULL) + { + if (errno == 0) + /* EOF on dirstream, which can mean e.g., that the current + directory has been removed. */ + __set_errno (ENOENT); + goto lose; + } if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) continue; - if (MATCHING_INO (d, thisino) || mount_point) + + if (use_d_ino) { - int entry_status; + bool match = (MATCHING_INO (d, thisino) || mount_point); + if (! match) + continue; + } + + { + int entry_status; #ifdef AT_FDCWD - entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW); + entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW); #else - /* Compute size needed for this file name, or for the file - name ".." in the same directory, whichever is larger. - Room for ".." might be needed the next time through - the outer loop. */ - size_t name_alloc = _D_ALLOC_NAMLEN (d); - size_t filesize = dotlen + MAX (sizeof "..", name_alloc); - - if (filesize < dotlen) - goto memory_exhausted; - - if (dotsize < filesize) - { - /* My, what a deep directory tree you have, Grandma. */ - size_t newsize = MAX (filesize, dotsize * 2); - size_t i; - if (newsize < dotsize) - goto memory_exhausted; - if (dotlist != dots) - free (dotlist); - dotlist = malloc (newsize); - if (dotlist == NULL) - goto lose; - dotsize = newsize; - - i = 0; - do - { - dotlist[i++] = '.'; - dotlist[i++] = '.'; - dotlist[i++] = '/'; - } - while (i < dotlen); - } - - strcpy (dotlist + dotlen, d->d_name); - entry_status = __lstat (dotlist, &st); + /* Compute size needed for this file name, or for the file + name ".." in the same directory, whichever is larger. + Room for ".." might be needed the next time through + the outer loop. */ + size_t name_alloc = _D_ALLOC_NAMLEN (d); + size_t filesize = dotlen + MAX (sizeof "..", name_alloc); + + if (filesize < dotlen) + goto memory_exhausted; + + if (dotsize < filesize) + { + /* My, what a deep directory tree you have, Grandma. */ + size_t newsize = MAX (filesize, dotsize * 2); + size_t i; + if (newsize < dotsize) + goto memory_exhausted; + if (dotlist != dots) + free (dotlist); + dotlist = malloc (newsize); + if (dotlist == NULL) + goto lose; + dotsize = newsize; + + i = 0; + do + { + dotlist[i++] = '.'; + dotlist[i++] = '.'; + dotlist[i++] = '/'; + } + while (i < dotlen); + } + + memcpy (dotlist + dotlen, d->d_name, _D_ALLOC_NAMLEN (d)); + entry_status = __lstat (dotlist, &st); #endif - /* We don't fail here if we cannot stat() a directory entry. - This can happen when (network) file systems fail. If this - entry is in fact the one we are looking for we will find - out soon as we reach the end of the directory without - having found anything. */ - if (entry_status == 0 && S_ISDIR (st.st_mode) - && st.st_dev == thisdev && st.st_ino == thisino) - break; - } - } - if (d == NULL) - { - if (errno == 0) - /* EOF on dirstream, which means that the current directory - has been removed. */ - __set_errno (ENOENT); - goto lose; + /* We don't fail here if we cannot stat() a directory entry. + This can happen when (network) file systems fail. If this + entry is in fact the one we are looking for we will find + out soon as we reach the end of the directory without + having found anything. */ + if (entry_status == 0 && S_ISDIR (st.st_mode) + && st.st_dev == thisdev && st.st_ino == thisino) + break; + } } - else - { - size_t dirroom = dirp - dir; - size_t namlen = _D_EXACT_NAMLEN (d); - if (dirroom <= namlen) + dirroom = dirp - dir; + namlen = _D_EXACT_NAMLEN (d); + + if (dirroom <= namlen) + { + if (size != 0) { - if (size != 0) - { - __set_errno (ERANGE); - goto lose; - } - else - { - char *tmp; - size_t oldsize = allocated; - - allocated += MAX (allocated, namlen); - if (allocated < oldsize - || ! (tmp = realloc (dir, allocated))) - goto memory_exhausted; - - /* Move current contents up to the end of the buffer. - This is guaranteed to be non-overlapping. */ - dirp = memcpy (tmp + allocated - (oldsize - dirroom), - tmp + dirroom, - oldsize - dirroom); - dir = tmp; - } + __set_errno (ERANGE); + goto lose; + } + else + { + char *tmp; + size_t oldsize = allocated; + + allocated += MAX (allocated, namlen); + if (allocated < oldsize + || ! (tmp = realloc (dir, allocated))) + goto memory_exhausted; + + /* Move current contents up to the end of the buffer. + This is guaranteed to be non-overlapping. */ + dirp = memcpy (tmp + allocated - (oldsize - dirroom), + tmp + dirroom, + oldsize - dirroom); + dir = tmp; } - dirp -= namlen; - memcpy (dirp, d->d_name, namlen); - *--dirp = '/'; } + dirp -= namlen; + memcpy (dirp, d->d_name, namlen); + *--dirp = '/'; thisdev = dotdev; thisino = dotino; @@ -372,7 +391,7 @@ __getcwd (char *buf, size_t size) used = dir + allocated - dirp; memmove (dir, dirp, used); - if (buf == NULL && size == 0) + if (size == 0) /* Ensure that the buffer is only as large as necessary. */ buf = realloc (dir, used);