Blame SOURCES/coreutils-8.22-mv-n-noreplace.patch

6ee4d0
From 76df06ff8fa39ae0cb0d167b7f622139778dc7d7 Mon Sep 17 00:00:00 2001
6ee4d0
From: Kamil Dudka <kdudka@redhat.com>
6ee4d0
Date: Thu, 4 Jan 2018 09:42:10 +0100
6ee4d0
Subject: [PATCH] mv -n: do not overwrite the destination
6ee4d0
6ee4d0
... if it is created by another process after mv has checked its
6ee4d0
non-existence.
6ee4d0
6ee4d0
* src/copy.c (copy_internal): Use renameat2 (..., RENAME_NOREPLACE)
6ee4d0
if called by mv -n.  If it fails with EEXIST in that case, pretend
6ee4d0
successful rename as if the existing destination file was detected
6ee4d0
by the preceding lstat call.
6ee4d0
6ee4d0
Fixes https://bugs.gnu.org/29961
6ee4d0
---
6ee4d0
 aclocal.m4                    |   1 +
6ee4d0
 bootstrap.conf                |   2 +
6ee4d0
 gnulib-tests/gnulib.mk        |  18 ++++
6ee4d0
 gnulib-tests/test-renameat.c  | 206 ++++++++++++++++++++++++++++++++++++++
6ee4d0
 gnulib-tests/test-renameat2.c | 209 ++++++++++++++++++++++++++++++++++++++
6ee4d0
 lib/gnulib.mk                 |  21 +++-
6ee4d0
 lib/renameat.c                |  25 +++++
6ee4d0
 lib/renameat2.c               | 227 ++++++++++++++++++++++++++++++++++++++++++
6ee4d0
 lib/renameat2.h               |  30 ++++++
6ee4d0
 m4/gnulib-comp.m4             |  22 ++++
6ee4d0
 m4/renameat.m4                |  25 +++++
6ee4d0
 src/copy.c                    |  27 ++++-
6ee4d0
 12 files changed, 808 insertions(+), 5 deletions(-)
6ee4d0
 create mode 100644 gnulib-tests/test-renameat.c
6ee4d0
 create mode 100644 gnulib-tests/test-renameat2.c
6ee4d0
 create mode 100644 lib/renameat.c
6ee4d0
 create mode 100644 lib/renameat2.c
6ee4d0
 create mode 100644 lib/renameat2.h
6ee4d0
 create mode 100644 m4/renameat.m4
6ee4d0
6ee4d0
diff --git a/aclocal.m4 b/aclocal.m4
6ee4d0
index 9c5a2b0..c678967 100644
6ee4d0
--- a/aclocal.m4
6ee4d0
+++ b/aclocal.m4
6ee4d0
@@ -1332,6 +1332,7 @@ m4_include([m4/realloc.m4])
6ee4d0
 m4_include([m4/regex.m4])
6ee4d0
 m4_include([m4/remove.m4])
6ee4d0
 m4_include([m4/rename.m4])
6ee4d0
+m4_include([m4/renameat.m4])
6ee4d0
 m4_include([m4/rewinddir.m4])
6ee4d0
 m4_include([m4/rmdir.m4])
6ee4d0
 m4_include([m4/rpmatch.m4])
6ee4d0
diff --git a/bootstrap.conf b/bootstrap.conf
6ee4d0
index 7def1f9..9b7c913 100644
6ee4d0
--- a/bootstrap.conf
6ee4d0
+++ b/bootstrap.conf
6ee4d0
@@ -199,6 +199,8 @@ gnulib_modules="
6ee4d0
   regex
6ee4d0
   remove
6ee4d0
   rename
6ee4d0
+  renameat
6ee4d0
+  renameat2
6ee4d0
   rmdir
6ee4d0
   root-dev-ino
6ee4d0
   rpmatch
6ee4d0
diff --git a/gnulib-tests/gnulib.mk b/gnulib-tests/gnulib.mk
6ee4d0
index b2da030..38d439c 100644
6ee4d0
--- a/gnulib-tests/gnulib.mk
6ee4d0
+++ b/gnulib-tests/gnulib.mk
6ee4d0
@@ -1676,6 +1676,24 @@ EXTRA_DIST += test-rename.h test-rename.c signature.h macros.h
6ee4d0
 
6ee4d0
 ## end   gnulib module rename-tests
6ee4d0
 
6ee4d0
+## begin gnulib module renameat-tests
6ee4d0
+
6ee4d0
+TESTS += test-renameat
6ee4d0
+check_PROGRAMS += test-renameat
6ee4d0
+test_renameat_LDADD = $(LDADD) @LIBINTL@
6ee4d0
+EXTRA_DIST += test-rename.h test-renameat.c signature.h macros.h
6ee4d0
+
6ee4d0
+## end   gnulib module renameat-tests
6ee4d0
+
6ee4d0
+## begin gnulib module renameat2-tests
6ee4d0
+
6ee4d0
+TESTS += test-renameat2
6ee4d0
+check_PROGRAMS += test-renameat2
6ee4d0
+test_renameat2_LDADD = $(LDADD) @LIBINTL@
6ee4d0
+EXTRA_DIST += test-rename.h test-renameat2.c signature.h macros.h
6ee4d0
+
6ee4d0
+## end   gnulib module renameat2-tests
6ee4d0
+
6ee4d0
 ## begin gnulib module rmdir-tests
6ee4d0
 
6ee4d0
 TESTS += test-rmdir
