On Mac OS X 10.5, I see this test failure: ../../gltests/test-faccessat.c:63: assertion 'faccessat (AT_FDCWD, BASE "link2/", F_OK, 0) != 0' failed FAIL test-faccessat (exit status: 134)
And indeed, POSIX <https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html> requires that faccessat() and access() fail with errno = ENOTDIR when the file name argument has a trailing slash and points to "an existing file that is neither a directory nor a symbolic link to a directory". Thus, we have an faccessat() bug here. On this platform, faccessat is emulated through access() and euidaccess(): $ nm gllib/faccessat.o U ___error U _access U _euidaccess 00000000 T _faccessat U _fchdir U _free_cwd U _openat_proc_name U _openat_restore_fail U _openat_save_fail U _restore_cwd U _rpl_free U _save_cwd So, the suspicion is that the bug comes from a bug in access() or euidaccess(). A 'dtruss' run ('dtruss' is the Mac OS X equivalent of 'strace') confirms it: $ dtruss ./test-faccessat ... symlink("test-faccessat.tfile\0", "test-faccessat.tlink2\0") = 0 0 access("test-faccessat.tlink2\0", 0x0, 0x0) = 0 0 access("test-faccessat.tlink2/\0", 0x0, 0x0) = 0 0 write_nocancel(0x2, "../../gltests/test-faccessat.c:63: assertion 'faccessat (AT_FDCWD, BASE \"link2/\", F_OK, 0) != 0' failed\n\0", 0x68) = 104 0 ... So, the first thing we have to do here is to get access() work right. 2023-10-03 Bruno Haible <[email protected]> access: Work around trailing slash bug on Mac OS X 10.5. * m4/access.m4 (gl_FUNC_ACCESS): Test whether access honors a trailing slash. Set REPLACE_ACCESS to 1 and define ACCESS_TRAILING_SLASH_BUG if not. * lib/access.c (access): Add an implementation for Unix-like platforms. * tests/test-access.c (main): Test for result if the argument has a trailing slash. * modules/access-tests (Depends-on): Add 'symlink'. * doc/posix-functions/access.texi: Mention the Mac OS X bug. diff --git a/doc/posix-functions/access.texi b/doc/posix-functions/access.texi index 8bfa2c1a67..fd114d300f 100644 --- a/doc/posix-functions/access.texi +++ b/doc/posix-functions/access.texi @@ -11,6 +11,10 @@ @item This function does not support the @code{X_OK} mode on some platforms: MSVC 14. +@item +This function does not reject trailing slashes on symlinks to non-directories +on some platforms: +Mac OS X 10.5. @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/access.c b/lib/access.c index e2c12b1f19..75d2148453 100644 --- a/lib/access.c +++ b/lib/access.c @@ -20,7 +20,10 @@ #include <unistd.h> #include <fcntl.h> -#include <io.h> + +#if defined _WIN32 && ! defined __CYGWIN__ + +# include <io.h> int access (const char *file, int mode) @@ -29,3 +32,35 @@ access (const char *file, int mode) mode = (mode & ~X_OK) | R_OK; return _access (file, mode); } + +#else + +# include <errno.h> +# include <string.h> +# include <sys/types.h> +# include <sys/stat.h> + +int +access (const char *file, int mode) +# undef access +{ + int ret = access (file, mode); +# if ACCESS_TRAILING_SLASH_BUG + if (ret == 0) + { + size_t file_len = strlen (file); + if (file_len > 0 && file[file_len - 1] == '/') + { + struct stat st; + if (stat (file, &st) == 0 && ! S_ISDIR (st.st_mode)) + { + errno = ENOTDIR; + return -1; + } + } + } +# endif + return ret; +} + +#endif diff --git a/m4/access.m4 b/m4/access.m4 index 259e2221fa..9fb9ee145e 100644 --- a/m4/access.m4 +++ b/m4/access.m4 @@ -1,4 +1,4 @@ -# access.m4 serial 2 +# access.m4 serial 3 dnl Copyright (C) 2019-2023 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -11,6 +11,55 @@ AC_DEFUN([gl_FUNC_ACCESS] dnl On native Windows, access (= _access) does not support the X_OK mode. dnl It works by chance on some versions of mingw. case "$host_os" in - mingw* | windows*) REPLACE_ACCESS=1 ;; + mingw* | windows*) + REPLACE_ACCESS=1 + ;; + *) + dnl Mac OS X 10.5 mistakenly allows access("link-to-file/",amode). + AC_CHECK_FUNCS_ONCE([lstat]) + AC_CACHE_CHECK([whether access honors trailing slash], + [gl_cv_func_access_slash_works], + [# Assume that if we have lstat, we can also check symlinks. + if test $ac_cv_func_lstat = yes; then + rm -rf conftest.f conftest.lnk + touch conftest.f || AC_MSG_ERROR([cannot create temporary files]) + ln -s conftest.f conftest.lnk + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <unistd.h> + ]], + [[int result = 0; + if (access ("conftest.lnk/", R_OK) == 0) + result |= 1; + return result; + ]])], + [gl_cv_func_access_slash_works=yes], + [gl_cv_func_access_slash_works=no], + dnl When crosscompiling, assume access is broken. + [case "$host_os" in + # Guess yes on Linux systems. + linux-* | linux) gl_cv_func_access_slash_works="guessing yes" ;; + # Guess yes on systems that emulate the Linux system calls. + midipix*) gl_cv_func_access_slash_works="guessing yes" ;; + # Guess yes on glibc systems. + *-gnu*) gl_cv_func_access_slash_works="guessing yes" ;; + # If we don't know, obey --enable-cross-guesses. + *) gl_cv_func_access_slash_works="$gl_cross_guess_normal" ;; + esac + ]) + rm -rf conftest.f conftest.lnk + else + gl_cv_func_access_slash_works="guessing yes" + fi + ]) + case "$gl_cv_func_access_slash_works" in + *yes) ;; + *) + REPLACE_ACCESS=1 + AC_DEFINE([ACCESS_TRAILING_SLASH_BUG], [1], + [Define if access does not correctly handle trailing slashes.]) + ;; + esac + ;; esac ]) diff --git a/modules/access-tests b/modules/access-tests index 4b35c21150..78344887cd 100644 --- a/modules/access-tests +++ b/modules/access-tests @@ -6,6 +6,7 @@ tests/macros.h Depends-on: creat root-uid +symlink configure.ac: AC_CHECK_FUNCS_ONCE([geteuid]) diff --git a/tests/test-access.c b/tests/test-access.c index 5f9a4ad216..771678f0f7 100644 --- a/tests/test-access.c +++ b/tests/test-access.c @@ -42,6 +42,7 @@ main () unlink (BASE "f1"); chmod (BASE "f2", 0600); unlink (BASE "f2"); + unlink (BASE "sl"); { errno = 0; @@ -59,9 +60,31 @@ main () { ASSERT (close (creat (BASE "f1", 0700)) == 0); + ASSERT (access (BASE "f1", F_OK) == 0); ASSERT (access (BASE "f1", R_OK) == 0); ASSERT (access (BASE "f1", W_OK) == 0); ASSERT (access (BASE "f1", X_OK) == 0); + + ASSERT (access (BASE "f1/", F_OK) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (access (BASE "f1/", R_OK) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (access (BASE "f1/", W_OK) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (access (BASE "f1/", X_OK) == -1); + ASSERT (errno == ENOTDIR); + + if (symlink (BASE "f1", BASE "sl") == 0) + { + ASSERT (access (BASE "sl/", F_OK) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (access (BASE "sl/", R_OK) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (access (BASE "sl/", W_OK) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (access (BASE "sl/", X_OK) == -1); + ASSERT (errno == ENOTDIR); + } } { ASSERT (close (creat (BASE "f2", 0600)) == 0); @@ -90,6 +113,7 @@ main () ASSERT (unlink (BASE "f1") == 0); ASSERT (chmod (BASE "f2", 0600) == 0); ASSERT (unlink (BASE "f2") == 0); + unlink (BASE "sl"); return 0; }
