From daa06b9967f5f51ec03169000be233b37887287b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Wed, 7 Oct 2009 10:52:15 -0600 Subject: [PATCH] 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 --- ChangeLog | 8 +++ MODULES.html.sh | 1 + lib/areadlink.h | 5 ++ lib/areadlinkat-with-size.c | 133 ++++++++++++++++++++++++++++++++++++ modules/areadlinkat-with-size | 26 +++++++ modules/areadlinkat-with-size-tests | 13 ++++ tests/test-areadlinkat-with-size.c | 98 ++++++++++++++++++++++++++ 7 files changed, 284 insertions(+) create mode 100644 lib/areadlinkat-with-size.c create mode 100644 modules/areadlinkat-with-size create mode 100644 modules/areadlinkat-with-size-tests create mode 100644 tests/test-areadlinkat-with-size.c diff --git a/ChangeLog b/ChangeLog index 7a831c7c4..dedcfc85a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,13 @@ 2009-10-07 Eric Blake + 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. diff --git a/MODULES.html.sh b/MODULES.html.sh index 4f164ef20..0d2c3cb29 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2449,6 +2449,7 @@ func_all_modules () 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 diff --git a/lib/areadlink.h b/lib/areadlink.h index 580276188..f1443f868 100644 --- a/lib/areadlink.h +++ b/lib/areadlink.h @@ -26,3 +26,8 @@ extern char *areadlink_with_size (char const *filename, size_t size_hint); #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 diff --git a/lib/areadlinkat-with-size.c b/lib/areadlinkat-with-size.c new file mode 100644 index 000000000..d1abb5b01 --- /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 . */ + +/* Written by Jim Meyering + and Eric Blake . */ + +#include + +#include "areadlink.h" + +#include +#include +#include +#include +#include +#include + +#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 */ diff --git a/modules/areadlinkat-with-size b/modules/areadlinkat-with-size new file mode 100644 index 000000000..7bd60d465 --- /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 diff --git a/modules/areadlinkat-with-size-tests b/modules/areadlinkat-with-size-tests new file mode 100644 index 000000000..f3d3878f3 --- /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@ diff --git a/tests/test-areadlinkat-with-size.c b/tests/test-areadlinkat-with-size.c new file mode 100644 index 000000000..43715d01f --- /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 . */ + +/* Written by Eric Blake , 2009. */ + +#include + +#include "areadlink.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- 2.11.0