changeset 14522:39a9dbe4bfe3

areadlink, areadlinkat: rewrite in terms of careadlinkat * lib/areadlink.c, lib/areadlinkat.c: Include careadlinkat.h instead of errno.h, limits.h, stdint.h, stdlib.h, string.h, unistd.h. (SSIZE_MAX, INITIAL_BUF_SIZE): Remove. (malloc, realloc): Remove #undefs. (areadlink, areadlinkat): Rewrite in terms of careadlinkat. * modules/areadlink (Depends-on): Add careadlinkat. Remove readlink, ssize_t, stdint, unistd. * modules/areadlinkat (Depends-on): Add careadlinkat. Remove areadlink, stdint. careadlinkat: new module * lib/allocator.h, lib/careadlinkat.h, lib/careadlinkat.c: * modules/careadlinkat: New files, written by me with a review and feedback from Ben Pfaff in <http://lists.gnu.org/archive/html/bug-gnulib/2011-04/msg00008.html>.
author Paul Eggert <eggert@cs.ucla.edu>
date Tue, 05 Apr 2011 09:52:32 -0700
parents bcaffcfa06a0
children 0ba2e3063130
files ChangeLog lib/allocator.h lib/areadlink.c lib/areadlinkat.c lib/careadlinkat.c lib/careadlinkat.h modules/areadlink modules/areadlinkat modules/careadlinkat
diffstat 9 files changed, 349 insertions(+), 184 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2011-04-05  Paul Eggert  <eggert@cs.ucla.edu>
+
+	areadlink, areadlinkat: rewrite in terms of careadlinkat
+	* lib/areadlink.c, lib/areadlinkat.c: Include careadlinkat.h
+	instead of errno.h, limits.h, stdint.h, stdlib.h, string.h, unistd.h.
+	(SSIZE_MAX, INITIAL_BUF_SIZE): Remove.
+	(malloc, realloc): Remove #undefs.
+	(areadlink, areadlinkat): Rewrite in terms of careadlinkat.
+	* modules/areadlink (Depends-on): Add careadlinkat.  Remove
+	readlink, ssize_t, stdint, unistd.
+	* modules/areadlinkat (Depends-on): Add careadlinkat.  Remove
+	areadlink, stdint.
+
+	careadlinkat: new module
+	* lib/allocator.h, lib/careadlinkat.h, lib/careadlinkat.c:
+	* modules/careadlinkat: New files, written by me with
+	a review and feedback from Ben Pfaff in
+	<http://lists.gnu.org/archive/html/bug-gnulib/2011-04/msg00008.html>.
+
 2011-04-01  Bruno Haible  <bruno@clisp.org>
 
 	wmemchr, wcschr, wcsrchr, wcspbrk, wcsstr: Avoid errors in C++ mode.
new file mode 100644
--- /dev/null
+++ b/lib/allocator.h
@@ -0,0 +1,53 @@
+/* Memory allocators such as malloc+free.
+
+   Copyright (C) 2011 Free Software Foundation, Inc.
+
+   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 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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert.  */
+
+#ifndef _GL_ALLOCATOR_H
+
+#include <stddef.h>
+
+/* An object describing a memory allocator family.  */
+
+struct allocator
+{
+  /* Do not use GCC attributes such as __attribute__ ((malloc)) with
+     the function types pointed at by these members, because these
+     attributes do not work with pointers to functions.  See
+     <http://lists.gnu.org/archive/html/bug-gnulib/2011-04/msg00007.html>.  */
+
+  /* Call MALLOC to allocate memory, like 'malloc'.  On failure MALLOC
+     should return NULL, though not necessarily set errno.  When given
+     a zero size it may return NULL even if successful.  */
+  void *(*malloc) (size_t);
+
+  /* If nonnull, call REALLOC to reallocate memory, like 'realloc'.
+     On failure REALLOC should return NULL, though not necessarily set
+     errno.  When given a zero size it may return NULL even if
+     successful.  */
+  void *(*realloc) (void *, size_t);
+
+  /* Call FREE to free memory, like 'free'.  */
+  void (*free) (void *);
+
+  /* If nonnull, call DIE if MALLOC or REALLOC fails.  DIE should not
+     return.  DIE can be used by code that detects memory overflow
+     while calculating sizes to be passed to MALLOC or REALLOC.  */
+  void (*die) (void);
+};
+
+#endif
--- a/lib/areadlink.c
+++ b/lib/areadlink.c
@@ -24,108 +24,16 @@
 /* Specification.  */
 #include "areadlink.h"
 
