From 028ee2f5c765e41b188465f9ad2617c3ba6d9a1e Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Sat, 26 Sep 2009 17:22:15 -0600 Subject: [PATCH] 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 --- ChangeLog | 9 + doc/posix-functions/rename.texi | 47 +++- modules/rename-tests | 19 ++ tests/test-rename.c | 53 ++++ tests/test-rename.h | 444 ++++++++++++++++++++++++++++++++ 5 files changed, 563 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 2e205ffc1c..f13340f892 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2009-10-02 Eric Blake + + 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. + 2009-10-02 Paolo Bonzini * lib/uname.c: Include diff --git a/doc/posix-functions/rename.texi b/doc/posix-functions/rename.texi index 84a03ab75e..65981db9d2 100644 --- a/doc/posix-functions/rename.texi +++ b/doc/posix-functions/rename.texi @@ -9,22 +9,51 @@ Gnulib module: 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. +@item +This function ignores trailing slashes on symlinks on some platforms, +such that @code{rename("link/","new")} corrupts @file{link}: +Solaris 9. +@item +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. +@item +This function will not always replace an existing destination on some platforms: mingw. +@item +This function mistakenly allows names ending in @samp{.} or @samp{..} +on some platforms: +Cygwin 1.5.x, mingw. +@item +This function does not reject attempts to rename existing directories +and non-directories onto one another on some platforms: +Cygwin 1.5.x, mingw. +@item +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 +@item +POSIX requires that @code{rename("symlink-to-dir/","dir2")} rename +@file{dir} 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 +@code{ENOTDIR} if either argument is a symlink with a trailing slash: +glibc, OpenBSD, Cygwin 1.7. +@item +This function will not rename a source that is currently opened by any process: mingw. -@item -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 0000000000..ed586d7e83 --- /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 0000000000..7bfdd848a7 --- /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 . */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 0000000000..5dc89180a8 --- /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 . */ + +/* 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 == EACCES); + 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; +} -- 2.30.2