bug-gnulib
[Top][All Lists]
Advanced

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

renameat (was: help with rename)


From: Eric Blake
Subject: renameat (was: help with rename)
Date: Thu, 01 Oct 2009 07:17:43 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

According to Jim Meyering on 10/1/2009 1:59 AM:
>> So I'm guessing that the bug was related to actual directories, where rename
>> ("dir/","new") failed (with what errno?) while rename("dir","new") succeeded;
> 
> That is definitely the case in question, as seen in the
> original tests/mv/trailing-slash.  I don't recall which errno.

I think my current patch series will still accommodate that ancient bug,
but I'm no longer worried if it doesn't (the new test-rename will catch
it, and we can deal with any user reports if they come in).

Here's the current state of the series, finally ready for review.  If we
check it in as-is, then coreutils will have everything it needs to ensure
consistent behavior of 'mv -T a b/' on every platform it already supports
except for cygwin 1.5 (which has never been a show-stopper for coreutils
releases in the past).  Or you can wait a bit longer for the next few
things I want to fix:

test-rename fails on mingw (but coreutils already fails to support mingw)

renameat is available on Solaris 9 and 10, and needs the same fixes as
rename (but coreutils isn't using renameat yet)

cygwin 1.5.x still has a bug with moving a full directory on top of an
empty one:
$ mkdir a b
$ touch a/f
$ mv -T a b
$ ls
b
$ rm b/f
$ rmdir b
$ ls
a

Oops - for some reason, Windows thinks it needs to revive directory "a"
once "b" is emptied out and removed.  Extremely weird; unfortunately the
fix requires that renaming on top of an existing directory will be
non-atomic on cygwin.  I'm debating about whether to rebase the current
series to fold that fix into the existing cygwin 1.5 fixes, or whether to
just do it as a separate patch.

Eric Blake (7):
      [1/7] rename-tests: new test, exposes several platform bugs
Key to all subsequent patches: coded to allow what we want to accept
natively, while hammering on every possible path I could conceive to
expose platform bugs.  Patches 2-5 fix the fallout from the exposed
failures.  Does not test the remaining cygwin 1.5.x bug with renaming onto
existing directories.

      [2/7] rename-dest-slash: fix NetBSD bug
In addition to the patched NetBSD bug of rename("dir","name/"), the
testsuite exposed a bug in rename("hard1","hard2") inappropriately
removing the hard link.  POSIX 2001 was not clear on this point, but POSIX
2008 states it must be a no-op.

      [3/7] rename: fix Solaris 9 bug
Solaris 9 blindly strips trailing slashes, meaning that
rename("file/","name") succeeded.  It also meant that
rename("link/","name") did not follow either POSIX or GNU semantics; this
patch chooses GNU semantics because it takes less effort.

      [4/7] rename: fix Solaris 10 bug
Solaris 10 fixed most trailing slash bugs, but missed one:
rename("file","name/") succeeded.  But since it already obeys POSIX
semantics, I did not want to cripple that (otherwise, I could have just
reused the Solaris 9 fix as-is).

      [5/7] rename: fix cygwin 1.5.x bugs
Cygwin 1.5 allowed rename("dir","file") to overwrite file, and failed to
reject trailing dot in rename("dir/.","name").  It also had the same hard
link bug as NetBSD.

      [6/7] rename-dest-slash: merge into rename module
Now that rename fixes everything that rename-dest-slash did, the merge
seems quite easy.  rename-dest-slash is now obsolete, and rename does all
the work.

      [7/7] renameat: new module
The bulk of this patch has been ready for 3 weeks now; all that was
missing was a working rpl_rename to base it on top of.  I still need to
write [8/7] to implement rpl_renameat for Solaris 9 and 10.

- --
Don't work too hard, make some time for fun as well!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkrEq/cACgkQ84KuGfSFAYCOEwCgw9S2BNIxz4S5yTYy6YLn+NHc
bsgAn1BrH2ejxYQjOwrIkg6evLbtjK+c
=Mvpg
-----END PGP SIGNATURE-----
>From dbf77f24e51de632ea611bd954952cb1f8048a43 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 26 Sep 2009 17:22:15 -0600
Subject: [PATCH 1/7] rename-tests: new test, exposes several platform bugs

This test passes on GNU/Linux, OpenBSD, and Cygwin 1.7.
Elsewhere, this test fails because of at least these bugs:
Solaris 10, cygwin 1.5.x, and mingw all mistakenly succeed on
rename("file","other/").  Solaris 9 and the gnulib replacement
for SunOS 4.1 mistakenly succeed on rename("file/","other").
Cygwin 1.5.x and mingw mistakenly succeed on rename("dir","d/.").
Cygwin 1.5.x and NetBSD 1.6 (even with the gnulib replacement)
mistakenly reduce the link count on rename("hard1","hard2").

* modules/rename-tests: New file.
* tests/test-rename.h: Likewise.
* tests/test-rename.c: Likewise.
* doc/posix-functions/rename.texi (rename): Improve documentation,
including bugs that will eventually be fixed in gnulib.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |    7 +
 doc/posix-functions/rename.texi |   47 ++++-
 modules/rename-tests            |   19 ++
 tests/test-rename.c             |   53 +++++
 tests/test-rename.h             |  444 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 561 insertions(+), 9 deletions(-)
 create mode 100644 modules/rename-tests
 create mode 100644 tests/test-rename.c
 create mode 100644 tests/test-rename.h

diff --git a/ChangeLog b/ChangeLog
index ecfb784..28d41ca 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2009-10-01  Eric Blake  <address@hidden>

+       rename-tests: new test, exposes several platform bugs
+       * modules/rename-tests: New file.
+       * tests/test-rename.h: Likewise.
+       * tests/test-rename.c: Likewise.
+       * doc/posix-functions/rename.texi (rename): Improve documentation,
+       including bugs that will eventually be fixed in gnulib.
+
        getopt: bump serial number
        * m4/getopt.m4: Increment serial number, to account for 2009-09-24
        change.
diff --git a/doc/posix-functions/rename.texi b/doc/posix-functions/rename.texi
index 84a03ab..65981db 100644
--- a/doc/posix-functions/rename.texi
+++ b/doc/posix-functions/rename.texi
@@ -9,22 +9,51 @@ rename
 Portability problems fixed by Gnulib:
 @itemize
 @item
-This function does not handle trailing slashes correctly on
-some platforms (the full rules for trailing slashes are complex):
-SunOS 4.1, mingw.
+This function does not allow trailing slashes when creating a
+destination directory, as in @code{rename("dir","new/")}:
+NetBSD 1.6.
 @item
-This function will not replace an existing destination on some
+This function does not reject trailing slashes on non-directories on
+some platforms, as in @code{rename("file","new/")}:
+Solaris 10, Cygwin 1.5.x, mingw.
address@hidden
+This function ignores trailing slashes on symlinks on some platforms,
+such that @code{rename("link/","new")} corrupts @file{link}:
+Solaris 9.
address@hidden
+This function incorrectly reduces the link count when comparing two
+spellings of a hard link on some platforms:
+NetBSD 1.6, Cygwin 1.5.x.
address@hidden
+This function will not always replace an existing destination on some
 platforms:
 mingw.
address@hidden
+This function mistakenly allows names ending in @samp{.} or @samp{..}
+on some platforms:
+Cygwin 1.5.x, mingw.
address@hidden
+This function does not reject attempts to rename existing directories
+and non-directories onto one another on some platforms:
+Cygwin 1.5.x, mingw.
address@hidden
+This function does not allow trailing slashes on source directories on
+older platforms, as in @samp{rename("dir/","new")}:
+SunOS 4.1.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
-This function will not replace a destination that is currently opened
address@hidden
+POSIX requires that @code{rename("symlink-to-dir/","dir2")} rename
address@hidden and leave @file{symlink-to-dir} dangling; likewise, it
+requires that @code{rename("dir","dangling/")} rename @file{dir} so
+that @file{dangling} is no longer a dangling symlink.  This behavior
+is counter-intuitive, so on some systems, @code{rename} fails with
address@hidden if either argument is a symlink with a trailing slash:
+glibc, OpenBSD, Cygwin 1.7.
address@hidden
+This function will not rename a source that is currently opened
 by any process:
 mingw.
address@hidden
-This function mistakenly allows names ending in @samp{.} or @samp{..}
-on some platforms:
-Cygwin 1.5.x.
 @end itemize
diff --git a/modules/rename-tests b/modules/rename-tests
new file mode 100644
index 0000000..ed586d7
--- /dev/null
+++ b/modules/rename-tests
@@ -0,0 +1,19 @@
+Files:
+tests/test-rename.h
+tests/test-rename.c
+
+Depends-on:
+errno
+link
+lstat
+progname
+stdbool
+symlink
+sys_stat
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-rename
+check_PROGRAMS += test-rename
+test_rename_LDADD = $(LDADD) @LIBINTL@
diff --git a/tests/test-rename.c b/tests/test-rename.c
new file mode 100644
index 0000000..7bfdd84
--- /dev/null
+++ b/tests/test-rename.c
@@ -0,0 +1,53 @@
+/* Test of rename() function.
+   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 2 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/>.  */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-rename.t"
+
+#include "test-rename.h"
+
+int
+main (int argc, char **argv)
+{
+  /* Remove any garbage left from previous partial runs.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  return test_rename (rename, true);
+}
diff --git a/tests/test-rename.h b/tests/test-rename.h
new file mode 100644
index 0000000..440e470
--- /dev/null
+++ b/tests/test-rename.h
@@ -0,0 +1,444 @@
+/* Test of rename() function.
+   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 2 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/>.  */
+
+/* This file is designed to test both rename(a,b) and
+   renameat(AT_FDCWD,a,AT_FDCWD,b).  FUNC is the function to test.
+   Assumes that BASE and ASSERT are already defined, and that
+   appropriate headers are already included.  If PRINT, warn before
+   skipping symlink tests with status 77.  */
+
+static int
+test_rename (int (*func) (char const *, char const *), bool print)
+{
+  /* Setup.  */
+  struct stat st;
+  int fd = creat (BASE "file", 0600);
+  ASSERT (0 <= fd);
+  ASSERT (write (fd, "hi", 2) == 2);
+  ASSERT (close (fd) == 0);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+
+  /* Obvious errors.  */
+
+  errno = 0; /* Missing source.  */
+  ASSERT (func (BASE "missing", BASE "missing") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "missing/", BASE "missing") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "missing", BASE "missing/") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0; /* Empty operand.  */
+  ASSERT (func ("", BASE "missing") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "file", "") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "", "") == -1);
+  ASSERT (errno == ENOENT);
+
+  /* Files.  */
+  errno = 0; /* Trailing slash.  */
+  ASSERT (func (BASE "file", BASE "file2/") == -1);
+  ASSERT (errno == ENOENT || errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "file/", BASE "file2") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (stat (BASE "file2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (func (BASE "file", BASE "file2") == 0); /* Simple rename.  */
+  errno = 0;
+  ASSERT (stat (BASE "file", &st) == -1);
+  ASSERT (errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (stat (BASE "file2", &st) == 0);
+  ASSERT (st.st_size == 2);
+  ASSERT (close (creat (BASE "file", 0600)) == 0); /* Overwrite.  */
+  errno = 0;
+  ASSERT (func (BASE "file2", BASE "file/") == -1);
+  ASSERT (errno == ENOTDIR);
+  ASSERT (func (BASE "file2", BASE "file") == 0);
+  memset (&st, 0, sizeof st);
+  ASSERT (stat (BASE "file", &st) == 0);
+  ASSERT (st.st_size == 2);
+  errno = 0;
+  ASSERT (stat (BASE "file2", &st) == -1);
+  ASSERT (errno == ENOENT);
+
+  /* Directories.  */
+  ASSERT (func (BASE "dir", BASE "dir2/") == 0); /* Simple rename.  */
+  errno = 0;
+  ASSERT (stat (BASE "dir", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (stat (BASE "dir2", &st) == 0);
+  ASSERT (func (BASE "dir2/", BASE "dir") == 0);
+  ASSERT (stat (BASE "dir", &st) == 0);
+  errno = 0;
+  ASSERT (stat (BASE "dir2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (func (BASE "dir", BASE "dir2") == 0);
+  errno = 0;
+  ASSERT (stat (BASE "dir", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (stat (BASE "dir2", &st) == 0);
+  ASSERT (mkdir (BASE "dir", 0700) == 0); /* Empty onto empty.  */
+  ASSERT (func (BASE "dir2", BASE "dir") == 0);
+  ASSERT (mkdir (BASE "dir2", 0700) == 0);
+  ASSERT (func (BASE "dir2", BASE "dir/") == 0);
+  ASSERT (mkdir (BASE "dir2", 0700) == 0);
+  ASSERT (func (BASE "dir2/", BASE "dir") == 0);
+  ASSERT (mkdir (BASE "dir2", 0700) == 0);
+  ASSERT (close (creat (BASE "dir/file", 0600)) == 0); /* Empty onto full.  */
+  errno = 0;
+  ASSERT (func (BASE "dir2", BASE "dir") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+  errno = 0;
+  ASSERT (func (BASE "dir2/", BASE "dir") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+  errno = 0;
+  ASSERT (func (BASE "dir2", BASE "dir/") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+  ASSERT (func (BASE "dir", BASE "dir2") == 0); /* Full onto empty.  */
+  errno = 0;
+  ASSERT (stat (BASE "dir", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (stat (BASE "dir2/file", &st) == 0);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  ASSERT (func (BASE "dir2/", BASE "dir") == 0);
+  ASSERT (stat (BASE "dir/file", &st) == 0);
+  errno = 0;
+  ASSERT (stat (BASE "dir2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (mkdir (BASE "dir2", 0700) == 0);
+  ASSERT (func (BASE "dir", BASE "dir2/") == 0);
+  errno = 0;
+  ASSERT (stat (BASE "dir", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (stat (BASE "dir2/file", &st) == 0);
+  ASSERT (unlink (BASE "dir2/file") == 0);
+  errno = 0; /* Reject trailing dot.  */
+  ASSERT (func (BASE "dir2", BASE "dir/.") == -1);
+  ASSERT (errno == EINVAL || errno == ENOENT);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  ASSERT (func (BASE "dir2", BASE "dir/.") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY || errno == EISDIR);
+  errno = 0;
+  ASSERT (func (BASE "dir2/.", BASE "dir") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY);
+  ASSERT (rmdir (BASE "dir") == 0);
+  errno = 0;
+  ASSERT (func (BASE "dir2", BASE "dir/.//") == -1);
+  ASSERT (errno == EINVAL || errno == ENOENT);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  ASSERT (func (BASE "dir2", BASE "dir/.//") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY || errno == EISDIR);
+  errno = 0;
+  ASSERT (func (BASE "dir2/.//", BASE "dir") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY);
+  ASSERT (rmdir (BASE "dir2") == 0);
+  errno = 0; /* Move into subdir.  */
+  ASSERT (func (BASE "dir", BASE "dir/sub") == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (stat (BASE "dir/sub", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (mkdir (BASE "dir/sub", 0700) == 0);
+  errno = 0;
+  ASSERT (func (BASE "dir", BASE "dir/sub") == -1);
+  ASSERT (errno == EINVAL);
+  ASSERT (stat (BASE "dir/sub", &st) == 0);
+  ASSERT (rmdir (BASE "dir/sub") == 0);
+
+  /* Mixing file and directory.  */
+  errno = 0; /* File onto dir.  */
+  ASSERT (func (BASE "file", BASE "dir") == -1);
+  ASSERT (errno == EISDIR || errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "file", BASE "dir/") == -1);
+  ASSERT (errno == EISDIR || errno == ENOTDIR);
+  errno = 0; /* Dir onto file.  */
+  ASSERT (func (BASE "dir", BASE "file") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "dir/", BASE "file") == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Hard links.  */
+  ASSERT (func (BASE "file", BASE "file") == 0); /* File onto self.  */
+  memset (&st, 0, sizeof st);
+  ASSERT (stat (BASE "file", &st) == 0);
+  ASSERT (st.st_size == 2);
+  ASSERT (func (BASE "dir", BASE "dir") == 0); /* Empty dir onto self.  */
+  ASSERT (stat (BASE "dir", &st) == 0);
+  ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+  ASSERT (func (BASE "dir", BASE "dir") == 0); /* Full dir onto self.  */
+  ASSERT (unlink (BASE "dir/file") == 0);
+  {
+    /*  Not all file systems support link.  Mingw doesn't have
+        reliable st_nlink on hard links, but our implementation does
+        fail with EPERM on poor file systems, and we can detect the
+        inferior stat() via st_ino.  Cygwin 1.5.x copies rather than
+        links files on those file systems, but there, st_nlink and
+        st_ino are reliable.  */
+    int ret = link (BASE "file", BASE "file2");
+    if (!ret)
+      {
+        memset (&st, 0, sizeof st);
+        ASSERT (stat (BASE "file2", &st) == 0);
+        if (st.st_ino && st.st_nlink != 2)
+          {
+            ASSERT (unlink (BASE "file2") == 0);
+            errno = EPERM;
+            ret = -1;
+          }
+      }
+    if (ret == -1)
+      {
+        /* If the device does not support hard links, errno is
+           EPERM on Linux, EOPNOTSUPP on FreeBSD.  */
+        switch (errno)
+          {
+          case EPERM:
+          case EOPNOTSUPP:
+            if (print)
+              fputs ("skipping test: "
+                     "hard links not supported on this file system\n",
+                     stderr);
+            ASSERT (unlink (BASE "file") == 0);
+            ASSERT (rmdir (BASE "dir") == 0);
+            return 77;
+          default:
+            perror ("link");
+            return 1;
+          }
+      }
+    ASSERT (ret == 0);
+  }
+  ASSERT (func (BASE "file", BASE "file2") == 0); /* File onto hard link.  */
+  memset (&st, 0, sizeof st);
+  ASSERT (stat (BASE "file", &st) == 0);
+  ASSERT (st.st_size == 2);
+  memset (&st, 0, sizeof st);
+  ASSERT (stat (BASE "file2", &st) == 0);
+  ASSERT (st.st_size == 2);
+  ASSERT (unlink (BASE "file2") == 0);
+
+  /* Symlinks.  */
+  if (symlink (BASE "file", BASE "link1"))
+    {
+      if (print)
+        fputs ("skipping test: symlinks not supported on this filesystem\n",
+               stderr);
+      ASSERT (unlink (BASE "file") == 0);
+      ASSERT (rmdir (BASE "dir") == 0);
+      return 77;
+    }
+  ASSERT (func (BASE "link1", BASE "link2") == 0); /* Simple rename.  */
+  ASSERT (stat (BASE "file", &st) == 0);
+  errno = 0;
+  ASSERT (lstat (BASE "link1", &st) == -1);
+  ASSERT (errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "link2", &st) == 0);
+  ASSERT (S_ISLNK (st.st_mode));
+  ASSERT (symlink (BASE "nowhere", BASE "link1") == 0); /* Overwrite.  */
+  ASSERT (func (BASE "link2", BASE "link1") == 0);
+  memset (&st, 0, sizeof st);
+  ASSERT (stat (BASE "link1", &st) == 0);
+  ASSERT (st.st_size == 2);
+  errno = 0;
+  ASSERT (lstat (BASE "link2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (symlink (BASE "link2", BASE "link2") == 0); /* Symlink loop.  */
+  ASSERT (func (BASE "link2", BASE "link2") == 0);
+  errno = 0;
+  ASSERT (func (BASE "link2/", BASE "link2") == -1);
+  ASSERT (errno == ELOOP || errno == ENOTDIR);
+  ASSERT (func (BASE "link2", BASE "link3") == 0);
+  ASSERT (unlink (BASE "link3") == 0);
+  ASSERT (symlink (BASE "nowhere", BASE "link2") == 0); /* Dangling link.  */
+  ASSERT (func (BASE "link2", BASE "link3") == 0);
+  errno = 0;
+  ASSERT (lstat (BASE "link2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "link3", &st) == 0);
+  errno = 0; /* Trailing slash on dangling.  */
+  ASSERT (func (BASE "link3/", BASE "link2") == -1);
+  ASSERT (errno == ENOENT || errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "link3", BASE "link2/") == -1);
+  ASSERT (errno == ENOENT || errno == ENOTDIR);
+  errno = 0;
+  ASSERT (lstat (BASE "link2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "link3", &st) == 0);
+  errno = 0; /* Trailing slash on link to file.  */
+  ASSERT (func (BASE "link1/", BASE "link2") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "link1", BASE "link3/") == -1);
+  ASSERT (errno == ENOENT || errno == ENOTDIR);
+
+  /* Mixing symlink and file.  */
+  ASSERT (close (creat (BASE "file2", 0600)) == 0); /* File onto link.  */
+  ASSERT (func (BASE "file2", BASE "link3") == 0);
+  errno = 0;
+  ASSERT (stat (BASE "file2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "link3", &st) == 0);
+  ASSERT (S_ISREG (st.st_mode));
+  ASSERT (unlink (BASE "link3") == 0);
+  ASSERT (symlink (BASE "nowhere", BASE "link2") == 0); /* Link onto file.  */
+  ASSERT (close (creat (BASE "file2", 0600)) == 0);
+  ASSERT (func (BASE "link2", BASE "file2") == 0);
+  errno = 0;
+  ASSERT (lstat (BASE "link2", &st) == -1);
+  ASSERT (errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "file2", &st) == 0);
+  ASSERT (S_ISLNK (st.st_mode));
+  ASSERT (unlink (BASE "file2") == 0);
+  errno = 0; /* Trailing slash.  */
+  ASSERT (func (BASE "file/", BASE "link1") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "file", BASE "link1/") == -1);
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "link1/", BASE "file") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "link1", BASE "file/") == -1);
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "file", &st) == 0);
+  ASSERT (S_ISREG (st.st_mode));
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "link1", &st) == 0);
+  ASSERT (S_ISLNK (st.st_mode));
+
+  /* Mixing symlink and directory.  */
+  errno = 0; /* Directory onto link.  */
+  ASSERT (func (BASE "dir", BASE "link1") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "dir/", BASE "link1") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "dir", BASE "link1/") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0; /* Link onto directory.  */
+  ASSERT (func (BASE "link1", BASE "dir") == -1);
+  ASSERT (errno == EISDIR || errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "link1", BASE "dir/") == -1);
+  ASSERT (errno == EISDIR || errno == ENOTDIR);
+  errno = 0;
+  ASSERT (func (BASE "link1/", BASE "dir") == -1);
+  ASSERT (errno == ENOTDIR);
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "link1", &st) == 0);
+  ASSERT (S_ISLNK (st.st_mode));
+  memset (&st, 0, sizeof st);
+  ASSERT (lstat (BASE "dir", &st) == 0);
+  ASSERT (S_ISDIR (st.st_mode));
+
+  /* POSIX requires rename("link-to-dir/","other") to rename "dir" and
+     leave "link-to-dir" dangling, but GNU rejects this.  POSIX
+     requires rename("dir","dangling/") to create the directory so
+     that "dangling/" now resolves, but GNU rejects this.  While we
+     prefer GNU behavior, we don't enforce it.  However, we do test
+     that the system either follows POSIX in both cases, or follows
+     GNU.  */
+  {
+    int result;
+    ASSERT (symlink (BASE "dir2", BASE "link2") == 0);
+    errno = 0;
+    result = func (BASE "dir", BASE "link2/");
+    if (result == 0)
+      {
+        /* POSIX.  */
+        errno = 0;
+        ASSERT (lstat (BASE "dir", &st) == -1);
+        ASSERT (errno == ENOENT);
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "dir2", &st) == 0);
+        ASSERT (S_ISDIR (st.st_mode));
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "link2", &st) == 0);
+        ASSERT (S_ISLNK (st.st_mode));
+        ASSERT (func (BASE "link2/", BASE "dir") == 0);
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "dir", &st) == 0);
+        ASSERT (S_ISDIR (st.st_mode));
+        errno = 0;
+        ASSERT (lstat (BASE "dir2", &st) == -1);
+        ASSERT (errno == ENOENT);
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "link2", &st) == 0);
+        ASSERT (S_ISLNK (st.st_mode));
+      }
+    else
+      {
+        /* GNU.  */
+        ASSERT (result == -1);
+        ASSERT (errno == ENOTDIR);
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "dir", &st) == 0);
+        ASSERT (S_ISDIR (st.st_mode));
+        errno = 0;
+        ASSERT (lstat (BASE "dir2", &st) == -1);
+        ASSERT (errno == ENOENT);
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "link2", &st) == 0);
+        ASSERT (S_ISLNK (st.st_mode));
+        ASSERT (unlink (BASE "link2") == 0);
+        ASSERT (symlink (BASE "dir", BASE "link2") == 0);
+        errno = 0; /* OpenBSD notices that link2/ and dir are the same.  */
+        result = func (BASE "link2/", BASE "dir");
+        if (result) /* GNU/Linux rejects attempts to use link2/.  */
+          {
+            ASSERT (result == -1);
+            ASSERT (errno == ENOTDIR);
+          }
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "dir", &st) == 0);
+        ASSERT (S_ISDIR (st.st_mode));
+        errno = 0;
+        ASSERT (lstat (BASE "dir2", &st) == -1);
+        ASSERT (errno == ENOENT);
+        memset (&st, 0, sizeof st);
+        ASSERT (lstat (BASE "link2", &st) == 0);
+        ASSERT (S_ISLNK (st.st_mode));
+      }
+  }
+
+  /* Clean up.  */
+  ASSERT (unlink (BASE "file") == 0);
+  ASSERT (rmdir (BASE "dir") == 0);
+  ASSERT (unlink (BASE "link1") == 0);
+  ASSERT (unlink (BASE "link2") == 0);
+
+  return 0;
+}
-- 
1.6.5.rc1


