changeset 12138:e86f65584aae

areadlinkat-with-size: new module * modules/areadlinkat-with-size: New module. * lib/areadlinkat-with-size.c (areadlinkat_with_size): New file. * lib/areadlink.h (areadlinkat): Declare it. * MODULES.html.sh (File system functions): Mention it. * modules/areadlinkat-with-size-tests: New test. * tests/test-areadlinkat-with-size.c: New file. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Wed, 07 Oct 2009 10:52:15 -0600
parents 581348c45fcc
children 66ce99c59da1
files ChangeLog MODULES.html.sh lib/areadlink.h lib/areadlinkat-with-size.c modules/areadlinkat-with-size modules/areadlinkat-with-size-tests tests/test-areadlinkat-with-size.c
diffstat 7 files changed, 284 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2009-10-07  Eric Blake  <ebb9@byu.net>
 
+	areadlinkat-with-size: new module
+	* modules/areadlinkat-with-size: New module.
+	* lib/areadlinkat-with-size.c (areadlinkat_with_size): New file.
+	* lib/areadlink.h (areadlinkat): Declare it.
+	* MODULES.html.sh (File system functions): Mention it.
+	* modules/areadlinkat-with-size-tests: New test.
+	* tests/test-areadlinkat-with-size.c: New file.
+
 	xreadlinkat: new module
 	* modules/xreadlinkat: New module.
 	* lib/xreadlinkat.c (xreadlinkat): New file.
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2449,6 +2449,7 @@
   func_module areadlink
   func_module areadlink-with-size
   func_module areadlinkat
+  func_module areadlinkat-with-size
   func_module backupfile
   func_module canonicalize
   func_module canonicalize-lgpl
--- a/lib/areadlink.h
+++ b/lib/areadlink.h
@@ -26,3 +26,8 @@
 #if GNULIB_AREADLINKAT
 extern char *areadlinkat (int fd, char const *filename);
 #endif
