summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Blake <ebb9@byu.net>2009-09-07 06:45:59 -0600
committerEric Blake <ebb9@byu.net>2009-10-02 06:29:58 -0600
commit1b6986bdbf9454a758ed2c24f8f7d91e34e6bc75 (patch)
tree84c0f85f21bf20cb0af7f14d7823dabc16923bfe
parent0d54f471901494f8fc5b638fbcc0768a42caddc7 (diff)
downloadgnulib-1b6986bdbf9454a758ed2c24f8f7d91e34e6bc75.tar.gz
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>
-rw-r--r--ChangeLog12
-rwxr-xr-xMODULES.html.sh1
-rw-r--r--doc/posix-functions/renameat.texi29
-rw-r--r--lib/renameat.c35
-rw-r--r--lib/stdio.in.h12
-rw-r--r--m4/renameat.m423
-rw-r--r--m4/stdio_h.m44
-rw-r--r--modules/renameat33
-rw-r--r--modules/renameat-tests14
-rw-r--r--modules/stdio2
-rw-r--r--tests/test-renameat.c185
11 files changed, 345 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index b2a45bf570..370f6644d4 100644
--- 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.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index e465e3a4df..ee13eec585 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -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
diff --git a/doc/posix-functions/renameat.texi b/doc/posix-functions/renameat.texi
index de7fa9ab9d..52f3921a66 100644
--- 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
diff --git a/lib/renameat.c b/lib/renameat.c
new file mode 100644
index 0000000000..244ee38edd
--- /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);
+}
diff --git a/lib/stdio.in.h b/lib/stdio.in.h
index 35109c399e..3777e85d32 100644
--- a/lib/stdio.in.h
+++ b/lib/stdio.in.h
@@ -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
index 0000000000..bda7660c4c
--- /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
+])
diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4
index 01af04da92..6f8c035a52 100644
--- 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 @@ 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
index 0000000000..efe4a0f7f9
--- /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
diff --git a/modules/renameat-tests b/modules/renameat-tests
new file mode 100644
index 0000000000..20122dad6c
--- /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@
diff --git a/modules/stdio b/modules/stdio
index 22b83f730d..8f75e19a6b 100644
--- a/modules/stdio
+++ b/modules/stdio
@@ -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
index 0000000000..09e5119024
--- /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;
+}