From: Eric Blake Date: Wed, 7 Oct 2009 16:15:33 +0000 (-0600) Subject: areadlinkat: new module X-Git-Tag: v0.1~5351 X-Git-Url: http://erislabs.org.uk/gitweb/?a=commitdiff_plain;h=9d145640e828e0075ab4fa4567e243699ad64f06;p=gnulib.git areadlinkat: new module * lib/at-func.c (FUNC_FAIL): New define. (AT_FUNC_NAME, VALIDATE_FLAG): Use it rather than raw -1. * modules/areadlinkat: New module. * lib/linkat.c (areadlinkat): Move... * lib/areadlinkat.c (areadlinkat): ...to new file. * lib/areadlink.h (areadlinkat): Declare it. * modules/linkat (Depends-on): Add areadlinkat. * MODULES.html.sh (File system functions): Mention it. * modules/areadlinkat-tests: New test. * tests/test-areadlinkat.c: New file. Signed-off-by: Eric Blake --- diff --git a/ChangeLog b/ChangeLog index 9a3148e94..0ac3c88e0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2009-10-07 Eric Blake + areadlinkat: new module + * lib/at-func.c (FUNC_FAIL): New define. + (AT_FUNC_NAME, VALIDATE_FLAG): Use it rather than raw -1. + * modules/areadlinkat: New module. + * lib/linkat.c (areadlinkat): Move... + * lib/areadlinkat.c (areadlinkat): ...to new file. + * lib/areadlink.h (areadlinkat): Declare it. + * modules/linkat (Depends-on): Add areadlinkat. + * MODULES.html.sh (File system functions): Mention it. + * modules/areadlinkat-tests: New test. + * tests/test-areadlinkat.c: New file. + areadlink, areadlink-with-size: add tests * modules/areadlink-tests: New test. * modules/areadlink-with-size-tests: Likewise. diff --git a/MODULES.html.sh b/MODULES.html.sh index ee13eec58..b418ab621 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2448,6 +2448,7 @@ func_all_modules () func_module acl func_module areadlink func_module areadlink-with-size + func_module areadlinkat func_module backupfile func_module canonicalize func_module canonicalize-lgpl diff --git a/lib/areadlink.h b/lib/areadlink.h index 8720ed9a6..580276188 100644 --- a/lib/areadlink.h +++ b/lib/areadlink.h @@ -1,6 +1,7 @@ /* Read symbolic links without size limitation. - Copyright (C) 2001, 2003, 2004, 2007 Free Software Foundation, Inc. + Copyright (C) 2001, 2003, 2004, 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 @@ -21,3 +22,7 @@ extern char *areadlink (char const *filename); extern char *areadlink_with_size (char const *filename, size_t size_hint); + +#if GNULIB_AREADLINKAT +extern char *areadlinkat (int fd, char const *filename); +#endif diff --git a/lib/areadlinkat.c b/lib/areadlinkat.c new file mode 100644 index 000000000..5e8bf9d2a --- /dev/null +++ b/lib/areadlinkat.c @@ -0,0 +1,143 @@ +/* areadlink.c -- readlink 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 Bruno Haible , + and Eric Blake . */ + +#include + +/* Specification. */ +#include "areadlink.h" + +#include +#include +#include +#include +#include +#include + +#ifndef SSIZE_MAX +# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) +#endif + +#if HAVE_READLINKAT + +/* 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 :-), + return NULL and set errno to ENOMEM. */ + +char * +areadlinkat (int fd, char const *filename) +{ + /* The initial buffer size for the link value. A power of 2 + detects arithmetic overflow earlier, but is not required. */ +# define INITIAL_BUF_SIZE 1024 + + /* 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; + } +} + +#else /* !HAVE_READLINKAT */ + +/* It is more efficient to change directories only once and call + areadlink, rather than repeatedly call the replacement + readlinkat. */ + +# define AT_FUNC_NAME areadlinkat +# define AT_FUNC_F1 areadlink +# define AT_FUNC_POST_FILE_PARAM_DECLS /* empty */ +# define AT_FUNC_POST_FILE_ARGS /* empty */ +# 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/lib/at-func.c b/lib/at-func.c index 373b5f6bc..b6aa0fd55 100644 --- a/lib/at-func.c +++ b/lib/at-func.c @@ -30,7 +30,7 @@ if (flag & ~AT_FUNC_USE_F1_COND) \ { \ errno = EINVAL; \ - return -1; \ + return FUNC_FAIL; \ } #else # define CALL_FUNC(F) (AT_FUNC_F1 (F AT_FUNC_POST_FILE_ARGS)) @@ -43,11 +43,18 @@ # define FUNC_RESULT int #endif +#ifdef AT_FUNC_FAIL +# define FUNC_FAIL AT_FUNC_FAIL +#else +# define FUNC_FAIL -1 +#endif + /* Call AT_FUNC_F1 to operate on FILE, which is in the directory open on descriptor FD. If AT_FUNC_USE_F1_COND is defined to a value, AT_FUNC_POST_FILE_PARAM_DECLS must inlude a parameter named flag; call AT_FUNC_F2 if FLAG is 0 or fail if FLAG contains more bits than - AT_FUNC_USE_F1_COND. If possible, do it without changing the + AT_FUNC_USE_F1_COND. Return int and fail with -1 unless AT_FUNC_RESULT + or AT_FUNC_FAIL are defined. If possible, do it without changing the working directory. Otherwise, resort to using save_cwd/fchdir, then AT_FUNC_F?/restore_cwd. If either the save_cwd or the restore_cwd fails, then give a diagnostic and exit nonzero. */ @@ -96,7 +103,7 @@ AT_FUNC_NAME (int fd, char const *file AT_FUNC_POST_FILE_PARAM_DECLS) begin with. */ free_cwd (&saved_cwd); errno = EBADF; - return -1; + return FUNC_FAIL; } if (fchdir (fd) != 0) @@ -104,7 +111,7 @@ AT_FUNC_NAME (int fd, char const *file AT_FUNC_POST_FILE_PARAM_DECLS) saved_errno = errno; free_cwd (&saved_cwd); errno = saved_errno; - return -1; + return FUNC_FAIL; } err = CALL_FUNC (file); @@ -121,3 +128,4 @@ AT_FUNC_NAME (int fd, char const *file AT_FUNC_POST_FILE_PARAM_DECLS) } #undef CALL_FUNC #undef FUNC_RESULT +#undef FUNC_FAIL diff --git a/lib/linkat.c b/lib/linkat.c index e0dd8f286..524ddde6a 100644 --- a/lib/linkat.c +++ b/lib/linkat.c @@ -189,83 +189,6 @@ linkat (int fd1, char const *file1, int fd2, char const *file2, int flag) # undef linkat -/* Read a symlink, like areadlink, but relative to FD. */ - -static char * -areadlinkat (int fd, char const *filename) -{ - /* The initial buffer size for the link value. A power of 2 - detects arithmetic overflow earlier, but is not required. */ -# define INITIAL_BUF_SIZE 1024 - - /* 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; - } -} - /* Create a link. If FILE1 is a symlink, create a hardlink to the canonicalized file. */ diff --git a/modules/areadlinkat b/modules/areadlinkat new file mode 100644 index 000000000..d5712d9cf --- /dev/null +++ b/modules/areadlinkat @@ -0,0 +1,26 @@ +Description: +Reading symbolic links without size limitation, relative to fd. + +Files: +lib/areadlink.h +lib/areadlinkat.c + +Depends-on: +areadlink +stdint +symlinkat + +configure.ac: +gl_MODULE_INDICATOR([areadlinkat]) + +Makefile.am: +lib_SOURCES += areadlinkat.c + +Include: +"areadlink.h" + +License: +GPL + +Maintainer: +Jim Meyering, Eric Blake diff --git a/modules/areadlinkat-tests b/modules/areadlinkat-tests new file mode 100644 index 000000000..ca0adc42e --- /dev/null +++ b/modules/areadlinkat-tests @@ -0,0 +1,14 @@ +Files: +tests/test-areadlink.h +tests/test-areadlinkat.c + +Depends-on: +stdbool +symlink + +configure.ac: + +Makefile.am: +TESTS += test-areadlinkat +check_PROGRAMS += test-areadlinkat +test_areadlinkat_LDADD = $(LDADD) @LIBINTL@ diff --git a/modules/linkat b/modules/linkat index 6b56144dc..0c94227dc 100644 --- a/modules/linkat +++ b/modules/linkat @@ -8,6 +8,7 @@ m4/linkat.m4 Depends-on: areadlink +areadlinkat dirname errno extensions diff --git a/tests/test-areadlink.h b/tests/test-areadlink.h index 1da2e1b7d..07ccf232d 100644 --- a/tests/test-areadlink.h +++ b/tests/test-areadlink.h @@ -66,7 +66,6 @@ test_areadlink (char * (*func) (char const *, size_t), bool print) ASSERT (errno == EINVAL); { /* Too small a guess is okay. */ - size_t len = strlen (BASE "dir"); char *buf = func (BASE "link", 1); ASSERT (buf); ASSERT (strcmp (buf, BASE "dir") == 0); diff --git a/tests/test-areadlinkat.c b/tests/test-areadlinkat.c new file mode 100644 index 000000000..6b0c96c77 --- /dev/null +++ b/tests/test-areadlinkat.c @@ -0,0 +1,98 @@ +/* Tests of areadlinkat. + 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.t" + +#include "test-areadlink.h" + +static int dfd = AT_FDCWD; + +/* Wrapper for testing areadlinkat. */ +static char * +do_areadlinkat (char const *name, size_t ignored _UNUSED_PARAMETER_) +{ + return areadlinkat (dfd, name); +} + +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, false); + dfd = open (".", O_RDONLY); + ASSERT (0 <= dfd); + ASSERT (test_areadlink (do_areadlinkat, 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 (dfd, BASE "link"); + ASSERT (buf); + ASSERT (strcmp (buf, "nowhere") == 0); + free (buf); + errno = 0; + ASSERT (areadlinkat (-1, BASE "link") == NULL); + ASSERT (errno == EBADF); + errno = 0; + ASSERT (areadlinkat (AT_FDCWD, BASE "link") == NULL); + ASSERT (errno == ENOENT); + ASSERT (chdir ("..") == 0); + ASSERT (rmdir (BASE "dir") == 0); + ASSERT (unlink (BASE "link") == 0); + } + + ASSERT (close (dfd) == 0); + return result; +}