>From ffd9f93cf4ed6b919b761c4723d65dad63e96402 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 30 Sep 2009 18:57:02 -0600
Subject: [PATCH 2/7] rename-dest-slash: fix NetBSD bug

rename("hard1","hard2") mistakenly removed the hard link "hard1".

* lib/rename-dest-slash.c (rpl_rename_dest_slash): Detect hard
links.
* modules/rename-dest-slash (Depends-on): Add same-inode.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                 |    5 +++++
 lib/rename-dest-slash.c   |   15 ++++++++++++---
 modules/rename-dest-slash |    3 ++-
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 28d41ca..ef6ce95 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-01  Eric Blake  <address@hidden>

+       rename-dest-slash: fix NetBSD bug
+       * lib/rename-dest-slash.c (rpl_rename_dest_slash): Detect hard
+       links.
+       * modules/rename-dest-slash (Depends-on): Add same-inode.
+
        rename-tests: new test, exposes several platform bugs
        * modules/rename-tests: New file.
        * tests/test-rename.h: Likewise.
diff --git a/lib/rename-dest-slash.c b/lib/rename-dest-slash.c
index 8066076..2014fb1 100644
--- a/lib/rename-dest-slash.c
+++ b/lib/rename-dest-slash.c
@@ -6,7 +6,7 @@
    (namely mv) relying on the rename syscall have more consistent
    semantics.