6ee4d0
diff --git a/gnulib-tests/test-renameat.c b/gnulib-tests/test-renameat.c
6ee4d0
new file mode 100644
6ee4d0
index 0000000..ac96d88
6ee4d0
--- /dev/null
6ee4d0
+++ b/gnulib-tests/test-renameat.c
6ee4d0
@@ -0,0 +1,206 @@
6ee4d0
+/* Tests of renameat.
6ee4d0
+   Copyright (C) 2009-2017 Free Software Foundation, Inc.
6ee4d0
+
6ee4d0
+   This program is free software: you can redistribute it and/or modify
6ee4d0
+   it under the terms of the GNU General Public License as published by
6ee4d0
+   the Free Software Foundation; either version 3 of the License, or
6ee4d0
+   (at your option) any later version.
6ee4d0
+
6ee4d0
+   This program is distributed in the hope that it will be useful,
6ee4d0
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ee4d0
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6ee4d0
+   GNU General Public License for more details.
6ee4d0
+
6ee4d0
+   You should have received a copy of the GNU General Public License
6ee4d0
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
6ee4d0
+
6ee4d0
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
6ee4d0
+
6ee4d0
+#include <config.h>
6ee4d0
+
6ee4d0
+#include <stdio.h>
6ee4d0
+
6ee4d0
+#include "signature.h"
6ee4d0
+SIGNATURE_CHECK (renameat, int, (int, char const *, int, char const *));
6ee4d0
+
6ee4d0
+#include <dirent.h>
6ee4d0
+#include <fcntl.h>
6ee4d0
+#include <errno.h>
6ee4d0
+#include <stdbool.h>
6ee4d0
+#include <stdlib.h>
6ee4d0
+#include <string.h>
6ee4d0
+#include <unistd.h>
6ee4d0
+#include <sys/stat.h>
6ee4d0
+
6ee4d0
+#include "filenamecat.h"
6ee4d0
+#include "ignore-value.h"
6ee4d0
+#include "macros.h"
6ee4d0
+
6ee4d0
+#define BASE "test-renameat.t"
6ee4d0
+
6ee4d0
+#include "test-rename.h"
6ee4d0
+
6ee4d0
+static int dfd1 = AT_FDCWD;
6ee4d0
+static int dfd2 = AT_FDCWD;
6ee4d0
+
6ee4d0
+/* Wrapper to test renameat like rename.  */
6ee4d0
+static int
6ee4d0
+do_rename (char const *name1, char const *name2)
6ee4d0
+{
6ee4d0
+  return renameat (dfd1, name1, dfd2, name2);
6ee4d0
+}
6ee4d0
+
6ee4d0
+int
6ee4d0
+main (void)
6ee4d0
+{
6ee4d0
+  int i;
6ee4d0
+  int dfd;
6ee4d0
+  char *cwd;
6ee4d0
+  int result;
6ee4d0
+
6ee4d0
+  /* Clean up any trash from prior testsuite runs.  */
6ee4d0
+  ignore_value (system ("rm -rf " BASE "*"));
6ee4d0
+
6ee4d0
+  /* Test behaviour for invalid file descriptors.  */
6ee4d0
+  {
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat (-1, "foo", AT_FDCWD, "bar") == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  {
6ee4d0
+    close (99);
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat (99, "foo", AT_FDCWD, "bar") == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  ASSERT (close (creat (BASE "oo", 0600)) == 0);
6ee4d0
+  {
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat (AT_FDCWD, BASE "oo", -1, "bar") == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  {
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat (AT_FDCWD, BASE "oo", 99, "bar") == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  ASSERT (unlink (BASE "oo") == 0);
6ee4d0
+
6ee4d0
+  /* Test basic rename functionality, using current directory.  */
6ee4d0
+  result = test_rename (do_rename, false);
6ee4d0
+  dfd1 = open (".", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd1);
6ee4d0
+  ASSERT (test_rename (do_rename, false) == result);
6ee4d0
+  dfd2 = dfd1;
6ee4d0
+  ASSERT (test_rename (do_rename, false) == result);
6ee4d0
+  dfd1 = AT_FDCWD;
6ee4d0
+  ASSERT (test_rename (do_rename, false) == result);
6ee4d0
+  ASSERT (close (dfd2) == 0);
6ee4d0
+
6ee4d0
+  /* Create locations to manipulate.  */
6ee4d0
+  ASSERT (mkdir (BASE "sub1", 0700) == 0);
6ee4d0
+  ASSERT (mkdir (BASE "sub2", 0700) == 0);
6ee4d0
+  dfd = creat (BASE "00", 0600);
6ee4d0
+  ASSERT (0 <= dfd);
6ee4d0
+  ASSERT (close (dfd) == 0);
6ee4d0
+  cwd = getcwd (NULL, 0);
6ee4d0
+  ASSERT (cwd);
6ee4d0
+
6ee4d0
+  dfd = open (BASE "sub1", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd);
6ee4d0
+  ASSERT (chdir (BASE "sub2") == 0);
6ee4d0
+
6ee4d0
+  /* There are 16 possible scenarios, based on whether an fd is
6ee4d0
+     AT_FDCWD or real, and whether a file is absolute or relative.
6ee4d0
+
6ee4d0
+     To ensure that we test all of the code paths (rather than
6ee4d0
+     triggering early normalization optimizations), we use a loop to
6ee4d0
+     repeatedly rename a file in the parent directory, use an fd open
6ee4d0
+     on subdirectory 1, all while executing in subdirectory 2; all
6ee4d0
+     relative names are thus given with a leading "../".  Finally, the
6ee4d0
+     last scenario (two relative paths given, neither one AT_FDCWD)
6ee4d0
+     has two paths, based on whether the two fds are equivalent, so we
6ee4d0
+     do the other variant after the loop.  */
6ee4d0
+  for (i = 0; i < 16; i++)
6ee4d0
+    {
6ee4d0
+      int fd1 = (i & 8) ? dfd : AT_FDCWD;
6ee4d0
+      char *file1 = file_name_concat ((i & 4) ? ".." : cwd, BASE "xx", NULL);
6ee4d0
+      int fd2 = (i & 2) ? dfd : AT_FDCWD;
6ee4d0
+      char *file2 = file_name_concat ((i & 1) ? ".." : cwd, BASE "xx", NULL);
6ee4d0
+
6ee4d0
+      ASSERT (sprintf (strchr (file1, '\0') - 2, "%02d", i) == 2);
6ee4d0
+      ASSERT (sprintf (strchr (file2, '\0') - 2, "%02d", i + 1) == 2);
6ee4d0
+      ASSERT (renameat (fd1, file1, fd2, file2) == 0);
6ee4d0
+      free (file1);
6ee4d0
+      free (file2);
6ee4d0
+    }
6ee4d0
+  dfd2 = open ("..", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd2);
6ee4d0
+  ASSERT (renameat (dfd, "../" BASE "16", dfd2, BASE "17") == 0);
6ee4d0
+  ASSERT (close (dfd2) == 0);
6ee4d0
+
6ee4d0
+  /* Now we change back to the parent directory, and set dfd to ".";
6ee4d0
+     using dfd in remaining tests will expose any bugs if emulation
6ee4d0
+     via /proc/self/fd doesn't check for empty names.  */
6ee4d0
+  ASSERT (chdir ("..") == 0);
6ee4d0
+  ASSERT (close (dfd) == 0);
6ee4d0
+  dfd = open (".", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd);
6ee4d0
+
6ee4d0
+  ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "sub1", dfd, BASE "sub2") == -1);
6ee4d0
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
6ee4d0
+  ASSERT (unlink (BASE "sub2/file") == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "sub1/.") == -1);
6ee4d0
+  ASSERT (errno == EINVAL || errno == EISDIR || errno == EBUSY
6ee4d0
+          || errno == ENOTEMPTY || errno == EEXIST);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "sub2/.", dfd, BASE "sub1") == -1);
6ee4d0
+  ASSERT (errno == EINVAL || errno == EBUSY || errno == EEXIST);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "17", dfd, BASE "sub1") == -1);
6ee4d0
+  ASSERT (errno == EISDIR);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "nosuch", dfd, BASE "18") == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, "", dfd, BASE "17") == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "17", dfd, "") == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "17") == -1);
6ee4d0
+  ASSERT (errno == ENOTDIR);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "17/", dfd, BASE "18") == -1);
6ee4d0
+  ASSERT (errno == ENOTDIR);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "17", dfd, BASE "18/") == -1);
6ee4d0
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
6ee4d0
+
6ee4d0
+  /* Finally, make sure we can overwrite existing files.  */
6ee4d0
+  ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "sub1") == 0);
6ee4d0
+  ASSERT (renameat (dfd, BASE "sub1/file", dfd, BASE "17") == 0);
6ee4d0
+
6ee4d0
+  /* Cleanup.  */
6ee4d0
+  ASSERT (close (dfd) == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (unlink (BASE "sub1/file") == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  ASSERT (unlink (BASE "17") == 0);
6ee4d0
+  ASSERT (rmdir (BASE "sub1") == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (rmdir (BASE "sub2") == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  free (cwd);
6ee4d0
+
6ee4d0
+  if (result)
6ee4d0
+    fputs ("skipping test: symlinks not supported on this file system\n",
6ee4d0
+           stderr);
6ee4d0
+  return result;
6ee4d0
+}
6ee4d0
diff --git a/gnulib-tests/test-renameat2.c b/gnulib-tests/test-renameat2.c
6ee4d0
new file mode 100644
6ee4d0
index 0000000..7c250ea
6ee4d0
--- /dev/null
6ee4d0
+++ b/gnulib-tests/test-renameat2.c
6ee4d0
@@ -0,0 +1,209 @@
6ee4d0
+/* Test renameat2.
6ee4d0
+   Copyright (C) 2009-2017 Free Software Foundation, Inc.
6ee4d0
+
6ee4d0
+   This program is free software: you can redistribute it and/or modify
6ee4d0
+   it under the terms of the GNU General Public License as published by
6ee4d0
+   the Free Software Foundation; either version 3 of the License, or
6ee4d0
+   (at your option) any later version.
6ee4d0
+
6ee4d0
+   This program is distributed in the hope that it will be useful,
6ee4d0
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ee4d0
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6ee4d0
+   GNU General Public License for more details.
6ee4d0
+
6ee4d0
+   You should have received a copy of the GNU General Public License
6ee4d0
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
6ee4d0
+
6ee4d0
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
6ee4d0
+
6ee4d0
+#include <config.h>
6ee4d0
+
6ee4d0
+#include <renameat2.h>
6ee4d0
+
6ee4d0
+#include <stdio.h>
6ee4d0
+
6ee4d0
+#include "signature.h"
6ee4d0
+SIGNATURE_CHECK (renameat2, int,
6ee4d0
+                 (int, char const *, int, char const *, unsigned int));
6ee4d0
+
6ee4d0
+#include <dirent.h>
6ee4d0
+#include <fcntl.h>
6ee4d0
+#include <errno.h>
6ee4d0
+#include <stdbool.h>
6ee4d0
+#include <stdlib.h>
6ee4d0
+#include <string.h>
6ee4d0
+#include <unistd.h>
6ee4d0
+#include <sys/stat.h>
6ee4d0
+
6ee4d0
+#include "filenamecat.h"
6ee4d0
+#include "ignore-value.h"
6ee4d0
+#include "macros.h"
6ee4d0
+
6ee4d0
+#define BASE "test-renameat2.t"
6ee4d0
+
6ee4d0
+#include "test-rename.h"
6ee4d0
+
6ee4d0
+static int dfd1 = AT_FDCWD;
6ee4d0
+static int dfd2 = AT_FDCWD;
6ee4d0
+
6ee4d0
+/* Wrapper to test renameat2 like rename.  */
6ee4d0
+static int
6ee4d0
+do_rename (char const *name1, char const *name2)
6ee4d0
+{
6ee4d0
+  return renameat2 (dfd1, name1, dfd2, name2, 0);
6ee4d0
+}
6ee4d0
+
6ee4d0
+int
6ee4d0
+main (void)
6ee4d0
+{
6ee4d0
+  int i;
6ee4d0
+  int dfd;
6ee4d0
+  char *cwd;
6ee4d0
+  int result;
6ee4d0
+
6ee4d0
+  /* Clean up any trash from prior testsuite runs.  */
6ee4d0
+  ignore_value (system ("rm -rf " BASE "*"));
6ee4d0
+
6ee4d0
+  /* Test behaviour for invalid file descriptors.  */
6ee4d0
+  {
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat2 (-1, "foo", AT_FDCWD, "bar", 0) == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  {
6ee4d0
+    close (99);
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat2 (99, "foo", AT_FDCWD, "bar", 0) == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  ASSERT (close (creat (BASE "oo", 0600)) == 0);
6ee4d0
+  {
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat2 (AT_FDCWD, BASE "oo", -1, "bar", 0) == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  {
6ee4d0
+    errno = 0;
6ee4d0
+    ASSERT (renameat2 (AT_FDCWD, BASE "oo", 99, "bar", 0) == -1);
6ee4d0
+    ASSERT (errno == EBADF);
6ee4d0
+  }
6ee4d0
+  ASSERT (unlink (BASE "oo") == 0);
6ee4d0
+
6ee4d0
+  /* Test basic rename functionality, using current directory.  */
6ee4d0
+  result = test_rename (do_rename, false);
6ee4d0
+  dfd1 = open (".", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd1);
6ee4d0
+  ASSERT (test_rename (do_rename, false) == result);
6ee4d0
+  dfd2 = dfd1;
6ee4d0
+  ASSERT (test_rename (do_rename, false) == result);
6ee4d0
+  dfd1 = AT_FDCWD;
6ee4d0
+  ASSERT (test_rename (do_rename, false) == result);
6ee4d0
+  ASSERT (close (dfd2) == 0);
6ee4d0
+
6ee4d0
+  /* Create locations to manipulate.  */
6ee4d0
+  ASSERT (mkdir (BASE "sub1", 0700) == 0);
6ee4d0
+  ASSERT (mkdir (BASE "sub2", 0700) == 0);
6ee4d0
+  dfd = creat (BASE "00", 0600);
6ee4d0
+  ASSERT (0 <= dfd);
6ee4d0
+  ASSERT (close (dfd) == 0);
6ee4d0
+  cwd = getcwd (NULL, 0);
6ee4d0
+  ASSERT (cwd);
6ee4d0
+
6ee4d0
+  dfd = open (BASE "sub1", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd);
6ee4d0
+  ASSERT (chdir (BASE "sub2") == 0);
6ee4d0
+
6ee4d0
+  /* There are 16 possible scenarios, based on whether an fd is
6ee4d0
+     AT_FDCWD or real, and whether a file is absolute or relative.
6ee4d0
+
6ee4d0
+     To ensure that we test all of the code paths (rather than
6ee4d0
+     triggering early normalization optimizations), we use a loop to
6ee4d0
+     repeatedly rename a file in the parent directory, use an fd open
6ee4d0
+     on subdirectory 1, all while executing in subdirectory 2; all
6ee4d0
+     relative names are thus given with a leading "../".  Finally, the
6ee4d0
+     last scenario (two relative paths given, neither one AT_FDCWD)
6ee4d0
+     has two paths, based on whether the two fds are equivalent, so we
6ee4d0
+     do the other variant after the loop.  */
6ee4d0
+  for (i = 0; i < 16; i++)
6ee4d0
+    {
6ee4d0
+      int fd1 = (i & 8) ? dfd : AT_FDCWD;
6ee4d0
+      char *file1 = file_name_concat ((i & 4) ? ".." : cwd, BASE "xx", NULL);
6ee4d0
+      int fd2 = (i & 2) ? dfd : AT_FDCWD;
6ee4d0
+      char *file2 = file_name_concat ((i & 1) ? ".." : cwd, BASE "xx", NULL);
6ee4d0
+
6ee4d0
+      ASSERT (sprintf (strchr (file1, '\0') - 2, "%02d", i) == 2);
6ee4d0
+      ASSERT (sprintf (strchr (file2, '\0') - 2, "%02d", i + 1) == 2);
6ee4d0
+      ASSERT (renameat2 (fd1, file1, fd2, file2, 0) == 0);
6ee4d0
+      free (file1);
6ee4d0
+      free (file2);
6ee4d0
+    }
6ee4d0
+  dfd2 = open ("..", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd2);
6ee4d0
+  ASSERT (renameat2 (dfd, "../" BASE "16", dfd2, BASE "17", 0) == 0);
6ee4d0
+  ASSERT (close (dfd2) == 0);
6ee4d0
+
6ee4d0
+  /* Now we change back to the parent directory, and set dfd to ".";
6ee4d0
+     using dfd in remaining tests will expose any bugs if emulation
6ee4d0
+     via /proc/self/fd doesn't check for empty names.  */
6ee4d0
+  ASSERT (chdir ("..") == 0);
6ee4d0
+  ASSERT (close (dfd) == 0);
6ee4d0
+  dfd = open (".", O_RDONLY);
6ee4d0
+  ASSERT (0 <= dfd);
6ee4d0
+
6ee4d0
+  ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "sub1", dfd, BASE "sub2", 0) == -1);
6ee4d0
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
6ee4d0
+  ASSERT (unlink (BASE "sub2/file") == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "sub2", dfd, BASE "sub1/.", 0) == -1);
6ee4d0
+  ASSERT (errno == EINVAL || errno == EISDIR || errno == EBUSY
6ee4d0
+          || errno == ENOTEMPTY || errno == EEXIST);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "sub2/.", dfd, BASE "sub1", 0) == -1);
6ee4d0
+  ASSERT (errno == EINVAL || errno == EBUSY || errno == EEXIST);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "17", dfd, BASE "sub1", 0) == -1);
6ee4d0
+  ASSERT (errno == EISDIR);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "nosuch", dfd, BASE "18", 0) == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, "", dfd, BASE "17", 0) == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "17", dfd, "", 0) == -1);
6ee4d0
+  ASSERT (errno == ENOENT);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "sub2", dfd, BASE "17", 0) == -1);
6ee4d0
+  ASSERT (errno == ENOTDIR);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "17/", dfd, BASE "18", 0) == -1);
6ee4d0
+  ASSERT (errno == ENOTDIR);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT (renameat2 (dfd, BASE "17", dfd, BASE "18/", 0) == -1);
6ee4d0
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
6ee4d0
+
6ee4d0
+  /* Finally, make sure we cannot overwrite existing files.  */
6ee4d0
+  ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
6ee4d0
+  errno = 0;
6ee4d0
+  ASSERT ((renameat2 (dfd, BASE "sub2", dfd, BASE "sub1", RENAME_NOREPLACE)
6ee4d0
+           == -1)
6ee4d0
+          && errno == EEXIST);
6ee4d0
+  ASSERT ((renameat2 (dfd, BASE "sub2/file", dfd, BASE "17", RENAME_NOREPLACE)
6ee4d0
+           == -1)
6ee4d0
+          && errno == EEXIST);
6ee4d0
+
6ee4d0
+  /* Cleanup.  */
6ee4d0
+  ASSERT (close (dfd) == 0);
6ee4d0
+  ASSERT (unlink (BASE "sub2/file") == 0);
6ee4d0
+  ASSERT (unlink (BASE "17") == 0);
6ee4d0
+  ASSERT (rmdir (BASE "sub1") == 0);
6ee4d0
+  ASSERT (rmdir (BASE "sub2") == 0);
6ee4d0
+  free (cwd);
6ee4d0
+
6ee4d0
+  if (result)
6ee4d0
+    fputs ("skipping test: symlinks not supported on this file system\n",
6ee4d0
+           stderr);
6ee4d0
+  return result;
6ee4d0
+}
6ee4d0
diff --git a/lib/gnulib.mk b/lib/gnulib.mk
6ee4d0
index 844791b..76729b0 100644
6ee4d0
--- a/lib/gnulib.mk
6ee4d0
+++ b/lib/gnulib.mk
6ee4d0
@@ -21,7 +21,7 @@
6ee4d0
 # the same distribution terms as the rest of that program.
