summaryrefslogtreecommitdiff
path: root/gl/lib/getcwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'gl/lib/getcwd.c')
-rw-r--r--gl/lib/getcwd.c84
1 files changed, 78 insertions, 6 deletions
diff --git a/gl/lib/getcwd.c b/gl/lib/getcwd.c
index 113332f1..28f69133 100644
--- a/gl/lib/getcwd.c
+++ b/gl/lib/getcwd.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 1991-2022 Free Software Foundation, Inc.
+/* Copyright (C) 1991-2023 Free Software Foundation, Inc.
This file is part of the GNU C Library.
This file is free software: you can redistribute it and/or modify
@@ -16,6 +16,7 @@
#if !_LIBC
# include <config.h>
+# include <stdio.h>
# include <unistd.h>
# include "pathmax.h"
#else
@@ -28,7 +29,6 @@
#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. */
@@ -114,13 +114,21 @@
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
-
+
#if defined _WIN32 && !defined __CYGWIN__
# if HAVE_MSVC_INVALID_PARAMETER_HANDLER
static char *
@@ -173,6 +181,9 @@ __getcwd_generic (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;
@@ -438,6 +449,67 @@ __getcwd_generic (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)