-   Copyright (C) 2006 Free Software Foundation, Inc.
+   Copyright (C) 2006, 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
@@ -35,6 +35,7 @@
 #include <stdlib.h>

 #include "dirname.h"
+#include "same-inode.h"
 #include "xalloc.h"

 static bool
@@ -63,14 +64,22 @@ has_trailing_slash (char const *file)
 int
 rpl_rename_dest_slash (char const *src, char const *dst)
 {
-  int ret_val = rename (src, dst);
+  struct stat sb;
+  struct stat db;
+  int ret_val;
+
+  if (lstat (src, &sb))
+    return -1;
+  if (lstat (dst, &db) == 0 && SAME_INODE (sb, db))
+    return 0;
+
+  ret_val = rename (src, dst);

   if (ret_val != 0 && errno == ENOENT && has_trailing_slash (dst))
     {
       int rename_errno = ENOENT;

       /* Fail now, unless SRC is a directory.  */
-      struct stat sb;
       if (lstat (src, &sb) == 0 && S_ISDIR (sb.st_mode))
        {
          char *dst_temp = xstrdup (dst);
diff --git a/modules/rename-dest-slash b/modules/rename-dest-slash
index d45fe5b..c0bc98d 100644
--- a/modules/rename-dest-slash
+++ b/modules/rename-dest-slash
@@ -6,8 +6,9 @@ lib/rename-dest-slash.c
 m4/rename-dest-slash.m4

 Depends-on:
-xalloc
 dirname
+same-inode
+xalloc

 configure.ac:
 gl_FUNC_RENAME_TRAILING_DEST_SLASH
-- 
1.6.5.rc1


>From c005e2ae5b191262ec221cf04d51c819c5d81c16 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 30 Sep 2009 16:19:00 -0600
Subject: [PATCH 3/7] rename: fix Solaris 9 bug

rename("file/","oops") mistakenly succeeded.

* lib/rename.c (rpl_rename): Rewrite to recognize trailing slash
on non-directory.  Avoid calling exit.
* modules/rename (Depends-on): Drop xalloc; add lstat, stdbool,
strdup.
* modules/rename-tests (Depends-on): Drop lstat.
* m4/rename.m4 (gl_FUNC_RENAME): Detect Solaris bug.
(gl_PREREQ_RENAME): Delete unused macro.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog            |    9 ++++
 lib/rename.c         |  110 +++++++++++++++++++++++++++++++++++++++++---------
 m4/rename.m4         |   70 ++++++++++++++------------------
 modules/rename       |    4 +-
 modules/rename-tests |    1 -
 5 files changed, 134 insertions(+), 60 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ef6ce95..1fe2091 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2009-10-01  Eric Blake  <address@hidden>

+       rename: fix Solaris 9 bug
+       * lib/rename.c (rpl_rename): Rewrite to recognize trailing slash
+       on non-directory.  Avoid calling exit.
+       * modules/rename (Depends-on): Drop xalloc; add lstat, stdbool,
+       strdup.
+       * modules/rename-tests (Depends-on): Drop lstat.
+       * m4/rename.m4 (gl_FUNC_RENAME): Detect Solaris bug.
+       (gl_PREREQ_RENAME): Delete unused macro.
+
        rename-dest-slash: fix NetBSD bug
        * lib/rename-dest-slash.c (rpl_rename_dest_slash): Detect hard
        links.
diff --git a/lib/rename.c b/lib/rename.c
index 5cd4dee..0709759 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -1,7 +1,4 @@
-/* Work around rename bugs in some systems.  On SunOS 4.1.1_U1
-   and mips-dec-ultrix4.4, rename fails when the source file has
-   a trailing slash.  On mingw, rename fails when the destination
-   exists.
+/* Work around rename bugs in some systems.

    Copyright (C) 2001, 2002, 2003, 2005, 2006, 2009 Free Software
    Foundation, Inc.
@@ -19,7 +16,7 @@
    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 Volker Borchert */
+/* Written by Volker Borchert, Eric Blake.  */

 #include <config.h>

@@ -140,36 +137,111 @@ rpl_rename (char const *src, char const *dst)
 # if RENAME_DEST_EXISTS_BUG
 #  error Please report your platform and this message to address@hidden
 # elif RENAME_TRAILING_SLASH_BUG
+
+#  include <errno.h>
 #  include <stdio.h>
 #  include <stdlib.h>
 #  include <string.h>
+#  include <sys/stat.h>

 #  include "dirname.h"
-#  include "xalloc.h"

-/* Rename the file SRC to DST, removing any trailing
-   slashes from SRC.  Needed for SunOS 4.1.1_U1.  */
+/* Rename the file SRC to DST, fixing any trailing slash bugs.  */

 int
 rpl_rename (char const *src, char const *dst)
 {
-  char *src_temp;
-  int ret_val;
-  size_t s_len = strlen (src);
+  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;
+
+  if (!src_len || !dst_len)
+    return rename (src, dst); /* Let strace see the ENOENT failure.  */
+
+  src_slash = src[src_len - 1] == '/';
+  dst_slash = dst[dst_len - 1] == '/';
+  if (!src_slash && !dst_slash)
+    return rename (src, 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 (lstat (src, &src_st))
+    return -1;
+  if (lstat (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 (s_len && src[s_len - 1] == '/')
+  /* If stripping the trailing slashes changes from a directory to a
+     symlink, follow the GNU behavior of rejecting the rename.
+     Technically, we could follow the POSIX behavior by chasing a
+     readlink trail, but that is counter-intuitive and harder.  */
+  if (src_slash)
     {
-      src_temp = xstrdup (src);
+      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 (lstat (src_temp, &src_st))
+        {
+          rename_errno = errno;
+          goto out;
+        }
+      if (S_ISLNK (src_st.st_mode))
+        goto out;
     }
-  else
-    src_temp = (char *) src;
-
-  ret_val = rename (src_temp, dst);
-
+  if (dst_slash)
+    {
+      dst_temp = strdup (dst);
+      if (!dst_temp)
+        {
+          rename_errno = ENOMEM;
+          goto out;
+        }
+      strip_trailing_slashes (dst_temp);
+      if (lstat (dst_temp, &dst_st))
+        {
+          if (errno != ENOENT)
+            {
+              rename_errno = errno;
+              goto out;
+            }
+        }
+      else if (S_ISLNK (dst_st.st_mode))
+        goto out;
+    }
+  ret_val = rename (src_temp, 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;
 }
 # endif /* RENAME_TRAILING_SLASH_BUG */
diff --git a/m4/rename.m4 b/m4/rename.m4
index 2e43a87..2c0d5e8 100644
--- a/m4/rename.m4
+++ b/m4/rename.m4
@@ -1,4 +1,4 @@
-# serial 15
+# serial 16

 # Copyright (C) 2001, 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -17,53 +17,45 @@ AC_DEFUN([gl_FUNC_RENAME],
 [
   AC_REQUIRE([AC_CANONICAL_HOST])
   AC_REQUIRE([gl_STDIO_H_DEFAULTS])
-  AC_CACHE_CHECK([whether rename is broken with a trailing slash],
-  gl_cv_func_rename_trailing_slash_bug,
-  [
-    rm -rf conftest.d1 conftest.d2
-    mkdir conftest.d1 ||
-      AC_MSG_ERROR([cannot create temporary directory])
-    AC_RUN_IFELSE([AC_LANG_SOURCE([[
+
+  dnl SunOS 4.1.1_U1 mistakenly forbids rename("dir/","name").
+  dnl Solaris 9 mistakenly allows rename("file/","name").
+  AC_CACHE_CHECK([whether rename honors trailing slash on source],
+    [gl_cv_func_rename_slash_src_works],
+    [rm -rf conftest.f conftest.d1 conftest.d2
+    touch conftest.f && mkdir conftest.d1 ||
+      AC_MSG_ERROR([cannot create temporary files])
+    AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #       include <stdio.h>
 #       include <stdlib.h>
-        int
-        main ()
-        {
-          exit (rename ("conftest.d1/", "conftest.d2") ? 1 : 0);
-        }
-      ]])],
-      [gl_cv_func_rename_trailing_slash_bug=no],
-      [gl_cv_func_rename_trailing_slash_bug=yes],
+]], [if (rename ("conftest.f/", "conftest.d2") == 0) return 1;
+     if (rename ("conftest.d1/", "conftest.d2") != 0) return 2;])],
+      [gl_cv_func_rename_slash_src_works=yes],
+      [gl_cv_func_rename_slash_src_works=no],
       dnl When crosscompiling, assume rename is broken.
-      [gl_cv_func_rename_trailing_slash_bug=yes])
-
-      rm -rf conftest.d1 conftest.d2
+      [gl_cv_func_rename_slash_src_works="guessing no"])
+    rm -rf conftest.f conftest.d1 conftest.d2
   ])
- AC_CACHE_CHECK([whether rename is broken when the destination exists],
-  gl_cv_func_rename_dest_exists_bug,
-  [
-    case "$host_os" in
+  if test "x$gl_cv_func_rename_slash_src_works" != xyes; then
+    AC_LIBOBJ([rename])
+    REPLACE_RENAME=1
+    AC_DEFINE([RENAME_TRAILING_SLASH_BUG], [1],
+      [Define if rename does not correctly handle slashes on the source
+       argument, such as on Solaris 9 or cygwin 1.5.])
+  fi
+
+  AC_CACHE_CHECK([whether rename is broken when the destination exists],
+    [gl_cv_func_rename_dest_exists_bug],
+    [case "$host_os" in
       mingw*) gl_cv_func_rename_dest_exists_bug=yes ;;
       *) gl_cv_func_rename_dest_exists_bug=no ;;
     esac
   ])
-  if test $gl_cv_func_rename_trailing_slash_bug = yes ||
-     test $gl_cv_func_rename_dest_exists_bug = yes; then
+  if test $gl_cv_func_rename_dest_exists_bug = yes; then
     AC_LIBOBJ([rename])
     REPLACE_RENAME=1
-    if test $gl_cv_func_rename_trailing_slash_bug = yes; then
-      AC_DEFINE([RENAME_TRAILING_SLASH_BUG], [1],
-       [Define if rename does not work for source file names with a trailing
-        slash, like the one from SunOS 4.1.1_U1.])
-    fi
-    if test $gl_cv_func_rename_dest_exists_bug = yes; then
-      AC_DEFINE([RENAME_DEST_EXISTS_BUG], [1],
-       [Define if rename does not work when the destination file exists,
-        as on Windows.])
-    fi
-    gl_PREREQ_RENAME
+    AC_DEFINE([RENAME_DEST_EXISTS_BUG], [1],
+      [Define if rename does not work when the destination file exists,
+       as on Windows.])
   fi
 ])
-
-# Prerequisites of lib/rename.c.
-AC_DEFUN([gl_PREREQ_RENAME], [:])
diff --git a/modules/rename b/modules/rename
index 8d2a968..649c2fa 100644
--- a/modules/rename
+++ b/modules/rename
@@ -6,9 +6,11 @@ lib/rename.c
 m4/rename.m4

 Depends-on:
-xalloc
 dirname
+lstat
+stdbool
 stdio
+strdup

 configure.ac:
 gl_FUNC_RENAME
diff --git a/modules/rename-tests b/modules/rename-tests
index ed586d7..9929faf 100644
--- a/modules/rename-tests
+++ b/modules/rename-tests
@@ -5,7 +5,6 @@ tests/test-rename.c
 Depends-on:
 errno
 link
-lstat
 progname
 stdbool
 symlink
-- 
1.6.5.rc1


>From 4130076161ce8d5c8b16cbb2a859385c737afb2f Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 30 Sep 2009 21:57:58 -0600
Subject: [PATCH 4/7] rename: fix Solaris 10 bug

rename("file","name/") mistakenly succeeded.  But since Solaris 10
already obeys POSIX behavior on rename("link/","name"), we avoid
blindly forcing GNU behavior of rejecting symlinks with trailing slash.

* m4/rename.m4 (gl_FUNC_RENAME): Detect Solaris bug.
* lib/rename.c (rpl_rename): Don't cripple POSIX behavior if this
was the only bug.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog    |    5 +++++
 lib/rename.c |   28 ++++++++++++++++++++++------
 m4/rename.m4 |   32 ++++++++++++++++++++++++++++++--
 3 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1fe2091..25fa378 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-01  Eric Blake  <address@hidden>

+       rename: fix Solaris 10 bug
+       * m4/rename.m4 (gl_FUNC_RENAME): Detect Solaris bug.
+       * lib/rename.c (rpl_rename): Don't cripple POSIX behavior if this
+       was the only bug.
+
        rename: fix Solaris 9 bug
        * lib/rename.c (rpl_rename): Rewrite to recognize trailing slash
        on non-directory.  Avoid calling exit.
diff --git a/lib/rename.c b/lib/rename.c
index 0709759..22310be 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -136,7 +136,7 @@ rpl_rename (char const *src, char const *dst)

 # if RENAME_DEST_EXISTS_BUG
 #  error Please report your platform and this message to address@hidden
-# elif RENAME_TRAILING_SLASH_BUG
+# elif RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_TRAILING_SLASH_DEST_BUG

 #  include <errno.h>
 #  include <stdio.h>
@@ -192,10 +192,24 @@ rpl_rename (char const *src, char const *dst)
       return -1;
     }

-  /* If stripping the trailing slashes changes from a directory to a
-     symlink, follow the GNU behavior of rejecting the rename.
-     Technically, we could follow the POSIX behavior by chasing a
-     readlink trail, but that is counter-intuitive and harder.  */
+#  if RENAME_TRAILING_SLASH_SOURCE_BUG
+  /* If the only bug was that a trailing slash was allowed on a
+     non-existing file destination, as in Solaris 10, then we've
+     already covered that situation.  But if there is any problem with
+     a trailing slash on an existing source or destination, as in
+     Solaris 9, then we must strip the offending slash and check that
+     we have not encountered a symlink instead of a directory.
+
+     Stripping a trailing slash interferes with POSIX semantics, where
+     rename behavior on a symlink with a trailing slash operates on
+     the corresponding target directory.  We prefer the GNU semantics
+     of rejecting any use of a symlink with trailing slash, but do not
+     enforce them, since Solaris 10 is able to obey POSIX semantics
+     and there might be clients expecting it, as counter-intuitive as
+     those semantics are.
+
+     Technically, we could also follow the POSIX behavior by chasing a
+     readlink trail, but that is harder to implement.  */
   if (src_slash)
     {
       src_temp = strdup (src);
@@ -234,6 +248,8 @@ rpl_rename (char const *src, char const *dst)
       else if (S_ISLNK (dst_st.st_mode))
         goto out;
     }
+#  endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
+
   ret_val = rename (src_temp, dst_temp);
   rename_errno = errno;
  out:
@@ -244,5 +260,5 @@ rpl_rename (char const *src, char const *dst)
   errno = rename_errno;
   return ret_val;
 }
-# endif /* RENAME_TRAILING_SLASH_BUG */
+# endif /* RENAME_TRAILING_SLASH_*_BUG */
 #endif /* ! W32 platform */
diff --git a/m4/rename.m4 b/m4/rename.m4
index 2c0d5e8..04921fa 100644
--- a/m4/rename.m4
+++ b/m4/rename.m4
@@ -1,4 +1,4 @@
-# serial 16
+# serial 17

 # Copyright (C) 2001, 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -18,8 +18,36 @@ AC_DEFUN([gl_FUNC_RENAME],
   AC_REQUIRE([AC_CANONICAL_HOST])
   AC_REQUIRE([gl_STDIO_H_DEFAULTS])

+  dnl Solaris 10 mistakenly allows rename("file","name/").
+  dnl This particular condition can be worked around without stripping
+  dnl trailing slash.
+  AC_CACHE_CHECK([whether rename honors trailing slash on destination],
+    [gl_cv_func_rename_slash_dst_works],
+    [rm -rf conftest.f conftest.f1
+    touch conftest.f ||
+      AC_MSG_ERROR([cannot create temporary files])
+    AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#       include <stdio.h>
+#       include <stdlib.h>
+]], [return !rename ("conftest.f", "conftest.f1/");])],
+      [gl_cv_func_rename_slash_dst_works=yes],
+      [gl_cv_func_rename_slash_dst_works=no],
+      dnl When crosscompiling, assume rename is broken.
+      [gl_cv_func_rename_slash_dst_works="guessing no"])
+    rm -rf conftest.f conftest.f1
+  ])
+  if test "x$gl_cv_func_rename_slash_dst_works" != xyes; then
+    AC_LIBOBJ([rename])
+    REPLACE_RENAME=1
+    AC_DEFINE([RENAME_TRAILING_SLASH_DEST_BUG], [1],
+      [Define if rename does not correctly handle slashes on the destination
+       argument, such as on Solaris 10.])
+  fi
+
   dnl SunOS 4.1.1_U1 mistakenly forbids rename("dir/","name").
   dnl Solaris 9 mistakenly allows rename("file/","name").
