bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] renameat2: new module


From: Paul Eggert
Subject: [PATCH] renameat2: new module
Date: Thu, 27 Jul 2017 12:11:28 -0700

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.
---
 ChangeLog               |  16 ++++
 MODULES.html.sh         |   1 +
 lib/renameat.c          | 140 +-------------------------------
 lib/renameat2.c         | 205 +++++++++++++++++++++++++++++++++++++++++++++++
 lib/renameat2.h         |  30 +++++++
 modules/renameat        |  17 +---
 modules/renameat-tests  |   2 +-
 modules/renameat2       |  43 ++++++++++
 modules/renameat2-tests |  20 +++++
 tests/test-renameat2.c  | 209 ++++++++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 530 insertions(+), 153 deletions(-)
 create mode 100644 lib/renameat2.c
 create mode 100644 lib/renameat2.h
 create mode 100644 modules/renameat2
 create mode 100644 modules/renameat2-tests
 create mode 100644 tests/test-renameat2.c

diff --git a/ChangeLog b/ChangeLog
index 36dd042..d922e31 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2017-07-27  Paul Eggert  <address@hidden>
+
+       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 <address@hidden>  (tiny change)
 
        vc-list-files: Adjust the script to support git worktrees
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 367d3cf..102223d 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 22151c2..af25a54 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 0000000..19df58c
--- /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 0000000..da3d78c
--- /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 77d93ea..9af0225 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 970ebd7..4e9bf2f 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 0000000..f0ce195
--- /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 0000000..efcc33d
--- /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 0000000..17a3b8e
--- /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 <address@hidden>, 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;
+}
-- 
2.7.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]