changeset 39152:abd3b4b846c4

faccessat: port to macOS (Bug#29231) macOS faccessat has the same bug that lstat does: if the file name ends in '/' it ignores the trailing slash. Problem reported for Emacs by Vincent Zhang. * doc/posix-functions/faccessat.texi (faccessat): Document this. * lib/faccessat.c (_GL_INCLUDING_UNISTD_H): Define and undef around the initial includes. Include errno.h, string.h, sys/stat.h. (orig_faccessat) [HAVE_FACCESSAT]: New function. Include "unistd.h" after defining it. (rpl_faccessat) [HAVE_FACCESSAT]: New implementation. * lib/unistd.in.h (faccessat) [REPLACE_FACCESSAT]: Handle in the usual way. * m4/faccessat.m4 (gl_FUNC_FACCESSAT): Replace faccessat if lstat dereferences symlinks, since faccessat is likely to have the same problem. * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Default REPLACE_ACCESSAT. * modules/faccessat (Depends-on): Add fstatat. Depend if REPLACE_FACCESSAT is 1, too. (configure.ac): Link if REPLACE_FACCESSAT is 1. * modules/faccessat-tests (Depends-on): Add symlink. * modules/unistd (unistd.h): Substitute REPLACE_FACCESSAT. * tests/test-faccessat.c (main): Test for the bug.
author Paul Eggert <eggert@cs.ucla.edu>
date Sat, 11 Nov 2017 22:33:38 -0800
parents 024ce94bd1c9
children 1f525c927feb
files ChangeLog doc/posix-functions/faccessat.texi lib/faccessat.c lib/unistd.in.h m4/faccessat.m4 m4/unistd_h.m4 modules/faccessat modules/faccessat-tests modules/unistd tests/test-faccessat.c
diffstat 10 files changed, 143 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Nov 11 20:39:12 2017 +0100
+++ b/ChangeLog	Sat Nov 11 22:33:38 2017 -0800
@@ -1,3 +1,28 @@
+2017-11-11  Paul Eggert  <eggert@cs.ucla.edu>
+
+	faccessat: port to macOS (Bug#29231)
+	macOS faccessat has the same bug that lstat does: if the file
+	name ends in '/' it ignores the trailing slash.
+	Problem reported for Emacs by Vincent Zhang.
+	* doc/posix-functions/faccessat.texi (faccessat): Document this.
+	* lib/faccessat.c (_GL_INCLUDING_UNISTD_H): Define and undef
+	around the initial includes.  Include errno.h, string.h, sys/stat.h.
+	(orig_faccessat) [HAVE_FACCESSAT]: New function.
+	Include "unistd.h" after defining it.
+	(rpl_faccessat) [HAVE_FACCESSAT]: New implementation.
+	* lib/unistd.in.h (faccessat) [REPLACE_FACCESSAT]:
+	Handle in the usual way.
+	* m4/faccessat.m4 (gl_FUNC_FACCESSAT): Replace faccessat if
+	lstat dereferences symlinks, since faccessat is likely to
+	have the same problem.
+	* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Default REPLACE_ACCESSAT.
+	* modules/faccessat (Depends-on): Add fstatat.
+	Depend if REPLACE_FACCESSAT is 1, too.
+	(configure.ac): Link if REPLACE_FACCESSAT is 1.
+	* modules/faccessat-tests (Depends-on): Add symlink.
+	* modules/unistd (unistd.h): Substitute REPLACE_FACCESSAT.
+	* tests/test-faccessat.c (main): Test for the bug.
+
 2017-11-11  Bruno Haible  <bruno@clisp.org>
 
 	getprogname: Fix compilation error on IRIX.
--- a/doc/posix-functions/faccessat.texi	Sat Nov 11 20:39:12 2017 +0100
+++ b/doc/posix-functions/faccessat.texi	Sat Nov 11 22:33:38 2017 -0800
@@ -13,6 +13,10 @@
 glibc 2.3.6, macOS 10.12, FreeBSD 7.4, NetBSD 6.1.5, OpenBSD 4.9, Minix 3.1.8,
 AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, MSVC 14,
 Interix 3.5, BeOS.
+@item
+On some platforms, @code{faccessat (dfd, "file/", amode, flag)}
+succeeds instead of failing when @file{file} is not a directory.
+macOS 10.13.
 @end itemize
 
 Portability problems not fixed by Gnulib:
--- a/lib/faccessat.c	Sat Nov 11 20:39:12 2017 +0100
+++ b/lib/faccessat.c	Sat Nov 11 22:33:38 2017 -0800
@@ -16,10 +16,31 @@
 
 /* written by Eric Blake */
 
+/* If the user's config.h happens to include <unistd.h>, let it include only
+   the system's <unistd.h> here, so that orig_faccessat doesn't recurse to
+   rpl_faccessat.  */
+#define _GL_INCLUDING_UNISTD_H
 #include <config.h>
 
 #include <unistd.h>
+#include <errno.h>
 #include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#undef _GL_INCLUDING_UNISTD_H
+
+#if HAVE_FACCESSAT
+static int
+orig_faccessat (int fd, char const *name, int mode, int flag)
+{
+  return faccessat (fd, name, mode, flag);
+}
+#endif
+
+/* Write "unistd.h" here, not <unistd.h>, otherwise OSF/1 5.1 DTK cc
+   eliminates this include because of the preliminary #include <unistd.h>
+   above.  */
+#include "unistd.h"
 
 #ifndef HAVE_ACCESS
 /* Mingw lacks access, but it also lacks real vs. effective ids, so
@@ -28,6 +49,29 @@
 # define access euidaccess
 #endif
 
+#if HAVE_FACCESSAT
+
+int
+rpl_faccessat (int fd, char const *file, int mode, int flag)
+{
+  int result = orig_faccessat (fd, file, mode, flag);
+
+  if (result == 0 && file[strlen (file) - 1] == '/')
+    {
+      struct stat st;
+      result = fstatat (fd, file, &st, 0);
+      if (result == 0 && !S_ISDIR (st.st_mode))
+        {
+          errno = ENOTDIR;
+          return -1;
+        }
+    }
+
+  return result;
+}
+
+#else /* !HAVE_FACCESSAT */
+
 /* Invoke access or euidaccess on file, FILE, using mode MODE, in the directory
    open on descriptor FD.  If possible, do it without changing the
    working directory.  Otherwise, resort to using save_cwd/fchdir, then
@@ -36,10 +80,12 @@
    Note that this implementation only supports AT_EACCESS, although some
    native versions also support AT_SYMLINK_NOFOLLOW.  */
 
-#define AT_FUNC_NAME faccessat
-#define AT_FUNC_F1 euidaccess
-#define AT_FUNC_F2 access
-#define AT_FUNC_USE_F1_COND AT_EACCESS
-#define AT_FUNC_POST_FILE_PARAM_DECLS , int mode, int flag
-#define AT_FUNC_POST_FILE_ARGS        , mode
-#include "at-func.c"
+# define AT_FUNC_NAME faccessat
+# define AT_FUNC_F1 euidaccess
+# define AT_FUNC_F2 access
+# define AT_FUNC_USE_F1_COND AT_EACCESS
+# define AT_FUNC_POST_FILE_PARAM_DECLS , int mode, int flag
+# define AT_FUNC_POST_FILE_ARGS        , mode
+# include "at-func.c"
+
+#endif
--- a/lib/unistd.in.h	Sat Nov 11 20:39:12 2017 +0100
+++ b/lib/unistd.in.h	Sat Nov 11 22:33:38 2017 -0800
@@ -461,13 +461,25 @@
 
 
 #if @GNULIB_FACCESSAT@
-# if !@HAVE_FACCESSAT@
+# if @REPLACE_FACCESSAT@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   undef faccessat
+#   define faccessat rpl_faccessat
+#  endif
+_GL_FUNCDECL_RPL (faccessat, int,
+                  (int fd, char const *name, int mode, int flag)
+                  _GL_ARG_NONNULL ((2)));
+_GL_CXXALIAS_RPL (faccessat, int,
+                  (int fd, char const *name, int mode, int flag));
+# else
+#  if !@HAVE_FACCESSAT@
 _GL_FUNCDECL_SYS (faccessat, int,
                   (int fd, char const *file, int mode, int flag)
                   _GL_ARG_NONNULL ((2)));
-# endif
+#  endif
 _GL_CXXALIAS_SYS (faccessat, int,
                   (int fd, char const *file, int mode, int flag));
+# endif
 _GL_CXXALIASWARN (faccessat);
 #elif defined GNULIB_POSIXCHECK
 # undef faccessat
--- a/m4/faccessat.m4	Sat Nov 11 20:39:12 2017 +0100
+++ b/m4/faccessat.m4	Sat Nov 11 22:33:38 2017 -0800
@@ -1,4 +1,4 @@
-# serial 6
+# serial 7
 # See if we need to provide faccessat replacement.
 
 dnl Copyright (C) 2009-2017 Free Software Foundation, Inc.
@@ -18,10 +18,12 @@
   AC_CHECK_FUNCS_ONCE([faccessat])
   if test $ac_cv_func_faccessat = no; then
     HAVE_FACCESSAT=0
+  elif test "$gl_cv_func_lstat_dereferences_slashed_symlink" != yes; then
+    REPLACE_FACCESSAT=1
   fi
 ])
 