+  dnl These bugs require stripping trailing slash to avoid corrupting
+  dnl symlinks with a trailing slash.
   AC_CACHE_CHECK([whether rename honors trailing slash on source],
     [gl_cv_func_rename_slash_src_works],
     [rm -rf conftest.f conftest.d1 conftest.d2
@@ -39,7 +67,7 @@ AC_DEFUN([gl_FUNC_RENAME],
   if test "x$gl_cv_func_rename_slash_src_works" != xyes; then
     AC_LIBOBJ([rename])
     REPLACE_RENAME=1
-    AC_DEFINE([RENAME_TRAILING_SLASH_BUG], [1],
+    AC_DEFINE([RENAME_TRAILING_SLASH_SOURCE_BUG], [1],
       [Define if rename does not correctly handle slashes on the source
        argument, such as on Solaris 9 or cygwin 1.5.])
   fi
-- 
1.6.5.rc1


>From 6dd4f72ba51c28e2b3d189811f749fbdae32d47f Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 29 Sep 2009 16:42:59 -0600
Subject: [PATCH 5/7] rename: fix cygwin 1.5.x bugs

On cygwin 1.5.x, rename("dir","file") mistakenly succeeded.
rename("hard1","hard2") mistakenly reduced the hard link count,
such that "hard1" disappears once "hard2" is unlinked.

* m4/rename.m4 (gl_FUNC_RENAME): Detect cygwin bugs.
* lib/rename.c (rpl_rename): Work around them.
* modules/rename (Depends-on): Add same-inode.
---
 ChangeLog      |    5 +++
 lib/rename.c   |   76 +++++++++++++++++++++++++++++++++++++++-----------------
 m4/rename.m4   |   29 +++++++++++++++------
 modules/rename |    1 +
 4 files changed, 80 insertions(+), 31 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 25fa378..0d90274 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-01  Eric Blake  <address@hidden>

+       rename: fix cygwin 1.5.x bugs
+       * m4/rename.m4 (gl_FUNC_RENAME): Detect cygwin bugs.
+       * lib/rename.c (rpl_rename): Work around them.
+       * modules/rename (Depends-on): Add same-inode.
+
        rename: fix Solaris 10 bug
        * m4/rename.m4 (gl_FUNC_RENAME): Detect Solaris bug.
        * lib/rename.c (rpl_rename): Don't cripple POSIX behavior if this
diff --git a/lib/rename.c b/lib/rename.c
index 22310be..68ea8e8 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -134,17 +134,14 @@ rpl_rename (char const *src, char const *dst)

 #else /* ! W32 platform */

-# if RENAME_DEST_EXISTS_BUG
-#  error Please report your platform and this message to address@hidden
-# elif RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_TRAILING_SLASH_DEST_BUG
-
-#  include <errno.h>
-#  include <stdio.h>
-#  include <stdlib.h>
-#  include <string.h>
-#  include <sys/stat.h>
+# include <errno.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <sys/stat.h>

-#  include "dirname.h"
+# include "dirname.h"
+# include "same-inode.h"

 /* Rename the file SRC to DST, fixing any trailing slash bugs.  */

@@ -165,10 +162,41 @@ rpl_rename (char const *src, char const *dst)
   if (!src_len || !dst_len)
     return rename (src, dst); /* Let strace see the ENOENT failure.  */

+# if RENAME_DEST_EXISTS_BUG
+  {
+    char *src_base = last_component (src);
+    char *dst_base = last_component (dst);
+    if (*src_base == '.')
+      {
+        size_t len = base_len (src_base);
+        if (len == 1 || (len == 2 && src_base[1] == '.'))
+          {
+            errno = EINVAL;
+            return -1;
+          }
+      }
+    if (*dst_base == '.')
+      {
+        size_t len = base_len (dst_base);
+        if (len == 1 || (len == 2 && dst_base[1] == '.'))
+          {
+            errno = EINVAL;
+            return -1;
+          }
+      }
+  }
+# endif /* RENAME_DEST_EXISTS_BUG */
+
   src_slash = src[src_len - 1] == '/';
   dst_slash = dst[dst_len - 1] == '/';
+
+# if !RENAME_DEST_EXISTS_BUG
+  /* If there are no trailing slashes, then trust the native
+     implementation unless we also suspect issues with hard link
+     detection.  */
   if (!src_slash && !dst_slash)
     return rename (src, dst);
+# endif /* !RENAME_DEST_EXISTS_BUG */

   /* Presence of a trailing slash requires directory semantics.  If
      the source does not exist, or if the destination cannot be turned
@@ -178,26 +206,29 @@ rpl_rename (char const *src, char const *dst)
     return -1;
   if (lstat (dst, &dst_st))
     {
-      if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
+      if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash))
         return -1;
     }
-  else if (!S_ISDIR (dst_st.st_mode))
-    {
-      errno = ENOTDIR;
-      return -1;
-    }
-  else if (!S_ISDIR (src_st.st_mode))
+  else
     {
-      errno = EISDIR;
-      return -1;
+      if (S_ISDIR (dst_st.st_mode) != S_ISDIR (src_st.st_mode))
+       {
+         errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR;
+         return -1;
+       }
+# if RENAME_DEST_EXISTS_BUG
+      if (SAME_INODE (src_st, dst_st))
+       return 0;
+# endif /* RENAME_DEST_EXISTS_BUG */
     }

-#  if RENAME_TRAILING_SLASH_SOURCE_BUG
+# if RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG
   /* If the only bug was that a trailing slash was allowed on a
      non-existing file destination, as in Solaris 10, then we've
      already covered that situation.  But if there is any problem with
      a trailing slash on an existing source or destination, as in
-     Solaris 9, then we must strip the offending slash and check that
+     Solaris 9, or if a directory can overwrite a symlink, as on
+     Cygwin 1.5, then we must strip the offending slash and check that
      we have not encountered a symlink instead of a directory.

      Stripping a trailing slash interferes with POSIX semantics, where
@@ -248,7 +279,7 @@ rpl_rename (char const *src, char const *dst)
       else if (S_ISLNK (dst_st.st_mode))
         goto out;
     }
-#  endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
+# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG */

   ret_val = rename (src_temp, dst_temp);
   rename_errno = errno;
@@ -260,5 +291,4 @@ rpl_rename (char const *src, char const *dst)
   errno = rename_errno;
   return ret_val;
 }
-# endif /* RENAME_TRAILING_SLASH_*_BUG */
 #endif /* ! W32 platform */
diff --git a/m4/rename.m4 b/m4/rename.m4
index 04921fa..c458f59 100644
--- a/m4/rename.m4
+++ b/m4/rename.m4
@@ -1,4 +1,4 @@
-# serial 17
+# serial 18

 # Copyright (C) 2001, 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -72,14 +72,27 @@ AC_DEFUN([gl_FUNC_RENAME],
        argument, such as on Solaris 9 or cygwin 1.5.])
   fi