-#include <errno.h>
-#include <limits.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#ifndef SSIZE_MAX
-# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
-#endif
-
-/* Use the system functions, not the gnulib overrides in this file.  */
-#undef malloc
-#undef realloc
-
-/* The initial buffer size for the link value.  A power of 2
-   detects arithmetic overflow earlier, but is not required.  */
-enum {
-  INITIAL_BUF_SIZE = 1024
-};
+#include "careadlinkat.h"
 
 /* Call readlink to get the symbolic link value of FILENAME.
    Return a pointer to that NUL-terminated string in malloc'd storage.
    If readlink fails, return NULL and set errno.
-   If realloc fails, or if the link value is longer than SIZE_MAX :-),
+   If allocation fails, or if the link value is longer than SIZE_MAX :-),
    return NULL and set errno to ENOMEM.  */
 
 char *
 areadlink (char const *filename)
 {
-  /* Allocate the initial buffer on the stack.  This way, in the common
-     case of a symlink of small size, we get away with a single small malloc()
-     instead of a big malloc() followed by a shrinking realloc().  */
-  char initial_buf[INITIAL_BUF_SIZE];
-
-  char *buffer = initial_buf;
-  size_t buf_size = sizeof initial_buf;
-
-  while (1)
-    {
-      /* Attempt to read the link into the current buffer.  */
-      ssize_t link_length = readlink (filename, buffer, buf_size);
-
-      /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
-         with errno == ERANGE if the buffer is too small.  */
-      if (link_length < 0 && errno != ERANGE)
-        {
-          if (buffer != initial_buf)
-            {
-              int saved_errno = errno;
-              free (buffer);
-              errno = saved_errno;
-            }
-          return NULL;
-        }
-
-      if ((size_t) link_length < buf_size)
-        {
-          buffer[link_length++] = '\0';
-
-          /* Return it in a chunk of memory as small as possible.  */
-          if (buffer == initial_buf)
-            {
-              buffer = (char *) malloc (link_length);
-              if (buffer == NULL)
-                {
-                  /* It's easier to set errno to ENOMEM than to rely on the
-                     'malloc-posix' gnulib module.  */
-                  errno = ENOMEM;
-                  return NULL;
-                }
-              memcpy (buffer, initial_buf, link_length);
-            }
-          else
-            {
-              /* Shrink buffer before returning it.  */
-              if ((size_t) link_length < buf_size)
-                {
-                  char *smaller_buffer = (char *) realloc (buffer, link_length);
-
-                  if (smaller_buffer != NULL)
-                    buffer = smaller_buffer;
-                }
-            }
-          return buffer;
-        }
-
-      if (buffer != initial_buf)
-        free (buffer);
-      buf_size *= 2;
-      if (SSIZE_MAX < buf_size || (SIZE_MAX / 2 < SSIZE_MAX && buf_size == 0))
-        {
-          errno = ENOMEM;
-          return NULL;
-        }
-      buffer = (char *) malloc (buf_size);
-      if (buffer == NULL)
-        {
-          /* It's easier to set errno to ENOMEM than to rely on the
-             'malloc-posix' gnulib module.  */
-          errno = ENOMEM;
-          return NULL;
-        }
-    }
+  return careadlinkat (AT_FDCWD, filename, NULL, 0, NULL, careadlinkatcwd);
 }
--- a/lib/areadlinkat.c
+++ b/lib/areadlinkat.c
@@ -25,101 +25,21 @@
 /* Specification.  */
 #include "areadlink.h"
 
-#include <errno.h>
-#include <limits.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#ifndef SSIZE_MAX
-# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
-#endif
+#include "careadlinkat.h"
 
 #if HAVE_READLINKAT
 
