1 //
2 // This module contains fallback implementations of functions used elsewhere in the code.
3 // Each fallback implementation is only enabled if the system doesn't provide it.
4 //
5 #include "config_ast.h"  // IWYU pragma: keep
6 
7 // We keep all these includes because it's simpler and cleaner to do this than wrap them in
8 // `#if !_lib_mkostemp` type pragmas.
9 #include <errno.h>     // IWYU pragma: keep
10 #include <fcntl.h>     // IWYU pragma: keep
11 #include <stdio.h>     // IWYU pragma: keep
12 #include <stdlib.h>    // IWYU pragma: keep
13 #include <string.h>    // IWYU pragma: keep
14 #include <sys/stat.h>  // IWYU pragma: keep
15 #include <unistd.h>    // IWYU pragma: keep
16 
17 #include "ast.h"         // IWYU pragma: keep
18 #include "ast_assert.h"  // IWYU pragma: keep
19 
20 //
21 // We define this symbol, which is otherwise unused, to ensure this module isn't empty. That's
22 // because empty modules can cause build time warnings.
23 //
24 int __do_not_use_this_fallback_sym = 0;
25 
26 #if !_lib_eaccess
27 #if _lib_euidaccess
28 // System doesn't have eaccess() but does have the equivalent euidaccess() so use that.
eaccess(const char * pathname,int mode)29 int eaccess(const char *pathname, int mode) { return euidaccess(pathname, mode); }
30 #elif _lib_faccessat
31 // System doesn't have eaccess() but does have faccessat() so use that.
eaccess(const char * pathname,int mode)32 int eaccess(const char *pathname, int mode) {
33     return faccessat(AT_FDCWD, pathname, mode, AT_EACCESS);
34 }
35 #else
36 // The platform doesn't have eaccess(), euidaccess(), or faccessat() so we have to roll our own.
37 // This may not be optimal in that it calls access() when it might be possible to return an answer
38 // based on what we already know. Since this is a fallback function we hope not to use we're
39 // striving for simplicity and clarity.
eaccess(const char * pathname,int mode)40 int eaccess(const char *pathname, int mode) {
41     // In case the user passed bogus bits for the mode tell them we can't determine what they want.
42     if (mode & ~(F_OK | X_OK | W_OK | R_OK)) {
43         errno = EINVAL;
44         return -1;
45     }
46 
47     uid_t uid = getuid();
48     gid_t gid = getgid();
49     uid_t euid = geteuid();
50     gid_t egid = getegid();
51 
52     // If we aren't suid or sgid then we can just use access().
53     // Similarly if we're just checking if the file exists just use access().
54     if ((euid == uid && egid == gid) || mode == F_OK) return access(pathname, mode);
55 
56     struct stat file_status;
57     if (stat(pathname, &file_status) != 0) return -1;
58 
59     if (euid == 0) {
60         // The root (super) user can read/write any file that exists.
61         if ((mode & X_OK) == 0) return 0;
62         // The root (super) user can execute any file with execute permission.
63         if (file_status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) return 0;
64     }
65 
66     // The bit shifts below rely on the common UNIX convention for encoding file permissions as
67     // three groups (ugo) of three bits (rwx). If we ever run on a platform with a different scheme
68     // (unlikely but not impossible) we'll have to do these tests in a less elegant manner.
69     if (euid == file_status.st_uid) {
70         if ((file_status.st_mode & (mode << 6)) == (mode << 6)) return 0;
71     }
72 
73     if (egid == file_status.st_gid) {
74         if ((file_status.st_mode & (mode << 3)) == (mode << 3)) return 0;
75     }
76 
77     // Our euid and egid did not match the file so just fall back to access().
78     return access(pathname, mode);
79 }
80 #endif  // _lib_faccessat
81 #endif  // !_lib_eaccess
82 
83 #if !_lib_mkostemp
84 // This is a fallback in case the system doesn't provide it.
mkostemp(char * template,int oflags)85 int mkostemp(char *template, int oflags) {
86     for (int i = 10; i; i--) {
87 #ifndef __clang_analyzer__
88         // cppcheck-suppress mktempCalled
89         char *tp = mktemp(template);
90         assert(tp);
91 #endif
92 
93         int fd = open(template, O_CREAT | O_RDWR | O_EXCL | oflags, S_IRUSR | S_IWUSR);
94         if (fd != -1) return fd;
95     }
96     return -1;
97 }
98 #endif  // !_lib_mkostemp
99 
100 #if _lib_lchmod_fchmodat_fallback
101 // This fallback is for platforms like OpenBSD which don't have lchmod() but provide the means for
102 // implementing it via fchmodat().
lchmod(const char * path,mode_t mode)103 int lchmod(const char *path, mode_t mode) {
104     return fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW);
105 }
106 #endif
107