X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fgetcwd.c;h=b9e57d31a9a0c12fb569b7a3d765bf220d9667e0;hb=1bf5f51e739b66371e96b8da60002fc23603d3f5;hp=3bc6e9a52ec498a140671b19a46ed851bbed4bb8;hpb=52c1af4e35c338d255296a29c0690f192052a902;p=gnulib.git diff --git a/lib/getcwd.c b/lib/getcwd.c index 3bc6e9a52..b9e57d31a 100644 --- a/lib/getcwd.c +++ b/lib/getcwd.c @@ -1,27 +1,22 @@ -/* Copyright (C) 1991,92,93,94,95,96,97,98,99,2004,2005 Free Software - Foundation, Inc. +/* Copyright (C) 1991-1999, 2004-2008 Free Software Foundation, Inc. This file is part of the GNU C Library. - This program is free software; you can redistribute it and/or modify + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along - 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 -#endif + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ #if !_LIBC -# include "getcwd.h" +# include +# include #endif #include @@ -32,29 +27,21 @@ #include /* For AT_FDCWD on Solaris 9. */ +/* If this host provides the openat function, then enable + code below to make getcwd more efficient and robust. */ +#ifdef HAVE_OPENAT +# define HAVE_OPENAT_SUPPORT 1 +#else +# define HAVE_OPENAT_SUPPORT 0 +#endif + #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) @@ -68,12 +55,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 @@ -102,12 +95,17 @@ #endif #if !_LIBC -# define __getcwd getcwd +# define __getcwd rpl_getcwd # define __lstat lstat # define __closedir closedir # 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 @@ -130,7 +128,7 @@ __getcwd (char *buf, size_t size) DEEP_NESTING = 100 }; -#ifdef AT_FDCWD +#if HAVE_OPENAT_SUPPORT int fd = AT_FDCWD; bool fd_needs_closing = false; #else @@ -148,13 +146,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)) @@ -201,9 +204,12 @@ __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 +#if HAVE_OPENAT_SUPPORT fd = openat (fd, "..", O_RDONLY); if (fd < 0) goto lose; @@ -230,10 +236,12 @@ __getcwd (char *buf, size_t size) mount_point = dotdev != thisdev; /* Search for the last directory. */ -#ifdef AT_FDCWD +#if HAVE_OPENAT_SUPPORT 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); @@ -241,110 +249,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; -#ifdef AT_FDCWD - 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; + bool match = (MATCHING_INO (d, thisino) || mount_point); + if (! match) + continue; + } - 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); + { + int entry_status; +#if HAVE_OPENAT_SUPPORT + 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); + } + + 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; @@ -359,7 +388,7 @@ __getcwd (char *buf, size_t size) if (dirp == &dir[allocated - 1]) *--dirp = '/'; -#ifndef AT_FDCWD +#if ! HAVE_OPENAT_SUPPORT if (dotlist != dots) free (dotlist); #endif @@ -367,7 +396,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); @@ -385,7 +414,7 @@ __getcwd (char *buf, size_t size) int save = errno; if (dirstream) __closedir (dirstream); -#ifdef AT_FDCWD +#if HAVE_OPENAT_SUPPORT if (fd_needs_closing) close (fd); #else