-  AC_CACHE_CHECK([whether rename is broken when the destination exists],
-    [gl_cv_func_rename_dest_exists_bug],
-    [case "$host_os" in
-      mingw*) gl_cv_func_rename_dest_exists_bug=yes ;;
-      *) gl_cv_func_rename_dest_exists_bug=no ;;
-    esac
+  dnl Cygwin 1.5.x mistakenly allows rename("dir","file").
+  dnl mingw mistakenly forbids rename("dir1","dir2").
+  dnl These bugs require stripping trailing slash to avoid corrupting
+  dnl symlinks with a trailing slash.
+  AC_CACHE_CHECK([whether rename manages existing destinations correctly],
+    [gl_cv_func_rename_dest_works],
+    [rm -rf conftest.f conftest.d1 conftest.d2
+    touch conftest.f && mkdir conftest.d1 conftest.d2 ||
+      AC_MSG_ERROR([cannot create temporary files])
+    AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#       include <stdio.h>
+#       include <stdlib.h>
+]], [if (rename ("conftest.d1", "conftest.d2") != 0) return 1;
+     if (rename ("conftest.d2", "conftest.f") == 0) return 2;])],
+      [gl_cv_func_rename_dest_works=yes],
+      [gl_cv_func_rename_dest_works=no],
+      dnl When crosscompiling, assume rename is broken.
+      [gl_cv_func_rename_dest_works="guessing no"])
+    rm -rf conftest.f conftest.f1 conftest.d1 conftest.d2
   ])