-# Prerequisites of lib/faccessat.m4.
+# Prerequisites of lib/faccessat.c.
 AC_DEFUN([gl_PREREQ_FACCESSAT],
 [
   AC_CHECK_FUNCS([access])
--- a/m4/unistd_h.m4	Sat Nov 11 20:39:12 2017 +0100
+++ b/m4/unistd_h.m4	Sat Nov 11 22:33:38 2017 -0800
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 70
+# unistd_h.m4 serial 71
 dnl Copyright (C) 2006-2017 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -159,6 +159,7 @@
   REPLACE_CLOSE=0;        AC_SUBST([REPLACE_CLOSE])
   REPLACE_DUP=0;          AC_SUBST([REPLACE_DUP])
   REPLACE_DUP2=0;         AC_SUBST([REPLACE_DUP2])
+  REPLACE_FACCESSAT=0;    AC_SUBST([REPLACE_FACCESSAT])
   REPLACE_FCHOWNAT=0;     AC_SUBST([REPLACE_FCHOWNAT])
   REPLACE_FTRUNCATE=0;    AC_SUBST([REPLACE_FTRUNCATE])
   REPLACE_GETCWD=0;       AC_SUBST([REPLACE_GETCWD])
--- a/modules/faccessat	Sat Nov 11 20:39:12 2017 +0100
+++ b/modules/faccessat	Sat Nov 11 22:33:38 2017 -0800
@@ -9,19 +9,20 @@
 Depends-on:
 unistd
 extensions
-at-internal     [test $HAVE_FACCESSAT = 0]
-dosname         [test $HAVE_FACCESSAT = 0]
-errno           [test $HAVE_FACCESSAT = 0]
-fchdir          [test $HAVE_FACCESSAT = 0]
-fcntl-h         [test $HAVE_FACCESSAT = 0]
-openat-die      [test $HAVE_FACCESSAT = 0]
-openat-h        [test $HAVE_FACCESSAT = 0]
-save-cwd        [test $HAVE_FACCESSAT = 0]
-euidaccess      [test $HAVE_FACCESSAT = 0]
+at-internal     [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+dosname         [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+errno           [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+fchdir          [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+fcntl-h         [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+fstatat         [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+openat-die      [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+openat-h        [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+save-cwd        [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
+euidaccess      [test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1]
 
 configure.ac:
 gl_FUNC_FACCESSAT
-if test $HAVE_FACCESSAT = 0; then
+if test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1; then
   AC_LIBOBJ([faccessat])
   gl_PREREQ_FACCESSAT
 fi
--- a/modules/faccessat-tests	Sat Nov 11 20:39:12 2017 +0100
+++ b/modules/faccessat-tests	Sat Nov 11 22:33:38 2017 -0800
@@ -5,6 +5,7 @@
 
 Depends-on:
 fcntl-h
+symlink
 
 configure.ac:
 
--- a/modules/unistd	Sat Nov 11 20:39:12 2017 +0100
+++ b/modules/unistd	Sat Nov 11 22:33:38 2017 -0800
@@ -135,6 +135,7 @@
 	      -e 's|@''REPLACE_CLOSE''@|$(REPLACE_CLOSE)|g' \
 	      -e 's|@''REPLACE_DUP''@|$(REPLACE_DUP)|g' \
 	      -e 's|@''REPLACE_DUP2''@|$(REPLACE_DUP2)|g' \
+	      -e 's|@''REPLACE_FACCESSAT''@|$(REPLACE_FACCESSAT)|g' \
 	      -e 's|@''REPLACE_FCHOWNAT''@|$(REPLACE_FCHOWNAT)|g' \
 	      -e 's|@''REPLACE_FTRUNCATE''@|$(REPLACE_FTRUNCATE)|g' \
 	      -e 's|@''REPLACE_GETCWD''@|$(REPLACE_GETCWD)|g' \
--- a/tests/test-faccessat.c	Sat Nov 11 20:39:12 2017 +0100
+++ b/tests/test-faccessat.c	Sat Nov 11 22:33:38 2017 -0800
@@ -26,6 +26,8 @@
 
 #include "macros.h"
 
+#define BASE "test-lstat.t"
+
 int
 main (void)
 {
@@ -42,5 +44,31 @@
     ASSERT (errno == EBADF);
   }
 
+  /* Test behavior with trailing slash.  */
+  ASSERT (faccessat (AT_FDCWD, ".", X_OK, 0) == 0);
+  ASSERT (faccessat (AT_FDCWD, "./", X_OK, 0) == 0);
+  ASSERT (close (open (BASE "file", O_CREAT | O_WRONLY, 0)) == 0);
+  ASSERT (faccessat (AT_FDCWD, BASE "file", F_OK, 0) == 0);
+  ASSERT (faccessat (AT_FDCWD, BASE "file/", F_OK, 0) != 0);
+  unlink (BASE "link1");
+  if (symlink (".", BASE "link1") == 0)
+    {
+      ASSERT (faccessat (AT_FDCWD, BASE "link1", X_OK, 0) == 0);
+      ASSERT (faccessat (AT_FDCWD, BASE "link1/", X_OK, 0) == 0);
+
+      unlink (BASE "link2");
+      ASSERT (symlink (BASE "file", BASE "link2") == 0);
+      ASSERT (faccessat (AT_FDCWD, BASE "link2", F_OK, 0) == 0);
+      ASSERT (faccessat (AT_FDCWD, BASE "link2/", F_OK, 0) != 0);
+      unlink (BASE "link2");
+
+      unlink (BASE "link3");
+      ASSERT (symlink (BASE "no-such-file", BASE "link3") == 0);
+      ASSERT (faccessat (AT_FDCWD, BASE "link3", F_OK, 0) != 0);
+      ASSERT (faccessat (AT_FDCWD, BASE "link3/", F_OK, 0) != 0);
+      unlink (BASE "link3");
+    }
+  unlink (BASE "link1");
+
   return 0;
 }