From c4194dcc56767f8f96bc005088b292f519b13910 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Tue, 8 Sep 2009 20:47:40 -0600 Subject: [PATCH] linkat: new module * modules/linkat: New file. * lib/at-func2.c (at_func2): Likewise. * lib/linkat.c (linkat): Likewise. * m4/linkat.m4 (gl_FUNC_LINKAT): Likewise. * lib/openat-priv.h (at_func2): Add declaration. * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses. * modules/unistd (Makefile.am): Substitute them. * lib/unistd.in.h (linkat): Declare it. * MODULES.html.sh (systems lacking POSIX:2008): Mention module. * doc/posix-functions/linkat.texi (linkat): Likewise. * doc/posix-functions/link.texi (link): Tweak wording. * tests/test-link.c (main): Move guts... * tests/test-link.h (test_link): ...into new file. * modules/linkat-tests: New test. * tests/test-linkat.c: Likewise. * modules/link-tests (Files): Ship new file. (Depends-on): Add stdbool. Signed-off-by: Eric Blake --- ChangeLog | 19 +++ MODULES.html.sh | 4 +- doc/posix-functions/link.texi | 4 + doc/posix-functions/linkat.texi | 10 +- lib/at-func2.c | 282 ++++++++++++++++++++++++++++++++ lib/linkat.c | 181 +++++++++++++++++++++ lib/openat-priv.h | 5 + lib/unistd.in.h | 15 ++ m4/linkat.m4 | 25 +++ m4/unistd_h.m4 | 4 +- modules/link-tests | 2 + modules/linkat | 40 +++++ modules/linkat-tests | 16 ++ modules/unistd | 2 + tests/test-link.c | 111 +------------ tests/test-link.h | 137 ++++++++++++++++ tests/test-linkat.c | 352 ++++++++++++++++++++++++++++++++++++++++ 17 files changed, 1095 insertions(+), 114 deletions(-) create mode 100644 lib/at-func2.c create mode 100644 lib/linkat.c create mode 100644 m4/linkat.m4 create mode 100644 modules/linkat create mode 100644 modules/linkat-tests create mode 100644 tests/test-link.h create mode 100644 tests/test-linkat.c diff --git a/ChangeLog b/ChangeLog index 0ba1bbe5f..855e69dba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,24 @@ 2009-09-23 Eric Blake + linkat: new module + * modules/linkat: New file. + * lib/at-func2.c (at_func2): Likewise. + * lib/linkat.c (linkat): Likewise. + * m4/linkat.m4 (gl_FUNC_LINKAT): Likewise. + * lib/openat-priv.h (at_func2): Add declaration. + * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses. + * modules/unistd (Makefile.am): Substitute them. + * lib/unistd.in.h (linkat): Declare it. + * MODULES.html.sh (systems lacking POSIX:2008): Mention module. + * doc/posix-functions/linkat.texi (linkat): Likewise. + * doc/posix-functions/link.texi (link): Tweak wording. + * tests/test-link.c (main): Move guts... + * tests/test-link.h (test_link): ...into new file. + * modules/linkat-tests: New test. + * tests/test-linkat.c: Likewise. + * modules/link-tests (Files): Ship new file. + (Depends-on): Add stdbool. + dirname: add library-safe mdir_name * lib/dirname.h (mdir_name): New prototype. * lib/dirname.c (dir_name): Move guts... diff --git a/MODULES.html.sh b/MODULES.html.sh index ad00ac883..42cb57c67 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2276,9 +2276,11 @@ func_all_modules () func_module iconv_open func_module inet_ntop func_module inet_pton + func_module link + func_module linkat + func_module listen func_module locale func_module lseek - func_module listen func_module lstat func_module malloc-posix func_module mbsnrtowcs diff --git a/doc/posix-functions/link.texi b/doc/posix-functions/link.texi index c785371a0..c06f0a6ce 100644 --- a/doc/posix-functions/link.texi +++ b/doc/posix-functions/link.texi @@ -19,4 +19,8 @@ Solaris, Cygwin 1.5.x. Portability problems not fixed by Gnulib: @itemize +@item +When the first argument is a symlink, some platforms create a +hard-link to what the symlink referenced, rather than to the symlink +itself. Use @samp{linkat} to force a particular behavior. @end itemize diff --git a/doc/posix-functions/linkat.texi b/doc/posix-functions/linkat.texi index 1c08c7e60..62fc43dc3 100644 --- a/doc/posix-functions/linkat.texi +++ b/doc/posix-functions/linkat.texi @@ -4,16 +4,16 @@ POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/linkat.html} -Gnulib module: --- +Gnulib module: linkat 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 +@end itemize diff --git a/lib/at-func2.c b/lib/at-func2.c new file mode 100644 index 000000000..a19b60b72 --- /dev/null +++ b/lib/at-func2.c @@ -0,0 +1,282 @@ +/* Define an at-style functions like linkat or renameat. + Copyright (C) 2006, 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 "openat-priv.h" + +#include +#include +#include +#include + +#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */ +#include "filenamecat.h" +#include "openat.h" +#include "same-inode.h" +#include "save-cwd.h" + +/* Call FUNC to operate on a pair of files, where FILE1 is relative to FD1, + and FILE2 is relative to FD2. If possible, do it without changing the + working directory. Otherwise, resort to using save_cwd/fchdir, + FUNC, restore_cwd (up to two times). If either the save_cwd or the + restore_cwd fails, then give a diagnostic and exit nonzero. */ +int +at_func2 (int fd1, char const *file1, + int fd2, char const *file2, + int (*func) (char const *file1, char const *file2)) +{ + struct saved_cwd saved_cwd; + int saved_errno; + int err; + char *file1_alt; + char *file2_alt; + struct stat st1; + struct stat st2; + + /* There are 16 possible scenarios, based on whether an fd is + AT_FDCWD or real, and whether a file is absolute or relative: + + fd1 file1 fd2 file2 action + 0 cwd abs cwd abs direct call + 1 cwd abs cwd rel direct call + 2 cwd abs fd abs direct call + 3 cwd abs fd rel chdir to fd2 + 4 cwd rel cwd abs direct call + 5 cwd rel cwd rel direct call + 6 cwd rel fd abs direct call + 7 cwd rel fd rel convert file1 to abs, then case 3 + 8 fd abs cwd abs direct call + 9 fd abs cwd rel direct call + 10 fd abs fd abs direct call + 11 fd abs fd rel chdir to fd2 + 12 fd rel cwd abs chdir to fd1 + 13 fd rel cwd rel convert file2 to abs, then case 12 + 14 fd rel fd abs chdir to fd1 + 15a fd1 rel fd1 rel chdir to fd1 + 15b fd1 rel fd2 rel chdir to fd1, then case 7 + + Try some optimizations to reduce fd to AT_FDCWD, or to at least + avoid converting an absolute name or doing a double chdir. */ + + if ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1)) + && (fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2))) + return func (file1, file2); /* Case 0-2, 4-6, 8-10. */ + + /* If /proc/self/fd works, we don't need any stat or chdir. */ + { + char proc_buf1[OPENAT_BUFFER_SIZE]; + char *proc_file1 = ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1)) + ? (char *) file1 + : openat_proc_name (proc_buf1, fd1, file1)); + if (proc_file1) + { + char proc_buf2[OPENAT_BUFFER_SIZE]; + char *proc_file2 = ((fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2)) + ? (char *) file2 + : openat_proc_name (proc_buf2, fd2, file2)); + if (proc_file2) + { + int proc_result = func (proc_file1, proc_file2); + int proc_errno = errno; + if (proc_file1 != proc_buf1 && proc_file1 != file1) + free (proc_file1); + if (proc_file2 != proc_buf2 && proc_file2 != file2) + free (proc_file2); + /* If the syscall succeeds, or if it fails with an unexpected + errno value, then return right away. Otherwise, fall through + and resort to using save_cwd/restore_cwd. */ + if (0 <= proc_result) + return proc_result; + if (! EXPECTED_ERRNO (proc_errno)) + { + errno = proc_errno; + return proc_result; + } + } + else if (proc_file1 != proc_buf1 && proc_file1 != file1) + free (proc_buf1); + } + } + + /* Cases 3, 7, 11-15 remain. Time to normalize directory fds, if + possible. */ + if (IS_ABSOLUTE_FILE_NAME (file1)) + fd1 = AT_FDCWD; /* Case 11 reduced to 3. */ + else if (IS_ABSOLUTE_FILE_NAME (file2)) + fd2 = AT_FDCWD; /* Case 14 reduced to 12. */ + + /* Cases 3, 7, 12, 13, 15 remain. */ + + if (fd1 == AT_FDCWD) /* Cases 3, 7. */ + { + if (stat (".", &st1) == -1 || fstat (fd2, &st2) == -1) + return -1; + if (!S_ISDIR (st2.st_mode)) + { + errno = ENOTDIR; + return -1; + } + if (SAME_INODE (st1, st2) == 1) /* Reduced to cases 1, 5. */ + return func (file1, file2); + } + else if (fd2 == AT_FDCWD) /* Cases 12, 13. */ + { + if (stat (".", &st2) == -1 || fstat (fd1, &st1) == -1) + return -1; + if (!S_ISDIR (st1.st_mode)) + { + errno = ENOTDIR; + return -1; + } + if (SAME_INODE (st1, st2) == 1) /* Reduced to cases 4, 5. */ + return func (file1, file2); + } + else if (fd1 != fd2) /* Case 15b. */ + { + if (fstat (fd1, &st1) == -1 || fstat (fd2, &st2) == -1) + return -1; + if (!S_ISDIR (st1.st_mode) || !S_ISDIR (st2.st_mode)) + { + errno = ENOTDIR; + return -1; + } + if (SAME_INODE (st1, st2) == 1) /* Reduced to case 15a. */ + { + fd2 = fd1; + if (stat (".", &st1) == 0 && SAME_INODE (st1, st2) == 1) + return func (file1, file2); /* Further reduced to case 5. */ + } + } + else /* Case 15a. */ + { + if (fstat (fd1, &st1) == -1) + return -1; + if (!S_ISDIR (st1.st_mode)) + { + errno = ENOTDIR; + return -1; + } + if (stat (".", &st2) == 0 && SAME_INODE (st1, st2) == 1) + return func (file1, file2); /* Reduced to case 5. */ + } + + /* Cases 3, 7, 12, 13, 15a, 15b remain. With all reductions in + place, it is time to start changing directories. */ + + if (save_cwd (&saved_cwd) != 0) + openat_save_fail (errno); + + if (fd1 != AT_FDCWD && fd2 != AT_FDCWD && fd1 != fd2) /* Case 15b. */ + { + if (fchdir (fd1) != 0) + { + saved_errno = errno; + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + fd1 = AT_FDCWD; /* Reduced to case 7. */ + } + + /* Cases 3, 7, 12, 13, 15a remain. Convert one relative name to + absolute, if necessary. */ + + file1_alt = (char *) file1; + file2_alt = (char *) file2; + + if (fd1 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file1)) /* Case 7. */ + { + /* It would be nicer to use: + file1_alt = file_name_concat (xgetcwd (), file1, NULL); + but libraries should not call xalloc_die. */ + char *cwd = getcwd (NULL, 0); + if (!cwd) + { + saved_errno = errno; + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + file1_alt = mfile_name_concat (cwd, file1, NULL); + if (!file1_alt) + { + saved_errno = errno; + free (cwd); + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + free (cwd); /* Reduced to case 3. */ + } + else if (fd2 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file2)) /* Case 13. */ + { + char *cwd = getcwd (NULL, 0); + if (!cwd) + { + saved_errno = errno; + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + file2_alt = mfile_name_concat (cwd, file2, NULL); + if (!file2_alt) + { + saved_errno = errno; + free (cwd); + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + free (cwd); /* Reduced to case 12. */ + } + + /* Cases 3, 12, 15a remain. Change to the correct directory. */ + if (fchdir (fd1 == AT_FDCWD ? fd2 : fd1) != 0) + { + saved_errno = errno; + free_cwd (&saved_cwd); + if (file1 != file1_alt) + free (file1_alt); + else if (file2 != file2_alt) + free (file2_alt); + errno = saved_errno; + return -1; + } + + /* Finally safe to perform the user's function, then clean up. */ + + err = func (file1_alt, file2_alt); + saved_errno = (err < 0 ? errno : 0); + + if (file1 != file1_alt) + free (file1_alt); + else if (file2 != file2_alt) + free (file2_alt); + + if (restore_cwd (&saved_cwd) != 0) + openat_restore_fail (errno); + + free_cwd (&saved_cwd); + + if (saved_errno) + errno = saved_errno; + return err; +} +#undef CALL_FUNC +#undef FUNC_RESULT diff --git a/lib/linkat.c b/lib/linkat.c new file mode 100644 index 000000000..bda062756 --- /dev/null +++ b/lib/linkat.c @@ -0,0 +1,181 @@ +/* Create a hard link relative to open directories. + 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 +#include +#include +#include + +#include "areadlink.h" +#include "dirname.h" +#include "filenamecat.h" +#include "openat-priv.h" + +#if HAVE_SYS_PARAM_H +# include +#endif +#ifndef MAXSYMLINKS +# ifdef SYMLOOP_MAX +# define MAXSYMLINKS SYMLOOP_MAX +# else +# define MAXSYMLINKS 20 +# endif +#endif + +/* Create a link. If FILE1 is a symlink, either create a hardlink to + that symlink, or fake it by creating an identical symlink. */ +#if LINK_FOLLOWS_SYMLINKS == 0 +# define link_immediate link +#else +static int +link_immediate (char const *file1, char const *file2) +{ + char *target = areadlink (file1); + if (target) + { + /* A symlink cannot be modified in-place. Therefore, creating + an identical symlink behaves like a hard link to a symlink, + except for incorrect st_ino and st_nlink. However, we must + be careful of EXDEV. */ + struct stat st1; + struct stat st2; + char *dir = mdir_name (file2); + if (!dir) + { + free (target); + errno = ENOMEM; + return -1; + } + if (lstat (file1, &st1) == 0 && stat (dir, &st2) == 0) + { + if (st1.st_dev == st2.st_dev) + { + int result = symlink (target, file2); + int saved_errno = errno; + free (target); + free (dir); + errno = saved_errno; + return result; + } + free (target); + free (dir); + errno = EXDEV; + return -1; + } + free (target); + free (dir); + } + if (errno == ENOMEM) + return -1; + return link (file1, file2); +} +#endif + +/* Create a link. If FILE1 is a symlink, create a hardlink to the + canonicalized file. */ +#if 0 < LINK_FOLLOWS_SYMLINKS +# define link_follow link +#else +static int +link_follow (char const *file1, char const *file2) +{ + char *name = (char *) file1; + char *target; + int result; + int i = MAXSYMLINKS; + + /* Using realpath or canonicalize_file_name is too heavy-handed: we + don't need an absolute name, and we don't need to resolve + intermediate symlinks, just the basename of each iteration. */ + while (i-- && (target = areadlink (name))) + { + if (IS_ABSOLUTE_FILE_NAME (target)) + { + if (name != file1) + free (name); + name = target; + } + else + { + char *dir = mdir_name (name); + if (name != file1) + free (name); + if (!dir) + { + free (target); + errno = ENOMEM; + return -1; + } + name = mfile_name_concat (dir, target, NULL); + free (dir); + free (target); + if (!name) + { + errno = ENOMEM; + return -1; + } + } + } + if (i < 0) + { + target = NULL; + errno = ELOOP; + } + if (!target && errno != EINVAL) + { + if (name != file1) + { + int saved_errno = errno; + free (name); + errno = saved_errno; + } + return -1; + } + result = link (name, file2); + if (name != file1) + { + int saved_errno = errno; + free (name); + errno = saved_errno; + } + return result; +} +#endif + +/* Create a link to FILE1, in the directory open on descriptor FD1, to FILE2, + in the directory open on descriptor FD2. If FILE1 is a symlink, FLAG + controls whether to dereference FILE1 first. If possible, do it without + changing the working directory. Otherwise, resort to using + save_cwd/fchdir, then rename/restore_cwd. If either the save_cwd or + the restore_cwd fails, then give a diagnostic and exit nonzero. */ + +int +linkat (int fd1, char const *file1, int fd2, char const *file2, int flag) +{ + if (flag & ~AT_SYMLINK_FOLLOW) + { + errno = EINVAL; + return -1; + } + return at_func2 (fd1, file1, fd2, file2, + flag ? link_follow : link_immediate); +} diff --git a/lib/openat-priv.h b/lib/openat-priv.h index fa286b509..53016a14e 100644 --- a/lib/openat-priv.h +++ b/lib/openat-priv.h @@ -36,4 +36,9 @@ char *openat_proc_name (char buf[OPENAT_BUFFER_SIZE], int fd, char const *file); || (Errno) == ENOSYS /* Solaris 8 */ \ || (Errno) == EOPNOTSUPP /* FreeBSD */) +/* Wrapper function shared among linkat and renameat. */ +int at_func2 (int fd1, char const *file1, + int fd2, char const *file2, + int (*func) (char const *file1, char const *file2)); + #endif /* _GL_HEADER_OPENAT_PRIV */ diff --git a/lib/unistd.in.h b/lib/unistd.in.h index 600f22408..8a96e792b 100644 --- a/lib/unistd.in.h +++ b/lib/unistd.in.h @@ -581,6 +581,21 @@ extern int link (const char *path1, const char *path2); link (path1, path2)) #endif +#if @GNULIB_LINKAT@ +/* Create a new hard link for an existing file, relative to two + directories. FLAG controls whether symlinks are followed. + Return 0 if successful, otherwise -1 and errno set. */ +# if !@HAVE_LINKAT@ +extern int linkat (int fd1, const char *path1, int fd2, const char *path2, + int flag); +# endif +#elif defined GNULIB_POSIXCHECK +# undef linkat +# define link(f1,path1,f2,path2,f) \ + (GL_LINK_WARNING ("linkat is unportable - " \ + "use gnulib module linkat for portability"), \ + linkat (f1, path1, f2, path2,f)) +#endif #if @GNULIB_LSEEK@ # if @REPLACE_LSEEK@ diff --git a/m4/linkat.m4 b/m4/linkat.m4 new file mode 100644 index 000000000..be68c5fc7 --- /dev/null +++ b/m4/linkat.m4 @@ -0,0 +1,25 @@ +# serial 1 +# See if we need to provide linkat 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_LINKAT], +[ + AC_REQUIRE([gl_FUNC_OPENAT]) + AC_REQUIRE([gl_FUNC_LINK]) + AC_REQUIRE([gl_FUNC_LINK_FOLLOWS_SYMLINK]) + AC_REQUIRE([gl_UNISTD_H_DEFAULTS]) + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_CHECK_FUNCS_ONCE([linkat symlink]) + AC_CHECK_HEADERS_ONCE([sys/param.h]) + if test $ac_cv_func_linkat = no; then + HAVE_LINKAT=0 + AC_LIBOBJ([linkat]) + AC_LIBOBJ([at-func2]) + fi +]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 index 23345823b..16daed884 100644 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -1,4 +1,4 @@ -# unistd_h.m4 serial 29 +# unistd_h.m4 serial 30 dnl Copyright (C) 2006-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, @@ -52,6 +52,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], GNULIB_GETUSERSHELL=0; AC_SUBST([GNULIB_GETUSERSHELL]) GNULIB_LCHOWN=0; AC_SUBST([GNULIB_LCHOWN]) GNULIB_LINK=0; AC_SUBST([GNULIB_LINK]) + GNULIB_LINKAT=0; AC_SUBST([GNULIB_LINKAT]) GNULIB_LSEEK=0; AC_SUBST([GNULIB_LSEEK]) GNULIB_PIPE2=0; AC_SUBST([GNULIB_PIPE2]) GNULIB_READLINK=0; AC_SUBST([GNULIB_READLINK]) @@ -79,6 +80,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], HAVE_GETPAGESIZE=1; AC_SUBST([HAVE_GETPAGESIZE]) HAVE_GETUSERSHELL=1; AC_SUBST([HAVE_GETUSERSHELL]) HAVE_LINK=1; AC_SUBST([HAVE_LINK]) + HAVE_LINKAT=1; AC_SUBST([HAVE_LINKAT]) HAVE_PIPE2=1; AC_SUBST([HAVE_PIPE2]) HAVE_READLINK=1; AC_SUBST([HAVE_READLINK]) HAVE_READLINKAT=1; AC_SUBST([HAVE_READLINKAT]) diff --git a/modules/link-tests b/modules/link-tests index ca61deb70..d8e7b1a2d 100644 --- a/modules/link-tests +++ b/modules/link-tests @@ -1,8 +1,10 @@ Files: +tests/test-link.h tests/test-link.c Depends-on: errno +stdbool sys_stat configure.ac: diff --git a/modules/linkat b/modules/linkat new file mode 100644 index 000000000..8d9dec341 --- /dev/null +++ b/modules/linkat @@ -0,0 +1,40 @@ +Description: +linkat(): create a hard link, relative to two directories + +Files: +lib/at-func2.c +lib/linkat.c +m4/linkat.m4 + +Depends-on: +areadlink +dirname +errno +extensions +fcntl-h +filenamecat +openat +link +link-follow +lstat +readlink +same-inode +stpcpy +symlink +unistd + +configure.ac: +gl_FUNC_LINKAT +gl_UNISTD_MODULE_INDICATOR([linkat]) + +Makefile.am: + +Include: + + + +License: +GPL + +Maintainer: +Jim Meyering, Eric Blake diff --git a/modules/linkat-tests b/modules/linkat-tests new file mode 100644 index 000000000..9fb650520 --- /dev/null +++ b/modules/linkat-tests @@ -0,0 +1,16 @@ +Files: +tests/test-linkat.c + +Depends-on: +areadlink-with-size +filenamecat +progname +same-inode +xgetcwd + +configure.ac: + +Makefile.am: +TESTS += test-linkat +check_PROGRAMS += test-linkat +test_linkat_LDADD = $(LDADD) @LIBINTL@ diff --git a/modules/unistd b/modules/unistd index 875efb0cc..d21a20447 100644 --- a/modules/unistd +++ b/modules/unistd @@ -45,6 +45,7 @@ unistd.h: unistd.in.h -e 's|@''GNULIB_GETUSERSHELL''@|$(GNULIB_GETUSERSHELL)|g' \ -e 's|@''GNULIB_LCHOWN''@|$(GNULIB_LCHOWN)|g' \ -e 's|@''GNULIB_LINK''@|$(GNULIB_LINK)|g' \ + -e 's|@''GNULIB_LINKAT''@|$(GNULIB_LINKAT)|g' \ -e 's|@''GNULIB_LSEEK''@|$(GNULIB_LSEEK)|g' \ -e 's|@''GNULIB_PIPE2''@|$(GNULIB_PIPE2)|g' \ -e 's|@''GNULIB_READLINK''@|$(GNULIB_READLINK)|g' \ @@ -71,6 +72,7 @@ unistd.h: unistd.in.h -e 's|@''HAVE_GETPAGESIZE''@|$(HAVE_GETPAGESIZE)|g' \ -e 's|@''HAVE_GETUSERSHELL''@|$(HAVE_GETUSERSHELL)|g' \ -e 's|@''HAVE_LINK''@|$(HAVE_LINK)|g' \ + -e 's|@''HAVE_LINKAT''@|$(HAVE_LINKAT)|g' \ -e 's|@''HAVE_PIPE2''@|$(HAVE_PIPE2)|g' \ -e 's|@''HAVE_READLINK''@|$(HAVE_READLINK)|g' \ -e 's|@''HAVE_READLINKAT''@|$(HAVE_READLINKAT)|g' \ diff --git a/tests/test-link.c b/tests/test-link.c index e09a0bb17..a77ffe76d 100644 --- a/tests/test-link.c +++ b/tests/test-link.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -40,117 +41,13 @@ #define BASE "test-link.t" +#include "test-link.h" + int main (int argc, char **argv) { - int fd; - int ret; - /* Remove any garbage left from previous partial runs. */ ASSERT (system ("rm -rf " BASE "*") == 0); - /* Create first file. */ - fd = open (BASE "a", O_CREAT | O_EXCL | O_WRONLY, 0600); - ASSERT (0 <= fd); - ASSERT (write (fd, "hello", 5) == 5); - ASSERT (close (fd) == 0); - - /* Not all file systems support link. Mingw doesn't have reliable - st_nlink on hard links, but our implementation does fail with - EPERM on poor file systems, and we can detect the inferior stat() - via st_ino. Cygwin 1.5.x copies rather than links files on those - file systems, but there, st_nlink and st_ino are reliable. */ - ret = link (BASE "a", BASE "b"); - if (!ret) - { - struct stat st; - ASSERT (stat (BASE "b", &st) == 0); - if (st.st_ino && st.st_nlink != 2) - { - ASSERT (unlink (BASE "b") == 0); - errno = EPERM; - ret = -1; - } - } - if (ret == -1) - { - /* If the device does not support hard links, errno is - EPERM on Linux, EOPNOTSUPP on FreeBSD. */ - switch (errno) - { - case EPERM: - case EOPNOTSUPP: - fputs ("skipping test: " - "hard links are not supported on this file system\n", stderr); - ASSERT (unlink (BASE "a") == 0); - return 77; - default: - perror ("link"); - return 1; - } - } - ASSERT (ret == 0); - - /* Now, for some behavior tests. Modify the contents of 'b', and - ensure that 'a' can see it, both while 'b' exists and after. */ - fd = open (BASE "b", O_APPEND | O_WRONLY); - ASSERT (0 <= fd); - ASSERT (write (fd, "world", 5) == 5); - ASSERT (close (fd) == 0); - { - char buf[11] = { 0 }; - fd = open (BASE "a", O_RDONLY); - ASSERT (0 <= fd); - ASSERT (read (fd, buf, 10) == 10); - ASSERT (strcmp (buf, "helloworld") == 0); - ASSERT (close (fd) == 0); - ASSERT (unlink (BASE "b") == 0); - fd = open (BASE "a", O_RDONLY); - ASSERT (0 <= fd); - ASSERT (read (fd, buf, 10) == 10); - ASSERT (strcmp (buf, "helloworld") == 0); - ASSERT (close (fd) == 0); - } - - /* Test for various error conditions. Assumes hard links to - directories are not permitted. */ - ASSERT (mkdir (BASE "d", 0700) == 0); - errno = 0; - ASSERT (link (BASE "a", ".") == -1); - ASSERT (errno == EEXIST || errno == EINVAL); - errno = 0; - ASSERT (link (BASE "a", BASE "a") == -1); - ASSERT (errno == EEXIST); - ASSERT (link (BASE "a", BASE "b") == 0); - errno = 0; - ASSERT (link (BASE "a", BASE "b") == -1); - ASSERT (errno == EEXIST); - errno = 0; - ASSERT (link (BASE "a", BASE "d") == -1); - ASSERT (errno == EEXIST); - errno = 0; - ASSERT (link (BASE "c", BASE "e") == -1); - ASSERT (errno == ENOENT); - errno = 0; - ASSERT (link (BASE "a", BASE "c/.") == -1); - ASSERT (errno == ENOENT); - errno = 0; - ASSERT (link (BASE "a/", BASE "c") == -1); - ASSERT (errno == ENOTDIR); - errno = 0; - ASSERT (link (BASE "a", BASE "c/") == -1); - ASSERT (errno == ENOTDIR || errno == ENOENT); - errno = 0; - ASSERT (link (BASE "d", BASE "c") == -1); - ASSERT (errno == EPERM || errno == EACCES); - - /* Clean up. */ - ASSERT (unlink (BASE "a") == 0); - ASSERT (unlink (BASE "b") == 0); - errno = 0; - ASSERT (unlink (BASE "c") == -1); - ASSERT (errno == ENOENT); - ASSERT (rmdir (BASE "d") == 0); - - return 0; + return test_link (link, true); } diff --git a/tests/test-link.h b/tests/test-link.h new file mode 100644 index 000000000..9ce189458 --- /dev/null +++ b/tests/test-link.h @@ -0,0 +1,137 @@ +/* Test of link() function. + 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 2 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 . */ + +#include + +/* This file is designed to test both link(a,b) and + linkat(AT_FDCWD,a,AT_FDCWD,b). FUNC is the function to test. + 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_link (int (*func) (char const *, char const *), bool print) +{ + int fd; + int ret; + + /* Create first file. */ + fd = open (BASE "a", O_CREAT | O_EXCL | O_WRONLY, 0600); + ASSERT (0 <= fd); + ASSERT (write (fd, "hello", 5) == 5); + ASSERT (close (fd) == 0); + + /* Not all file systems support link. Mingw doesn't have reliable + st_nlink on hard links, but our implementation does fail with + EPERM on poor file systems, and we can detect the inferior stat() + via st_ino. Cygwin 1.5.x copies rather than links files on those + file systems, but there, st_nlink and st_ino are reliable. */ + ret = func (BASE "a", BASE "b"); + if (!ret) + { + struct stat st; + ASSERT (stat (BASE "b", &st) == 0); + if (st.st_ino && st.st_nlink != 2) + { + ASSERT (unlink (BASE "b") == 0); + errno = EPERM; + ret = -1; + } + } + if (ret == -1) + { + /* If the device does not support hard links, errno is + EPERM on Linux, EOPNOTSUPP on FreeBSD. */ + switch (errno) + { + case EPERM: + case EOPNOTSUPP: + if (print) + fputs ("skipping test: " + "hard links not supported on this file system\n", + stderr); + ASSERT (unlink (BASE "a") == 0); + return 77; + default: + perror ("link"); + return 1; + } + } + ASSERT (ret == 0); + + /* Now, for some behavior tests. Modify the contents of 'b', and + ensure that 'a' can see it, both while 'b' exists and after. */ + fd = open (BASE "b", O_APPEND | O_WRONLY); + ASSERT (0 <= fd); + ASSERT (write (fd, "world", 5) == 5); + ASSERT (close (fd) == 0); + { + char buf[11] = { 0 }; + fd = open (BASE "a", O_RDONLY); + ASSERT (0 <= fd); + ASSERT (read (fd, buf, 10) == 10); + ASSERT (strcmp (buf, "helloworld") == 0); + ASSERT (close (fd) == 0); + ASSERT (unlink (BASE "b") == 0); + fd = open (BASE "a", O_RDONLY); + ASSERT (0 <= fd); + ASSERT (read (fd, buf, 10) == 10); + ASSERT (strcmp (buf, "helloworld") == 0); + ASSERT (close (fd) == 0); + } + + /* Test for various error conditions. Assumes hard links to + directories are not permitted. */ + ASSERT (mkdir (BASE "d", 0700) == 0); + errno = 0; + ASSERT (func (BASE "a", ".") == -1); + ASSERT (errno == EEXIST || errno == EINVAL); + errno = 0; + ASSERT (func (BASE "a", BASE "a") == -1); + ASSERT (errno == EEXIST); + ASSERT (func (BASE "a", BASE "b") == 0); + errno = 0; + ASSERT (func (BASE "a", BASE "b") == -1); + ASSERT (errno == EEXIST); + errno = 0; + ASSERT (func (BASE "a", BASE "d") == -1); + ASSERT (errno == EEXIST); + errno = 0; + ASSERT (func (BASE "c", BASE "e") == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (func (BASE "a", BASE "c/.") == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (func (BASE "a/", BASE "c") == -1); + ASSERT (errno == ENOTDIR); + errno = 0; + ASSERT (func (BASE "a", BASE "c/") == -1); + ASSERT (errno == ENOTDIR || errno == ENOENT); + errno = 0; + ASSERT (func (BASE "d", BASE "c") == -1); + ASSERT (errno == EPERM || errno == EACCES); + + /* Clean up. */ + ASSERT (unlink (BASE "a") == 0); + ASSERT (unlink (BASE "b") == 0); + errno = 0; + ASSERT (unlink (BASE "c") == -1); + ASSERT (errno == ENOENT); + ASSERT (rmdir (BASE "d") == 0); + + return 0; +} diff --git a/tests/test-linkat.c b/tests/test-linkat.c new file mode 100644 index 000000000..afb179941 --- /dev/null +++ b/tests/test-linkat.c @@ -0,0 +1,352 @@ +/* Tests of linkat. + 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 + +#include "areadlink.h" +#include "filenamecat.h" +#include "same-inode.h" +#include "xgetcwd.h" + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +#define BASE "test-linkat.t" + +#include "test-link.h" + +static int dfd1 = AT_FDCWD; +static int dfd2 = AT_FDCWD; +static int flag = AT_SYMLINK_FOLLOW; + +/* Wrapper to test linkat like link. */ +static int +do_link (char const *name1, char const *name2) +{ + return linkat (dfd1, name1, dfd2, name2, flag); +} + +/* Wrapper to see if two symlinks act the same. */ +static void +check_same_link (char const *name1, char const *name2) +{ + struct stat st1; + struct stat st2; + char *contents1; + char *contents2; + ASSERT (lstat (name1, &st1) == 0); + ASSERT (lstat (name2, &st2) == 0); + contents1 = areadlink_with_size (name1, st1.st_size); + contents2 = areadlink_with_size (name2, st2.st_size); + ASSERT (contents1); + ASSERT (contents2); + ASSERT (strcmp (contents1, contents2) == 0); + if (!LINK_FOLLOWS_SYMLINKS) + ASSERT (SAME_INODE (st1, st2)); + free (contents1); + free (contents2); +} + +int +main () +{ + int i; + int dfd; + char *cwd; + int result; + + /* Clean up any trash from prior testsuite runs. */ + ASSERT (system ("rm -rf " BASE "*") == 0); + + /* Test basic link functionality, without mentioning symlinks. */ + { + result = test_link (do_link, false); + dfd1 = open (".", O_RDONLY); + ASSERT (0 <= dfd1); + ASSERT (test_link (do_link, false) == result); + dfd2 = dfd1; + ASSERT (test_link (do_link, false) == result); + dfd1 = AT_FDCWD; + ASSERT (test_link (do_link, false) == result); + flag = 0; + ASSERT (test_link (do_link, false) == result); + dfd1 = dfd2; + ASSERT (test_link (do_link, false) == result); + dfd2 = AT_FDCWD; + ASSERT (test_link (do_link, false) == result); + ASSERT (close (dfd1) == 0); + dfd1 = AT_FDCWD; + ASSERT (test_link (do_link, false) == result); + } + + /* Create locations to manipulate. */ + ASSERT (mkdir (BASE "sub1", 0700) == 0); + ASSERT (mkdir (BASE "sub2", 0700) == 0); + dfd = creat (BASE "00", 0600); + ASSERT (0 <= dfd); + ASSERT (close (dfd) == 0); + cwd = xgetcwd (); + + dfd = open (BASE "sub1", O_RDONLY); + ASSERT (0 <= dfd); + ASSERT (chdir (BASE "sub2") == 0); + + /* There are 16 possible scenarios, based on whether an fd is + AT_FDCWD or real, whether a file is absolute or relative, coupled + with whether flag is set for 32 iterations. + + To ensure that we test all of the code paths (rather than + triggering early normalization optimizations), we use a loop to + repeatedly rename a file in the parent directory, use an fd open + on subdirectory 1, all while executing in subdirectory 2; all + relative names are thus given with a leading "../". Finally, the + last scenario (two relative paths given, neither one AT_FDCWD) + has two paths, based on whether the two fds are equivalent, so we + do the other variant after the loop. */ + for (i = 0; i < 32; i++) + { + int flag = (i & 0x10 ? AT_SYMLINK_FOLLOW : 0); + int fd1 = (i & 8) ? dfd : AT_FDCWD; + char *file1 = file_name_concat ((i & 4) ? ".." : cwd, BASE "xx", NULL); + int fd2 = (i & 2) ? dfd : AT_FDCWD; + char *file2 = file_name_concat ((i & 1) ? ".." : cwd, BASE "xx", NULL); + + ASSERT (sprintf (strchr (file1, '\0') - 2, "%02d", i) == 2); + ASSERT (sprintf (strchr (file2, '\0') - 2, "%02d", i + 1) == 2); + ASSERT (linkat (fd1, file1, fd2, file2, flag) == 0); + ASSERT (unlinkat (fd1, file1, 0) == 0); + free (file1); + free (file2); + } + dfd2 = open ("..", O_RDONLY); + ASSERT (0 <= dfd2); + ASSERT (linkat (dfd, "../" BASE "32", dfd2, BASE "33", 0) == 0); + ASSERT (linkat (dfd, "../" BASE "33", dfd2, BASE "34", + AT_SYMLINK_FOLLOW) == 0); + ASSERT (close (dfd2) == 0); + + /* Now we change back to the parent directory, and set dfd to ".", + in order to test behavior on symlinks. */ + ASSERT (chdir ("..") == 0); + ASSERT (close (dfd) == 0); + if (symlink (BASE "sub1", BASE "link1")) + { + ASSERT (unlink (BASE "32") == 0); + ASSERT (unlink (BASE "33") == 0); + ASSERT (unlink (BASE "34") == 0); + ASSERT (rmdir (BASE "sub1") == 0); + ASSERT (rmdir (BASE "sub2") == 0); + free (cwd); + fputs ("skipping test: symlinks not supported on this filesystem\n", + stderr); + return result; + } + dfd = open (".", O_RDONLY); + ASSERT (0 <= dfd); + ASSERT (symlink (BASE "34", BASE "link2") == 0); + ASSERT (symlink (BASE "link3", BASE "link3") == 0); + ASSERT (symlink (BASE "nowhere", BASE "link4") == 0); + + /* Link cannot overwrite existing files. */ + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1", 0) == -1); + ASSERT (errno == EEXIST); + errno = 0; + ASSERT (linkat (dfd, BASE "link1/", dfd, BASE "sub1", 0) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1/", 0) == -1); + ASSERT (errno == EEXIST); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES); + errno = 0; + ASSERT (linkat (dfd, BASE "link1/", dfd, BASE "sub1", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1/", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link2", 0) == -1); + ASSERT (errno == EEXIST); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link2", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link3", 0) == -1); + ASSERT (errno == EEXIST || errno == ELOOP); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link3", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES + || errno == ELOOP); + errno = 0; + ASSERT (linkat (dfd, BASE "link2", dfd, BASE "link3", 0) == -1); + ASSERT (errno == EEXIST || errno == ELOOP); + errno = 0; + ASSERT (linkat (dfd, BASE "link2", dfd, BASE "link3", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == ELOOP); + + /* AT_SYMLINK_FOLLOW only follows first argument, not second. */ + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link4", 0) == -1); + ASSERT (errno == EEXIST); + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link4", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES); + errno = 0; + ASSERT (linkat (dfd, BASE "34", dfd, BASE "link4", 0) == -1); + ASSERT (errno == EEXIST); + errno = 0; + ASSERT (linkat (dfd, BASE "34", dfd, BASE "link4", AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EEXIST); + + /* Trailing slash handling. */ + errno = 0; + ASSERT (linkat (dfd, BASE "link2/", dfd, BASE "link5", 0) == -1); + ASSERT (errno == ENOTDIR); + errno = 0; + ASSERT (linkat (dfd, BASE "link2/", dfd, BASE "link5", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ENOTDIR); + errno = 0; + ASSERT (linkat (dfd, BASE "link3/", dfd, BASE "link5", 0) == -1); + ASSERT (errno == ELOOP); + errno = 0; + ASSERT (linkat (dfd, BASE "link3/", dfd, BASE "link5", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ELOOP); + errno = 0; + ASSERT (linkat (dfd, BASE "link4/", dfd, BASE "link5", 0) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (linkat (dfd, BASE "link4/", dfd, BASE "link5", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ENOENT); + + /* Check for hard links to symlinks. */ + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link5", 0) == 0); + check_same_link (BASE "link1", BASE "link5"); + ASSERT (unlink (BASE "link5") == 0); + errno = 0; + ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link5", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == EPERM || errno == EACCES); + ASSERT (linkat (dfd, BASE "link2", dfd, BASE "link5", 0) == 0); + check_same_link (BASE "link2", BASE "link5"); + ASSERT (unlink (BASE "link5") == 0); + ASSERT (linkat (dfd, BASE "link2", dfd, BASE "file", AT_SYMLINK_FOLLOW) == 0); + errno = 0; + ASSERT (areadlink (BASE "file") == NULL); + ASSERT (errno == EINVAL); + ASSERT (unlink (BASE "file") == 0); + ASSERT (linkat (dfd, BASE "link3", dfd, BASE "link5", 0) == 0); + check_same_link (BASE "link3", BASE "link5"); + ASSERT (unlink (BASE "link5") == 0); + errno = 0; + ASSERT (linkat (dfd, BASE "link3", dfd, BASE "link5", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ELOOP); + ASSERT (linkat (dfd, BASE "link4", dfd, BASE "link5", 0) == 0); + check_same_link (BASE "link4", BASE "link5"); + ASSERT (unlink (BASE "link5") == 0); + errno = 0; + ASSERT (linkat (dfd, BASE "link4", dfd, BASE "link5", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ENOENT); + + /* Check that symlink to symlink to file is followed all the way. */ + ASSERT (symlink (BASE "link2", BASE "link5") == 0); + ASSERT (linkat (dfd, BASE "link5", dfd, BASE "link6", 0) == 0); + check_same_link (BASE "link5", BASE "link6"); + ASSERT (unlink (BASE "link6") == 0); + ASSERT (linkat (dfd, BASE "link5", dfd, BASE "file", AT_SYMLINK_FOLLOW) == 0); + errno = 0; + ASSERT (areadlink (BASE "file") == NULL); + ASSERT (errno == EINVAL); + ASSERT (unlink (BASE "link5") == 0); + ASSERT (symlink (BASE "link3", BASE "link5") == 0); + errno = 0; + ASSERT (linkat (dfd, BASE "link5", dfd, BASE "file", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ELOOP); + ASSERT (unlink (BASE "link5") == 0); + ASSERT (symlink (BASE "link4", BASE "link5") == 0); + errno = 0; + ASSERT (linkat (dfd, BASE "link5", dfd, BASE "file", + AT_SYMLINK_FOLLOW) == -1); + ASSERT (errno == ENOENT); + + /* Now for some real fun with directory crossing. */ + ASSERT (symlink (cwd, BASE "sub1/link") == 0); + ASSERT (symlink (".././/" BASE "sub1/link/" BASE "link2", + BASE "sub2/link") == 0); + ASSERT (close (dfd) == 0); + dfd = open (BASE "sub1", O_RDONLY); + ASSERT (0 <= dfd); + dfd2 = open (BASE "sub2", O_RDONLY); + ASSERT (0 < dfd2); + ASSERT (linkat (dfd, "../" BASE "sub2/link", dfd2, "./..//" BASE "sub1/file", + AT_SYMLINK_FOLLOW) == 0); + errno = 0; + ASSERT (areadlink (BASE "sub1/file") == NULL); + ASSERT (errno == EINVAL); + + /* Cleanup. */ + ASSERT (close (dfd) == 0); + ASSERT (close (dfd2) == 0); + ASSERT (unlink (BASE "sub1/file") == 0); + ASSERT (unlink (BASE "sub1/link") == 0); + ASSERT (unlink (BASE "sub2/link") == 0); + ASSERT (unlink (BASE "32") == 0); + ASSERT (unlink (BASE "33") == 0); + ASSERT (unlink (BASE "34") == 0); + ASSERT (rmdir (BASE "sub1") == 0); + ASSERT (rmdir (BASE "sub2") == 0); + ASSERT (unlink (BASE "link1") == 0); + ASSERT (unlink (BASE "link2") == 0); + ASSERT (unlink (BASE "link3") == 0); + ASSERT (unlink (BASE "link4") == 0); + ASSERT (unlink (BASE "link5") == 0); + ASSERT (unlink (BASE "file") == 0); + free (cwd); + return result; +} -- 2.11.0