+
+#if GNULIB_AREADLINKAT_WITH_SIZE
+extern char *areadlinkat_with_size (int fd, char const *filename,
+                                    size_t size_hint);
+#endif
new file mode 100644
--- /dev/null
+++ b/lib/areadlinkat-with-size.c
@@ -0,0 +1,133 @@
+/* readlinkat wrapper to return the link name in malloc'd storage.
+   Unlike xreadlinkat, only call exit on failure to change directory.
+
+   Copyright (C) 2001, 2003-2007, 2009 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 Jim Meyering <jim@meyering.net>
+   and Eric Blake <ebb9@byu.net>.  */
+
+#include <config.h>
+
+#include "areadlink.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#if HAVE_READLINKAT
+
+# ifndef SSIZE_MAX
+#  define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+# endif
+
+/* SYMLINK_MAX is used only for an initial memory-allocation sanity
+   check, so it's OK to guess too small on hosts where there is no
+   arbitrary limit to symbolic link length.  */
+# ifndef SYMLINK_MAX
+#  define SYMLINK_MAX 1024
+# endif
+
+# define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
+
+/* Call readlinkat to get the symbolic link value of FILE, relative to FD.
+   SIZE is a hint as to how long the link is expected to be;
+   typically it is taken from st_size.  It need not be correct.
+   Return a pointer to that NUL-terminated string in malloc'd storage.
+   If readlinkat fails, malloc fails, or if the link value is longer
+   than SSIZE_MAX, return NULL (caller may use errno to diagnose).
+   However, failure to change directory during readlinkat will issue
+   a diagnostic and exit.  */
+
+char *
+areadlinkat_with_size (int fd, char const *file, size_t size)
+{
+  /* Some buggy file systems report garbage in st_size.  Defend
+     against them by ignoring outlandish st_size values in the initial
+     memory allocation.  */
+  size_t symlink_max = SYMLINK_MAX;
+  size_t INITIAL_LIMIT_BOUND = 8 * 1024;
+  size_t initial_limit = (symlink_max < INITIAL_LIMIT_BOUND
+                          ? symlink_max + 1
+                          : INITIAL_LIMIT_BOUND);
+
+  /* The initial buffer size for the link value.  */
+  size_t buf_size = size < initial_limit ? size + 1 : initial_limit;
+
+  while (1)
+    {
+      ssize_t r;
+      size_t link_length;
+      char *buffer = malloc (buf_size);
+
+      if (buffer == NULL)
+        return NULL;
+      r = readlinkat (fd, file, buffer, buf_size);
+      link_length = r;
+
+      /* 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 (r < 0 && errno != ERANGE)
+        {
+          int saved_errno = errno;
+          free (buffer);
+          errno = saved_errno;
+          return NULL;
+        }
+
+      if (link_length < buf_size)
+        {
+          buffer[link_length] = 0;
+          return buffer;
+        }
+
+      free (buffer);
+      if (buf_size <= MAXSIZE / 2)
+        buf_size *= 2;
+      else if (buf_size < MAXSIZE)
+        buf_size = MAXSIZE;
+      else
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+    }
+}
+
+#else /* !HAVE_READLINKAT */
+
+
+/* It is more efficient to change directories only once and call
+   areadlink_with_size, rather than repeatedly call the replacement
+   readlinkat.  */
+
+# define AT_FUNC_NAME areadlinkat_with_size
+# define AT_FUNC_F1 areadlink_with_size
+# define AT_FUNC_POST_FILE_PARAM_DECLS , size_t size
+# define AT_FUNC_POST_FILE_ARGS        , size
+# define AT_FUNC_RESULT char *
+# define AT_FUNC_FAIL NULL
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+# undef AT_FUNC_RESULT
+# undef AT_FUNC_FAIL
+
+#endif /* !HAVE_READLINKAT */
new file mode 100644
--- /dev/null
+++ b/modules/areadlinkat-with-size
@@ -0,0 +1,26 @@
+Description:
+Read a symbolic link, without size limitations, relative to fd.
+
+Files:
+lib/areadlink.h
+lib/areadlinkat-with-size.c
+
+Depends-on:
+areadlink-with-size
+stdint
+symlinkat
+
+configure.ac:
+gl_MODULE_INDICATOR([areadlinkat-with-size])
+
+Makefile.am:
+lib_SOURCES += areadlinkat-with-size.c
+
+Include:
+"areadlink.h"
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
new file mode 100644
--- /dev/null
+++ b/modules/areadlinkat-with-size-tests
@@ -0,0 +1,13 @@
+Files:
+tests/test-areadlink.h
+tests/test-areadlinkat-with-size.c
+
+Depends-on:
+stdbool
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-areadlinkat-with-size
+check_PROGRAMS += test-areadlinkat-with-size
+test_areadlinkat_with_size_LDADD = $(LDADD) @LIBINTL@
new file mode 100644
--- /dev/null
+++ b/tests/test-areadlinkat-with-size.c
@@ -0,0 +1,98 @@
+/* Tests of areadlinkat_with_size.
+   Copyright (C) 2009 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 Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include "areadlink.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-areadlinkat-with-size.t"
+
+#include "test-areadlink.h"
+
+static int dfd = AT_FDCWD;
+
+/* Wrapper for testing areadlinkat_with_size.  */
+static char *
+do_areadlinkat_with_size (char const *name, size_t size)
+{
+  return areadlinkat_with_size (dfd, name, size);
+}
+
+int
+main ()
+{
+  int result;
+
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Basic tests.  */
+  result = test_areadlink (do_areadlinkat_with_size, false);
+  dfd = open (".", O_RDONLY);
+  ASSERT (0 <= dfd);
+  ASSERT (test_areadlink (do_areadlinkat_with_size, false) == result);
+
+  /* Relative tests.  */
+  if (result == 77)
+    fputs ("skipping test: symlinks not supported on this filesystem\n",
+           stderr);
+  else
+    {
+      char *buf;
+      ASSERT (symlink ("nowhere", BASE "link") == 0);
+      ASSERT (mkdir (BASE "dir", 0700) == 0);
+      ASSERT (chdir (BASE "dir") == 0);
+      buf = areadlinkat_with_size (dfd, BASE "link", strlen (BASE "link"));
+      ASSERT (buf);
+      ASSERT (strcmp (buf, "nowhere") == 0);
+      free (buf);
+      errno = 0;
+      ASSERT (areadlinkat_with_size (-1, BASE "link", 1) == NULL);
+      ASSERT (errno == EBADF);
+      errno = 0;
+      ASSERT (areadlinkat_with_size (AT_FDCWD, BASE "link", 1) == NULL);
+      ASSERT (errno == ENOENT);
+      ASSERT (chdir ("..") == 0);
+      ASSERT (rmdir (BASE "dir") == 0);
+      ASSERT (unlink (BASE "link") == 0);
+    }
+
+  ASSERT (close (dfd) == 0);
+  return result;
+}