summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog16
-rwxr-xr-xMODULES.html.sh1
-rw-r--r--lib/renameat.c140
-rw-r--r--lib/renameat2.c205
-rw-r--r--lib/renameat2.h30
-rw-r--r--modules/renameat17
-rw-r--r--modules/renameat-tests2
-rw-r--r--modules/renameat243
-rw-r--r--modules/renameat2-tests20
-rw-r--r--tests/test-renameat2.c209
10 files changed, 530 insertions, 153 deletions
diff --git a/ChangeLog b/ChangeLog
index 36dd0424ed..d922e31a18 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2017-07-27 Paul Eggert <eggert@cs.ucla.edu>
+
+ renameat2: new module
+ Although the Linux syscall renameat2 is not in glibc (yet?), it is
+ useful to have access to its RENAME_NOREPLACE flag.
+ * MODULES.html.sh (func_all_modules): Add renameat2.
+ * lib/renameat2.c, lib/renameat2.h, modules/renameat2:
+ * modules/renameat2-tests, tests/test-renameat2.c: New files.
+ * lib/renameat.c (renameat): Move most of the implementation
+ to renameat2, and just call renameat2.
+ * modules/renameat (Files): Remove lib/at-func2.c.
+ (Depends-on): Depend only on renameat2.
+ (Include): Remove <fcntl.h>.
+ * modules/renameat-tests (test_renameat_LDADD): Add $(LIB_EACCESS),
+ since renameat (via renameat2) might use faccessat.
+
2017-07-27 Erik Skultety <eskultet@redhat.com> (tiny change)
vc-list-files: Adjust the script to support git worktrees
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 367d3cf906..102223d179 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2677,6 +2677,7 @@ func_all_modules ()
func_module qset-acl
func_module read-file
func_module readlinkat
+ func_module renameat2
func_module same
func_module save-cwd
func_module savedir
diff --git a/lib/renameat.c b/lib/renameat.c
index 22151c2d88..af25a54fab 100644
--- a/lib/renameat.c
+++ b/lib/renameat.c
@@ -1,5 +1,5 @@
/* Rename a file relative to open directories.
- Copyright (C) 2009-2017 Free Software Foundation, Inc.
+ Copyright 2017 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
@@ -14,144 +14,12 @@
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 <stdio.h>
-
-#if HAVE_RENAMEAT
-
-# include <errno.h>
-# include <stdbool.h>
-# include <stdlib.h>
-# include <string.h>
-# include <sys/stat.h>
-
-# include "dirname.h"
-# include "openat.h"
-
-# undef renameat
-
-/* renameat does not honor trailing / on Solaris 10. Solve it in a
- similar manner to rename. No need to worry about bugs not present
- on Solaris, since all other systems either lack renameat or honor
- trailing slash correctly. */
-
-int
-rpl_renameat (int fd1, char const *src, int fd2, char const *dst)
-{
- size_t src_len = strlen (src);
- size_t dst_len = strlen (dst);
- char *src_temp = (char *) src;
- char *dst_temp = (char *) dst;
- bool src_slash;
- bool dst_slash;
- int ret_val = -1;
- int rename_errno = ENOTDIR;
- struct stat src_st;
- struct stat dst_st;
-
- /* Let strace see any ENOENT failure. */
- if (!src_len || !dst_len)
- return renameat (fd1, src, fd2, dst);
-
- src_slash = src[src_len - 1] == '/';
- dst_slash = dst[dst_len - 1] == '/';
- if (!src_slash && !dst_slash)
- return renameat (fd1, src, fd2, dst);
-
- /* Presence of a trailing slash requires directory semantics. If
- the source does not exist, or if the destination cannot be turned
- into a directory, give up now. Otherwise, strip trailing slashes
- before calling rename. */
- if (lstatat (fd1, src, &src_st))
- return -1;
- if (lstatat (fd2, dst, &dst_st))
- {
- if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
- return -1;
- }
- else if (!S_ISDIR (dst_st.st_mode))
- {
- errno = ENOTDIR;
- return -1;
- }
- else if (!S_ISDIR (src_st.st_mode))
- {
- errno = EISDIR;
- return -1;
- }
-
-# if RENAME_TRAILING_SLASH_SOURCE_BUG
- /* See the lengthy comment in rename.c why Solaris 9 is forced to
- GNU behavior, while Solaris 10 is left with POSIX behavior,
- regarding symlinks with trailing slash. */
- if (src_slash)
- {
- src_temp = strdup (src);
- if (!src_temp)
- {
- /* Rather than rely on strdup-posix, we set errno ourselves. */
- rename_errno = ENOMEM;
- goto out;
- }
- strip_trailing_slashes (src_temp);
- if (lstatat (fd1, src_temp, &src_st))
- {
- rename_errno = errno;
- goto out;
- }
- if (S_ISLNK (src_st.st_mode))
- goto out;
- }
- if (dst_slash)
- {
- dst_temp = strdup (dst);
- if (!dst_temp)
- {
- rename_errno = ENOMEM;
- goto out;
- }
- strip_trailing_slashes (dst_temp);
- if (lstatat (fd2, dst_temp, &dst_st))
- {
- if (errno != ENOENT)
- {
- rename_errno = errno;
- goto out;
- }
- }
- else if (S_ISLNK (dst_st.st_mode))
- goto out;
- }
-# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
-
- ret_val = renameat (fd1, src_temp, fd2, dst_temp);
- rename_errno = errno;
- out:
- if (src_temp != src)
- free (src_temp);
- if (dst_temp != dst)
- free (dst_temp);
- errno = rename_errno;
- return ret_val;
-}
-
-#else /* !HAVE_RENAMEAT */
-
-# include "openat-priv.h"
-
-/* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
- the directory open on descriptor FD2. 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. */
+#include "renameat2.h"
int
-renameat (int fd1, char const *file1, int fd2, char const *file2)
+renameat (int fd1, char const *src, int fd2, char const *dst)
{
- return at_func2 (fd1, file1, fd2, file2, rename);
+ return renameat2 (fd1, src, fd2, dst, 0);
}
-
-#endif /* !HAVE_RENAMEAT */
diff --git a/lib/renameat2.c b/lib/renameat2.c
new file mode 100644
index 0000000000..19df58c1d4
--- /dev/null
+++ b/lib/renameat2.c
@@ -0,0 +1,205 @@
+/* Rename a file relative to open directories.
+ Copyright (C) 2009-2017 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 and Paul Eggert */
+
+#include <config.h>
+
+#include "renameat2.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef __linux__
+# include <sys/syscall.h>
+#endif
+
+static int
+errno_fail (int e)
+{
+ errno = e;
+ return -1;
+}
+
+#if HAVE_RENAMEAT
+
+# include <stdbool.h>
+# include <stdlib.h>
+# include <string.h>
+# include <sys/stat.h>
+
+# include "dirname.h"
+# include "openat.h"
+
+#else
+# include "openat-priv.h"
+
+static int
+rename_noreplace (char const *src, char const *dst)
+{
+ /* This has a race between the call to faccessat and the call to rename. */
+ int r = faccessat (AT_FDCWD, dst, F_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW);
+ reurn (r == 0 ? errno_fail (EEXIST)
+ : errno == ENOENT ? rename (src, dst)
+ : r);
+}
+#endif
+
+
+/* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
+ the directory open on descriptor FD2. 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.
+
+ Obey FLAGS when doing the renaming. If FLAGS is zero, this
+ function is equivalent to renameat (FD1, SRC, FD2, DST). */
+
+int
+renameat2 (int fd1, char const *src, int fd2, char const *dst,
+ unsigned int flags)
+{
+#ifdef SYS_renameat2
+ int r = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
+ if (! (r < 0 && errno == ENOSYS))
+ return r;
+#endif
+
+#if HAVE_RENAMEAT
+ {
+ size_t src_len;
+ size_t dst_len;
+ char *src_temp = (char *) src;
+ char *dst_temp = (char *) dst;
+ bool src_slash;
+ bool dst_slash;
+ int ret_val;
+ int rename_errno = ENOTDIR;
+ struct stat src_st;
+ struct stat dst_st;
+
+ if (flags != 0)
+ {
+ int r;
+ /* RENAME_NOREPLACE is the only flag currently supported. */
+ if (flags & ~RENAME_NOREPLACE)
+ return errno_fail (ENOTSUP);
+ /* This has a race between the call to faccessat and the
+ call to renameat. */
+ r = faccessat (fd2, dst, F_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW);
+ if (r == 0)
+ return errno_fail (EEXIST);
+ if (errno != ENOENT)
+ return r;
+ }
+
+ /* Let strace see any ENOENT failure. */
+ src_len = strlen (src);
+ dst_len = strlen (dst);
+ if (!src_len || !dst_len)
+ return renameat (fd1, src, fd2, dst);
+
+ src_slash = src[src_len - 1] == '/';
+ dst_slash = dst[dst_len - 1] == '/';
+ if (!src_slash && !dst_slash)
+ return renameat (fd1, src, fd2, dst);
+
+ /* Presence of a trailing slash requires directory semantics. If
+ the source does not exist, or if the destination cannot be turned
+ into a directory, give up now. Otherwise, strip trailing slashes
+ before calling rename. */
+ if (lstatat (fd1, src, &src_st))
+ return -1;
+ if (lstatat (fd2, dst, &dst_st))
+ {
+ if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
+ return -1;
+ }
+ else if (!S_ISDIR (dst_st.st_mode))
+ return errno_fail (ENOTDIR);
+ else if (!S_ISDIR (src_st.st_mode))
+ return errno_fail (EISDIR);
+
+# if RENAME_TRAILING_SLASH_SOURCE_BUG
+ /* See the lengthy comment in rename.c why Solaris 9 is forced to
+ GNU behavior, while Solaris 10 is left with POSIX behavior,
+ regarding symlinks with trailing slash. */
+ if (src_slash)
+ {
+ src_temp = strdup (src);
+ if (!src_temp)
+ {
+ /* Rather than rely on strdup-posix, we set errno ourselves. */
+ rename_errno = ENOMEM;
+ goto out;
+ }
+ strip_trailing_slashes (src_temp);
+ if (lstatat (fd1, src_temp, &src_st))
+ {
+ rename_errno = errno;
+ goto out;
+ }
+ if (S_ISLNK (src_st.st_mode))
+ goto out;
+ }
+ if (dst_slash)
+ {
+ dst_temp = strdup (dst);
+ if (!dst_temp)
+ {
+ rename_errno = ENOMEM;
+ goto out;
+ }
+ strip_trailing_slashes (dst_temp);
+ if (lstatat (fd2, dst_temp, &dst_st))
+ {
+ if (errno != ENOENT)
+ {
+ rename_errno = errno;
+ goto out;
+ }
+ }
+ else if (S_ISLNK (dst_st.st_mode))
+ goto out;
+ }
+# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
+
+ /* renameat does not honor trailing / on Solaris 10. Solve it in a
+ similar manner to rename. No need to worry about bugs not present
+ on Solaris, since all other systems either lack renameat or honor
+ trailing slash correctly. */
+
+ ret_val = renameat (fd1, src_temp, fd2, dst_temp);
+ rename_errno = errno;
+ out:
+ if (src_temp != src)
+ free (src_temp);
+ if (dst_temp != dst)
+ free (dst_temp);
+ errno = rename_errno;
+ return ret_val;
+ }
+#else /* !HAVE_RENAMEAT */
+
+ /* RENAME_NOREPLACE is the only flag currently supported. */
+ if (flags & ~RENAME_NOREPLACE)
+ return errno_fail (ENOTSUP);
+ return at_func2 (fd1, file1, fd2, file2,
+ flags ? rename_noreplace : rename);
+
+#endif /* !HAVE_RENAMEAT */
+}
diff --git a/lib/renameat2.h b/lib/renameat2.h
new file mode 100644
index 0000000000..da3d78c603
--- /dev/null
+++ b/lib/renameat2.h
@@ -0,0 +1,30 @@
+/* Rename a file relative to open directories.
+ Copyright 2017 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 Paul Eggert */
+
+/* Get RENAME_* macros from linux/fs.h if present, otherwise supply
+ the traditional Linux values. */
+#ifdef __linux__
+# include <linux/fs.h>
+#endif
+#ifndef RENAME_NOREPLACE
+# define RENAME_NOREPLACE (1 << 0)
+# define RENAME_EXCHANGE (1 << 1)
+# define RENAME_WHITEOUT (1 << 2)
+#endif
+
+extern int renameat2 (int, char const *, int, char const *, unsigned int);
diff --git a/modules/renameat b/modules/renameat
index 77d93eab1a..9af0225052 100644
--- a/modules/renameat
+++ b/modules/renameat
@@ -2,25 +2,11 @@ Description:
renameat() function: rename a file, relative to two directories
Files:
-lib/at-func2.c
lib/renameat.c
m4/renameat.m4
Depends-on:
-stdio
-extensions
-fcntl-h
-filenamecat-lgpl [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
-openat-h [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
-statat [test $REPLACE_RENAMEAT = 1]
-stdbool [test $REPLACE_RENAMEAT = 1]
-at-internal [test $HAVE_RENAMEAT = 0]
-dosname [test $HAVE_RENAMEAT = 0]
-getcwd-lgpl [test $HAVE_RENAMEAT = 0]
-openat-die [test $HAVE_RENAMEAT = 0]
-rename [test $HAVE_RENAMEAT = 0]
-same-inode [test $HAVE_RENAMEAT = 0]
-save-cwd [test $HAVE_RENAMEAT = 0]
+renameat2 [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
configure.ac:
gl_FUNC_RENAMEAT
@@ -35,7 +21,6 @@ gl_STDIO_MODULE_INDICATOR([renameat])
Makefile.am:
Include:
-<fcntl.h>
<stdio.h>
License:
diff --git a/modules/renameat-tests b/modules/renameat-tests
index 970ebd7baa..4e9bf2fa17 100644
--- a/modules/renameat-tests
+++ b/modules/renameat-tests
@@ -17,4 +17,4 @@ configure.ac:
Makefile.am:
TESTS += test-renameat
check_PROGRAMS += test-renameat
-test_renameat_LDADD = $(LDADD) @LIBINTL@
+test_renameat_LDADD = $(LDADD) $(LIB_EACCESS) @LIBINTL@
diff --git a/modules/renameat2 b/modules/renameat2
new file mode 100644
index 0000000000..f0ce1958d0
--- /dev/null
+++ b/modules/renameat2
@@ -0,0 +1,43 @@
+Description:
+renameat2() function: rename a file, relative to two directories
+
+Files:
+lib/at-func2.c
+lib/renameat2.c
+lib/renameat2.h
+m4/renameat.m4
+
+Depends-on:
+stdio
+extensions
+faccessat
+fcntl-h
+filenamecat-lgpl [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
+openat-h [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
+statat [test $REPLACE_RENAMEAT = 1]
+stdbool [test $REPLACE_RENAMEAT = 1]
+at-internal [test $HAVE_RENAMEAT = 0]
+dosname [test $HAVE_RENAMEAT = 0]
+getcwd-lgpl [test $HAVE_RENAMEAT = 0]
+openat-die [test $HAVE_RENAMEAT = 0]
+rename [test $HAVE_RENAMEAT = 0]
+same-inode [test $HAVE_RENAMEAT = 0]
+save-cwd [test $HAVE_RENAMEAT = 0]
+
+configure.ac:
+gl_FUNC_RENAMEAT
+if test $HAVE_RENAMEAT = 0; then
+ AC_LIBOBJ([at-func2])
+fi
+
+Makefile.am:
+lib_SOURCES += renameat2.c
+
+Include:
+"renameat2.h"
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
diff --git a/modules/renameat2-tests b/modules/renameat2-tests
new file mode 100644
index 0000000000..efcc33d33e
--- /dev/null
+++ b/modules/renameat2-tests
@@ -0,0 +1,20 @@
+Files:
+tests/test-rename.h
+tests/test-renameat2.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+ignore-value
+filenamecat
+getcwd-lgpl
+opendir
+readdir
+closedir
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-renameat2
+check_PROGRAMS += test-renameat2
+test_renameat2_LDADD = $(LDADD) $(LIB_EACCESS) @LIBINTL@
diff --git a/tests/test-renameat2.c b/tests/test-renameat2.c
new file mode 100644
index 0000000000..17a3b8eda9
--- /dev/null
+++ b/tests/test-renameat2.c
@@ -0,0 +1,209 @@
+/* Test renameat2.
+ Copyright (C) 2009-2017 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 <renameat2.h>
+
+#include <stdio.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (renameat2, int,
+ (int, char const *, int, char const *, unsigned int));
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "filenamecat.h"
+#include "ignore-value.h"
+#include "macros.h"
+
+#define BASE "test-renameat2.t"
+
+#include "test-rename.h"
+
+static int dfd1 = AT_FDCWD;
+static int dfd2 = AT_FDCWD;
+
+/* Wrapper to test renameat2 like rename. */
+static int
+do_rename (char const *name1, char const *name2)
+{
+ return renameat2 (dfd1, name1, dfd2, name2, 0);
+}
+
+int
+main (void)
+{
+ int i;
+ int dfd;
+ char *cwd;
+ int result;
+
+ /* Clean up any trash from prior testsuite runs. */
+ ignore_value (system ("rm -rf " BASE "*"));
+
+ /* Test behaviour for invalid file descriptors. */
+ {
+ errno = 0;
+ ASSERT (renameat2 (-1, "foo", AT_FDCWD, "bar", 0) == -1);
+ ASSERT (errno == EBADF);
+ }
+ {
+ close (99);
+ errno = 0;
+ ASSERT (renameat2 (99, "foo", AT_FDCWD, "bar", 0) == -1);
+ ASSERT (errno == EBADF);
+ }
+ ASSERT (close (creat (BASE "oo", 0600)) == 0);
+ {
+ errno = 0;
+ ASSERT (renameat2 (AT_FDCWD, BASE "oo", -1, "bar", 0) == -1);
+ ASSERT (errno == EBADF);
+ }
+ {
+ errno = 0;
+ ASSERT (renameat2 (AT_FDCWD, BASE "oo", 99, "bar", 0) == -1);
+ ASSERT (errno == EBADF);
+ }
+ ASSERT (unlink (BASE "oo") == 0);
+
+ /* Test basic rename functionality, using current directory. */
+ result = test_rename (do_rename, false);
+ dfd1 = open (".", O_RDONLY);
+ ASSERT (0 <= dfd1);
+ ASSERT (test_rename (do_rename, false) == result);
+ dfd2 = dfd1;
+ ASSERT (test_rename (do_rename, false) == result);
+ dfd1 = AT_FDCWD;
+ ASSERT (test_rename (do_rename, false) == result);
+ ASSERT (close (dfd2) == 0);
+
+ /* 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 = getcwd (NULL, 0);
+ ASSERT (cwd);
+
+ 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, and whether a file is absolute or relative.
+
+ 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 < 16; i++)
+ {
+ 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 (renameat2 (fd1, file1, fd2, file2, 0) == 0);
+ free (file1);
+ free (file2);
+ }
+ dfd2 = open ("..", O_RDONLY);
+ ASSERT (0 <= dfd2);
+ ASSERT (renameat2 (dfd, "../" BASE "16", dfd2, BASE "17", 0) == 0);
+ ASSERT (close (dfd2) == 0);
+
+ /* Now we change back to the parent directory, and set dfd to ".";
+ using dfd in remaining tests will expose any bugs if emulation
+ via /proc/self/fd doesn't check for empty names. */
+ ASSERT (chdir ("..") == 0);
+ ASSERT (close (dfd) == 0);
+ dfd = open (".", O_RDONLY);
+ ASSERT (0 <= dfd);
+
+ ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "sub1", dfd, BASE "sub2", 0) == -1);
+ ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+ ASSERT (unlink (BASE "sub2/file") == 0);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "sub2", dfd, BASE "sub1/.", 0) == -1);
+ ASSERT (errno == EINVAL || errno == EISDIR || errno == EBUSY
+ || errno == ENOTEMPTY || errno == EEXIST);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "sub2/.", dfd, BASE "sub1", 0) == -1);
+ ASSERT (errno == EINVAL || errno == EBUSY || errno == EEXIST);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "17", dfd, BASE "sub1", 0) == -1);
+ ASSERT (errno == EISDIR);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "nosuch", dfd, BASE "18", 0) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (renameat2 (dfd, "", dfd, BASE "17", 0) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "17", dfd, "", 0) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "sub2", dfd, BASE "17", 0) == -1);
+ ASSERT (errno == ENOTDIR);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "17/", dfd, BASE "18", 0) == -1);
+ ASSERT (errno == ENOTDIR);
+ errno = 0;
+ ASSERT (renameat2 (dfd, BASE "17", dfd, BASE "18/", 0) == -1);
+ ASSERT (errno == ENOTDIR || errno == ENOENT);
+
+ /* Finally, make sure we cannot overwrite existing files. */
+ ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
+ errno = 0;
+ ASSERT ((renameat2 (dfd, BASE "sub2", dfd, BASE "sub1", RENAME_NOREPLACE)
+ == -1)
+ && errno == EEXIST);
+ ASSERT ((renameat2 (dfd, BASE "sub2/file", dfd, BASE "17", RENAME_NOREPLACE)
+ == -1)
+ && errno == EEXIST);
+
+ /* Cleanup. */
+ ASSERT (close (dfd) == 0);
+ ASSERT (unlink (BASE "sub2/file") == 0);
+ ASSERT (unlink (BASE "17") == 0);
+ ASSERT (rmdir (BASE "sub1") == 0);
+ ASSERT (rmdir (BASE "sub2") == 0);
+ free (cwd);
+
+ if (result)
+ fputs ("skipping test: symlinks not supported on this file system\n",
+ stderr);
+ return result;
+}