-  if test $gl_cv_func_rename_dest_exists_bug = yes; then
+  if test "x$gl_cv_func_rename_dest_works" != xyes; then
     AC_LIBOBJ([rename])
     REPLACE_RENAME=1
     AC_DEFINE([RENAME_DEST_EXISTS_BUG], [1],
diff --git a/modules/rename b/modules/rename
index 649c2fa..09baa83 100644
--- a/modules/rename
+++ b/modules/rename
@@ -8,6 +8,7 @@ m4/rename.m4
 Depends-on:
 dirname
 lstat
+same-inode
 stdbool
 stdio
 strdup
-- 
1.6.5.rc1


>From 58e75773313a08a050b09bcb8ea0a23c93108c8a Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 26 Sep 2009 15:18:13 -0600
Subject: [PATCH 6/7] rename-dest-slash: merge into rename module

Tested that NetBSD workaround still passes unit test.

* modules/rename-dest-slash (Status): Mark obsolete.
(Depends-on): Add rename.
(Files): Let rename do it all.
* m4/rename.m4 (gl_FUNC_RENAME): Also test for NetBSD bugs,
subsuming the test from gl_FUNC_RENAME_TRAILING_DEST_SLASH...
* m4/rename-dest-slash.m4: ...so this file can be deleted.
* lib/rename-dest-slash.c (rpl_rename_dest_slash): Delete.
* lib/rename.c (rpl_rename): Update comments.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                 |   10 +++++
 lib/rename-dest-slash.c   |   96 ---------------------------------------------
 lib/rename.c              |    6 ++-
 m4/rename-dest-slash.m4   |   41 -------------------
 m4/rename.m4              |   42 +++++++++++++-------
 modules/rename-dest-slash |   13 +++---
 6 files changed, 48 insertions(+), 160 deletions(-)
 delete mode 100644 lib/rename-dest-slash.c
 delete mode 100644 m4/rename-dest-slash.m4

diff --git a/ChangeLog b/ChangeLog
index 0d90274..6bd126f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2009-10-01  Eric Blake  <address@hidden>

+       rename-dest-slash: merge into rename module
+       * modules/rename-dest-slash (Status): Mark obsolete.
+       (Depends-on): Add rename.
+       (Files): Let rename do it all.
+       * m4/rename.m4 (gl_FUNC_RENAME): Also test for NetBSD bugs,
+       subsuming the test from gl_FUNC_RENAME_TRAILING_DEST_SLASH...
+       * m4/rename-dest-slash.m4: ...so this file can be deleted.
+       * lib/rename-dest-slash.c (rpl_rename_dest_slash): Delete.
+       * lib/rename.c (rpl_rename): Update comments.
+
        rename: fix cygwin 1.5.x bugs
        * m4/rename.m4 (gl_FUNC_RENAME): Detect cygwin bugs.
        * lib/rename.c (rpl_rename): Work around them.
diff --git a/lib/rename-dest-slash.c b/lib/rename-dest-slash.c
deleted file mode 100644
index 2014fb1..0000000
--- a/lib/rename-dest-slash.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/* A rename wrapper to make tools like mv -- that would normally rely
-   on the underlying rename syscall -- work more consistently.
-   On at least NetBSD 1.6, `rename ("dir", "B/")' fails when B doesn't
-   exist, whereas it succeeds on Linux-2.6.x and Solaris 10.  This wrapper
-   provides an interface for systems like the former so that the tools
-   (namely mv) relying on the rename syscall have more consistent
-   semantics.
-
-   Copyright (C) 2006, 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 Jim Meyering */
-
-#include <config.h>
-#undef rename
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "dirname.h"
-#include "same-inode.h"
-#include "xalloc.h"
-
-static bool
-has_trailing_slash (char const *file)
-{
-  /* Don't count "/", "//", etc., as having a trailing slash.  */
-  bool has_non_slash = false;
-  bool ends_in_slash = false;
-
-  for (file += FILE_SYSTEM_PREFIX_LEN (file); *file; file++)
-    {
-      ends_in_slash = ISSLASH (*file);
-      has_non_slash |= ~ ends_in_slash;
-    }
-
-  return has_non_slash & ends_in_slash;
-}
-
-/* This is a rename wrapper for systems where the rename syscall
-   works differently than desired when SRC is a directory and DST does
-   not exist but is specified with a trailing slash.  On NetBSD 6.1,
-   rename fails in that case.  On Linux and Solaris systems, it succeeds.
-   This wrapper makes it succeed on NetBSD by running the originally
-   requested rename, and if it fails due to the above scenario, calling
-   it again with DST's trailing slashes removed.  */
-int
-rpl_rename_dest_slash (char const *src, char const *dst)
-{
-  struct stat sb;
-  struct stat db;
-  int ret_val;
-
-  if (lstat (src, &sb))
-    return -1;
-  if (lstat (dst, &db) == 0 && SAME_INODE (sb, db))
-    return 0;
-
-  ret_val = rename (src, dst);
-
-  if (ret_val != 0 && errno == ENOENT && has_trailing_slash (dst))
-    {
-      int rename_errno = ENOENT;
-
-      /* Fail now, unless SRC is a directory.  */
-      if (lstat (src, &sb) == 0 && S_ISDIR (sb.st_mode))
-       {
-         char *dst_temp = xstrdup (dst);
-         strip_trailing_slashes (dst_temp);
-         ret_val = rename (src, dst_temp);
-         rename_errno = errno;
-         free (dst_temp);
-       }
-
-      errno = rename_errno;
-    }
-
-  return ret_val;
-}
diff --git a/lib/rename.c b/lib/rename.c
index 68ea8e8..a59328e 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -228,8 +228,10 @@ rpl_rename (char const *src, char const *dst)
      already covered that situation.  But if there is any problem with
      a trailing slash on an existing source or destination, as in
      Solaris 9, or if a directory can overwrite a symlink, as on
-     Cygwin 1.5, then we must strip the offending slash and check that
-     we have not encountered a symlink instead of a directory.
+     Cygwin 1.5, or if directories cannot be created with trailing
+     slash, as on NetBSD 1.6, then we must strip the offending slash
+     and check that we have not encountered a symlink instead of a
+     directory.

      Stripping a trailing slash interferes with POSIX semantics, where
      rename behavior on a symlink with a trailing slash operates on
diff --git a/m4/rename-dest-slash.m4 b/m4/rename-dest-slash.m4
deleted file mode 100644
index 2ddbbcb..0000000
--- a/m4/rename-dest-slash.m4
+++ /dev/null
@@ -1,41 +0,0 @@
-# serial 4
-
-# Copyright (C) 2006, 2009 Free Software Foundation, Inc.
-# This file is free software; the Free Software Foundation
-# gives unlimited permission to copy and/or distribute it,
-# with or without modifications, as long as this notice is preserved.
-
-# Derived from rename.m4.
-
-# A rename wrapper to make tools like mv -- that would normally
-# rely on the underlying rename syscall -- work more consistently.
-
-AC_DEFUN([gl_FUNC_RENAME_TRAILING_DEST_SLASH],
-[
- AC_CACHE_CHECK([whether rename is broken with respect to destination slashes],
-  gl_cv_func_rename_trailing_dest_slash_bug,
-  [
-    rm -rf conftest.d1 conftest.d2
-    mkdir conftest.d1 ||
-      AC_MSG_ERROR([cannot create temporary directory])
-    AC_RUN_IFELSE([AC_LANG_SOURCE([[
-#       include <stdio.h>
-        int
-        main ()
-        {
-          return (rename ("conftest.d1", "conftest.d2/") ? 1 : 0);
-        }
-      ]])],
-      [gl_cv_func_rename_trailing_dest_slash_bug=no],
-      [gl_cv_func_rename_trailing_dest_slash_bug=yes],
-      dnl When crosscompiling, assume rename is broken.
-      [gl_cv_func_rename_trailing_dest_slash_bug=yes])
-
-      rm -rf conftest.d1 conftest.d2
-  ])
-  if test $gl_cv_func_rename_trailing_dest_slash_bug = yes; then
-    AC_LIBOBJ([rename-dest-slash])
-    AC_DEFINE([rename], [rpl_rename_dest_slash],
-      [Define to rpl_rename_dest_slash if the replacement function should be 
used.])
-  fi
-])
diff --git a/m4/rename.m4 b/m4/rename.m4
index c458f59..db08dd5 100644
--- a/m4/rename.m4
+++ b/m4/rename.m4
@@ -1,4 +1,4 @@
-# serial 18
+# serial 19

 # Copyright (C) 2001, 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -19,29 +19,33 @@ AC_DEFUN([gl_FUNC_RENAME],
   AC_REQUIRE([gl_STDIO_H_DEFAULTS])

   dnl Solaris 10 mistakenly allows rename("file","name/").
-  dnl This particular condition can be worked around without stripping
-  dnl trailing slash.
+  dnl NetBSD 1.6 mistakenly forbids rename("dir","name/").
+  dnl The Solaris bug can be worked around without stripping
+  dnl trailing slash, while the NetBSD bug requires stripping;
+  dnl the two conditions can be distinguished by whether hard
+  dnl links are also broken.
   AC_CACHE_CHECK([whether rename honors trailing slash on destination],
     [gl_cv_func_rename_slash_dst_works],
-    [rm -rf conftest.f conftest.f1
-    touch conftest.f ||
+    [rm -rf conftest.f conftest.f1 conftest.d1 conftest.d2
+    touch conftest.f && mkdir conftest.d1 ||
       AC_MSG_ERROR([cannot create temporary files])
     AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #       include <stdio.h>
 #       include <stdlib.h>
-]], [return !rename ("conftest.f", "conftest.f1/");])],
+]], [if (rename ("conftest.f", "conftest.f1/") == 0) return 1;
+     if (rename ("conftest.d1", "conftest.d2/") != 0) return 2;])],
       [gl_cv_func_rename_slash_dst_works=yes],
       [gl_cv_func_rename_slash_dst_works=no],
       dnl When crosscompiling, assume rename is broken.
       [gl_cv_func_rename_slash_dst_works="guessing no"])
