X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fgetcwd.c;h=7d47072101e7fdc4d339c8cd77a92b115c89afe6;hb=b1ef417aa5830669125c4f4aafd0a163c8902125;hp=86070ead87730d140653ac7a6371cb3f26c57d17;hpb=d1eb42bdbbaf00d38b2a479e9aa40207b79bd090;p=gnulib.git diff --git a/lib/getcwd.c b/lib/getcwd.c index 86070ead8..7d4707210 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-2010 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 "config.h" -#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,38 +27,27 @@ #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) #endif -#if HAVE_UNISTD_H || _LIBC -# include -#endif - +#include #include #include @@ -71,18 +55,10 @@ # ifndef mempcpy # define mempcpy __mempcpy # endif -#else -# include "mempcpy.h" #endif #include -#ifdef ENAMETOOLONG -# define is_ENAMETOOLONG(x) ((x) == ENAMETOOLONG) -#else -# define is_ENAMETOOLONG(x) 0 -#endif - #ifndef MAX # define MAX(a, b) ((a) < (b) ? (b) : (a)) #endif @@ -91,7 +67,7 @@ #endif #ifndef PATH_MAX -# ifdef MAXPATHLEN +# ifdef MAXPATHLEN # define PATH_MAX MAXPATHLEN # else # define PATH_MAX 1024 @@ -105,12 +81,21 @@ #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, + and we do not leak fds to any single-threaded code that could use stdio, + therefore save some unnecessary recursion in fchdir.c. + FIXME - if the kernel ever adds support for multi-thread safety for + avoiding standard fds, then we should use opendir_safer and + openat_safer. */ +#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 @@ -133,7 +118,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 @@ -151,26 +136,48 @@ __getcwd (char *buf, size_t size) size_t allocated = size; size_t used; -#if HAVE_PARTLY_WORKING_GETCWD && !defined AT_FDCWD - /* 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. */ +#if HAVE_RAW_DECL_GETCWD + /* 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. + + 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)) + if (dir) return dir; + + /* Solaris getcwd (NULL, 0) fails with errno == EINVAL, but it has + internal magic that lets it work even if an ancestor directory is + inaccessible, which is better in many cases. So in this case try + again with a buffer that's almost always big enough. */ + if (errno == EINVAL && buf == NULL && size == 0) + { + char big_buffer[BIG_FILE_NAME_LENGTH + 1]; + dir = getcwd (big_buffer, sizeof big_buffer); + if (dir) + return strdup (dir); + } + +# 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 (errno != ERANGE && errno != ENAMETOOLONG && errno != ENOENT) + return NULL; +# endif #endif if (size == 0) { if (buf != NULL) - { - __set_errno (EINVAL); - return NULL; - } + { + __set_errno (EINVAL); + return NULL; + } allocated = BIG_FILE_NAME_LENGTH + 1; } @@ -179,7 +186,7 @@ __getcwd (char *buf, size_t size) { dir = malloc (allocated); if (dir == NULL) - return NULL; + return NULL; } else dir = buf; @@ -204,12 +211,15 @@ __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; + goto lose; fd_needs_closing = true; parent_status = fstat (fd, &st); #else @@ -219,13 +229,13 @@ __getcwd (char *buf, size_t size) parent_status = __lstat (dotlist, &st); #endif if (parent_status != 0) - goto lose; + goto lose; if (dirstream && __closedir (dirstream) != 0) - { - dirstream = NULL; - goto lose; - } + { + dirstream = NULL; + goto lose; + } /* Figure out if this directory is a mount point. */ dotdev = st.st_dev; @@ -233,121 +243,144 @@ __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; + goto lose; + /* Reset fd. It may have been closed by fdopendir. */ + fd = dirfd (dirstream); fd_needs_closing = false; #else dirstream = __opendir (dotlist); if (dirstream == NULL) - goto lose; + goto lose; dotlist[dotlen++] = '/'; #endif - /* Clear errno to distinguish EOF from error if readdir returns - NULL. */ - __set_errno (0); - while ((d = __readdir (dirstream)) != NULL) - { - 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) - { - int entry_status; -#ifdef AT_FDCWD - entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW); + 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 (use_d_ino) + { + bool match = (MATCHING_INO (d, thisino) || mount_point); + if (! match) + continue; + } + + { + 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); - } - - 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; - } - else - { - size_t dirroom = dirp - dir; - size_t namlen = _D_EXACT_NAMLEN (d); - - if (dirroom <= namlen) - { - 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; - } - } - dirp -= namlen; - memcpy (dirp, d->d_name, namlen); - *--dirp = '/'; - } + /* 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; + } + } + + dirroom = dirp - dir; + namlen = _D_EXACT_NAMLEN (d); + + if (dirroom <= namlen) + { + 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; + } + } + dirp -= namlen; + memcpy (dirp, d->d_name, namlen); + *--dirp = '/'; thisdev = dotdev; thisino = dotino; @@ -362,7 +395,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 @@ -370,7 +403,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); @@ -388,7 +421,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