changeset 12099:01c66cf797b5

renameat: new module 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>
author Eric Blake <ebb9@byu.net>
date Mon, 07 Sep 2009 06:45:59 -0600
parents 3d66373d8171
children 8e81b30aa8b7
files ChangeLog MODULES.html.sh doc/posix-functions/renameat.texi lib/renameat.c lib/stdio.in.h m4/renameat.m4 m4/stdio_h.m4 modules/renameat modules/renameat-tests modules/stdio tests/test-renameat.c
diffstat 11 files changed, 345 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- 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.
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2346,6 +2346,7 @@
   func_module nanosleep
   func_module regex
   func_module rename
+  func_module renameat
   func_module rmdir
   func_module search
   func_module sigaction
--- a/doc/posix-functions/renameat.texi
+++ b/doc/posix-functions/renameat.texi
@@ -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
new file mode 100644
--- /dev/null
+++ b/lib/renameat.c
@@ -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);
+}
--- a/lib/stdio.in.h
+++ b/lib/stdio.in.h
@@ -444,6 +444,18 @@
     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
new file mode 100644
--- /dev/null
+++ b/m4/renameat.m4
@@ -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
+])
--- a/m4/stdio_h.m4
+++ b/m4/stdio_h.m4
@@ -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 @@
   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 @@
   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])
new file mode 100644
--- /dev/null
+++ b/modules/renameat
@@ -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
new file mode 100644
--- /dev/null
+++ b/modules/renameat-tests
@@ -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@
--- a/modules/stdio
+++ b/modules/stdio
@@ -54,6 +54,7 @@
 	      -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 @@
 	      -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' \
new file mode 100644
--- /dev/null
+++ b/tests/test-renameat.c
@@ -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;
+}