-/* 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 <http://www.gnu.org/licenses/>. */
#if !_LIBC
-# include "getcwd.h"
+# include <config.h>
+# include <unistd.h>
#endif
#include <errno.h>
#include <fcntl.h> /* 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 <dirent.h>
-# ifndef _D_EXACT_NAMLEN
-# define _D_EXACT_NAMLEN(d) strlen ((d)->d_name)
-# endif
-#else
-# define dirent direct
-# if HAVE_SYS_NDIR_H
-# include <sys/ndir.h>
-# endif
-# if HAVE_SYS_DIR_H
-# include <sys/dir.h>
-# endif
-# if HAVE_NDIR_H
-# include <ndir.h>
-# endif
-#endif
+#include <dirent.h>
#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)
# ifndef mempcpy
# define mempcpy __mempcpy
# endif
-#else
-# include "mempcpy.h"
#endif
#include <limits.h>
-#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
#endif
#ifndef PATH_MAX
-# ifdef MAXPATHLEN
+# ifdef MAXPATHLEN
# define PATH_MAX MAXPATHLEN
# else
# define PATH_MAX 1024
#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
\f
/* 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
DEEP_NESTING = 100
};
-#ifdef AT_FDCWD
+#if HAVE_OPENAT_SUPPORT
int fd = AT_FDCWD;
bool fd_needs_closing = false;
#else
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;
}
{
dir = malloc (allocated);
if (dir == NULL)
- return NULL;
+ return NULL;
}
else
dir = buf;
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
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;
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
for (;;)
- {
- /* Clear errno to distinguish EOF from error if readdir returns
- NULL. */
- __set_errno (0);
- d = __readdir (dirstream);
- if (d == NULL)
- {
- if (errno == 0)
- /* EOF on dirstream, which means 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)
- {
- int entry_status;
-#ifdef AT_FDCWD
- entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW);
+ {
+ /* 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;
- }
- }
+ /* 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;
- }
- }
+ {
+ 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 = '/';
if (dirp == &dir[allocated - 1])
*--dirp = '/';
-#ifndef AT_FDCWD
+#if ! HAVE_OPENAT_SUPPORT
if (dotlist != dots)
free (dotlist);
#endif
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);
int save = errno;
if (dirstream)
__closedir (dirstream);
-#ifdef AT_FDCWD
+#if HAVE_OPENAT_SUPPORT
if (fd_needs_closing)
close (fd);
#else