-    rm -rf conftest.f conftest.f1
+    rm -rf conftest.f conftest.f1 conftest.d1 conftest.d2
   ])
   if test "x$gl_cv_func_rename_slash_dst_works" != xyes; then
     AC_LIBOBJ([rename])
     REPLACE_RENAME=1
     AC_DEFINE([RENAME_TRAILING_SLASH_DEST_BUG], [1],
       [Define if rename does not correctly handle slashes on the destination
-       argument, such as on Solaris 10.])
+       argument, such as on Solaris 10 or NetBSD 1.6.])
   fi

   dnl SunOS 4.1.1_U1 mistakenly forbids rename("dir/","name").
@@ -72,6 +76,7 @@ AC_DEFUN([gl_FUNC_RENAME],
        argument, such as on Solaris 9 or cygwin 1.5.])
   fi

+  dnl NetBSD 1.6 mistakenly reduces hard link count on rename("h1,"h2").
   dnl Cygwin 1.5.x mistakenly allows rename("dir","file").
   dnl mingw mistakenly forbids rename("dir1","dir2").
   dnl These bugs require stripping trailing slash to avoid corrupting
@@ -81,15 +86,22 @@ AC_DEFUN([gl_FUNC_RENAME],
     [rm -rf conftest.f conftest.d1 conftest.d2
     touch conftest.f && mkdir conftest.d1 conftest.d2 ||
       AC_MSG_ERROR([cannot create temporary files])
-    AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+    if ln conftest.f conftest.f1 && set x `ls -i conftest.f conftest.f1` &&
+        test "$2" = "$4"; then
+      AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #       include <stdio.h>
 #       include <stdlib.h>
 ]], [if (rename ("conftest.d1", "conftest.d2") != 0) return 1;
-     if (rename ("conftest.d2", "conftest.f") == 0) return 2;])],
-      [gl_cv_func_rename_dest_works=yes],
-      [gl_cv_func_rename_dest_works=no],
-      dnl When crosscompiling, assume rename is broken.
-      [gl_cv_func_rename_dest_works="guessing no"])
+     if (rename ("conftest.d2", "conftest.f") == 0) return 2;
+     if (rename ("conftest.f", "conftest.f1")
+         || rename ("conftest.f", "conftest.f")) return 3;])],
+        [gl_cv_func_rename_dest_works=yes],
+        [gl_cv_func_rename_dest_works=no],
+        dnl When crosscompiling, assume rename is broken.
+        [gl_cv_func_rename_dest_works="guessing no"])
+    else
+      gl_cv_func_rename_dest_works="guessing no"
+    fi
     rm -rf conftest.f conftest.f1 conftest.d1 conftest.d2
   ])
   if test "x$gl_cv_func_rename_dest_works" != xyes; then
