summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Blake <ebb9@byu.net>2009-09-08 20:47:40 -0600
committerEric Blake <ebb9@byu.net>2009-09-23 21:04:27 -0600
commitc4194dcc56767f8f96bc005088b292f519b13910 (patch)
treee5d90eb9b6ce3dfb8cb85b2957e6f24a0e60554e
parentc6dc1761b3e928d2de0a6116cd933b3147ffd7d8 (diff)
downloadgnulib-c4194dcc56767f8f96bc005088b292f519b13910.tar.gz
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 <ebb9@byu.net>
-rw-r--r--ChangeLog19
-rwxr-xr-xMODULES.html.sh4
-rw-r--r--doc/posix-functions/link.texi4
-rw-r--r--doc/posix-functions/linkat.texi10
-rw-r--r--lib/at-func2.c282
-rw-r--r--lib/linkat.c181
-rw-r--r--lib/openat-priv.h5
-rw-r--r--lib/unistd.in.h15
-rw-r--r--m4/linkat.m425
-rw-r--r--m4/unistd_h.m44
-rw-r--r--modules/link-tests2
-rw-r--r--modules/linkat40
-rw-r--r--modules/linkat-tests16
-rw-r--r--modules/unistd2
-rw-r--r--tests/test-link.c111
-rw-r--r--tests/test-link.h137
-rw-r--r--tests/test-linkat.c352
17 files changed, 1095 insertions, 114 deletions
diff --git a/ChangeLog b/ChangeLog
index 0ba1bbe5f5..855e69dbaf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,24 @@
2009-09-23 Eric Blake <ebb9@byu.net>
+ 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 ad00ac883d..42cb57c67e 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 c785371a0f..c06f0a6ce9 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 1c08c7e60d..62fc43dc33 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 0000000000..a19b60b72f
--- /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 <http://www.gnu.org/licenses/>. */
+
+/* written by Jim Meyering and Eric Blake */
+
+#include <config.h>
+
+#include "openat-priv.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 0000000000..bda0627569
--- /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 <http://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include "areadlink.h"
+#include "dirname.h"
+#include "filenamecat.h"
+#include "openat-priv.h"
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#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 fa286b5097..53016a14ec 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 600f224082..8a96e792b0 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 0000000000..be68c5fc79
--- /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 23345823b7..16daed884c 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 ca61deb707..d8e7b1a2d5 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 0000000000..8d9dec3417
--- /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:
+<fcntl.h>
+<unistd.h>
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
diff --git a/modules/linkat-tests b/modules/linkat-tests
new file mode 100644
index 0000000000..9fb6505201
--- /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 875efb0cc9..d21a20447c 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 e09a0bb170..a77ffe76d7 100644
--- a/tests/test-link.c
+++ b/tests/test-link.c
@@ -20,6 +20,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -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 0000000000..9ce1894588
--- /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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* 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 0000000000..afb1799412
--- /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 <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}