summary refs log tree commit diff
path: root/sysdeps/unix
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@gmail.com>2011-05-08 08:37:19 -0400
committerUlrich Drepper <drepper@gmail.com>2011-05-08 08:37:19 -0400
commit7fb90fb89bbdf273ab7ab96517fe1b156cd7aee1 (patch)
treec9fb5b27f0c75b57cd3090e2f3c857feba542f41 /sysdeps/unix
parent28377d1bf58625172a1734b92e835591d4d23a18 (diff)
downloadglibc-7fb90fb89bbdf273ab7ab96517fe1b156cd7aee1.tar.gz
glibc-7fb90fb89bbdf273ab7ab96517fe1b156cd7aee1.tar.xz
glibc-7fb90fb89bbdf273ab7ab96517fe1b156cd7aee1.zip
Fix Linux getcwd for long paths
The getcwd syscall (so far?) can only handle path up to one page
in size.  There is no limit about directory hierarchy depth, though,
and the POSIX getcwd is supposed to handle this.  In that case fall
back to the generic getcwd.

Additionally, optimize the generic getcwd to use openat when possible
to change the asymptotic performance from O(N^2) to O(n).
Diffstat (limited to 'sysdeps/unix')
-rw-r--r--sysdeps/unix/rewinddir.c3
-rw-r--r--sysdeps/unix/sysv/linux/Makefile2
-rw-r--r--sysdeps/unix/sysv/linux/dl-getcwd.c1
-rw-r--r--sysdeps/unix/sysv/linux/getcwd.c61
4 files changed, 49 insertions, 18 deletions
diff --git a/sysdeps/unix/rewinddir.c b/sysdeps/unix/rewinddir.c
index 051e93595e..89b0e6d20d 100644
--- a/sysdeps/unix/rewinddir.c
+++ b/sysdeps/unix/rewinddir.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 1991, 1995-1998, 2005 Free Software Foundation, Inc.
+/* Copyright (C) 1991, 1995-1998, 2005, 2011 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -35,3 +35,4 @@ rewinddir (dirp)
   dirp->size = 0;
   __libc_lock_unlock (dirp->lock);
 }
+libc_hidden_def (rewinddir)
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 7066ffe6da..61fbfb4fc8 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -147,7 +147,7 @@ sysdep_routines += xstatconv internal_statvfs internal_statvfs64 \
 endif
 
 ifeq ($(subdir),elf)
-sysdep-rtld-routines += dl-brk dl-sbrk
+sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd
 
 CPPFLAGS-lddlibc4 += -DNOT_IN_libc
 endif
diff --git a/sysdeps/unix/sysv/linux/dl-getcwd.c b/sysdeps/unix/sysv/linux/dl-getcwd.c
new file mode 100644
index 0000000000..4bd5657f1e
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-getcwd.c
@@ -0,0 +1 @@
+#include "getcwd.c"
diff --git a/sysdeps/unix/sysv/linux/getcwd.c b/sysdeps/unix/sysv/linux/getcwd.c
index 911d85f43d..db3e292964 100644
--- a/sysdeps/unix/sysv/linux/getcwd.c
+++ b/sysdeps/unix/sysv/linux/getcwd.c
@@ -1,5 +1,5 @@
 /* Determine current working directory.  Linux version.
-   Copyright (C) 1997,1998,1999,2000,2002,2003,2006
+   Copyright (C) 1997,1998,1999,2000,2002,2003,2006,2011
 	Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
@@ -45,20 +45,13 @@
    compiling under 2.1.92+ the libc still runs under older kernels. */
 # define no_syscall_getcwd 0
 # define have_new_dcache 1
-/* This is a trick since we don't define generic_getcwd.  */
-# define generic_getcwd getcwd
 #else
-/* The "proc" filesystem provides an easy method to retrieve the value.
-   For each process, the corresponding directory contains a symbolic link
-   named `cwd'.  Reading the content of this link immediate gives us the
-   information.  But we have to take care for systems which do not have
-   the proc filesystem mounted.  Use the POSIX implementation in this case.  */
-static char *generic_getcwd (char *buf, size_t size) internal_function;
-
 # if __NR_getcwd
 /* Kernel 2.1.92 introduced a third way to get the current working
    directory: a syscall.  We've got to be careful that even when
-   compiling under 2.1.92+ the libc still runs under older kernels. */
+   compiling under 2.1.92+ the libc still runs under older kernels.
+   An additional problem is that the system call does not return
+   the path of directories longer than one page.  */
 static int no_syscall_getcwd;
 static int have_new_dcache;
 # else
@@ -67,6 +60,13 @@ static int have_new_dcache = 1;
 # endif
 #endif
 
+/* The "proc" filesystem provides an easy method to retrieve the value.
+   For each process, the corresponding directory contains a symbolic link
+   named `cwd'.  Reading the content of this link immediate gives us the
+   information.  But we have to take care for systems which do not have
+   the proc filesystem mounted.  Use the POSIX implementation in this case.  */
+static char *generic_getcwd (char *buf, size_t size) internal_function;
+
 char *
 __getcwd (char *buf, size_t size)
 {
@@ -124,6 +124,33 @@ __getcwd (char *buf, size_t size)
 	  return buf;
 	}
 
+      // XXX This should not be necessary but the full getcwd implementation
+      // drags in too much for the current build proces of ld.so to handle
+#ifndef NOT_IN_libc
+      /* The system call cannot handle paths longer than a page.
+	 Neither can the magic symlink in /proc/self.  Just use the
+	 generic implementation right away.  */
+      if (errno == ENAMETOOLONG)
+	{
+# ifndef NO_ALLOCATION
+	  if (buf == NULL && size == 0)
+	    {
+	      free (path);
+	      path = NULL;
+	    }
+# endif
+
+	  result = generic_getcwd (path, size);
+
+# ifndef NO_ALLOCATION
+	  if (result == NULL && buf == NULL && size != 0)
+	    free (path);
+# endif
+
+	  return result;
+	}
+#endif
+
 # if __ASSUME_GETCWD_SYSCALL
       /* It should never happen that the `getcwd' syscall failed because
 	 the buffer is too small if we allocated the buffer ourselves
@@ -196,7 +223,7 @@ __getcwd (char *buf, size_t size)
 
 #ifndef NO_ALLOCATION
   /* Don't put restrictions on the length of the path unless the user does.  */
-  if (size == 0)
+  if (buf == NULL && size == 0)
     {
       free (path);
       path = NULL;
@@ -214,9 +241,11 @@ __getcwd (char *buf, size_t size)
 }
 weak_alias (__getcwd, getcwd)
 
-#if __ASSUME_GETCWD_SYSCALL == 0
+      // XXX This should not be necessary but the full getcwd implementation
+      // drags in too much for the current build proces of ld.so to handle
+#ifndef NOT_IN_libc
 /* Get the code for the generic version.  */
-# define GETCWD_RETURN_TYPE	static char * internal_function
-# define __getcwd		generic_getcwd
-# include <sysdeps/posix/getcwd.c>
+#define GETCWD_RETURN_TYPE	static char * internal_function
+#define __getcwd		generic_getcwd
+#include <sysdeps/posix/getcwd.c>
 #endif