Eric Blake <ebb9 <at> byu.net> writes: > The patch copies from areadlink.c, as well as link_follow earlier in > linkat.c, to create two new fd-relative helpers. For now, I didn't see > any reason to expose them, but areadlinkat may someday be worth making > into a full-blown module.
Further looking shows that: findutils' listfile.c currently uses a home-rolled get_link_name_at which blindly changes the working directory, and calls xalloc_die on allocation failure. Perfect candidate for xreadlinkat, to be more efficient on platforms with native readlinkat. And xreadlinkat implies areadlinkat. coreutils' copy.c currently uses areadlink_with_size for copying/moving symlinks across partitions, but since we want to rewrite this to use fts, we will need an *at variant. So, I've written three new modules: areadlinkat, areadlinkat-with-size, and xreadlinkat; mirroring the three existing modules areadlink, areadlink-with- size, xreadlink. Eric Blake (4): areadlink, areadlink-with-size: add tests areadlinkat: new module xreadlinkat: new module areadlinkat-with-size: new module >From 9d21d0d57b72c90a8f2c1bcdef09eb888e58d71b Mon Sep 17 00:00:00 2001 From: Eric Blake <e...@byu.net> Date: Wed, 7 Oct 2009 09:40:32 -0600 Subject: [PATCH 1/4] areadlink, areadlink-with-size: add tests * modules/areadlink-tests: New test. * modules/areadlink-with-size-tests: Likewise. * tests/test-areadlink.h: New file. * tests/test-areadlink.c: Likewise. * tests/test-areadlink-with-size.c: Likewise. Signed-off-by: Eric Blake <e...@byu.net> --- ChangeLog | 7 +++ modules/areadlink-tests | 13 ++++++ modules/areadlink-with-size-tests | 13 ++++++ tests/test-areadlink-with-size.c | 55 ++++++++++++++++++++++++ tests/test-areadlink.c | 62 +++++++++++++++++++++++++++ tests/test-areadlink.h | 84 +++++++++++++++++++++++++++++++++++++ 6 files changed, 234 insertions(+), 0 deletions(-) create mode 100644 modules/areadlink-tests create mode 100644 modules/areadlink-with-size-tests create mode 100644 tests/test-areadlink-with-size.c create mode 100644 tests/test-areadlink.c create mode 100644 tests/test-areadlink.h diff --git a/ChangeLog b/ChangeLog index f69762a..ebdc931 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2009-10-07 Eric Blake <e...@byu.net> + areadlink, areadlink-with-size: add tests + * modules/areadlink-tests: New test. + * modules/areadlink-with-size-tests: Likewise. + * tests/test-areadlink.h: New file. + * tests/test-areadlink.c: Likewise. + * tests/test-areadlink-with-size.c: Likewise. + utimens: minor optimization * lib/utimens.c (gl_futimens): Use one less system call. * modules/utimens (Depends-on): Add dup2. diff --git a/modules/areadlink-tests b/modules/areadlink-tests new file mode 100644 index 0000000..0e7796e --- /dev/null +++ b/modules/areadlink-tests @@ -0,0 +1,13 @@ +Files: +tests/test-areadlink.h +tests/test-areadlink.c + +Depends-on: +stdbool +symlink + +configure.ac: + +Makefile.am: +TESTS += test-areadlink +check_PROGRAMS += test-areadlink diff --git a/modules/areadlink-with-size-tests b/modules/areadlink-with-size- tests new file mode 100644 index 0000000..9752034 --- /dev/null +++ b/modules/areadlink-with-size-tests @@ -0,0 +1,13 @@ +Files: +tests/test-areadlink.h +tests/test-areadlink-with-size.c + +Depends-on: +stdbool +symlink + +configure.ac: + +Makefile.am: +TESTS += test-areadlink-with-size +check_PROGRAMS += test-areadlink-with-size diff --git a/tests/test-areadlink-with-size.c b/tests/test-areadlink-with-size.c new file mode 100644 index 0000000..2a175a5 --- /dev/null +++ b/tests/test-areadlink-with-size.c @@ -0,0 +1,55 @@ +/* Tests of areadlink_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 <e...@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-areadlink-with-size.t" + +#include "test-areadlink.h" + +int +main () +{ + /* Remove any leftovers from a previous partial run. */ + ASSERT (system ("rm -rf " BASE "*") == 0); + + return test_areadlink (areadlink_with_size, true); +} diff --git a/tests/test-areadlink.c b/tests/test-areadlink.c new file mode 100644 index 0000000..657ea6f --- /dev/null +++ b/tests/test-areadlink.c @@ -0,0 +1,62 @@ +/* Tests of areadlink. + 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 <e...@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-areadlink.t" + +#include "test-areadlink.h" + +/* Wrapper for testing areadlink. */ +static char * +do_areadlink (char const *name, size_t ignored _UNUSED_PARAMETER_) +{ + return areadlink (name); +} + +int +main () +{ + /* Remove any leftovers from a previous partial run. */ + ASSERT (system ("rm -rf " BASE "*") == 0); + + return test_areadlink (do_areadlink, true); +} diff --git a/tests/test-areadlink.h b/tests/test-areadlink.h new file mode 100644 index 0000000..1da2e1b --- /dev/null +++ b/tests/test-areadlink.h @@ -0,0 +1,84 @@ +/* Tests of areadlink and friends. + 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 <e...@byu.net>, 2009. */ + +/* This file is designed to test areadlink(a), + areadlink_with_size(a,b), and areadlinkat(AT_FDCWD,a). FUNC is the + function to test; a length is always supplied, but may be ignored. + Assumes that BASE and ASSERT are already defined, and that + appropriate headers are already included. If PRINT, warn before + skipping symlink tests with status 77. */ + +static int +test_areadlink (char * (*func) (char const *, size_t), bool print) +{ + /* Sanity checks of failures. Mingw lacks symlink, but areadlink can + still distinguish between various errors. */ + errno = 0; + ASSERT (func ("no_such", 1) == NULL); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (func ("no_such/", 1) == NULL); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (func ("", 1) == NULL); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (func (".", 1) == NULL); + ASSERT (errno == EINVAL); + errno = 0; + ASSERT (func ("./", 1) == NULL); + ASSERT (errno == EINVAL); + ASSERT (close (creat (BASE "file", 0600)) == 0); + errno = 0; + ASSERT (func (BASE "file", 1) == NULL); + ASSERT (errno == EINVAL); + errno = 0; + ASSERT (func (BASE "file/", 1) == NULL); + ASSERT (errno == ENOTDIR); + ASSERT (unlink (BASE "file") == 0); + + /* Now test actual symlinks. */ + if (symlink (BASE "dir", BASE "link")) + { + if (print) + fputs ("skipping test: symlinks not supported on this filesystem\n", + stderr); + return 77; + } + ASSERT (mkdir (BASE "dir", 0700) == 0); + errno = 0; + ASSERT (func (BASE "link/", 1) == NULL); + 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); + free (buf); + /* Too large a guess is okay. */ + buf = func (BASE "link", 10000000); + ASSERT (buf); + ASSERT (strcmp (buf, BASE "dir") == 0); + free (buf); + } + ASSERT (rmdir (BASE "dir") == 0); + ASSERT (unlink (BASE "link") == 0); + + return 0; +} -- 1.6.4.2 >From 22e25b18f419a637370b2517b945adf70bc2b21e Mon Sep 17 00:00:00 2001 From: Eric Blake <e...@byu.net> Date: Wed, 7 Oct 2009 10:15:33 -0600 Subject: [PATCH 2/4] 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): Use areadlinkat, not areadlink. * MODULES.html.sh (File system functions): Mention it. * modules/areadlinkat-tests: New test. * tests/test-areadlinkat.c: New file. Signed-off-by: Eric Blake <e...@byu.net> --- ChangeLog | 12 ++++ MODULES.html.sh | 1 + lib/areadlink.h | 7 ++- lib/areadlinkat.c | 143 +++++++++++++++++++++++++++++++++++++++++++++ lib/at-func.c | 16 ++++- lib/linkat.c | 77 ------------------------ modules/areadlinkat | 26 ++++++++ modules/areadlinkat-tests | 14 +++++ modules/linkat | 2 +- tests/test-areadlink.h | 1 - tests/test-areadlinkat.c | 98 +++++++++++++++++++++++++++++++ 11 files changed, 313 insertions(+), 84 deletions(-) create mode 100644 lib/areadlinkat.c create mode 100644 modules/areadlinkat create mode 100644 modules/areadlinkat-tests create mode 100644 tests/test-areadlinkat.c diff --git a/ChangeLog b/ChangeLog index ebdc931..34a5c6f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2009-10-07 Eric Blake <e...@byu.net> + 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): Use areadlinkat, not areadlink. + * 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 ee13eec..d5aa421 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2447,6 +2447,7 @@ func_all_modules () func_begin_table func_module acl func_module areadlink + func_module areadlinkat func_module areadlink-with-size func_module backupfile func_module canonicalize diff --git a/lib/areadlink.h b/lib/areadlink.h index 8720ed9..5802761 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 0000000..5e8bf9d --- /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 <http://www.gnu.org/licenses/>. */ + +/* Written by Jim Meyering <j...@meyering.net>, + and Bruno Haible <br...@clisp.org>, + and Eric Blake <e...@byu.net>. */ + +#include <config.h> + +/* Specification. */ +#include "areadlink.h" + +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> + +#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 373b5f6..b6aa0fd 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 e0dd8f2..524ddde 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 0000000..d5712d9 --- /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 0000000..ca0adc4 --- /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 6b56144..23ebad4 100644 --- a/modules/linkat +++ b/modules/linkat @@ -7,7 +7,7 @@ lib/linkat.c m4/linkat.m4 Depends-on: -areadlink +areadlinkat dirname errno extensions diff --git a/tests/test-areadlink.h b/tests/test-areadlink.h index 1da2e1b..07ccf23 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 0000000..6b0c96c --- /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 <http://www.gnu.org/licenses/>. */ + +/* Written by Eric Blake <e...@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.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; +} -- 1.6.4.2 >From 1267acaab66f083e966d7011bf2f7191f5883b8f Mon Sep 17 00:00:00 2001 From: Eric Blake <e...@byu.net> Date: Wed, 7 Oct 2009 10:39:29 -0600 Subject: [PATCH 3/4] xreadlinkat: new module * modules/xreadlinkat: New module. * lib/xreadlinkat.c (xreadlinkat): New file. * lib/xreadlink.h (xreadlinkat): Declare it. * MODULES.html.sh (File system functions): Mention it. Signed-off-by: Eric Blake <e...@byu.net> --- ChangeLog | 6 ++++++ MODULES.html.sh | 1 + lib/xreadlink.h | 7 ++++++- lib/xreadlinkat.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ modules/xreadlinkat | 25 +++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 1 deletions(-) create mode 100644 lib/xreadlinkat.c create mode 100644 modules/xreadlinkat diff --git a/ChangeLog b/ChangeLog index 34a5c6f..23c2a28 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2009-10-07 Eric Blake <e...@byu.net> + xreadlinkat: new module + * modules/xreadlinkat: New module. + * lib/xreadlinkat.c (xreadlinkat): New file. + * lib/xreadlink.h (xreadlinkat): Declare it. + * MODULES.html.sh (File system functions): Mention it. + areadlinkat: new module * lib/at-func.c (FUNC_FAIL): New define. (AT_FUNC_NAME, VALIDATE_FLAG): Use it rather than raw -1. diff --git a/MODULES.html.sh b/MODULES.html.sh index d5aa421..db0e07f 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2495,6 +2495,7 @@ func_all_modules () func_module write-any-file func_module xgetcwd func_module xreadlink + func_module xreadlinkat func_end_table element="File system as inode set" diff --git a/lib/xreadlink.h b/lib/xreadlink.h index 3eda90f..2a27689 100644 --- a/lib/xreadlink.h +++ b/lib/xreadlink.h @@ -1,6 +1,7 @@ /* Reading 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 @@ -20,3 +21,7 @@ #include <stddef.h> extern char *xreadlink (char const *filename); + +#if GNULIB_XREADLINKAT +extern char *xreadlinkat (int fd, char const *filename); +#endif diff --git a/lib/xreadlinkat.c b/lib/xreadlinkat.c new file mode 100644 index 0000000..471fc2c --- /dev/null +++ b/lib/xreadlinkat.c @@ -0,0 +1,46 @@ +/* xreadlinkat.c -- readlink wrapper to return the link name in malloc'd storage + + 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 <j...@meyering.net>, + and Bruno Haible <br...@clisp.org>, + and Eric Blake <e...@byu.net>. */ + +#include <config.h> + +/* Specification. */ +#include "xreadlink.h" + +#include <errno.h> + +#include "areadlink.h" +#include "xalloc.h" + +/* 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 :-), + give a diagnostic and exit. */ + +char * +xreadlinkat (int fd, char const *filename) +{ + char *result = areadlinkat (fd, filename); + if (result == NULL && errno == ENOMEM) + xalloc_die (); + return result; +} diff --git a/modules/xreadlinkat b/modules/xreadlinkat new file mode 100644 index 0000000..81f87de --- /dev/null +++ b/modules/xreadlinkat @@ -0,0 +1,25 @@ +Description: +Reading symbolic links without size limitation, relative to fd. + +Files: +lib/xreadlink.h +lib/xreadlinkat.c + +Depends-on: +areadlinkat +xalloc + +configure.ac: +gl_MODULE_INDICATOR([xreadlinkat]) + +Makefile.am: +lib_SOURCES += xreadlinkat.c + +Include: +"xreadlink.h" + +License: +GPL + +Maintainer: +Jim Meyering, Eric Blake -- 1.6.4.2 >From 3ebc114c534da8ce3b15143f1826438bf04e7b94 Mon Sep 17 00:00:00 2001 From: Eric Blake <e...@byu.net> Date: Wed, 7 Oct 2009 10:52:15 -0600 Subject: [PATCH 4/4] 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 <e...@byu.net> --- ChangeLog | 8 ++ MODULES.html.sh | 3 +- 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, 285 insertions(+), 1 deletions(-) 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 23c2a28..a985132 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,13 @@ 2009-10-07 Eric Blake <e...@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. diff --git a/MODULES.html.sh b/MODULES.html.sh index db0e07f..0d2c3cb 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2447,8 +2447,9 @@ func_all_modules () func_begin_table func_module acl func_module areadlink - func_module areadlinkat 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 5802761..f1443f8 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 0000000..d1abb5b --- /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 <j...@meyering.net> + and Eric Blake <e...@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 */ diff --git a/modules/areadlinkat-with-size b/modules/areadlinkat-with-size new file mode 100644 index 0000000..7bd60d4 --- /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 0000000..f3d3878 --- /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 0000000..43715d0 --- /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 <e...@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; +} -- 1.6.4.2