-/* The initial buffer size for the link value.  A power of 2
-   detects arithmetic overflow earlier, but is not required.  */
-enum {
-  INITIAL_BUF_SIZE = 1024
-};
-
 /* Call readlinkat to get the symbolic link value of FILENAME relative to FD.
    Return a pointer to that NUL-terminated string in malloc'd storage.
    If readlinkat fails, return NULL and set errno (although failure to
    change directory will issue a diagnostic and exit).
-   If realloc fails, or if the link value is longer than SIZE_MAX :-),
+   If allocation fails, or if the link value is longer than SIZE_MAX :-),
    return NULL and set errno to ENOMEM.  */
 
 char *
 areadlinkat (int fd, char const *filename)
 {
-  /* Allocate the initial buffer on the stack.  This way, in the common
-     case of a symlink of small size, we get away with a single small malloc()
-     instead of a big malloc() followed by a shrinking realloc().  */
-  char initial_buf[INITIAL_BUF_SIZE];
-
-  char *buffer = initial_buf;
-  size_t buf_size = sizeof initial_buf;
-
-  while (1)
-    {
-      /* Attempt to read the link into the current buffer.  */
-       ssize_t link_length = readlinkat (fd, filename, buffer, buf_size);
-
-      /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
-         with errno == ERANGE if the buffer is too small.  */
-      if (link_length < 0 && errno != ERANGE)
-        {
-          if (buffer != initial_buf)
-            {
-              int saved_errno = errno;
-              free (buffer);
-              errno = saved_errno;
-            }
-          return NULL;
-        }
-
-      if ((size_t) link_length < buf_size)
-        {
-          buffer[link_length++] = '\0';
-
-          /* Return it in a chunk of memory as small as possible.  */
-          if (buffer == initial_buf)
-            {
-              buffer = (char *) malloc (link_length);
-              if (buffer == NULL)
-                /* errno is ENOMEM.  */
-                return NULL;
-              memcpy (buffer, initial_buf, link_length);
-            }
-          else
-            {
-              /* Shrink buffer before returning it.  */
-              if ((size_t) link_length < buf_size)
-                {
-                  char *smaller_buffer = (char *) realloc (buffer, link_length);
-
-                  if (smaller_buffer != NULL)
-                    buffer = smaller_buffer;
-                }
-            }
-          return buffer;
-        }
-
-      if (buffer != initial_buf)
-        free (buffer);
-      buf_size *= 2;
-      if (SSIZE_MAX < buf_size || (SIZE_MAX / 2 < SSIZE_MAX && buf_size == 0))
-        {
-          errno = ENOMEM;
-          return NULL;
-        }
-      buffer = (char *) malloc (buf_size);
-      if (buffer == NULL)
-        /* errno is ENOMEM.  */
-        return NULL;
-    }
+  return careadlinkat (fd, filename, NULL, 0, NULL, readlinkat);
 }
 
 #else /* !HAVE_READLINKAT */
