+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.
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:
/* 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
# 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
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
#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
-# serial 6
+# serial 7
# See if we need to provide faccessat replacement.
dnl Copyright (C) 2009-2017 Free Software Foundation, Inc.
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])
-# 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,
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])
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
Depends-on:
fcntl-h
+symlink
configure.ac:
-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' \
#include "macros.h"
+#define BASE "test-lstat.t"
+
int
main (void)
{
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;
}