6ee4d0
 #
6ee4d0
 # Generated by gnulib-tool.
6ee4d0
-# Reproduce by: gnulib-tool --import --dir=. --local-dir=gl --lib=libcoreutils --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=gnulib-tests --aux-dir=build-aux --with-tests --avoid=canonicalize-lgpl --avoid=dummy --makefile-name=gnulib.mk --no-conditional-dependencies --no-libtool --macro-prefix=gl acl alignof alloca announce-gen areadlink-with-size argmatch argv-iter assert autobuild backupfile base64 buffer-lcm c-strcase c-strtod c-strtold calloc-gnu canon-host canonicalize chown cloexec closein closeout config-h configmake crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 cycle-check d-ino d-type di-set diacrit dirfd dirname do-release-commit-and-tag dtoastr dup2 environ error euidaccess exclude exitfail faccessat fadvise fchdir fchmodat fchownat fclose fcntl fcntl-safer fd-reopen fdatasync fdl fdopen fdutimensat file-type fileblocks filemode filenamecat filevercmp fnmatch-gnu fopen-safer fprintftime freopen freopen-safer fseeko fstatat fsusage fsync ftello ftoastr ftruncate fts full-read full-write getgroups gethrxtime getline getloadavg getlogin getndelim2 getopt-gnu getpagesize getpass-gnu gettext-h gettime gettimeofday getugroups getusershell git-version-gen gitlog-to-changelog gnu-make gnu-web-doc-update gnumakefile gnupload group-member hard-locale hash hash-pjw heap host-os human idcache ignore-value inttostr inttypes isapipe isatty isblank largefile lchmod lchown ldtoastr lib-ignore linebuffer link link-follow linkat long-options lstat maintainer-makefile malloc-gnu manywarnings mbrlen mbrtowc mbsalign mbswidth memcasecmp memchr memcmp2 mempcpy memrchr mgetgroups mkancesdirs mkdir mkdir-p mkfifo mknod mkstemp mktime modechange mountlist mpsort netinet_in non-recursive-gnulib-prefix-hack nproc obstack parse-datetime pathmax perl physmem pipe posix-shell posixtm posixver priv-set progname propername pthread putenv quote quotearg randint randperm read-file readlink readtokens readtokens0 readutmp realloc-gnu regex remove rename rmdir root-dev-ino rpmatch safe-read same save-cwd savedir savewd selinux-at settime sig2str sigaction smack ssize_t stat-macros stat-size stat-time statat stdbool stdlib-safer stpcpy stpncpy strdup-posix strftime strncat strnumcmp strpbrk strsignal strtod strtoimax strtoumax symlink sys_ioctl sys_resource sys_stat sys_wait termios timer-time timespec tzset uname unicodeio unistd-safer unlink-busy unlinkat unlocked-io unsetenv update-copyright uptime useless-if-before-free userspec utimecmp utimens vasprintf-posix vc-list-files verify verror version-etc-fsf wcswidth wcwidth winsz-ioctl winsz-termios write-any-file xalloc xfreopen xfts xgetcwd xgetgroups xgethostname xmemcoll xnanosleep xprintf xprintf-posix xreadlink xstrtod xstrtoimax xstrtol xstrtold xstrtoumax yesno
6ee4d0
+# Reproduce by: gnulib-tool --import --dir=. --local-dir=gl --lib=libcoreutils --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=gnulib-tests --aux-dir=build-aux --with-tests --avoid=canonicalize-lgpl --avoid=dummy --makefile-name=gnulib.mk --no-conditional-dependencies --no-libtool --macro-prefix=gl acl alignof alloca announce-gen areadlink-with-size argmatch argv-iter assert autobuild backupfile base64 buffer-lcm c-strcase c-strtod c-strtold calloc-gnu canon-host canonicalize chown cloexec closein closeout config-h configmake crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 cycle-check d-ino d-type di-set diacrit dirfd dirname do-release-commit-and-tag dtoastr dup2 environ error euidaccess exclude exitfail faccessat fadvise fchdir fchmodat fchownat fclose fcntl fcntl-safer fd-reopen fdatasync fdl fdopen fdutimensat file-type fileblocks filemode filenamecat filevercmp fnmatch-gnu fopen-safer fprintftime freopen freopen-safer fseeko fstatat fsusage fsync ftello ftoastr ftruncate fts full-read full-write getgroups gethrxtime getline getloadavg getlogin getndelim2 getopt-gnu getpagesize getpass-gnu gettext-h gettime gettimeofday getugroups getusershell git-version-gen gitlog-to-changelog gnu-make gnu-web-doc-update gnumakefile gnupload group-member hard-locale hash hash-pjw heap host-os human idcache ignore-value inttostr inttypes isapipe isatty isblank largefile lchmod lchown ldtoastr lib-ignore linebuffer link link-follow linkat long-options lstat maintainer-makefile malloc-gnu manywarnings mbrlen mbrtowc mbsalign mbswidth memcasecmp memchr memcmp2 mempcpy memrchr mgetgroups mkancesdirs mkdir mkdir-p mkfifo mknod mkstemp mktime modechange mountlist mpsort netinet_in non-recursive-gnulib-prefix-hack nproc obstack parse-datetime pathmax perl physmem pipe posix-shell posixtm posixver priv-set progname propername pthread putenv quote quotearg randint randperm read-file readlink readtokens readtokens0 readutmp realloc-gnu regex remove rename renameat renameat2 rmdir root-dev-ino rpmatch safe-read same save-cwd savedir savewd selinux-at settime sig2str sigaction smack ssize_t stat-macros stat-size stat-time statat stdbool stdlib-safer stpcpy stpncpy strdup-posix strftime strncat strnumcmp strpbrk strsignal strtod strtoimax strtoumax symlink sys_ioctl sys_resource sys_stat sys_wait termios timer-time timespec tzset uname unicodeio unistd-safer unlink-busy unlinkat unlocked-io unsetenv update-copyright uptime useless-if-before-free userspec utimecmp utimens vasprintf-posix vc-list-files verify verror version-etc-fsf wcswidth wcwidth winsz-ioctl winsz-termios write-any-file xalloc xfreopen xfts xgetcwd xgetgroups xgethostname xmemcoll xnanosleep xprintf xprintf-posix xreadlink xstrtod xstrtoimax xstrtol xstrtold xstrtoumax yesno
6ee4d0
 