new file mode 100644
--- /dev/null
+++ b/lib/careadlinkat.c
@@ -0,0 +1,175 @@
+/* Read symbolic links into a buffer without size limitation, relative to fd.
+
+   Copyright (C) 2001, 2003-2004, 2007, 2009-2011 Free Software Foundation,
+   Inc.
+
+   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 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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert, Bruno Haible, and Jim Meyering.  */
+
+#include <config.h>
+
+#include "careadlinkat.h"
+
+#include "allocator.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Use the system functions, not the gnulib overrides, because this
+   module does not depend on GNU or POSIX semantics.  */
+#undef malloc
+#undef realloc
+
+/* Define this independently so that stdint.h is not a prerequisite.  */
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+#if ! HAVE_READLINKAT
+/* Ignore FD.  Get the symbolic link value of FILENAME and put it into
+   BUFFER, with size BUFFER_SIZE.  This function acts like readlink
+   but has readlinkat's signature.  */
+ssize_t
+careadlinkatcwd (int fd, char const *filename, char *buffer,
+                 size_t buffer_size)
+{
+  (void) fd;
+  return readlink (filename, buffer, buffer_size);
+}
+#endif
+
+/* A standard allocator.  For now, only careadlinkat needs this, but
+   perhaps it should be moved to the allocator module.  */
+static struct allocator const standard_allocator =
+  { malloc, realloc, free, NULL };
+
+/* Assuming the current directory is FD, get the symbolic link value
+   of FILENAME as a null-terminated string and put it into a buffer.
+   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
+   working directory, as in openat.
+
+   If the link is small enough to fit into BUFFER put it there.
+   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
+   if BUFFER_SIZE is zero.
+
+   If the link is not small, put it into a dynamically allocated
+   buffer managed by ALLOC.  It is the caller's responsibility to free
+   the returned value if it is nonnull and is not BUFFER.  A null
+   ALLOC stands for the standard allocator.
+
+   The PREADLINKAT function specifies how to read links.
+
+   If successful, return the buffer address; otherwise return NULL and
+   set errno.  */
+
+char *
+careadlinkat (int fd, char const *filename,
+              char *buffer, size_t buffer_size,
+              struct allocator const *alloc,
+              ssize_t (*preadlinkat) (int, char const *, char *, size_t))
+{
+  char *buf;
+  size_t buf_size;
+  size_t buf_size_max =
+    SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
+  char stack_buf[1024];
+
+  if (! alloc)
+    alloc = &standard_allocator;
+
+  if (! buffer_size)
+    {
+      /* Allocate the initial buffer on the stack.  This way, in the
+         common case of a symlink of small size, we get away with a
+         single small malloc() instead of a big malloc() followed by a
+         shrinking realloc().  */
+      buffer = stack_buf;
+      buffer_size = sizeof stack_buf;
+    }
+
+  buf = buffer;
+  buf_size = buffer_size;
+
+  do
+    {
+      /* Attempt to read the link into the current buffer.  */
+      ssize_t link_length = preadlinkat (fd, filename, buf, buf_size);
+      size_t link_size;
+      if (link_length < 0)
+        {
+          /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
+             with errno == ERANGE if the buffer is too small.  */
+          int readlinkat_errno = errno;
+          if (readlinkat_errno != ERANGE)
+            {
+              if (buf != buffer)
+                {
+                  alloc->free (buf);
+                  errno = readlinkat_errno;
+                }
+              return NULL;
+            }
+        }
+
+      link_size = link_length;
+
+      if (link_size < buf_size)
+        {
+          buf[link_size++] = '\0';
+
+          if (buf == stack_buf)
+            {
+              char *b = (char *) alloc->malloc (link_size);
+              if (! b)
+                break;
+              memcpy (b, buf, link_size);
+              buf = b;
+            }
+          else if (link_size < buf_size && buf != buffer && alloc->realloc)
+            {
+              /* Shrink BUF before returning it.  */
+              char *b = (char *) alloc->realloc (buf, link_size);
+              if (b)
+                buf = b;
+            }
+
+          return buf;
+        }
+
+      if (buf != buffer)
+        alloc->free (buf);
+
+      if (buf_size <= buf_size_max / 2)
+        buf_size *= 2;
+      else if (buf_size < buf_size_max)
+        buf_size = buf_size_max;
+      else
+        break;
+      buf = (char *) alloc->malloc (buf_size);
+    }
+  while (buf);
+
+  if (alloc->die)
+    alloc->die ();
+  errno = ENOMEM;
+  return NULL;
+}
new file mode 100644
--- /dev/null
+++ b/lib/careadlinkat.h
@@ -0,0 +1,67 @@
+/* Read symbolic links into a buffer without size limitation, relative to fd.
+
+   Copyright (C) 2011 Free Software Foundation, Inc.
+
+   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 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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert, Bruno Haible, and Jim Meyering.  */
+
+#ifndef _GL_CAREADLINKAT_H
+
+#include <fcntl.h>
+#include <unistd.h>
+
+struct allocator;
+
+/* Assuming the current directory is FD, get the symbolic link value
+   of FILENAME as a null-terminated string and put it into a buffer.
+   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
+   working directory, as in openat.
+
+   If the link is small enough to fit into BUFFER put it there.
+   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
+   if BUFFER_SIZE is zero.
+
+   If the link is not small, put it into a dynamically allocated
+   buffer managed by ALLOC.  It is the caller's responsibility to free
+   the returned value if it is nonnull and is not BUFFER.
+
+   The PREADLINKAT function specifies how to read links.
+
+   If successful, return the buffer address; otherwise return NULL and
+   set errno.  */
+
+char *careadlinkat (int fd, char const *filename,
+                    char *buffer, size_t buffer_size,
+                    struct allocator const *alloc,
+                    ssize_t (*preadlinkat) (int, char const *,
+                                            char *, size_t));
+
+/* Suitable values for careadlinkat's FD and PREADLINKAT arguments,
+   when doing a plain readlink.  */
+#if HAVE_READLINKAT
+# define careadlinkatcwd readlinkat
+#else
+/* Define AT_FDCWD independently, so that the careadlinkat module does
+   not depend on the fcntl-h module.  The value does not matter, since
+   careadlinkatcwd ignores it, but we might as well use the same value
+   as fcntl-h.  */
+# ifndef AT_FDCWD
+#  define AT_FDCWD (-3041965)
+# endif
+ssize_t careadlinkatcwd (int fd, char const *filename,
+                         char *buffer, size_t buffer_size);
+#endif
+
+#endif /* _GL_CAREADLINKAT_H */
--- a/modules/areadlink
+++ b/modules/areadlink
@@ -6,10 +6,7 @@
 lib/areadlink.c
 
 Depends-on:
-readlink
-ssize_t
-stdint
-unistd
+careadlinkat
 
 configure.ac:
 
--- a/modules/areadlinkat
+++ b/modules/areadlinkat
@@ -6,8 +6,7 @@
 lib/areadlinkat.c
 
 Depends-on:
-areadlink
-stdint
+careadlinkat
 readlinkat
 
 configure.ac:
new file mode 100644
--- /dev/null
+++ b/modules/careadlinkat
@@ -0,0 +1,27 @@
+Description:
+Read symbolic links into a buffer without size limitation, relative to fd.
+
+Files:
+lib/careadlinkat.c
+lib/careadlinkat.h
+lib/allocator.h
+
+Depends-on:
+readlink
+ssize_t
+unistd
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([readlinkat])
+
+Makefile.am:
+lib_SOURCES += careadlinkat.c
+
+Include:
+"careadlinkat.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+Paul Eggert, Bruno Haible, Jim Meyering