/* Test of rename() function.
   Copyright (C) 2009-2022 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 <https://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.  */

/* Tests whether a file, given by a file name without slashes, exists in
   the current directory, by scanning the directory entries.  */
static bool
dentry_exists (const char *filename)
{
  bool exists = false;
  DIR *dir = opendir (".");

  ASSERT (dir != NULL);
  for (;;)
    {
      struct dirent *d = readdir (dir);
      if (d == NULL)
        break;
      if (strcmp (d->d_name, filename) == 0)
        {
          exists = true;
          break;
        }
    }
  ASSERT (closedir (dir) == 0);
  return exists;
}

/* Asserts that a specific file, given by a file name without slashes, does
   not exist in the current directory.  */
static void
assert_nonexistent (const char *filename)
{
  struct stat st;

  /* The usual way to test the presence of a file is via stat() or lstat().  */
  errno = 0;
  if (stat (filename, &st) == -1)
    ASSERT (errno == ENOENT);
  else
    {
      /* But after renaming a directory over an empty directory on an NFS-
         mounted file system, on Linux 2.6.18, for a period of 30 seconds the
         old directory name is "present" according to stat() but "nonexistent"
         according to dentry_exists().  */
      ASSERT (!dentry_exists (filename));
      /* Remove the old directory name, so that subsequent mkdir calls
         succeed.  */
      (void) rmdir (filename);
    }
}

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);

  /* Files present here:
       {BASE}file
       {BASE}dir/
   */

  /* Obvious errors.  */

  { /* Missing source.  */
    {
      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;
      ASSERT (func (BASE "missing", BASE "missing/") == -1);
      ASSERT (errno == ENOENT);
    }
  }
  { /* Empty operand.  */
    {
      errno = 0;
      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.  */

  { /* Trailing slash.  */
    {
      errno = 0;
      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);
    }
  }
  { /* Simple rename.  */
    ASSERT (func (BASE "file", BASE "file2") == 0);
    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);
  }
  /* Files present here:
       {BASE}file2
       {BASE}dir/
   */
  { /* Overwrite.  */
    ASSERT (close (creat (BASE "file", 0600)) == 0);
    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);
  }
  /* Files present here:
       {BASE}file
       {BASE}dir/
   */

  /* Directories.  */

  { /* Simple rename.  */
    {
      ASSERT (func (BASE "dir", BASE "dir2/") == 0);
      errno = 0;
      ASSERT (stat (BASE "dir", &st) == -1);
      ASSERT (errno == ENOENT);
      ASSERT (stat (BASE "dir2", &st) == 0);
    }
    /* Files present here:
         {BASE}file
         {BASE}dir2/
     */
    {
      ASSERT (func (BASE "dir2/", BASE "dir") == 0);
      ASSERT (stat (BASE "dir", &st) == 0);
      errno = 0;
      ASSERT (stat (BASE "dir2", &st) == -1);
      ASSERT (errno == ENOENT);
    }
    /* Files present here:
         {BASE}file
         {BASE}dir/
     */
    {
      ASSERT (func (BASE "dir", BASE "dir2") == 0);
      errno = 0;
      ASSERT (stat (BASE "dir", &st) == -1);
      ASSERT (errno == ENOENT);
      ASSERT (stat (BASE "dir2", &st) == 0);
    }
    /* Files present here:
         {BASE}file
         {BASE}dir2/
     */
    { /* Empty onto empty.  */
      ASSERT (mkdir (BASE "dir", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir2/
       */
      ASSERT (func (BASE "dir2", BASE "dir") == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
       */
      ASSERT (mkdir (BASE "dir2", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir2/
       */
      ASSERT (func (BASE "dir2", BASE "dir/") == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
       */
      ASSERT (mkdir (BASE "dir2", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir2/
       */
      ASSERT (func (BASE "dir2/", BASE "dir") == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
       */
      ASSERT (mkdir (BASE "dir2", 0700) == 0);
    }
    /* Files present here:
         {BASE}file
         {BASE}dir/
         {BASE}dir2/
     */
    { /* Empty onto full.  */
      ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir/file
           {BASE}dir2/
       */
      {
        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);
      }
    }
    { /* Full onto empty.  */
      ASSERT (func (BASE "dir", BASE "dir2") == 0);
      assert_nonexistent (BASE "dir");
      ASSERT (stat (BASE "dir2/file", &st) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir2/
           {BASE}dir2/file
       */
      ASSERT (mkdir (BASE "dir", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir2/
           {BASE}dir2/file
       */
      {
        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);
      }
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir/file
       */
      ASSERT (mkdir (BASE "dir2", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir/file
           {BASE}dir2/
       */
      {
        ASSERT (func (BASE "dir", BASE "dir2/") == 0);
        assert_nonexistent (BASE "dir");
        ASSERT (stat (BASE "dir2/file", &st) == 0);
      }
      /* Files present here:
           {BASE}file
           {BASE}dir2/
           {BASE}dir2/file
       */
      ASSERT (unlink (BASE "dir2/file") == 0);
    }
    /* Files present here:
         {BASE}file
         {BASE}dir2/
     */
    { /* Reject trailing dot.  */
      {
        errno = 0;
        ASSERT (func (BASE "dir2", BASE "dir/.") == -1);
        ASSERT (errno == EINVAL || errno == ENOENT);
      }
      ASSERT (mkdir (BASE "dir", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir2/
       */
      {
        errno = 0;
        ASSERT (func (BASE "dir2", BASE "dir/.") == -1);
        ASSERT (errno == EINVAL || errno == EBUSY || errno == EISDIR
                || errno == ENOTEMPTY || errno == EEXIST
                || errno == ENOENT /* WSL */);
      }
      {
        errno = 0;
        ASSERT (func (BASE "dir2/.", BASE "dir") == -1);
        ASSERT (errno == EINVAL || errno == EBUSY || errno == EEXIST
                || errno == ENOENT /* WSL */);
      }
      ASSERT (rmdir (BASE "dir") == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir2/
       */
      {
        errno = 0;
        ASSERT (func (BASE "dir2", BASE "dir/.//") == -1);
        ASSERT (errno == EINVAL || errno == ENOENT);
      }
      ASSERT (mkdir (BASE "dir", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir2/
       */
      {
        errno = 0;
        ASSERT (func (BASE "dir2", BASE "dir/.//") == -1);
        ASSERT (errno == EINVAL || errno == EBUSY || errno == EISDIR
                || errno == ENOTEMPTY || errno == EEXIST
                || errno == ENOENT /* WSL */);
      }
      {
        errno = 0;
        ASSERT (func (BASE "dir2/.//", BASE "dir") == -1);
        ASSERT (errno == EINVAL || errno == EBUSY || errno == EEXIST
                || errno == ENOENT /* WSL */);
      }
      ASSERT (rmdir (BASE "dir2") == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
       */
    }
    { /* Move into subdir.  */
      {
        errno = 0;
        ASSERT (func (BASE "dir", BASE "dir/sub") == -1);
        ASSERT (errno == EINVAL || errno == EACCES);
      }
      {
        errno = 0;
        ASSERT (stat (BASE "dir/sub", &st) == -1);
        ASSERT (errno == ENOENT);
      }
      ASSERT (mkdir (BASE "dir/sub", 0700) == 0);
      /* Files present here:
           {BASE}file
           {BASE}dir/
           {BASE}dir/sub/
       */
      {
        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);
    }
  }
  /* Files present here:
       {BASE}file
       {BASE}dir/
   */

  /* Mixing file and directory.  */

  {
    { /* File onto dir.  */
      {
        errno = 0;
        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);
      }
    }
    { /* Dir onto file.  */
      {
        errno = 0;
        ASSERT (func (BASE "dir", BASE "file") == -1);
        ASSERT (errno == ENOTDIR);
      }
      {
        errno = 0;
        ASSERT (func (BASE "dir/", BASE "file") == -1);
        ASSERT (errno == ENOTDIR);
      }
    }
  }

  /* Hard links.  */

  { /* File onto self.  */
    ASSERT (func (BASE "file", BASE "file") == 0);
    memset (&st, 0, sizeof st);
    ASSERT (stat (BASE "file", &st) == 0);
    ASSERT (st.st_size == 2);
  }
  /* Files present here:
       {BASE}file
       {BASE}dir/
   */
  { /* Empty dir onto self.  */
    ASSERT (func (BASE "dir", BASE "dir") == 0);
    ASSERT (stat (BASE "dir", &st) == 0);
  }
  /* Files present here:
       {BASE}file
       {BASE}dir/
   */
  ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
  /* Files present here:
       {BASE}file
       {BASE}dir/
       {BASE}dir/file
   */
  { /* Full dir onto self.  */
    ASSERT (func (BASE "dir", BASE "dir") == 0);
  }
  ASSERT (unlink (BASE "dir/file") == 0);
  /* Files present here:
       {BASE}file
       {BASE}dir/
   */
  {
    /*  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);
  }
  /* Files present here:
       {BASE}file
       {BASE}file2       (hard link to file)
       {BASE}dir/
   */
  { /* File onto hard link.  */
    ASSERT (func (BASE "file", BASE "file2") == 0);
    memset (&st, 0, sizeof st);
    if (stat (BASE "file", &st) != 0)
      {
        /* This can happen on NetBSD.  */
        ASSERT (errno == ENOENT);
        ASSERT (link (BASE "file2", BASE "file") == 0);
        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);
  }
  /* Files present here:
       {BASE}file
       {BASE}file2
       {BASE}dir/
   */
  ASSERT (unlink (BASE "file2") == 0);
  /* Files present here:
       {BASE}file
       {BASE}dir/
   */

  /* Symlinks.  */

  if (symlink (BASE "file", BASE "link1"))
    {
      if (print)
        fputs ("skipping test: symlinks not supported on this file system\n",
               stderr);
      ASSERT (unlink (BASE "file") == 0);
      ASSERT (rmdir (BASE "dir") == 0);
      return 77;
    }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */
  { /* Simple rename.  */
    ASSERT (func (BASE "link1", BASE "link2") == 0);
    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));
  }
  /* Files present here:
       {BASE}file
       {BASE}link2 -> {BASE}file
       {BASE}dir/
   */
  { /* Overwrite.  */
    ASSERT (symlink (BASE "nowhere", BASE "link1") == 0);
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}nowhere
         {BASE}link2 -> {BASE}file
         {BASE}dir/
     */
    {
      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);
    }
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */
  { /* Symlink loop.  */
    ASSERT (symlink (BASE "link2", BASE "link2") == 0);
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}file
         {BASE}link2 -> {BASE}link2
         {BASE}dir/
     */
    {
      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);
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}file
         {BASE}link3 -> {BASE}link2
         {BASE}dir/
     */
    ASSERT (unlink (BASE "link3") == 0);
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */
  { /* Dangling link.  */
    ASSERT (symlink (BASE "nowhere", BASE "link2") == 0);
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}file
         {BASE}link2 -> {BASE}nowhere
         {BASE}dir/
     */
    {
      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);
    }
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}link3 -> {BASE}nowhere
       {BASE}dir/
   */
  { /* Trailing slash on dangling.  */
    {
      errno = 0;
      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);
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}link3 -> {BASE}nowhere
       {BASE}dir/
   */
  { /* Trailing slash on link to file.  */
    {
      errno = 0;
      ASSERT (func (BASE "link1/", BASE "link2") == -1);
      ASSERT (errno == ENOTDIR);
    }
    {
      errno = 0;
      ASSERT (func (BASE "link1", BASE "link3/") == -1);
      ASSERT (errno == ENOENT || errno == ENOTDIR);
    }
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}link3 -> {BASE}nowhere
       {BASE}dir/
   */

  /* Mixing symlink and file.  */

  { /* File onto link.  */
    ASSERT (close (creat (BASE "file2", 0600)) == 0);
    /* Files present here:
         {BASE}file
         {BASE}file2
         {BASE}link1 -> {BASE}file
         {BASE}link3 -> {BASE}nowhere
         {BASE}dir/
     */
    {
      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));
    }
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}file
         {BASE}link3
         {BASE}dir/
     */
    ASSERT (unlink (BASE "link3") == 0);
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */
  { /* Link onto file.  */
    ASSERT (symlink (BASE "nowhere", BASE "link2") == 0);
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}file
         {BASE}link2 -> {BASE}nowhere
         {BASE}dir/
     */
    ASSERT (close (creat (BASE "file2", 0600)) == 0);
    /* Files present here:
         {BASE}file
         {BASE}file2
         {BASE}link1 -> {BASE}file
         {BASE}link2 -> {BASE}nowhere
         {BASE}dir/
     */
    {
      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));
    }
    /* Files present here:
         {BASE}file
         {BASE}file2 -> {BASE}nowhere
         {BASE}link1 -> {BASE}file
         {BASE}dir/
     */
    ASSERT (unlink (BASE "file2") == 0);
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */
  { /* Trailing slash.  */
    {
      errno = 0;
      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));
    }
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */

  /* Mixing symlink and directory.  */

  { /* Directory onto link.  */
    {
      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;
      ASSERT (func (BASE "dir", BASE "link1/") == -1);
      ASSERT (errno == ENOTDIR);
    }
  }
  { /* Link onto directory.  */
    {
      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 == 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));
    }
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}dir/
   */

  /* 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);
    /* Files present here:
         {BASE}file
         {BASE}link1 -> {BASE}file
         {BASE}link2 -> {BASE}dir2
         {BASE}dir/
     */
    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));
        /* Files present here:
             {BASE}file
             {BASE}link1 -> {BASE}file
             {BASE}link2 -> {BASE}dir2
             {BASE}dir2/
         */
        {
          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);
        /* Files present here:
             {BASE}file
             {BASE}link1 -> {BASE}file
             {BASE}link2 -> {BASE}dir
             {BASE}dir/
         */
        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 || errno == EISDIR);
          }
        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));
      }
  }
  /* Files present here:
       {BASE}file
       {BASE}link1 -> {BASE}file
       {BASE}link2 -> {BASE}dir or {BASE}dir2
       {BASE}dir/
   */

  /* Clean up.  */
  ASSERT (unlink (BASE "file") == 0);
  ASSERT (rmdir (BASE "dir") == 0);
  ASSERT (unlink (BASE "link1") == 0);
  ASSERT (unlink (BASE "link2") == 0);

  return 0;
}