@@ -97,6 +109,6 @@ AC_DEFUN([gl_FUNC_RENAME],
     REPLACE_RENAME=1
     AC_DEFINE([RENAME_DEST_EXISTS_BUG], [1],
       [Define if rename does not work when the destination file exists,
-       as on Windows.])
+       as with NetBSD 1.6 on hard links, or Windows on directories.])
   fi
 ])
diff --git a/modules/rename-dest-slash b/modules/rename-dest-slash
index c0bc98d..f52b3b8 100644
--- a/modules/rename-dest-slash
+++ b/modules/rename-dest-slash
@@ -1,17 +1,18 @@
 Description:
 rename() function: change the name or location of a file.

+Status:
+obsolete
+
+Notice:
+This module is obsolete; use the rename module instead.
+
 Files:
-lib/rename-dest-slash.c
-m4/rename-dest-slash.m4

 Depends-on:
-dirname
-same-inode
-xalloc
+rename

 configure.ac:
-gl_FUNC_RENAME_TRAILING_DEST_SLASH

 Makefile.am:

-- 
1.6.5.rc1


>From 33b8e24ebaca968075870a630d92b7a3ad11ef20 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 7 Sep 2009 06:45:59 -0600
Subject: [PATCH 7/7] 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.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                         |   12 +++
 MODULES.html.sh                   |    1 +
 doc/posix-functions/renameat.texi |   29 +++++-
 lib/renameat.c                    |   35 +++++++
 lib/stdio.in.h                    |   12 +++
 m4/renameat.m4                    |   23 +++++
 m4/stdio_h.m4                     |    4 +-
 modules/renameat                  |   33 +++++++
 modules/renameat-tests            |   14 +++
 modules/stdio                     |    2 +
 tests/test-renameat.c             |  191 +++++++++++++++++++++++++++++++++++++
 11 files changed, 351 insertions(+), 5 deletions(-)
 create mode 100644 lib/renameat.c
 create mode 100644 m4/renameat.m4
 create mode 100644 modules/renameat
 create mode 100644 modules/renameat-tests
 create mode 100644 tests/test-renameat.c

diff --git a/ChangeLog b/ChangeLog
index 6bd126f..db0c482 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-10-01  Eric Blake  <address@hidden>

+       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-dest-slash: merge into rename module
        * modules/rename-dest-slash (Status): Mark obsolete.
        (Depends-on): Add rename.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 42cb57c..bb40a9a 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 de7fa9a..52f3921 100644
--- a/doc/posix-functions/renameat.texi
+++ b/doc/posix-functions/renameat.texi
@@ -4,16 +4,37 @@ renameat

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/renameat.html}

-Gnulib module: ---
+Gnulib module: renameat

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+This function does not reject trailing slashes on non-directories on
+some platforms, as in @code{renameat(fd,"file",fd,"new/")}:
+Solaris 10.
address@hidden
+This function ignores trailing slashes on symlinks on some platforms,
+such that @code{renameat(fd,"link/",fd,"new")} corrupts @file{link}:
+Solaris 9.
address@hidden
+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
address@hidden 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
address@hidden if either argument is a symlink with a trailing slash:
+glibc, OpenBSD, Cygwin 1.7.
address@hidden
+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 0000000..244ee38
--- /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 35109c3..3777e85 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 address@hidden@
+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 0000000..bda7660
--- /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 01af04d..6f8c035 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 0000000..efe4a0f
--- /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 0000000..20122da
--- /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 22b83f7..8f75e19 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 0000000..0a8418e
--- /dev/null
+++ b/tests/test-renameat.c
@@ -0,0 +1,191 @@
+/* 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 <address@hidden>, 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);
+    }
+  {
+    int fd2 = open ("..", O_RDONLY);
+    ASSERT (0 <= fd2);
+    ASSERT (renameat (dfd, "../" BASE "16", fd2, BASE "17") == 0);
+    ASSERT (close (fd2) == 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);
+
+  {
+    int fd = creat (BASE "sub2/file", 0600);
+    ASSERT (0 <= fd);
+    ASSERT (close (fd) == 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 = 0;
+  ASSERT (renameat (dfd, BASE "sub2/.", dfd, BASE "sub1") == -1);
+  ASSERT (errno == EINVAL);
+  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;
+}
-- 
1.6.5.rc1


reply via email to

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