bug-gnulib
[Top][All Lists]
Advanced

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

access: Work around trailing slash bug on Mac OS X 10.5


From: Bruno Haible
Subject: access: Work around trailing slash bug on Mac OS X 10.5
Date: Tue, 03 Oct 2023 16:38:42 +0200

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  <bruno@clisp.org>

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






reply via email to

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