Skip to content
Browse files

Implement an `eaccess()` fallback

Some systems we want to support don't provide `faccessat()` or `eaccess()`
or `eaccessuid()`. So implement build time feature tests and provide
fallback implementations.

Resolves #941
  • Loading branch information...
krader1961 committed Oct 10, 2018
1 parent c7752ec commit 9910b97d9ddb8fec7cfba4a104fc0a223c5ea36a
Showing with 85 additions and 13 deletions.
  1. +4 −0 .cppcheck.suppressions
  2. +8 −8
  3. +6 −0 features/
  4. +67 −5 src/lib/libast/misc/fallbacks.c
@@ -20,3 +20,7 @@ getpwuidCalled
// The fallbacks.c module is likely to need cppcheck suppressions. But due to
// how they are parsed, independent of any enclosing `#if` directives, they are
// also likely to cause warnings like "Unmatched suppression: mktempCalled".
@@ -86,6 +86,9 @@
#mesondefine _lib_creat64
#mesondefine _lib_dllload
#mesondefine _lib_dlopen
#mesondefine _lib_eaccess
#mesondefine _lib_euidaccess
#mesondefine _lib_faccessat
#mesondefine _lib_fdopendir
#mesondefine _lib_fstat64
#mesondefine _lib_fstatvfs64
@@ -228,15 +231,12 @@ extern void ast_free(void *ptr);
// This is not used and is not expected to be used so make sure if it is used
// it causes a problem.
#define valloc(s) valloc_has_no_ast_wrapper(s)

// Some systems (e.g., macOS, OpenBSD) don't implement `eaccess()`. But all
// the systems we currently test on do provide `faccessat()` so use that. If
// and when we evern get built on a system without `faccessat()` we can
// implement build time feature test and implement a more complex workaround.
#include <fcntl.h>
#include <unistd.h>
#define eaccess(p, f) faccessat(AT_FDCWD, p, f, AT_EACCESS)
#if !_lib_eaccess
// We need a prototype for the fallback implementation.
int eaccess(const char *pathname, int mode);

extern char *sh_getenv(const char *name);
#define getenv(x) sh_getenv(x)
@@ -63,6 +63,12 @@ if not cc.has_function('isnanl', prefix: '#include <math.h>', args: feature_test
feature_data.set('isnanl', 'isnan')

cc.has_function('eaccess', prefix: '#include <unistd.h>', args: feature_test_args))
cc.has_function('euidaccess', prefix: '#include <unistd.h>', args: feature_test_args))
cc.has_function('faccessat', prefix: '#include <unistd.h>', args: feature_test_args))
cc.has_function('mkostemp', prefix: '\n'.join(['#include <stdlib.h>', '#include <unistd.h>']),
args: feature_test_args))
@@ -6,10 +6,13 @@

// We keep all these includes because it's simpler and cleaner to do this than wrap them in
// `#if !_lib_mkostemp` type pragmas.
#include <stdio.h> // IWYU pragma: keep
#include <stdlib.h> // IWYU pragma: keep
#include <string.h> // IWYU pragma: keep
#include <unistd.h> // IWYU pragma: keep
#include <errno.h> // IWYU pragma: keep
#include <fcntl.h> // IWYU pragma: keep
#include <stdio.h> // IWYU pragma: keep
#include <stdlib.h> // IWYU pragma: keep
#include <string.h> // IWYU pragma: keep
#include <sys/stat.h> // IWYU pragma: keep
#include <unistd.h> // IWYU pragma: keep

#include "ast.h" // IWYU pragma: keep
#include "ast_assert.h" // IWYU pragma: keep
@@ -22,12 +25,71 @@ void do_not_use_this_fallback() {

#if !_lib_eaccess
#if _lib_euidaccess
// System doesn't have eaccess() but does have the equivalent euidaccess() so use that.
int eaccess(const char *pathname, int mode) {
return euidaccess(pathname, mode);
#elif _lib_faccessat
// System doesn't have eaccess() but does have faccessat() so use that.
int eaccess(const char *pathname, int mode) {
return faccessat(AT_FDCWD, pathname, mode, AT_EACCESS);
// The platform doesn't have eaccess(), euidaccess(), or faccessat() so we have to roll our own.
// This may not be optimal in that it calls access() when it might be possible to return an answer
// based on what we already know. Since this is a fallback function we hope not to use we're
// striving for simplicity and clarity.
int eaccess(const char *pathname, int mode) {
// In case the user passed bogus bits for the mode tell them we can't determine what they want.
if (mode & ~(F_OK | X_OK | W_OK | R_OK)) {
errno = EINVAL;
return -1;

uid_t uid = getuid ();
gid_t gid = getgid ();
uid_t euid = geteuid ();
gid_t egid = getegid ();

// If we aren't suid or sgid then we can just use access().
// Similarly if we're just checking if the file exists just use access().
if ((euid == uid && egid == gid) || mode == F_OK) return access(pathname, mode);

struct stat file_status;
if (stat(pathname, &file_status) != 0) return -1;

if (euid == 0) {
// The root (super) user can read/write any file that exists.
if ((mode & X_OK) == 0) return 0;
// The root (super) user can execute any file with execute permission.
if (file_status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) return 0;

// The bit shifts below rely on the common UNIX convention for encoding file permissions as
// three groups (ugo) of three bits (rwx). If we ever run on a platform with a different scheme
// (unlikely but not impossible) we'll have to do these tests in a less elegant manner.
if (euid == file_status.st_uid) {
if ((file_status.st_mode & (mode << 6)) == (mode << 6)) return 0;

if (egid == file_status.st_gid) {
if ((file_status.st_mode & (mode << 3)) == (mode << 3)) return 0;

// Our euid and egid did not match the file so just fall back to access().
return access(pathname, mode);
#endif // _lib_faccessat
#endif // !_lib_eaccess

#if !_lib_mkostemp
// This is a fallback in case the system doesn't provide it.
int mkostemp(char *template, int oflags) {
for (int i = 10; i; i--) {
#ifndef __clang_analyzer__
// cppcheck-suppress mktempCalled
// cppcheck-suppress mktempCalled
char *tp = mktemp(template);

0 comments on commit 9910b97

Please sign in to comment.
You can’t perform that action at this time.