6ee4d0
 
6ee4d0
 MOSTLYCLEANFILES += lib/core lib/*.stackdump
6ee4d0
@@ -3202,6 +3202,25 @@ EXTRA_lib_libcoreutils_a_SOURCES += lib/rename.c
6ee4d0
 
6ee4d0
 ## end   gnulib module rename
6ee4d0
 
6ee4d0
+## begin gnulib module renameat
6ee4d0
+
6ee4d0
+
6ee4d0
+EXTRA_DIST += lib/renameat.c
6ee4d0
+
6ee4d0
+EXTRA_lib_libcoreutils_a_SOURCES += lib/renameat.c
6ee4d0
+
6ee4d0
+## end   gnulib module renameat
6ee4d0
+
6ee4d0
+## begin gnulib module renameat2
6ee4d0
+
6ee4d0
+lib_libcoreutils_a_SOURCES += lib/renameat2.c
6ee4d0
+
6ee4d0
+EXTRA_DIST += lib/at-func2.c lib/renameat2.h
6ee4d0
+
6ee4d0
+EXTRA_lib_libcoreutils_a_SOURCES += lib/at-func2.c
6ee4d0
+
6ee4d0
+## end   gnulib module renameat2
6ee4d0
+
6ee4d0
 ## begin gnulib module rewinddir
6ee4d0
 
6ee4d0
 
6ee4d0
diff --git a/lib/renameat.c b/lib/renameat.c
6ee4d0
new file mode 100644
6ee4d0
index 0000000..48cee4b
6ee4d0
--- /dev/null
6ee4d0
+++ b/lib/renameat.c
6ee4d0
@@ -0,0 +1,25 @@
6ee4d0
+/* Rename a file relative to open directories.
6ee4d0
+   Copyright 2017 Free Software Foundation, Inc.
6ee4d0
+
6ee4d0
+   This program is free software: you can redistribute it and/or modify
6ee4d0
+   it under the terms of the GNU General Public License as published by
6ee4d0
+   the Free Software Foundation; either version 3 of the License, or
6ee4d0
+   (at your option) any later version.
6ee4d0
+
6ee4d0
+   This program is distributed in the hope that it will be useful,
6ee4d0
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ee4d0
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6ee4d0
+   GNU General Public License for more details.
6ee4d0
+
6ee4d0
+   You should have received a copy of the GNU General Public License
6ee4d0
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
6ee4d0
+
6ee4d0
+#include <config.h>
6ee4d0
+#include <stdio.h>
6ee4d0
+#include "renameat2.h"
6ee4d0
+
6ee4d0
+int
6ee4d0
+renameat (int fd1, char const *src, int fd2, char const *dst)
6ee4d0
+{
6ee4d0
+  return renameat2 (fd1, src, fd2, dst, 0);
6ee4d0
+}
6ee4d0
diff --git a/lib/renameat2.c b/lib/renameat2.c
6ee4d0
new file mode 100644
6ee4d0
index 0000000..26cde86
6ee4d0
--- /dev/null
6ee4d0
+++ b/lib/renameat2.c
6ee4d0
@@ -0,0 +1,227 @@
6ee4d0
+/* Rename a file relative to open directories.
6ee4d0
+   Copyright (C) 2009-2017 Free Software Foundation, Inc.
6ee4d0
+
6ee4d0
+   This program is free software: you can redistribute it and/or modify
6ee4d0
+   it under the terms of the GNU General Public License as published by
6ee4d0
+   the Free Software Foundation; either version 3 of the License, or
6ee4d0
+   (at your option) any later version.
6ee4d0
+
6ee4d0
+   This program is distributed in the hope that it will be useful,
6ee4d0
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ee4d0
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6ee4d0
+   GNU General Public License for more details.
6ee4d0
+
6ee4d0
+   You should have received a copy of the GNU General Public License
6ee4d0
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
6ee4d0
+
6ee4d0
+/* written by Eric Blake and Paul Eggert */
6ee4d0
+
6ee4d0
+#include <config.h>
6ee4d0
+
6ee4d0
+#include "renameat2.h"
6ee4d0
+
6ee4d0
+#include <errno.h>
6ee4d0
+#include <stdio.h>
6ee4d0
+#include <sys/stat.h>
6ee4d0
+#include <unistd.h>
6ee4d0
+
6ee4d0
+#ifdef __linux__
6ee4d0
+# include <sys/syscall.h>
6ee4d0
+#endif
6ee4d0
+
6ee4d0
+static int
6ee4d0
+errno_fail (int e)
6ee4d0
+{
6ee4d0
+  errno = e;
6ee4d0
+  return -1;
6ee4d0
+}
6ee4d0
+
6ee4d0
+#if HAVE_RENAMEAT
6ee4d0
+
6ee4d0
+# include <stdbool.h>
6ee4d0
+# include <stdlib.h>
6ee4d0
+# include <string.h>
6ee4d0
+
6ee4d0
+# include "dirname.h"
6ee4d0
+# include "openat.h"
6ee4d0
+
6ee4d0
+#else
6ee4d0
+# include "openat-priv.h"
6ee4d0
+
6ee4d0
+static int
6ee4d0
+rename_noreplace (char const *src, char const *dst)
6ee4d0
+{
6ee4d0
+  /* This has a race between the call to lstat and the call to rename.  */
6ee4d0
+  struct stat st;
6ee4d0
+  return (lstat (dst, &st) == 0 || errno == EOVERFLOW ? errno_fail (EEXIST)
6ee4d0
+          : errno == ENOENT ? rename (src, dst)
6ee4d0
+          : -1);
6ee4d0
+}
6ee4d0
+#endif
6ee4d0
+
6ee4d0
+#undef renameat
6ee4d0
+
6ee4d0
+/* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
6ee4d0
+   the directory open on descriptor FD2.  If possible, do it without
6ee4d0
+   changing the working directory.  Otherwise, resort to using
6ee4d0
+   save_cwd/fchdir, then rename/restore_cwd.  If either the save_cwd or
6ee4d0
+   the restore_cwd fails, then give a diagnostic and exit nonzero.
6ee4d0
+
6ee4d0
+   Obey FLAGS when doing the renaming.  If FLAGS is zero, this
6ee4d0
+   function is equivalent to renameat (FD1, SRC, FD2, DST).  */
6ee4d0
+
6ee4d0
+int
6ee4d0
+renameat2 (int fd1, char const *src, int fd2, char const *dst,
6ee4d0
+           unsigned int flags)
6ee4d0
+{
6ee4d0
+  int ret_val = -1;
6ee4d0
+  int err = EINVAL;
6ee4d0
+
6ee4d0
+#ifdef SYS_renameat2
6ee4d0
+  ret_val = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
6ee4d0
+  err = errno;
6ee4d0
+#elif defined RENAME_EXCL
6ee4d0
+  if (! (flags & ~(RENAME_EXCHANGE | RENAME_NOREPLACE)))
6ee4d0
+    {
6ee4d0
+      ret_val = renameatx_np (fd1, src, fd2, dst,
6ee4d0
+                             ((flags & RENAME_EXCHANGE ? RENAME_SWAP : 0)
6ee4d0
+                              | (flags & RENAME_NOREPLACE ? RENAME_EXCL : 0)));
6ee4d0
+      err = errno;
6ee4d0
+    }
6ee4d0
+#endif
6ee4d0
+
6ee4d0
+  if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
6ee4d0
+    return ret_val;
6ee4d0
+
6ee4d0
+#if HAVE_RENAMEAT
6ee4d0
+  {
6ee4d0
+  size_t src_len;
6ee4d0
+  size_t dst_len;
6ee4d0
+  char *src_temp = (char *) src;
6ee4d0
+  char *dst_temp = (char *) dst;
6ee4d0
+  bool src_slash;
6ee4d0
+  bool dst_slash;
6ee4d0
+  int rename_errno = ENOTDIR;
6ee4d0
+  struct stat src_st;
6ee4d0
+  struct stat dst_st;
6ee4d0
+  bool dst_found_nonexistent = false;
6ee4d0
+
6ee4d0
+  if (flags != 0)
6ee4d0
+    {
6ee4d0
+      /* RENAME_NOREPLACE is the only flag currently supported.  */
6ee4d0
+      if (flags & ~RENAME_NOREPLACE)
6ee4d0
+        return errno_fail (ENOTSUP);
6ee4d0
+      else
6ee4d0
+        {
6ee4d0
+          /* This has a race between the call to lstatat and the calls to
6ee4d0
+             renameat below.  */
6ee4d0
+          if (lstatat (fd2, dst, &dst_st) == 0 || errno == EOVERFLOW)
6ee4d0
+            return errno_fail (EEXIST);
6ee4d0
+          if (errno != ENOENT)
6ee4d0
+            return -1;
6ee4d0
+          dst_found_nonexistent = true;
6ee4d0
+        }
6ee4d0
+    }
6ee4d0
+
6ee4d0
+  /* Let strace see any ENOENT failure.  */
6ee4d0
+  src_len = strlen (src);
6ee4d0
+  dst_len = strlen (dst);
6ee4d0
+  if (!src_len || !dst_len)
6ee4d0
+    return renameat (fd1, src, fd2, dst);
6ee4d0
+
6ee4d0
+  src_slash = src[src_len - 1] == '/';
6ee4d0
+  dst_slash = dst[dst_len - 1] == '/';
6ee4d0
+  if (!src_slash && !dst_slash)
6ee4d0
+    return renameat (fd1, src, fd2, dst);
6ee4d0
+
6ee4d0
+  /* Presence of a trailing slash requires directory semantics.  If
6ee4d0
+     the source does not exist, or if the destination cannot be turned
6ee4d0
+     into a directory, give up now.  Otherwise, strip trailing slashes
6ee4d0
+     before calling rename.  */
6ee4d0
+  if (lstatat (fd1, src, &src_st))
6ee4d0
+    return -1;
6ee4d0
+  if (dst_found_nonexistent)
6ee4d0
+    {
6ee4d0
+      if (!S_ISDIR (src_st.st_mode))
6ee4d0
+        return errno_fail (ENOENT);
6ee4d0
+    }
6ee4d0
+  else if (lstatat (fd2, dst, &dst_st))
6ee4d0
+    {
6ee4d0
+      if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
6ee4d0
+        return -1;
6ee4d0
+    }
6ee4d0
+  else if (!S_ISDIR (dst_st.st_mode))
6ee4d0
+    return errno_fail (ENOTDIR);
6ee4d0
+  else if (!S_ISDIR (src_st.st_mode))
6ee4d0
+    return errno_fail (EISDIR);
6ee4d0
+
6ee4d0
+# if RENAME_TRAILING_SLASH_SOURCE_BUG
6ee4d0
+  /* See the lengthy comment in rename.c why Solaris 9 is forced to
6ee4d0
+     GNU behavior, while Solaris 10 is left with POSIX behavior,
6ee4d0
+     regarding symlinks with trailing slash.  */
6ee4d0
+  ret_val = -1;
6ee4d0
+  if (src_slash)
6ee4d0
+    {
6ee4d0
+      src_temp = strdup (src);
6ee4d0
+      if (!src_temp)
6ee4d0
+        {
6ee4d0
+          /* Rather than rely on strdup-posix, we set errno ourselves.  */
6ee4d0
+          rename_errno = ENOMEM;
6ee4d0
+          goto out;
6ee4d0
+        }
6ee4d0
+      strip_trailing_slashes (src_temp);
6ee4d0
+      if (lstatat (fd1, src_temp, &src_st))
6ee4d0
+        {
6ee4d0
+          rename_errno = errno;
6ee4d0
+          goto out;
6ee4d0
+        }
6ee4d0
+      if (S_ISLNK (src_st.st_mode))
6ee4d0
+        goto out;
6ee4d0
+    }
6ee4d0
+  if (dst_slash)
6ee4d0
+    {
6ee4d0
+      dst_temp = strdup (dst);
6ee4d0
+      if (!dst_temp)
6ee4d0
+        {
6ee4d0
+          rename_errno = ENOMEM;
6ee4d0
+          goto out;
6ee4d0
+        }
6ee4d0
+      strip_trailing_slashes (dst_temp);
6ee4d0
+      if (lstatat (fd2, dst_temp, &dst_st))
6ee4d0
+        {
6ee4d0
+          if (errno != ENOENT)
6ee4d0
+            {
6ee4d0
+              rename_errno = errno;
6ee4d0
+              goto out;
6ee4d0
+            }
6ee4d0
+        }
6ee4d0
+      else if (S_ISLNK (dst_st.st_mode))
6ee4d0
+        goto out;
6ee4d0
+    }
6ee4d0
+# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
6ee4d0
+
6ee4d0
+  /* renameat does not honor trailing / on Solaris 10.  Solve it in a
6ee4d0
+     similar manner to rename.  No need to worry about bugs not present
6ee4d0
+     on Solaris, since all other systems either lack renameat or honor
6ee4d0
+     trailing slash correctly.  */
6ee4d0
+
6ee4d0
+  ret_val = renameat (fd1, src_temp, fd2, dst_temp);
6ee4d0
+  rename_errno = errno;
6ee4d0
+  goto out;
6ee4d0
+ out:
6ee4d0
+  if (src_temp != src)
6ee4d0
+    free (src_temp);
6ee4d0
+  if (dst_temp != dst)
6ee4d0
+    free (dst_temp);
6ee4d0
+  errno = rename_errno;
6ee4d0
+  return ret_val;
6ee4d0
+  }
6ee4d0
+#else /* !HAVE_RENAMEAT */
6ee4d0
+
6ee4d0
+  /* RENAME_NOREPLACE is the only flag currently supported.  */
6ee4d0
+  if (flags & ~RENAME_NOREPLACE)
6ee4d0
+    return errno_fail (ENOTSUP);
6ee4d0
+  return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
6ee4d0
+
6ee4d0
+#endif /* !HAVE_RENAMEAT */
6ee4d0
+}
6ee4d0
diff --git a/lib/renameat2.h b/lib/renameat2.h
6ee4d0
new file mode 100644
6ee4d0
index 0000000..179210f
6ee4d0
--- /dev/null
6ee4d0
+++ b/lib/renameat2.h
6ee4d0
@@ -0,0 +1,30 @@
6ee4d0
+/* Rename a file relative to open directories.
6ee4d0
+   Copyright 2017 Free Software Foundation, Inc.
6ee4d0
+
6ee4d0
+   This program is free software: you can redistribute it and/or modify
6ee4d0
+   it under the terms of the GNU General Public License as published by
6ee4d0
+   the Free Software Foundation; either version 3 of the License, or
6ee4d0
+   (at your option) any later version.
6ee4d0
+
6ee4d0
+   This program is distributed in the hope that it will be useful,
6ee4d0
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ee4d0
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6ee4d0
+   GNU General Public License for more details.
6ee4d0
+
6ee4d0
+   You should have received a copy of the GNU General Public License
6ee4d0
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
6ee4d0
+
6ee4d0
+/* written by Paul Eggert */
6ee4d0
+
6ee4d0
+/* Get RENAME_* macros from linux/fs.h if present, otherwise supply
6ee4d0
+   the traditional Linux values.  */
6ee4d0
+#if HAVE_LINUX_FS_H
6ee4d0
+# include <linux/fs.h>
6ee4d0
+#endif
6ee4d0
+#ifndef RENAME_NOREPLACE
6ee4d0
+# define RENAME_NOREPLACE  (1 << 0)
6ee4d0
+# define RENAME_EXCHANGE   (1 << 1)
6ee4d0
+# define RENAME_WHITEOUT   (1 << 2)
6ee4d0
+#endif
6ee4d0
+
6ee4d0
+extern int renameat2 (int, char const *, int, char const *, unsigned int);
6ee4d0
diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4
6ee4d0
index 4ef3c43..309e308 100644
6ee4d0
--- a/m4/gnulib-comp.m4
6ee4d0
+++ b/m4/gnulib-comp.m4
6ee4d0
@@ -547,6 +547,10 @@ AC_DEFUN([gl_EARLY],
6ee4d0
   # Code from module remove-tests:
6ee4d0
   # Code from module rename:
6ee4d0
   # Code from module rename-tests:
6ee4d0
+  # Code from module renameat:
6ee4d0
+  # Code from module renameat-tests:
6ee4d0
+  # Code from module renameat2:
6ee4d0
+  # Code from module renameat2-tests:
6ee4d0
   # Code from module rewinddir:
6ee4d0
   # Code from module rmdir:
6ee4d0
   # Code from module rmdir-tests:
6ee4d0
@@ -1696,6 +1700,18 @@ AC_DEFUN([gl_INIT],
6ee4d0
     AC_LIBOBJ([rename])
6ee4d0
   fi
6ee4d0
   gl_STDIO_MODULE_INDICATOR([rename])
6ee4d0
+  gl_FUNC_RENAMEAT
6ee4d0
+  if test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1; then
6ee4d0
+    AC_LIBOBJ([renameat])
6ee4d0
+  fi
6ee4d0
+  if test $HAVE_RENAMEAT = 0; then
6ee4d0
+    AC_LIBOBJ([at-func2])
6ee4d0
+  fi
6ee4d0
+  gl_STDIO_MODULE_INDICATOR([renameat])
6ee4d0
+  gl_FUNC_RENAMEAT
6ee4d0
+  if test $HAVE_RENAMEAT = 0; then
6ee4d0
+    AC_LIBOBJ([at-func2])
6ee4d0
+  fi
6ee4d0
   gl_FUNC_REWINDDIR
6ee4d0
   if test $HAVE_REWINDDIR = 0; then
6ee4d0
     AC_LIBOBJ([rewinddir])
6ee4d0
@@ -2868,6 +2884,9 @@ AC_DEFUN([gl_FILE_LIST], [
6ee4d0
   lib/regexec.c
6ee4d0
   lib/remove.c
6ee4d0
   lib/rename.c
6ee4d0
+  lib/renameat.c
6ee4d0
+  lib/renameat2.c
6ee4d0
+  lib/renameat2.h
6ee4d0
   lib/rewinddir.c
6ee4d0
   lib/rmdir.c
6ee4d0
   lib/root-dev-ino.c
6ee4d0
@@ -3372,6 +3391,7 @@ AC_DEFUN([gl_FILE_LIST], [
6ee4d0
   m4/regex.m4
6ee4d0
   m4/remove.m4
6ee4d0
   m4/rename.m4
6ee4d0
+  m4/renameat.m4
6ee4d0
   m4/rewinddir.m4
6ee4d0
   m4/rmdir.m4
6ee4d0
   m4/rpmatch.m4
6ee4d0
@@ -3794,6 +3814,8 @@ AC_DEFUN([gl_FILE_LIST], [
6ee4d0
   tests/test-remove.c
6ee4d0
   tests/test-rename.c
6ee4d0
   tests/test-rename.h
6ee4d0
+  tests/test-renameat.c
6ee4d0
+  tests/test-renameat2.c
6ee4d0
   tests/test-rmdir.c
6ee4d0
   tests/test-rmdir.h
6ee4d0
   tests/test-sameacls.c
6ee4d0
diff --git a/m4/renameat.m4 b/m4/renameat.m4
6ee4d0
new file mode 100644
6ee4d0
index 0000000..1b97774
6ee4d0
--- /dev/null
6ee4d0
+++ b/m4/renameat.m4
6ee4d0
@@ -0,0 +1,25 @@
6ee4d0
+# serial 3
6ee4d0
+# See if we need to provide renameat replacement.
6ee4d0
+
6ee4d0
+dnl Copyright (C) 2009-2017 Free Software Foundation, Inc.
6ee4d0
+dnl This file is free software; the Free Software Foundation
6ee4d0
+dnl gives unlimited permission to copy and/or distribute it,
6ee4d0
+dnl with or without modifications, as long as this notice is preserved.
6ee4d0
+
6ee4d0
+# Written by Eric Blake.
6ee4d0
+
6ee4d0
+AC_DEFUN([gl_FUNC_RENAMEAT],
6ee4d0
+[
6ee4d0
+  AC_REQUIRE([gl_FUNC_OPENAT])
6ee4d0
+  AC_REQUIRE([gl_FUNC_RENAME])
6ee4d0
+  AC_REQUIRE([gl_STDIO_H_DEFAULTS])
6ee4d0
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
6ee4d0
+  AC_CHECK_HEADERS([linux/fs.h])
6ee4d0
+  AC_CHECK_FUNCS_ONCE([renameat])
6ee4d0
+  if test $ac_cv_func_renameat = no; then
6ee4d0
+    HAVE_RENAMEAT=0
6ee4d0
+  elif test $REPLACE_RENAME = 1; then
6ee4d0
+    dnl Solaris 9 and 10 have the same bugs in renameat as in rename.
6ee4d0
+    REPLACE_RENAMEAT=1
6ee4d0
+  fi
6ee4d0
+])
6ee4d0
diff --git a/src/copy.c b/src/copy.c
6ee4d0
index 2a804945e..be4e357a8 100644
6ee4d0
--- a/src/copy.c
6ee4d0
+++ b/src/copy.c
6ee4d0
@@ -51,6 +51,7 @@
6ee4d0
 #include "ignore-value.h"
6ee4d0
 #include "ioblksize.h"
6ee4d0
 #include "quote.h"
6ee4d0
+#include "renameat2.h"
6ee4d0
 #include "root-uid.h"
6ee4d0
 #include "same.h"
6ee4d0
 #include "savedir.h"
6ee4d0
@@ -2093,8 +2094,9 @@ copy_internal (char const *src_name, char const *dst_name,
6ee4d0
 
6ee4d0
   /* If the source is a directory, we don't always create the destination
6ee4d0
      directory.  So --verbose should not announce anything until we're
6ee4d0
-     sure we'll create a directory. */
6ee4d0
-  if (x->verbose && !S_ISDIR (src_mode))
6ee4d0
+     sure we'll create a directory.  In move mode we delay the diagnostic
6ee4d0
+     message until we know whether renameat2() has actually succeeded. */
6ee4d0
+  if (x->verbose && !S_ISDIR (src_mode) && !x->move_mode)
6ee4d0
     emit_verbose (src_name, dst_name, backup_succeeded ? dst_backup : NULL);
6ee4d0
 
6ee4d0
   /* Associate the destination file name with the source device and inode
6ee4d0
@@ -2196,9 +2198,14 @@ copy_internal (char const *src_name, char const *dst_name,
6ee4d0
 
6ee4d0
   if (x->move_mode)
6ee4d0
     {
6ee4d0
-      if (rename (src_name, dst_name) == 0)
6ee4d0
+      int flags = 0;
6ee4d0
+      if (x->interactive == I_ALWAYS_NO)
6ee4d0
+        /* do not replace DST_NAME if it was created since our last check */
6ee4d0
+        flags = RENAME_NOREPLACE;
6ee4d0
+
6ee4d0
+      if (renameat2 (AT_FDCWD, src_name, AT_FDCWD, dst_name, flags) == 0)
6ee4d0
         {
6ee4d0
-          if (x->verbose && S_ISDIR (src_mode))
6ee4d0
+          if (x->verbose)
6ee4d0
             emit_verbose (src_name, dst_name,
6ee4d0
                           backup_succeeded ? dst_backup : NULL);
6ee4d0
 
6ee4d0
@@ -2226,6 +2233,15 @@ copy_internal (char const *src_name, char const *dst_name,
6ee4d0
           return true;
6ee4d0
         }
6ee4d0
 
6ee4d0
+      if ((flags & RENAME_NOREPLACE) && (errno == EEXIST))
6ee4d0
+        {
6ee4d0
+          /* Pretend the rename succeeded, so the caller (mv)
6ee4d0
+             doesn't end up removing the source file.  */
6ee4d0
+          if (rename_succeeded)
6ee4d0
+            *rename_succeeded = true;
6ee4d0
+          return true;
6ee4d0
+        }
6ee4d0
+
6ee4d0
       /* FIXME: someday, consider what to do when moving a directory into
6ee4d0
          itself but when source and destination are on different devices.  */
6ee4d0
 
6ee4d0
@@ -2301,6 +2317,9 @@ copy_internal (char const *src_name, char const *dst_name,
6ee4d0
           return false;
6ee4d0
         }
6ee4d0
 
6ee4d0
+      if (x->verbose && !S_ISDIR (src_mode))
6ee4d0
+        emit_verbose (src_name, dst_name, backup_succeeded ? dst_backup : NULL);
6ee4d0
+
6ee4d0
       new_dst = true;
6ee4d0
     }
6ee4d0
 
6ee4d0
-- 
6ee4d0
2.13.6
6ee4d0