renameat: new module
authorEric Blake <ebb9@byu.net>
Mon, 7 Sep 2009 12:45:59 +0000 (06:45 -0600)
committerEric Blake <ebb9@byu.net>
Fri, 2 Oct 2009 12:29:58 +0000 (06:29 -0600)
Passes on Linux and cygwin 1.7 native renameat, and on systems
lacking renameat, but fails on Solaris 9 and 10 for now.

* modules/renameat: New file.
* lib/renameat.c (renameat): Likewise.
* m4/renameat.m4 (gl_FUNC_RENAMEAT): Likewise.
* m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
* modules/stdio (Makefile.am): Substitute them.
* lib/stdio.in.h (renameat): Declare it.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/renameat.texi (renameat): Likewise.
* modules/renameat-tests: New test.
* tests/test-renameat.c: Likewise.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
MODULES.html.sh
doc/posix-functions/renameat.texi
lib/renameat.c [new file with mode: 0644]
lib/stdio.in.h
m4/renameat.m4 [new file with mode: 0644]
m4/stdio_h.m4
modules/renameat [new file with mode: 0644]
modules/renameat-tests [new file with mode: 0644]
modules/stdio
tests/test-renameat.c [new file with mode: 0644]

index b2a45bf..370f664 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-10-02  Eric Blake  <ebb9@byu.net>
 
+       renameat: new module
+       * modules/renameat: New file.
+       * lib/renameat.c (renameat): Likewise.
+       * m4/renameat.m4 (gl_FUNC_RENAMEAT): Likewise.
+       * m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
+       * modules/stdio (Makefile.am): Substitute them.
+       * lib/stdio.in.h (renameat): Declare it.
+       * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+       * doc/posix-functions/renameat.texi (renameat): Likewise.
+       * modules/renameat-tests: New test.
+       * tests/test-renameat.c: Likewise.
+
        rename: fix mingw bugs
        * lib/rename.c (rpl_rename) [W32]: Fix trailing slash and
        directory overwrite bugs.
index e465e3a..ee13eec 100755 (executable)
@@ -2346,6 +2346,7 @@ func_all_modules ()
   func_module nanosleep
   func_module regex
   func_module rename
+  func_module renameat
   func_module rmdir
   func_module search
   func_module sigaction
index de7fa9a..52f3921 100644 (file)
@@ -4,16 +4,37 @@
 
 POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/renameat.html}
 
-Gnulib module: ---
+Gnulib module: renameat
 
 Portability problems fixed by Gnulib:
 @itemize
+@item
+This function does not reject trailing slashes on non-directories on
+some platforms, as in @code{renameat(fd,"file",fd,"new/")}:
+Solaris 10.
+@item
+This function ignores trailing slashes on symlinks on some platforms,
+such that @code{renameat(fd,"link/",fd,"new")} corrupts @file{link}:
+Solaris 9.
+@item
+This function is missing on some platforms:
+glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 8, Cygwin 1.5.x, mingw,
+Interix 3.5, BeOS.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
-5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
+POSIX requires that @code{renameat(fd,"symlink-to-dir/",fd,"dir2")} rename
+@file{dir} and leave @file{symlink-to-dir} dangling; likewise, it
+requires that @code{renameat(fd,"dir",fd,"dangling/")} rename @file{dir} so
+that @file{dangling} is no longer a dangling symlink.  This behavior
+is counter-intuitive, so on some systems, @code{renameat} fails with
+@code{ENOTDIR} if either argument is a symlink with a trailing slash:
+glibc, OpenBSD, Cygwin 1.7.
+@item
+This function will not rename a source that is currently opened
+by any process:
+mingw.
 @end itemize
diff --git a/lib/renameat.c b/lib/renameat.c
new file mode 100644 (file)
index 0000000..244ee38
--- /dev/null
@@ -0,0 +1,35 @@
+/* Rename a file relative to open directories.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#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.  */
+
+int
+renameat (int fd1, char const *file1, int fd2, char const *file2)
+{
+  return at_func2 (fd1, file1, fd2, file2, rename);
+}
index 35109c3..3777e85 100644 (file)
@@ -444,6 +444,18 @@ extern int rename (const char *old, const char *new);
     rename (o, n))
 #endif
 
