# HG changeset patch # User Eric Blake # Date 1252172955 21600 # Node ID 55cb5282dd847649fe4435c83b1d99dd4b72891e # Parent 403b7eb1cb8882d1506e777bc4401c24e4d95db5 symlinkat: new module * modules/symlinkat: New file. * lib/symlinkat.c: Likewise. * m4/symlinkat.m4 (gl_FUNC_SYMLINKAT): Likewise. * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses. * modules/unistd (Makefile.am): Use them. * lib/unistd.in.h (symlinkat, readlinkat): Declare them. (faccessat) [GNULIB_POSIXCHECK]: Fix typo. * lib/at-func.c (FUNC_RESULT): New macro, defaulting to int. * MODULES.html.sh (File system functions): Mention module. * doc/posix-functions/symlinkat.texi (symlinkat): Likewise. * doc/posix-functions/readlinkat.texi (readlinkat): Likewise. * modules/symlinkat-tests: New test. * tests/test-symlinkat.c: Likewise. Signed-off-by: Eric Blake diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2009-09-05 Eric Blake + symlinkat: new module + * modules/symlinkat: New file. + * lib/symlinkat.c: Likewise. + * m4/symlinkat.m4 (gl_FUNC_SYMLINKAT): Likewise. + * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses. + * modules/unistd (Makefile.am): Use them. + * lib/unistd.in.h (symlinkat, readlinkat): Declare them. + (faccessat) [GNULIB_POSIXCHECK]: Fix typo. + * lib/at-func.c (FUNC_RESULT): New macro, defaulting to int. + * MODULES.html.sh (File system functions): Mention module. + * doc/posix-functions/symlinkat.texi (symlinkat): Likewise. + * doc/posix-functions/readlinkat.texi (readlinkat): Likewise. + * modules/symlinkat-tests: New test. + * tests/test-symlinkat.c: Likewise. + test-openat-safer: add more checks * tests/test-openat-safer.c (main): Check more code paths. @@ -65,7 +80,7 @@ faccessat: new module * modules/faccessat: New file. - * lib/faccessat.m4: Likewise. + * lib/faccessat.c: Likewise. * m4/faccessat.m4 (gl_FUNC_FACCESSAT): Likewise. * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness. * modules/unistd (Makefile.am): Use it. diff --git a/MODULES.html.sh b/MODULES.html.sh --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2478,6 +2478,7 @@ func_module savewd func_module stat-macros func_module stat-time + func_module symlinkat func_module tmpdir func_module unlinkdir func_module utimecmp diff --git a/doc/posix-functions/readlinkat.texi b/doc/posix-functions/readlinkat.texi --- a/doc/posix-functions/readlinkat.texi +++ b/doc/posix-functions/readlinkat.texi @@ -4,16 +4,28 @@ POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html} -Gnulib module: --- +Gnulib module: readlinkat Portability problems fixed by Gnulib: @itemize -@end itemize - -Portability problems not fixed by Gnulib: -@itemize @item This function is missing on some platforms: glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. @end itemize + +Portability problems not fixed by Gnulib: +@itemize +@item +This function always fails on platforms that don't support symlinks: +mingw +@item +When @code{readlink} is called on a directory: In the case of NFS mounted +directories, Cygwin sets @code{errno} to @code{ENOENT} or @code{EIO} instead of +@code{EINVAL}. To avoid this problem, check for a directory before calling +@code{readlink}. +@item +When @code{readlink} is called on a file that is not a symbolic link: +Irix may set @code{errno} to @code{ENXIO} instead of @code{EINVAL}. Cygwin +may set errno to @code{EACCES} instead of @code{EINVAL}. +@end itemize diff --git a/doc/posix-functions/symlinkat.texi b/doc/posix-functions/symlinkat.texi --- a/doc/posix-functions/symlinkat.texi +++ b/doc/posix-functions/symlinkat.texi @@ -4,16 +4,20 @@ POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html} -Gnulib module: --- +Gnulib module: symlinkat Portability problems fixed by Gnulib: @itemize -@end itemize - -Portability problems not fixed by Gnulib: -@itemize @item This function is missing on some platforms: glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. @end itemize + +Portability problems not fixed by Gnulib: +@itemize +@item +This function always fails with @samp{ENOSYS} on platforms that don't +support symlinks: +mingw +@end itemize diff --git a/lib/at-func.c b/lib/at-func.c --- a/lib/at-func.c +++ b/lib/at-func.c @@ -32,6 +32,12 @@ # define VALIDATE_FLAG(F) /* empty */ #endif +#ifdef AT_FUNC_RESULT +# define FUNC_RESULT AT_FUNC_RESULT +#else +# define FUNC_RESULT int +#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; @@ -40,12 +46,14 @@ 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. */ -int +FUNC_RESULT AT_FUNC_NAME (int fd, char const *file AT_FUNC_POST_FILE_PARAM_DECLS) { + /* Be careful to choose names unlikely to conflict with + AT_FUNC_POST_FILE_PARAM_DECLS. */ struct saved_cwd saved_cwd; int saved_errno; - int err; + FUNC_RESULT err; VALIDATE_FLAG (flag); @@ -53,13 +61,13 @@ return CALL_FUNC (file); { - char buf[OPENAT_BUFFER_SIZE]; - char *proc_file = openat_proc_name (buf, fd, file); + char proc_buf[OPENAT_BUFFER_SIZE]; + char *proc_file = openat_proc_name (proc_buf, fd, file); if (proc_file) { - int proc_result = CALL_FUNC (proc_file); + FUNC_RESULT proc_result = CALL_FUNC (proc_file); int proc_errno = errno; - if (proc_file != buf) + if (proc_file != proc_buf) free (proc_file); /* If the syscall succeeds, or if it fails with an unexpected errno value, then return right away. Otherwise, fall through @@ -98,3 +106,4 @@ return err; } #undef CALL_FUNC +#undef FUNC_RESULT diff --git a/lib/symlinkat.c b/lib/symlinkat.c new file mode 100644 --- /dev/null +++ b/lib/symlinkat.c @@ -0,0 +1,106 @@ +/* Create a symlink relative to an open directory. + 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 */ + +#include + +#include + +#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */ +#include "openat.h" +#include "openat-priv.h" +#include "save-cwd.h" + +#if !HAVE_SYMLINK +/* Mingw lacks symlink, so this wrapper is trivial. */ + +# include + +int +symlinkat (char const *path1 _UNUSED_PARAMETER_, int fd _UNUSED_PARAMETER_, + char const *path2 _UNUSED_PARAMETER_) +{ + errno = ENOSYS; + return -1; +} + +#else /* HAVE_SYMLINK */ + +/* Our openat helper functions expect the directory parameter first, + not second. These shims make life easier. */ + +/* Like symlink, but with arguments reversed. */ +static int +symlink_reversed (char const *file, char const *contents) +{ + return symlink (contents, file); +} + +/* Like symlinkat, but with arguments reversed. */ + +static int +symlinkat_reversed (int fd, char const *file, char const *contents); + +# define AT_FUNC_NAME symlinkat_reversed +# define AT_FUNC_F1 symlink_reversed +# define AT_FUNC_POST_FILE_PARAM_DECLS , char const *contents +# define AT_FUNC_POST_FILE_ARGS , contents +# 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 + +/* Create a symlink FILE, in the directory open on descriptor FD, + holding CONTENTS. If possible, do it without changing the + working directory. Otherwise, resort to using save_cwd/fchdir, + then mkdir/restore_cwd. If either the save_cwd or the restore_cwd + fails, then give a diagnostic and exit nonzero. */ + +int +symlinkat (char const *contents, int fd, char const *file) +{ + return symlinkat_reversed (fd, file, contents); +} + +#endif /* HAVE_SYMLINK */ + +/* Gnulib provides a readlink stub for mingw; use it for distinction + between EINVAL and ENOENT, rather than always failing with ENOSYS. */ + +/* POSIX 2008 says that unlike readlink, readlinkat returns 0 for + success instead of the buffer length. But this would render + readlinkat worthless since readlink does not guarantee a + NUL-terminated buffer. Assume this was a bug in POSIX. */ + +/* Read the contents of symlink FILE into buffer BUF of size LEN, in the + directory open on descriptor FD. If possible, do it without changing + the working directory. Otherwise, resort to using save_cwd/fchdir, + then mkdir/restore_cwd. If either the save_cwd or the restore_cwd + fails, then give a diagnostic and exit nonzero. */ + +#define AT_FUNC_NAME readlinkat +#define AT_FUNC_F1 readlink +#define AT_FUNC_POST_FILE_PARAM_DECLS , char *buf, size_t len +#define AT_FUNC_POST_FILE_ARGS , buf, len +#define AT_FUNC_RESULT ssize_t +#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 diff --git a/lib/unistd.in.h b/lib/unistd.in.h --- a/lib/unistd.in.h +++ b/lib/unistd.in.h @@ -181,9 +181,28 @@ # define faccessat(d,n,m,f) \ (GL_LINK_WARNING ("faccessat is not portable - " \ "use gnulib module faccessat for portability"), \ - fchownat (d, n, m, f)) + faccessat (d, n, m, f)) #endif +#if @GNULIB_SYMLINKAT@ +# if !@HAVE_SYMLINKAT@ +int symlinkat (char const *contents, int fd, char const *file); +# endif +# if !@HAVE_READLINKAT@ +ssize_t readlinkat (int fd, char const *file, char *buf, size_t len); +# endif +#elif defined GNULIB_POSIXCHECK +# undef symlinkat +# define symlinkat(c,d,n) \ + (GL_LINK_WARNING ("symlinkat is not portable - " \ + "use gnulib module symlinkat for portability"), \ + symlinkat (c, d, n)) +# undef readlinkat +# define readlinkat(d,n,b,l) \ + (GL_LINK_WARNING ("faccessat is not portable - " \ + "use gnulib module symlinkat for portability"), \ + readlinkat (d, n, b, l)) +#endif #if @GNULIB_CLOSE@ # if @REPLACE_CLOSE@ diff --git a/m4/symlinkat.m4 b/m4/symlinkat.m4 new file mode 100644 --- /dev/null +++ b/m4/symlinkat.m4 @@ -0,0 +1,22 @@ +# serial 1 +# See if we need to provide symlinkat/readlinkat replacement. + +dnl Copyright (C) 2009 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Written by Eric Blake. + +AC_DEFUN([gl_FUNC_SYMLINKAT], +[ + AC_REQUIRE([gl_FUNC_OPENAT]) + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_CHECK_FUNCS_ONCE([symlink symlinkat readlinkat]) + if test $ac_cv_func_symlinkat = no; then + # No known system has readlinkat but not symlinkat + HAVE_SYMLINKAT=0 + HAVE_READLINKAT=0 + AC_LIBOBJ([symlinkat]) + fi +]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -56,6 +56,7 @@ GNULIB_PIPE2=0; AC_SUBST([GNULIB_PIPE2]) GNULIB_READLINK=0; AC_SUBST([GNULIB_READLINK]) GNULIB_SLEEP=0; AC_SUBST([GNULIB_SLEEP]) + GNULIB_SYMLINKAT=0; AC_SUBST([GNULIB_SYMLINKAT]) GNULIB_UNISTD_H_GETOPT=0; AC_SUBST([GNULIB_UNISTD_H_GETOPT]) GNULIB_UNISTD_H_SIGPIPE=0; AC_SUBST([GNULIB_UNISTD_H_SIGPIPE]) GNULIB_WRITE=0; AC_SUBST([GNULIB_WRITE]) @@ -74,7 +75,9 @@ HAVE_LINK=1; AC_SUBST([HAVE_LINK]) HAVE_PIPE2=1; AC_SUBST([HAVE_PIPE2]) HAVE_READLINK=1; AC_SUBST([HAVE_READLINK]) + HAVE_READLINKAT=1; AC_SUBST([HAVE_READLINKAT]) HAVE_SLEEP=1; AC_SUBST([HAVE_SLEEP]) + HAVE_SYMLINKAT=1; AC_SUBST([HAVE_SYMLINKAT]) HAVE_DECL_ENVIRON=1; AC_SUBST([HAVE_DECL_ENVIRON]) HAVE_DECL_GETLOGIN_R=1; AC_SUBST([HAVE_DECL_GETLOGIN_R]) HAVE_OS_H=0; AC_SUBST([HAVE_OS_H]) diff --git a/modules/symlinkat b/modules/symlinkat new file mode 100644 --- /dev/null +++ b/modules/symlinkat @@ -0,0 +1,29 @@ +Description: +symlinkat() and readlinkat(): manage symlinks relative to a directory + +Files: +lib/symlinkat.c +m4/symlinkat.m4 + +Depends-on: +extensions +fcntl-h +openat +readlink +unistd + +configure.ac: +gl_FUNC_SYMLINKAT +gl_UNISTD_MODULE_INDICATOR([symlinkat]) + +Makefile.am: + +Include: + + + +License: +GPL + +Maintainer: +Jim Meyering, Eric Blake diff --git a/modules/symlinkat-tests b/modules/symlinkat-tests new file mode 100644 --- /dev/null +++ b/modules/symlinkat-tests @@ -0,0 +1,11 @@ +Files: +tests/test-symlinkat.c + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-symlinkat +check_PROGRAMS += test-symlinkat +test_symlinkat_LDADD = $(LDADD) @LIBINTL@ diff --git a/modules/unistd b/modules/unistd --- a/modules/unistd +++ b/modules/unistd @@ -50,6 +50,7 @@ -e 's|@''GNULIB_PIPE2''@|$(GNULIB_PIPE2)|g' \ -e 's|@''GNULIB_READLINK''@|$(GNULIB_READLINK)|g' \ -e 's|@''GNULIB_SLEEP''@|$(GNULIB_SLEEP)|g' \ + -e 's|@''GNULIB_SYMLINKAT''@|$(GNULIB_SYMLINKAT)|g' \ -e 's|@''GNULIB_UNISTD_H_GETOPT''@|$(GNULIB_UNISTD_H_GETOPT)|g' \ -e 's|@''GNULIB_UNISTD_H_SIGPIPE''@|$(GNULIB_UNISTD_H_SIGPIPE)|g' \ -e 's|@''GNULIB_WRITE''@|$(GNULIB_WRITE)|g' \ @@ -68,7 +69,9 @@ -e 's|@''HAVE_LINK''@|$(HAVE_LINK)|g' \ -e 's|@''HAVE_PIPE2''@|$(HAVE_PIPE2)|g' \ -e 's|@''HAVE_READLINK''@|$(HAVE_READLINK)|g' \ + -e 's|@''HAVE_READLINKAT''@|$(HAVE_READLINKAT)|g' \ -e 's|@''HAVE_SLEEP''@|$(HAVE_SLEEP)|g' \ + -e 's|@''HAVE_SYMLINKAT''@|$(HAVE_SYMLINKAT)|g' \ -e 's|@''HAVE_UNLINKAT''@|$(HAVE_UNLINKAT)|g' \ -e 's|@''HAVE_DECL_ENVIRON''@|$(HAVE_DECL_ENVIRON)|g' \ -e 's|@''HAVE_DECL_GETLOGIN_R''@|$(HAVE_DECL_GETLOGIN_R)|g' \ diff --git a/tests/test-symlinkat.c b/tests/test-symlinkat.c new file mode 100644 --- /dev/null +++ b/tests/test-symlinkat.c @@ -0,0 +1,120 @@ +/* Tests of symlinkat and readlinkat. + 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 + +#include +#include +#include +#include +#include +#include + +#ifndef HAVE_SYMLINK +# define HAVE_SYMLINK 0 +#endif + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +int +main () +{ + char buf[80]; + + /* Create handle for future use. */ + int dfd = openat (AT_FDCWD, ".", O_RDONLY); + ASSERT (0 <= dfd); + + /* Sanity checks of failures. Mingw lacks symlinkat, but readlinkat + can still distinguish between various errors. */ + errno = 0; + ASSERT (readlinkat (AT_FDCWD, "no_such", buf, sizeof buf) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (readlinkat (dfd, "no_such", buf, sizeof buf) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (readlinkat (AT_FDCWD, "", buf, sizeof buf) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (readlinkat (dfd, "", buf, sizeof buf) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (readlinkat (AT_FDCWD, ".", buf, sizeof buf) == -1); + ASSERT (errno == EINVAL); + errno = 0; + ASSERT (readlinkat (dfd, ".", buf, sizeof buf) == -1); + ASSERT (errno == EINVAL); + errno = 0; + ASSERT (symlinkat ("who cares", AT_FDCWD, "") == -1); + ASSERT (errno == ENOENT || errno == ENOSYS); + errno = 0; + ASSERT (symlinkat ("who cares", dfd, "") == -1); + ASSERT (errno == ENOENT || errno == ENOSYS); + + /* Skip everything else on mingw. */ + if (HAVE_SYMLINK) + { + const char *linkname = "test-symlinkat.link"; + const char *contents = "don't matter!"; + int exp = strlen (contents); + + /* Create link while cwd is '.', then read it in '..'. */ + ASSERT (symlinkat (contents, AT_FDCWD, linkname) == 0); + errno = 0; + ASSERT (symlinkat (contents, dfd, linkname) == -1); + ASSERT (errno == EEXIST); + ASSERT (chdir ("..") == 0); + errno = 0; + ASSERT (readlinkat (AT_FDCWD, linkname, buf, sizeof buf) == -1); + ASSERT (errno == ENOENT); + ASSERT (readlinkat (dfd, linkname, buf, sizeof buf) == exp); + ASSERT (strncmp (contents, buf, exp) == 0); + ASSERT (unlinkat (dfd, linkname, 0) == 0); + + /* Create link while cwd is '..', then read it in '.'. */ + ASSERT (symlinkat (contents, dfd, linkname) == 0); + ASSERT (fchdir (dfd) == 0); + errno = 0; + ASSERT (symlinkat (contents, AT_FDCWD, linkname) == -1); + ASSERT (errno == EEXIST); + buf[0] = '\0'; + ASSERT (readlinkat (AT_FDCWD, linkname, buf, sizeof buf) == exp); + ASSERT (strncmp (contents, buf, exp) == 0); + buf[0] = '\0'; + ASSERT (readlinkat (dfd, linkname, buf, sizeof buf) == exp); + ASSERT (strncmp (contents, buf, exp) == 0); + ASSERT (unlink (linkname) == 0); + } + + ASSERT (close (dfd) == 0); + + return 0; +}