From: Paul Eggert Date: Thu, 27 Jul 2017 19:08:39 +0000 (-0700) Subject: renameat2: new module X-Git-Tag: v1.0~6042 X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=ec046e66851edd0fe624d7d3f5f6345ffde64d76;p=gnulib.git 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 . * modules/renameat-tests (test_renameat_LDADD): Add $(LIB_EACCESS), since renameat (via renameat2) might use faccessat. --- diff --git a/ChangeLog b/ChangeLog index 36dd0424ed..d922e31a18 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2017-07-27 Paul Eggert + + 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 . + * modules/renameat-tests (test_renameat_LDADD): Add $(LIB_EACCESS), + since renameat (via renameat2) might use faccessat. + 2017-07-27 Erik Skultety (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 . */ -/* written by Eric Blake */ - #include - #include - -#if HAVE_RENAMEAT - -# include -# include -# include -# include -# include - -# 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 . */ + +/* written by Eric Blake and Paul Eggert */ + +#include + +#include "renameat2.h" + +#include +#include +#include + +#ifdef __linux__ +# include +#endif + +static int +errno_fail (int e) +{ + errno = e; + return -1; +} + +#if HAVE_RENAMEAT + +# include +# include +# include +# include + +# 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 . */ + +/* written by Paul Eggert */ + +/* Get RENAME_* macros from linux/fs.h if present, otherwise supply + the traditional Linux values. */ +#ifdef __linux__ +# include +#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: - 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 . */ + +/* Written by Eric Blake , 2009. */ + +#include + +#include + +#include + +#include "signature.h" +SIGNATURE_CHECK (renameat2, int, + (int, char const *, int, char const *, unsigned int)); + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}