+#if @GNULIB_RENAMEAT@
+# if !@HAVE_RENAMEAT@
+extern int renameat (int fd1, char const *file1, int fd2, char const *file2);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef renameat
+# define renameat(d1,f1,d2,f2)            \
+    (GL_LINK_WARNING ("renameat is not portable - " \
+                      "use gnulib module renameat for portability"), \
+     renameat (d1, f1, d2, f2))
+#endif
+
 #if @GNULIB_SNPRINTF@
 # if @REPLACE_SNPRINTF@
 #  define snprintf rpl_snprintf
diff --git a/m4/renameat.m4 b/m4/renameat.m4
new file mode 100644 (file)
index 0000000..bda7660
--- /dev/null
@@ -0,0 +1,23 @@
+# serial 1
+# See if we need to provide renameat replacement.
+
+dnl Copyright (C) 2009 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_RENAMEAT],
+[
+  AC_REQUIRE([gl_FUNC_OPENAT])
+  AC_REQUIRE([gl_FUNC_RENAME])
+  AC_REQUIRE([gl_STDIO_H_DEFAULTS])
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+  AC_CHECK_FUNCS_ONCE([renameat])
+  if test $ac_cv_func_renameat = no; then
+    HAVE_RENAMEAT=0
+    AC_LIBOBJ([renameat])
+    AC_LIBOBJ([at-func2])
+  fi
+])
index 01af04d..6f8c035 100644 (file)
@@ -1,4 +1,4 @@
-# stdio_h.m4 serial 19
+# stdio_h.m4 serial 20
 dnl Copyright (C) 2007-2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -69,6 +69,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
   GNULIB_PUTS=0;                 AC_SUBST([GNULIB_PUTS])
   GNULIB_REMOVE=0;               AC_SUBST([GNULIB_REMOVE])
   GNULIB_RENAME=0;               AC_SUBST([GNULIB_RENAME])
+  GNULIB_RENAMEAT=0;             AC_SUBST([GNULIB_RENAMEAT])
   GNULIB_SNPRINTF=0;             AC_SUBST([GNULIB_SNPRINTF])
   GNULIB_SPRINTF_POSIX=0;        AC_SUBST([GNULIB_SPRINTF_POSIX])
   GNULIB_STDIO_H_SIGPIPE=0;      AC_SUBST([GNULIB_STDIO_H_SIGPIPE])
@@ -90,6 +91,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
   HAVE_DPRINTF=1;                AC_SUBST([HAVE_DPRINTF])
   HAVE_FSEEKO=1;                 AC_SUBST([HAVE_FSEEKO])
   HAVE_FTELLO=1;                 AC_SUBST([HAVE_FTELLO])
+  HAVE_RENAMEAT=1;               AC_SUBST([HAVE_RENAMEAT])
   HAVE_VASPRINTF=1;              AC_SUBST([HAVE_VASPRINTF])
   HAVE_VDPRINTF=1;               AC_SUBST([HAVE_VDPRINTF])
   REPLACE_DPRINTF=0;             AC_SUBST([REPLACE_DPRINTF])
diff --git a/modules/renameat b/modules/renameat
new file mode 100644 (file)
index 0000000..efe4a0f
--- /dev/null
@@ -0,0 +1,33 @@
+Description:
+renameat(): rename a file, relative to two directories
+
+Files:
+lib/at-func2.c
+lib/renameat.c
+m4/renameat.m4
+
+Depends-on:
+extensions
+fcntl-h
+filenamecat
+openat
+rename
+same-inode
+stdio
+stpcpy
+
+configure.ac:
+gl_FUNC_RENAMEAT
+gl_STDIO_MODULE_INDICATOR([renameat])
+
+Makefile.am:
+
+Include:
+<fcntl.h>
+<stdio.h>
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
diff --git a/modules/renameat-tests b/modules/renameat-tests
new file mode 100644 (file)
index 0000000..20122da
--- /dev/null
@@ -0,0 +1,14 @@
+Files:
+tests/test-rename.h
+tests/test-renameat.c
+
+Depends-on:
+progname
+xgetcwd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-renameat
+check_PROGRAMS += test-renameat
+test_renameat_LDADD = $(LDADD) @LIBINTL@
index 22b83f7..8f75e19 100644 (file)
@@ -54,6 +54,7 @@ stdio.h: stdio.in.h
              -e 's|@''GNULIB_PUTS''@|$(GNULIB_PUTS)|g' \
              -e 's|@''GNULIB_REMOVE''@|$(GNULIB_REMOVE)|g' \
              -e 's|@''GNULIB_RENAME''@|$(GNULIB_RENAME)|g' \
