diff mbox series

[v2,4/5] linux: Simplify getcwd implementation

Message ID 20200901203704.172996-4-adhemerval.zanella@linaro.org
State New
Headers show
Series [v2,1/5] Sync getcwd with gnulib | expand

Commit Message

Adhemerval Zanella Sept. 1, 2020, 8:37 p.m. UTC
Changes from previous version:

  - Move the linux and loader changes to its own patch.

---

Instead of allocating and reallocate/free the buffer, a temporary
buffer of PATH_MAX size is used and output buffer is allocated only
the syscall returns a valid buffer.

The NO_ALLOCATION is removed and instead the buffer allocation path
is also used on loader (and the stretegy of using the temporary stack
buffer should play nice with the loader bump allocator).  The fallback
generic algorithm is not built for rtld, since it would eventually
fail to open paths larger than PATH_MAX.

Checked on x86_64-linux-gnu and i686-linux-gnu.
---
 elf/dl-object.c                  |  30 +++------
 sysdeps/unix/sysv/linux/getcwd.c | 112 +++++++++----------------------
 2 files changed, 42 insertions(+), 100 deletions(-)
diff mbox series

Patch

diff --git a/elf/dl-object.c b/elf/dl-object.c
index d2cdf135cc..717fb253bb 100644
--- a/elf/dl-object.c
+++ b/elf/dl-object.c
@@ -202,38 +202,30 @@  _dl_new_object (char *realname, const char *libname, int type,
 	}
       else
 	{
-	  size_t len = realname_len;
-	  char *result = NULL;
-
-	  /* Get the current directory name.  */
-	  origin = NULL;
-	  do
+	  /* The rtld __getcwd implementation does not handle paths larger
+	     than PATH_MAX (which would be invalid to be used on subsequent
+	     open calls).  */
+	  origin = __getcwd (NULL, 0);
+	  if (origin == NULL)
 	    {
-	      char *new_origin;
-
-	      len += 128;
-	      new_origin = (char *) realloc (origin, len);
-	      if (new_origin == NULL)
-		/* We exit the loop.  Note that result == NULL.  */
-		break;
-	      origin = new_origin;
+	      origin = (char *) -1;
+	      goto out;
 	    }
-	  while ((result = __getcwd (origin, len - realname_len)) == NULL
-		 && errno == ERANGE);
+	  size_t len = strlen (origin);
+	  char *result = realloc (origin, len + realname_len);
 
 	  if (result == NULL)
 	    {
-	      /* We were not able to determine the current directory.
-		 Note that free(origin) is OK if origin == NULL.  */
 	      free (origin);
 	      origin = (char *) -1;
 	      goto out;
 	    }
+	  origin = result;
+	  cp = origin + len;
 
 	  /* Find the end of the path and see whether we have to add a
 	     slash.  We could use rawmemchr but this need not be
 	     fast.  */
-	  cp = (strchr) (origin, '\0');
 	  if (cp[-1] != '/')
 	    *cp++ = '/';
 	}
diff --git a/sysdeps/unix/sysv/linux/getcwd.c b/sysdeps/unix/sysv/linux/getcwd.c
index c5886f5283..5ef0b092ec 100644
--- a/sysdeps/unix/sysv/linux/getcwd.c
+++ b/sysdeps/unix/sysv/linux/getcwd.c
@@ -17,115 +17,65 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <assert.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
-#include <sys/param.h>
 
 #include <sysdep.h>
 #include <sys/syscall.h>
 
-
-/* If we compile the file for use in ld.so we don't need the feature
-   that getcwd() allocates the buffers itself.  */
-#if IS_IN (rtld)
-# define NO_ALLOCATION	1
-#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.  */
-
-/* Get the code for the generic version.  */
+#if !IS_IN (rtld)
+/* The generic implementation is used for valid paths larger than
+   PATH_MAX (where the syscall returns a positive value with errno
+   set to ENAMETOOLONG).  */
 #define GETCWD_RETURN_TYPE	static char *
 #include <sysdeps/posix/getcwd.c>
+#endif
 
 char *
 __getcwd (char *buf, size_t size)
 {
-  char *path;
-  char *result;
+  char *r = NULL;
+  int len;
 
-#ifndef NO_ALLOCATION
-  size_t alloc_size = size;
-  if (size == 0)
+  if (buf != NULL)
     {
-      if (buf != NULL)
+      if (size == 0)
 	{
 	  __set_errno (EINVAL);
 	  return NULL;
-	}
 
-      alloc_size = MAX (PATH_MAX, __getpagesize ());
-    }
-
-  if (buf == NULL)
-    {
-      path = malloc (alloc_size);
-      if (path == NULL)
-	return NULL;
+	}
+      len = INLINE_SYSCALL_CALL (getcwd, buf, size);
+      if (len > 0)
+	r = buf;
     }
   else
-#else
-# define alloc_size size
-#endif
-    path = buf;
-
-  int retval;
-
-  retval = INLINE_SYSCALL (getcwd, 2, path, alloc_size);
-  if (retval > 0 && path[0] == '/')
-    {
-#ifndef NO_ALLOCATION
-      if (buf == NULL && size == 0)
-	/* Ensure that the buffer is only as large as necessary.  */
-	buf = realloc (path, (size_t) retval);
-
-      if (buf == NULL)
-	/* Either buf was NULL all along, or `realloc' failed but
-	   we still have the original string.  */
-	buf = path;
-#endif
-
-      return buf;
-    }
-
-  /* The system call either cannot handle paths longer than a page
-     or can succeed without returning an absolute path.  Just use the
-     generic implementation right away.  */
-  if (retval >= 0 || errno == ENAMETOOLONG)
     {
-#ifndef NO_ALLOCATION
-      if (buf == NULL && size == 0)
+      char tmp[PATH_MAX];
+      size_t tmps = size == 0 || size > PATH_MAX
+		    ? PATH_MAX : size;
+      len = INLINE_SYSCALL_CALL (getcwd, tmp, tmps);
+      if (len > 0)
 	{
-	  free (path);
-	  path = NULL;
-	}
-#endif
-
-      result = __getcwd_generic (path, size);
+	  /* Allocates a buffer with at least SIZE bytes, even it is larger
+	     than required buffer.  */
+	  r = malloc (size > len ? size : len);
+	  if (r == NULL)
+	    return NULL;
 
-#ifndef NO_ALLOCATION
-      if (result == NULL && buf == NULL && size != 0)
-	free (path);
-#endif
-
-      return result;
+	  memcpy (r, tmp, len);
+	}
     }
 
-  /* It should never happen that the `getcwd' syscall failed because
-     the buffer is too small if we allocated the buffer ourselves
-     large enough.  */
-  assert (errno != ERANGE || buf != NULL || size != 0);
+  if (len > 0 && r[0] == '/')
+    return r;
 
-#ifndef NO_ALLOCATION
-  if (buf == NULL)
-    free (path);
+#if !IS_IN (rtld)
+  if (len >=0 || errno == ENAMETOOLONG)
+    return __getcwd_generic (buf, size);
 #endif
 
   return NULL;