summaryrefslogtreecommitdiff
path: root/lib/getcwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/getcwd.c')
-rw-r--r--lib/getcwd.c167
1 files changed, 128 insertions, 39 deletions
diff --git a/lib/getcwd.c b/lib/getcwd.c
index 45470fcbe9..214950a144 100644
--- a/lib/getcwd.c
+++ b/lib/getcwd.c
@@ -1,12 +1,12 @@
-/* Copyright (C) 1991-1999, 2004-2020 Free Software Foundation, Inc.
+/* Copyright (C) 1991-2024 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
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
+ This file 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 3 of the License,
+ or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
+ This file 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.
@@ -16,13 +16,19 @@
#if !_LIBC
# include <config.h>
+# include <stdio.h>
# include <unistd.h>
+# include "pathmax.h"
+#else
+# define HAVE_OPENAT 1
+# define D_INO_IN_DIRENT 1
+# define HAVE_MSVC_INVALID_PARAMETER_HANDLER 0
+# define HAVE_MINIMALLY_WORKING_GETCWD 0
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <stdbool.h>
#include <stddef.h>
#include <fcntl.h> /* For AT_FDCWD on Solaris 9. */
@@ -65,8 +71,6 @@
# define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
-#include "pathmax.h"
-
/* In this file, PATH_MAX only serves as a threshold for choosing among two
algorithms. */
#ifndef PATH_MAX
@@ -84,11 +88,24 @@
#endif
#if !_LIBC
-# define __getcwd rpl_getcwd
-# define __lstat lstat
+# define GETCWD_RETURN_TYPE char *
+# define __close_nocancel_nostatus close
+# define __getcwd_generic rpl_getcwd
+# undef stat64
+# define stat64 stat
+# define __fstat64 fstat
+# define __fstatat64 fstatat
+# define __lstat64 lstat
# define __closedir closedir
# define __opendir opendir
-# define __readdir readdir
+# define __readdir64 readdir
+# define __fdopendir fdopendir
+# define __openat openat
+# define __rewinddir rewinddir
+# define __openat64 openat
+# define dirent64 dirent
+#else
+# include <not-cancel.h>
#endif
/* The results of opendir() in this file are not used with dirfd and fchdir,
@@ -97,14 +114,22 @@
FIXME - if the kernel ever adds support for multi-thread safety for
avoiding standard fds, then we should use opendir_safer and
openat_safer. */
-#ifdef GNULIB_defined_opendir
+#ifdef GNULIB_defined_DIR
+# undef DIR
# undef opendir
-#endif
-#ifdef GNULIB_defined_closedir
# undef closedir
+# undef readdir
+# undef rewinddir
+#else
+# ifdef GNULIB_defined_opendir
+# undef opendir
+# endif
+# ifdef GNULIB_defined_closedir
+# undef closedir
+# endif
#endif
-
-#ifdef _MSC_VER
+
+#if defined _WIN32 && !defined __CYGWIN__
# if HAVE_MSVC_INVALID_PARAMETER_HANDLER
static char *
getcwd_nothrow (char *buf, size_t size)
@@ -133,13 +158,13 @@ getcwd_nothrow (char *buf, size_t size)
#endif
/* 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
- SIZE was too small. If successful, returns BUF. In GNU, if BUF is
- NULL, an array is allocated with 'malloc'; the array is SIZE bytes long,
- unless SIZE == 0, in which case it is as big as necessary. */
+ bytes of BUF. Returns NULL with errno set if the directory couldn't be
+ determined or SIZE was too small. If successful, returns BUF. In GNU,
+ if BUF is NULL, an array is allocated with 'malloc'; the array is SIZE
+ bytes long, unless SIZE == 0, in which case it is as big as necessary. */
-char *
-__getcwd (char *buf, size_t size)
+GETCWD_RETURN_TYPE
+__getcwd_generic (char *buf, size_t size)
{
/* Lengths of big file name components and entire file names, and a
deep level of file name nesting. These numbers are not upper
@@ -156,6 +181,9 @@ __getcwd (char *buf, size_t size)
#if HAVE_OPENAT_SUPPORT
int fd = AT_FDCWD;
bool fd_needs_closing = false;
+# if defined __linux__
+ bool proc_fs_not_mounted = false;
+# endif
#else
char dots[DEEP_NESTING * sizeof ".." + BIG_FILE_NAME_COMPONENT_LENGTH + 1];
char *dotlist = dots;
@@ -167,7 +195,7 @@ __getcwd (char *buf, size_t size)
ino_t rootino, thisino;
char *dir;
register char *dirp;
- struct stat st;
+ struct stat64 st;
size_t allocated = size;
size_t used;
@@ -210,7 +238,6 @@ __getcwd (char *buf, size_t size)
return NULL;
# endif
#endif
-
if (size == 0)
{
if (buf != NULL)
@@ -234,19 +261,19 @@ __getcwd (char *buf, size_t size)
dirp = dir + allocated;
*--dirp = '\0';
- if (__lstat (".", &st) < 0)
+ if (__lstat64 (".", &st) < 0)
goto lose;
thisdev = st.st_dev;
thisino = st.st_ino;
- if (__lstat ("/", &st) < 0)
+ if (__lstat64 ("/", &st) < 0)
goto lose;
rootdev = st.st_dev;
rootino = st.st_ino;
while (!(thisdev == rootdev && thisino == rootino))
{
- struct dirent *d;
+ struct dirent64 *d;
dev_t dotdev;
ino_t dotino;
bool mount_point;
@@ -257,16 +284,16 @@ __getcwd (char *buf, size_t size)
/* Look at the parent directory. */
#if HAVE_OPENAT_SUPPORT
- fd = openat (fd, "..", O_RDONLY);
+ fd = __openat64 (fd, "..", O_RDONLY);
if (fd < 0)
goto lose;
fd_needs_closing = true;
- parent_status = fstat (fd, &st);
+ parent_status = __fstat64 (fd, &st);
#else
dotlist[dotlen++] = '.';
dotlist[dotlen++] = '.';
dotlist[dotlen] = '\0';
- parent_status = __lstat (dotlist, &st);
+ parent_status = __lstat64 (dotlist, &st);
#endif
if (parent_status != 0)
goto lose;
@@ -284,7 +311,7 @@ __getcwd (char *buf, size_t size)
/* Search for the last directory. */
#if HAVE_OPENAT_SUPPORT
- dirstream = fdopendir (fd);
+ dirstream = __fdopendir (fd);
if (dirstream == NULL)
goto lose;
fd_needs_closing = false;
@@ -299,7 +326,7 @@ __getcwd (char *buf, size_t size)
/* Clear errno to distinguish EOF from error if readdir returns
NULL. */
__set_errno (0);
- d = __readdir (dirstream);
+ d = __readdir64 (dirstream);
/* When we've iterated through all directory entries without finding
one with a matching d_ino, rewind the stream and consider each
@@ -311,8 +338,8 @@ __getcwd (char *buf, size_t size)
if (d == NULL && errno == 0 && use_d_ino)
{
use_d_ino = false;
- rewinddir (dirstream);
- d = __readdir (dirstream);
+ __rewinddir (dirstream);
+ d = __readdir64 (dirstream);
}
if (d == NULL)
@@ -338,7 +365,7 @@ __getcwd (char *buf, size_t size)
{
int entry_status;
#if HAVE_OPENAT_SUPPORT
- entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW);
+ entry_status = __fstatat64 (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.
@@ -375,7 +402,7 @@ __getcwd (char *buf, size_t size)
}
memcpy (dotlist + dotlen, d->d_name, _D_ALLOC_NAMLEN (d));
- entry_status = __lstat (dotlist, &st);
+ entry_status = __lstat64 (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
@@ -422,6 +449,67 @@ __getcwd (char *buf, size_t size)
thisdev = dotdev;
thisino = dotino;
+
+#if HAVE_OPENAT_SUPPORT
+ /* On some platforms, a system call returns the directory that FD points
+ to. This is useful if some of the ancestor directories of the
+ directory are unreadable, because in this situation the loop that
+ climbs up the ancestor hierarchy runs into an EACCES error.
+ For example, in some Android app, /data/data/com.termux is readable,
+ but /data/data and /data are not. */
+# if defined __linux__
+ /* On Linux, in particular, if /proc is mounted,
+ readlink ("/proc/self/fd/<fd>")
+ returns the directory, if its length is < 4096. (If the length is
+ >= 4096, it fails with error ENAMETOOLONG, even if the buffer that we
+ pass to the readlink function would be large enough.) */
+ if (!proc_fs_not_mounted)
+ {
+ char namebuf[14 + 10 + 1];
+ sprintf (namebuf, "/proc/self/fd/%u", (unsigned int) fd);
+ char linkbuf[4096];
+ ssize_t linklen = readlink (namebuf, linkbuf, sizeof linkbuf);
+ if (linklen < 0)
+ {
+ if (errno != ENAMETOOLONG)
+ /* If this call was not successful, the next one will likely be
+ not successful either. */
+ proc_fs_not_mounted = true;
+ }
+ else
+ {
+ dirroom = dirp - dir;
+ if (dirroom < linklen)
+ {
+ if (size != 0)
+ {
+ __set_errno (ERANGE);
+ goto lose;
+ }
+ else
+ {
+ char *tmp;
+ size_t oldsize = allocated;
+
+ allocated += linklen - dirroom;
+ if (allocated < oldsize
+ || ! (tmp = realloc (dir, allocated)))
+ goto memory_exhausted;
+
+ /* Move current contents up to the end of the buffer. */
+ dirp = memmove (tmp + dirroom + (allocated - oldsize),
+ tmp + dirroom,
+ oldsize - dirroom);
+ dir = tmp;
+ }
+ }
+ dirp -= linklen;
+ memcpy (dirp, linkbuf, linklen);
+ break;
+ }
+ }
+# endif
+#endif
}
if (dirstream && __closedir (dirstream) != 0)
@@ -461,7 +549,7 @@ __getcwd (char *buf, size_t size)
__closedir (dirstream);
#if HAVE_OPENAT_SUPPORT
if (fd_needs_closing)
- close (fd);
+ __close_nocancel_nostatus (fd);
#else
if (dotlist != dots)
free (dotlist);
@@ -473,6 +561,7 @@ __getcwd (char *buf, size_t size)
return NULL;
}
-#ifdef weak_alias
+#if defined _LIBC && !defined GETCWD_RETURN_TYPE
+libc_hidden_def (__getcwd)
weak_alias (__getcwd, getcwd)
#endif

Return to:

Send suggestions and report system problems to the System administrator.