+             -e 's|@''GNULIB_RENAMEAT''@|$(GNULIB_RENAMEAT)|g' \
              -e 's|@''GNULIB_SNPRINTF''@|$(GNULIB_SNPRINTF)|g' \
              -e 's|@''GNULIB_SPRINTF_POSIX''@|$(GNULIB_SPRINTF_POSIX)|g' \
              -e 's|@''GNULIB_STDIO_H_SIGPIPE''@|$(GNULIB_STDIO_H_SIGPIPE)|g' \
@@ -72,6 +73,7 @@ stdio.h: stdio.in.h
              -e 's|@''HAVE_DECL_SNPRINTF''@|$(HAVE_DECL_SNPRINTF)|g' \
              -e 's|@''HAVE_DECL_VSNPRINTF''@|$(HAVE_DECL_VSNPRINTF)|g' \
              -e 's|@''HAVE_DPRINTF''@|$(HAVE_DPRINTF)|g' \
+             -e 's|@''HAVE_RENAMEAT''@|$(HAVE_RENAMEAT)|g' \
              -e 's|@''HAVE_VASPRINTF''@|$(HAVE_VASPRINTF)|g' \
              -e 's|@''HAVE_VDPRINTF''@|$(HAVE_VDPRINTF)|g' \
              -e 's|@''REPLACE_DPRINTF''@|$(REPLACE_DPRINTF)|g' \
diff --git a/tests/test-renameat.c b/tests/test-renameat.c
new file mode 100644 (file)
index 0000000..09e5119
--- /dev/null
@@ -0,0 +1,185 @@
+/* Tests of renameat.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "filenamecat.h"
+#include "xgetcwd.h"
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-renameat.t"
+
+#include "test-rename.h"
+
+static int dfd1 = AT_FDCWD;
+static int dfd2 = AT_FDCWD;
+
+/* Wrapper to test renameat like rename.  */
+static int
+do_rename (char const *name1, char const *name2)
+{
+  return renameat (dfd1, name1, dfd2, name2);
+}
+
+int
+main ()
+{
+  int i;
+  int dfd;
+  char *cwd;
+  int result;
+
+  /* Clean up any trash from prior testsuite runs.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Test basic 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 = xgetcwd ();
+
+  dfd = open (BASE "sub1", O_RDONLY);
+  ASSERT (0 <= dfd);
+  ASSERT (chdir (BASE "sub2") == 0);
+
+  /* There are 16 possible scenarios, based on whether an fd is
+     AT_FDCWD or real, 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 (renameat (fd1, file1, fd2, file2) == 0);
+      free (file1);
+      free (file2);
+    }
+  dfd2 = open ("..", O_RDONLY);
+  ASSERT (0 <= dfd2);
+  ASSERT (renameat (dfd, "../" BASE "16", dfd2, BASE "17") == 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 (renameat (dfd, BASE "sub1", dfd, BASE "sub2") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+  ASSERT (unlink (BASE "sub2/file") == 0);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "sub1/.") == -1);
+  ASSERT (errno == EINVAL || errno == EISDIR || errno == EBUSY);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2/.", dfd, BASE "sub1") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17", dfd, BASE "sub1") == -1);
+  ASSERT (errno == EISDIR);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "nosuch", dfd, BASE "18") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (renameat (dfd, "", dfd, BASE "17") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17", dfd, "") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "17") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17/", dfd, BASE "18") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17", dfd, BASE "18/") == -1);
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
+
+  /* Finally, make sure we can overwrite existing files.  */
+  ASSERT (close (creat (BASE "sub2/file", 0600)) == 0);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "sub1") == 0);
+  ASSERT (renameat (dfd, BASE "sub1/file", dfd, BASE "17") == 0);
+
+  /* Cleanup.  */
+  ASSERT (close (dfd) == 0);
+  errno = 0;
+  ASSERT (unlink (BASE "sub1/file") == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (unlink (BASE "17") == 0);
+  ASSERT (rmdir (BASE "sub1") == 0);
+  errno = 0;
+  ASSERT (rmdir (BASE "sub2") == -1);
+  ASSERT (errno == ENOENT);
+  free (cwd);
+
+  if (result)
+    fputs ("skipping test: symlinks not supported on this filesystem\n",
+           stderr);
+  return result;
+}