/* Test of rename() function. Copyright (C) 2009-2023 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 . */ /* 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, EACCES on Android within Termux. */ switch (errno) { case EPERM: case EOPNOTSUPP: #if defined __ANDROID__ case EACCES: #endif 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; }