mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
1926 lines
73 KiB
C
1926 lines
73 KiB
C
#define _GNU_SOURCE
|
|
#include <dirent.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <pwd.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/auxv.h>
|
|
#include <sys/inotify.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statfs.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/sysinfo.h>
|
|
#include <sys/time.h>
|
|
#include <sys/un.h>
|
|
#include <sys/utsname.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
|
|
#define FP_STUB_ADDR 0xffff00000UL
|
|
#define FP_STUB_SIZE 4096UL
|
|
#define MAX_BINDS 256
|
|
#define FP_SLOW (-100000L)
|
|
#define MAX_LINKS 16
|
|
|
|
typedef long (*stub_fn)(long nr, long a, long b, long c, long d, long e, long f);
|
|
static stub_fn fp_sys;
|
|
static int fp_on;
|
|
|
|
struct bind { char *host; size_t hlen; char *guest; size_t glen; int canon; int dropped; };
|
|
static struct bind binds[MAX_BINDS];
|
|
static int nbinds;
|
|
static char root[PATH_MAX];
|
|
static size_t rootlen;
|
|
static long long ttl_ns = 2000000000LL;
|
|
static unsigned long hits, misses;
|
|
|
|
// A seccomp filter the guest installs itself (Firefox's content sandbox) can trap a trampoline
|
|
// syscall that the host allows; proot answers those with ENOSYS. The call is then repeated
|
|
// through libc, where the guest's own SIGSYS handler sees the guest path, and the process stops
|
|
// using the fast path.
|
|
static __thread int fp_refused;
|
|
static int optional_syscall(long nr) {
|
|
return nr == SYS_statx || nr == SYS_memfd_create || nr == SYS_faccessat2 || nr == SYS_close_range ||
|
|
nr == SYS_openat2 || nr == SYS_renameat2;
|
|
}
|
|
static long sc(long nr, long a, long b, long c, long d, long e) {
|
|
if (fp_refused) return -ENOSYS;
|
|
long r = fp_sys(nr, a, b, c, d, e, 0);
|
|
if (r == -ENOSYS && !optional_syscall(nr)) {
|
|
__atomic_store_n(&fp_on, 0, __ATOMIC_RELAXED);
|
|
fp_refused = 1;
|
|
}
|
|
return r;
|
|
}
|
|
static inline int refused(void) {
|
|
if (!fp_refused) return 0;
|
|
fp_refused = 0;
|
|
return 1;
|
|
}
|
|
static inline int answered(long r) { return !refused() && r != FP_SLOW; }
|
|
static void fp_init(void);
|
|
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
|
|
static inline int fp_ready(void) {
|
|
pthread_once(&init_once, fp_init);
|
|
return fp_on;
|
|
}
|
|
|
|
static int under(const char *path, const char *dir, size_t dlen) {
|
|
if (dlen == 1 && dir[0] == '/') return path[0] == '/';
|
|
return strncmp(path, dir, dlen) == 0 && (path[dlen] == 0 || path[dlen] == '/');
|
|
}
|
|
|
|
|
|
static void stats(void) {
|
|
char line[160];
|
|
int n = snprintf(line, sizeof line, "fastpath[%d] %s: %lu fast, %lu to proot\n", getpid(),
|
|
program_invocation_short_name, hits, misses);
|
|
if (write(2, line, n) < 0) {}
|
|
}
|
|
|
|
static const char *miss_log;
|
|
static long fp_open(int dirfd, const char *path, int flags, mode_t mode);
|
|
|
|
static void missed(const char *call, int dirfd, const char *path) {
|
|
misses++;
|
|
if (!miss_log) return;
|
|
char line[2 * PATH_MAX + 128], base[PATH_MAX] = "";
|
|
if (path && path[0] != '/' && dirfd != AT_FDCWD) {
|
|
char link[40];
|
|
snprintf(link, sizeof link, "/proc/self/fd/%d", dirfd);
|
|
long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)base, sizeof base - 1, 0);
|
|
base[n > 0 ? n : 0] = 0;
|
|
}
|
|
int n = snprintf(line, sizeof line, "%d %s %s %s%s%s\n", getpid(), program_invocation_short_name, call,
|
|
base, base[0] ? "/" : "", path ? path : "(null)");
|
|
long fd = fp_open(AT_FDCWD, miss_log, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
|
|
if (fd < 0) return;
|
|
sc(SYS_write, fd, (long)line, n < (int)sizeof line ? n : (int)sizeof line - 1, 0, 0);
|
|
sc(SYS_close, fd, 0, 0, 0, 0);
|
|
}
|
|
|
|
static long canonical(char *path, size_t size) {
|
|
long fd = sc(SYS_openat, AT_FDCWD, (long)path, O_PATH | O_CLOEXEC, 0, 0);
|
|
if (fd < 0) return fd;
|
|
char link[40], out[PATH_MAX];
|
|
snprintf(link, sizeof link, "/proc/self/fd/%ld", fd);
|
|
long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)out, sizeof out - 1, 0);
|
|
sc(SYS_close, fd, 0, 0, 0, 0);
|
|
if (n <= 0 || out[0] != '/') return -ENOENT;
|
|
if ((size_t)n >= size) return -ENAMETOOLONG;
|
|
out[n] = 0;
|
|
memcpy(path, out, n + 1);
|
|
return 0;
|
|
}
|
|
|
|
static long tracer;
|
|
|
|
static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_mutex_t bind_lock;
|
|
static pthread_mutex_t long_lock;
|
|
static void after_fork(void) { pthread_mutex_init(&cache_lock, NULL); pthread_mutex_init(&bind_lock, NULL); pthread_mutex_init(&long_lock, NULL); }
|
|
|
|
static int traced_by_match(const char *key) {
|
|
static char buf[32768];
|
|
char path[48], want[160];
|
|
long fd = sc(SYS_openat, AT_FDCWD, (long)"/proc/self/status", O_RDONLY | O_CLOEXEC, 0, 0);
|
|
if (fd < 0) return 0;
|
|
long n = sc(SYS_read, fd, (long)buf, 4095, 0, 0);
|
|
sc(SYS_close, fd, 0, 0, 0, 0);
|
|
if (n <= 0) return 0;
|
|
buf[n] = 0;
|
|
const char *line = strstr(buf, "\nTracerPid:");
|
|
if (!line) return 0;
|
|
tracer = strtol(line + 11, NULL, 10);
|
|
if (tracer <= 0) return 0;
|
|
snprintf(path, sizeof path, "/proc/%ld/environ", tracer);
|
|
if (snprintf(want, sizeof want, "PROOT_FASTPATH=%s", key) >= (int)sizeof want) return 0;
|
|
fd = sc(SYS_openat, AT_FDCWD, (long)path, O_RDONLY | O_CLOEXEC, 0, 0);
|
|
if (fd < 0) return 0;
|
|
size_t have = 0;
|
|
for (long r; have < sizeof buf - 1 && (r = sc(SYS_read, fd, (long)buf + have, sizeof buf - 1 - have, 0, 0)) > 0;) have += r;
|
|
sc(SYS_close, fd, 0, 0, 0, 0);
|
|
buf[have] = 0;
|
|
int found = 0;
|
|
for (size_t i = 0; i < have; i += strlen(buf + i) + 1) {
|
|
if (strcmp(buf + i, want) == 0) found = 1;
|
|
if (strncmp(buf + i, "PROOT_NO_SECCOMP=", 17) == 0) return 0;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static long init_sys4(long nr, long a, long b, long c, long d) {
|
|
static long (*real_syscall)(long, ...);
|
|
if (!real_syscall) real_syscall = (long (*)(long, ...))dlsym(RTLD_NEXT, "syscall");
|
|
return real_syscall(nr, a, b, c, d);
|
|
}
|
|
|
|
static long init_sys(long nr, long a, long b, long c) { return init_sys4(nr, a, b, c, 0); }
|
|
|
|
static int program_32bit(const char *prog, int scripts) {
|
|
char head[256];
|
|
long fd = init_sys(SYS_openat, AT_FDCWD, (long)prog, O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) return 1;
|
|
long n = init_sys(SYS_read, fd, (long)head, sizeof head - 1);
|
|
init_sys(SYS_close, fd, 0, 0);
|
|
if (n < 0) return 1;
|
|
head[n] = 0;
|
|
if (n >= 2 && head[0] == '#' && head[1] == '!') {
|
|
if (scripts <= 0) return 1;
|
|
char *p = head + 2;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
char *e = p;
|
|
while (*e && *e != ' ' && *e != '\t' && *e != '\n' && *e != '\r') e++;
|
|
if (e == p || (!*e && n == (long)sizeof head - 1)) return 1;
|
|
*e = 0;
|
|
return program_32bit(p, scripts - 1);
|
|
}
|
|
return !(n >= 5 && memcmp(head, "\x7f" "ELF", 4) == 0 && head[4] == 2);
|
|
}
|
|
|
|
static int fex_runs_32bit(void) {
|
|
if (strcmp(program_invocation_short_name, "FEX") != 0 && strcmp(program_invocation_short_name, "FEXInterpreter") != 0)
|
|
return 0;
|
|
char args[PATH_MAX + 64];
|
|
long fd = init_sys(SYS_openat, AT_FDCWD, (long)"/proc/self/cmdline", O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) return 1;
|
|
long n = init_sys(SYS_read, fd, (long)args, sizeof args - 1);
|
|
init_sys(SYS_close, fd, 0, 0);
|
|
if (n <= 0) return 1;
|
|
args[n] = 0;
|
|
const char *prog = args + strlen(args) + 1;
|
|
if (prog >= args + n || !*prog) return 1;
|
|
return program_32bit(prog, 4);
|
|
}
|
|
|
|
static int in_tracer(struct bind *bd) {
|
|
char h[PATH_MAX];
|
|
if (snprintf(h, sizeof h, "/proc/%ld%s", tracer, bd->host + 10) >= (int)sizeof h) return 0;
|
|
long e = canonical(h, sizeof h);
|
|
if (e == -EACCES || e == -EPERM) return 0;
|
|
memcpy(bd->host, h, strlen(h) + 1);
|
|
bd->hlen = strlen(bd->host);
|
|
bd->dropped = e < 0;
|
|
bd->canon = 1;
|
|
return 1;
|
|
}
|
|
|
|
static void fp_init(void) {
|
|
const char *r = getenv("PROOT_FP_ROOT"), *b = getenv("PROOT_FP_BINDS"), *t = getenv("PROOT_FP_TTL_MS");
|
|
const char *key = getenv("PROOT_FP_KEY");
|
|
if (!r || !*r || !key || !*key || getenv("PROOT_FP_OFF")) return;
|
|
if (fex_runs_32bit()) return;
|
|
void *p = mmap((void *)FP_STUB_ADDR, FP_STUB_SIZE, PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
|
|
if (p != (void *)FP_STUB_ADDR) {
|
|
if (p != MAP_FAILED) { munmap(p, FP_STUB_SIZE); return; }
|
|
if (errno != EEXIST) return;
|
|
} else {
|
|
static const uint32_t code[] = {
|
|
0xAA0003E8, 0xAA0103E0, 0xAA0203E1,
|
|
0xAA0303E2, 0xAA0403E3, 0xAA0503E4,
|
|
0xAA0603E5, 0xD4000001, 0xD65F03C0,
|
|
};
|
|
memcpy(p, code, sizeof code);
|
|
__builtin___clear_cache((char *)p, (char *)p + sizeof code);
|
|
if (mprotect(p, FP_STUB_SIZE, PROT_READ | PROT_EXEC) != 0) { munmap(p, FP_STUB_SIZE); return; }
|
|
}
|
|
fp_sys = (stub_fn)FP_STUB_ADDR;
|
|
if (!traced_by_match(key)) { fp_sys = NULL; return; }
|
|
if (t) ttl_ns = atoll(t) * 1000000LL;
|
|
snprintf(root, sizeof root, "%s", r);
|
|
canonical(root, sizeof root);
|
|
rootlen = strlen(root);
|
|
if (rootlen <= 1) { fp_sys = NULL; return; }
|
|
const char *s = b;
|
|
int unheld = 0;
|
|
for (; s && *s && nbinds < MAX_BINDS;) {
|
|
const char *end = strchr(s, '|');
|
|
size_t len = end ? (size_t)(end - s) : strlen(s);
|
|
char spec[2 * PATH_MAX];
|
|
if (len > 0 && len < sizeof spec) {
|
|
memcpy(spec, s, len);
|
|
spec[len] = 0;
|
|
char *colon = strchr(spec, ':');
|
|
if (colon) *colon = 0;
|
|
struct bind *bd = &binds[nbinds];
|
|
static char hosts[MAX_BINDS][PATH_MAX], guests[MAX_BINDS][512];
|
|
const char *g = colon ? colon + 1 : spec;
|
|
if (strlen(g) >= sizeof guests[0]) { unheld = 1; break; }
|
|
bd->host = hosts[nbinds];
|
|
bd->guest = guests[nbinds];
|
|
snprintf(bd->guest, sizeof guests[0], "%s", g);
|
|
snprintf(bd->host, PATH_MAX, "%.*s", PATH_MAX - 1, spec);
|
|
bd->hlen = strlen(bd->host);
|
|
bd->glen = strlen(bd->guest);
|
|
if (bd->guest[0] == '/' && bd->glen > 1 && bd->hlen > 0) nbinds++;
|
|
} else if (len > 0) {
|
|
unheld = 1;
|
|
break;
|
|
}
|
|
s = end ? end + 1 : NULL;
|
|
}
|
|
if (unheld || (s && *s)) { fp_sys = NULL; return; }
|
|
for (int i = 0; i < nbinds; i++)
|
|
if (under(binds[i].host, "/proc/self", 10) && !in_tracer(&binds[i])) { fp_sys = NULL; return; }
|
|
pthread_atfork(NULL, NULL, after_fork);
|
|
fp_on = 1;
|
|
if (getenv("PROOT_FP_STATS")) atexit(stats);
|
|
miss_log = getenv("PROOT_FP_LOG");
|
|
if (miss_log && miss_log[0] != '/') miss_log = NULL;
|
|
}
|
|
|
|
static pthread_mutex_t bind_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static int held(struct bind *bd) {
|
|
if (!__atomic_load_n(&bd->canon, __ATOMIC_ACQUIRE)) {
|
|
if (pthread_mutex_trylock(&bind_lock) != 0) return -1;
|
|
if (!bd->canon) {
|
|
bd->dropped = canonical(bd->host, PATH_MAX) < 0;
|
|
bd->hlen = strlen(bd->host);
|
|
__atomic_store_n(&bd->canon, 1, __ATOMIC_RELEASE);
|
|
}
|
|
pthread_mutex_unlock(&bind_lock);
|
|
}
|
|
return !bd->dropped;
|
|
}
|
|
|
|
|
|
static int reserved(const char *g) {
|
|
return under(g, "/dev/fd", 7) || under(g, "/dev/stdin", 10) || under(g, "/dev/stdout", 11)
|
|
|| under(g, "/dev/stderr", 11);
|
|
}
|
|
|
|
static int bind_of(const char *g, struct bind **out) {
|
|
struct bind *bd = NULL;
|
|
for (int i = 0; i < nbinds; i++) {
|
|
if (!under(g, binds[i].guest, binds[i].glen) || (bd && binds[i].glen < bd->glen)) continue;
|
|
int h = held(&binds[i]);
|
|
if (h < 0) return -1;
|
|
if (h) bd = &binds[i];
|
|
}
|
|
*out = bd;
|
|
return 0;
|
|
}
|
|
|
|
static int to_host(const char *g, char *host, struct bind **through) {
|
|
struct bind *bd;
|
|
if (bind_of(g, &bd) < 0) return -1;
|
|
if (through) *through = bd;
|
|
int n = bd ? snprintf(host, PATH_MAX, "%s%s", bd->host, g + bd->glen)
|
|
: snprintf(host, PATH_MAX, "%s%s", root, strcmp(g, "/") ? g : "");
|
|
return n < PATH_MAX ? 0 : -1;
|
|
}
|
|
|
|
static int glue_above_binding(const char *g, size_t len) {
|
|
int glue = 0;
|
|
for (int i = 0; i < nbinds && glue == 0; i++)
|
|
if (binds[i].glen > len && under(binds[i].guest, g, len)) glue = held(&binds[i]);
|
|
return glue;
|
|
}
|
|
|
|
static int guest_name(const char *h, int k, char *g) {
|
|
char back[PATH_MAX];
|
|
size_t deepest = 0;
|
|
if (under(h, root, rootlen)) {
|
|
if (k > 0 || snprintf(g, PATH_MAX, "%s", h[rootlen] ? h + rootlen : "/") >= PATH_MAX) return -1;
|
|
return reserved(g) || to_host(g, back, NULL) < 0 || strcmp(back, h) != 0 ? -1 : 0;
|
|
}
|
|
for (int i = 0; i < nbinds; i++) {
|
|
int in = held(&binds[i]);
|
|
if (in < 0) return -1;
|
|
if (in && under(h, binds[i].host, binds[i].hlen) && binds[i].hlen > deepest) deepest = binds[i].hlen;
|
|
}
|
|
for (int i = 0; deepest && i < nbinds; i++) {
|
|
if (binds[i].dropped || binds[i].hlen != deepest || !under(h, binds[i].host, binds[i].hlen)) continue;
|
|
if (k-- > 0) continue;
|
|
if (snprintf(g, PATH_MAX, "%s%s", binds[i].guest, h + binds[i].hlen) >= PATH_MAX) return -1;
|
|
return reserved(g) || to_host(g, back, NULL) < 0 || strcmp(back, h) != 0 ? -1 : 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int to_guest(const char *h, char *g, size_t size) {
|
|
char other[PATH_MAX];
|
|
if (size < PATH_MAX || guest_name(h, 0, g) < 0) return -1;
|
|
for (int k = 1; guest_name(h, k, other) == 0; k++)
|
|
if (strcmp(other, g) != 0) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int host_order[MAX_BINDS + 1], norder, ordered;
|
|
|
|
static int contains(const char *a, const char *b) {
|
|
size_t la = strlen(a);
|
|
return strlen(b) > la && under(b, a, la);
|
|
}
|
|
|
|
static const char *side_of(int i, int host) {
|
|
return host ? (i < 0 ? root : binds[i].host) : (i < 0 ? "/" : binds[i].guest);
|
|
}
|
|
|
|
static int insort(int *list, int m, int i, int host) {
|
|
const char *p = side_of(i, host);
|
|
int prev = -1;
|
|
for (int j = 0; j < m; j++) {
|
|
const char *q = side_of(list[j], host);
|
|
if (!strcmp(p, q) && !host) {
|
|
list[j] = i;
|
|
return m;
|
|
}
|
|
if (!strcmp(p, q) || contains(p, q)) prev = j;
|
|
}
|
|
memmove(list + prev + 2, list + prev + 1, (m - prev - 1) * sizeof *list);
|
|
list[prev + 1] = i;
|
|
return m + 1;
|
|
}
|
|
|
|
static int order_binds(void) {
|
|
if (__atomic_load_n(&ordered, __ATOMIC_ACQUIRE)) return 0;
|
|
for (int i = 0; i < nbinds; i++)
|
|
if (held(&binds[i]) < 0) return -1;
|
|
if (pthread_mutex_trylock(&bind_lock) != 0) return -1;
|
|
if (!ordered) {
|
|
int pending[MAX_BINDS + 1], m = 1;
|
|
pending[0] = -1;
|
|
for (int i = 0; i < nbinds; i++)
|
|
if (!binds[i].dropped) m = insort(pending, m, i, 0);
|
|
norder = 0;
|
|
for (int j = m - 1; j >= 0; j--) norder = insort(host_order, norder, pending[j], 1);
|
|
__atomic_store_n(&ordered, 1, __ATOMIC_RELEASE);
|
|
}
|
|
pthread_mutex_unlock(&bind_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int host_bind(const char *h) {
|
|
if (order_binds() < 0) return -2;
|
|
for (int j = 0; j < norder; j++)
|
|
if (host_order[j] >= 0 && under(h, binds[host_order[j]].host, binds[host_order[j]].hlen)) return host_order[j];
|
|
return -1;
|
|
}
|
|
|
|
static int guest_text(const char *h, char *g) {
|
|
if (h[0] == '/' && under(h, root, rootlen)) return to_guest(h, g, PATH_MAX);
|
|
int i = h[0] == '/' ? host_bind(h) : -1;
|
|
if (i == -2) return -1;
|
|
if (i >= 0)
|
|
return snprintf(g, PATH_MAX, "%s%s", binds[i].guest, h + binds[i].hlen) >= PATH_MAX || reserved(g) ? -1 : 0;
|
|
memcpy(g, h, strlen(h) + 1);
|
|
return 0;
|
|
}
|
|
|
|
static long detranslated(char *text, const char *at) {
|
|
if (text[0] != '/') return 0;
|
|
if (!under(at, root, rootlen) && !under(text, root, rootlen)) {
|
|
int own = host_bind(at), to = host_bind(text);
|
|
if (to == -1) return 0;
|
|
if (own == -2 || to == -2) return FP_SLOW;
|
|
if (own >= 0 && to >= 0 && strcmp(binds[own].host, binds[to].host) == 0) {
|
|
const struct bind *bd = &binds[to];
|
|
if (strcmp(bd->host, bd->guest) == 0) return 0;
|
|
char g[PATH_MAX];
|
|
if (snprintf(g, sizeof g, "%s%s", bd->guest, text + bd->hlen) >= (int)sizeof g) return FP_SLOW;
|
|
strcpy(text, g);
|
|
return 1;
|
|
}
|
|
}
|
|
if (!under(text, root, rootlen)) return 0;
|
|
if (!text[rootlen]) strcpy(text, "/");
|
|
else memmove(text, text + rootlen, strlen(text + rootlen) + 1);
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define SLOTS 512
|
|
#define SLOT_PATH 256
|
|
struct slot { long long stamp; unsigned hash; unsigned gen; char literal[SLOT_PATH]; char guest[SLOT_PATH]; };
|
|
static struct slot cache[SLOTS];
|
|
static unsigned generation;
|
|
|
|
static long long now_ns(void) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
|
|
return ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
|
}
|
|
|
|
static unsigned hash_of(const char *s) {
|
|
unsigned h = 2166136261u;
|
|
while (*s) h = (h ^ (unsigned char)*s++) * 16777619u;
|
|
return h;
|
|
}
|
|
|
|
static void forget(void) { __atomic_add_fetch(&generation, 1, __ATOMIC_RELEASE); }
|
|
|
|
static int cached(const char *literal, unsigned h, char *guest) {
|
|
if (ttl_ns <= 0 || pthread_mutex_trylock(&cache_lock) != 0) return 0;
|
|
struct slot *e = &cache[h % SLOTS];
|
|
int hit = e->stamp && e->hash == h && e->gen == __atomic_load_n(&generation, __ATOMIC_ACQUIRE)
|
|
&& now_ns() - e->stamp < ttl_ns && strcmp(e->literal, literal) == 0;
|
|
if (hit) memcpy(guest, e->guest, strlen(e->guest) + 1);
|
|
pthread_mutex_unlock(&cache_lock);
|
|
return hit;
|
|
}
|
|
|
|
static void remember(const char *literal, unsigned h, const char *guest) {
|
|
if (ttl_ns <= 0 || strlen(literal) >= SLOT_PATH || strlen(guest) >= SLOT_PATH) return;
|
|
unsigned gen = __atomic_load_n(&generation, __ATOMIC_ACQUIRE);
|
|
if (pthread_mutex_trylock(&cache_lock) != 0) return;
|
|
struct slot *e = &cache[h % SLOTS];
|
|
e->hash = h;
|
|
e->gen = gen;
|
|
strcpy(e->literal, literal);
|
|
strcpy(e->guest, guest);
|
|
e->stamp = now_ns();
|
|
pthread_mutex_unlock(&cache_lock);
|
|
}
|
|
|
|
static int binding_below(const char *g) {
|
|
size_t len = strlen(g);
|
|
for (int i = 0; i < nbinds; i++)
|
|
if (binds[i].glen > len && under(binds[i].guest, g, len)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct res {
|
|
char guest[PATH_MAX];
|
|
char host[PATH_MAX];
|
|
struct stat st;
|
|
int exists;
|
|
int named;
|
|
int libc;
|
|
int moving;
|
|
int magic;
|
|
};
|
|
|
|
static int gone(const char *h, size_t n) {
|
|
return n > 10 && strcmp(h + n - 10, " (deleted)") == 0;
|
|
}
|
|
|
|
static long own_cwd(char *g) {
|
|
char h[PATH_MAX];
|
|
long n = sc(SYS_readlinkat, AT_FDCWD, (long)"/proc/self/cwd", (long)h, sizeof h - 1, 0);
|
|
if (n <= 0 || h[0] != '/') return FP_SLOW;
|
|
h[n] = 0;
|
|
if (gone(h, n) || to_guest(h, g, PATH_MAX) < 0) return FP_SLOW;
|
|
return (long)strlen(g);
|
|
}
|
|
|
|
static char exe_text[PATH_MAX];
|
|
static int exe_len;
|
|
static long own_exe(char *g) {
|
|
int n = __atomic_load_n(&exe_len, __ATOMIC_ACQUIRE);
|
|
if (n > 0) {
|
|
memcpy(g, exe_text, n + 1);
|
|
return n;
|
|
}
|
|
long t = init_sys4(SYS_readlinkat, AT_FDCWD, (long)"/proc/self/exe", (long)g, PATH_MAX - 1);
|
|
if (t <= 0 || t >= PATH_MAX - 1) return FP_SLOW;
|
|
g[t] = 0;
|
|
static pthread_mutex_t exe_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
if (pthread_mutex_trylock(&exe_lock) == 0) {
|
|
if (!exe_len) {
|
|
memcpy(exe_text, g, t + 1);
|
|
__atomic_store_n(&exe_len, (int)t, __ATOMIC_RELEASE);
|
|
}
|
|
pthread_mutex_unlock(&exe_lock);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
static long proc_readlink(const struct res *r, char *text, size_t size, int *changed) {
|
|
const char *name = strrchr(r->guest, '/') + 1;
|
|
size_t blen = name - 1 - r->guest;
|
|
char base[32];
|
|
int pid = blen > 6 ? atoi(r->guest + 6) : 0;
|
|
int bn = snprintf(base, sizeof base, "/proc/%d", pid);
|
|
if (pid != 0 && (size_t)bn == blen && strncmp(r->guest, base, blen) == 0
|
|
&& (!strcmp(name, "exe") || !strcmp(name, "cwd") || !strcmp(name, "root"))) {
|
|
if (pid != getpid() || !strcmp(name, "root")) return FP_SLOW;
|
|
*changed = 1;
|
|
return name[0] == 'e' ? own_exe(text) : own_cwd(text);
|
|
}
|
|
char h[PATH_MAX];
|
|
long t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)h, sizeof h - 1, 0);
|
|
if (t < 0) return t;
|
|
if (t >= (long)sizeof h - 1) return FP_SLOW;
|
|
if (size && (size_t)t > size) t = size;
|
|
h[t] = 0;
|
|
if (guest_text(h, text) < 0) return FP_SLOW;
|
|
*changed = strcmp(h, text) != 0;
|
|
return (long)strlen(text);
|
|
}
|
|
|
|
static int fd_link(const char *g) {
|
|
const char *p = g + 6;
|
|
if (strncmp(g, "/proc/", 6) != 0 || *p < '1' || *p > '9') return 0;
|
|
while (*p >= '0' && *p <= '9') p++;
|
|
if (strncmp(p, "/fd/", 4) != 0 || p[4] < '0' || p[4] > '9') return 0;
|
|
return 1;
|
|
}
|
|
|
|
static long proc_link(struct res *r, char *text, int final) {
|
|
int changed;
|
|
if (!under(r->host, "/proc", 5)) return FP_SLOW;
|
|
r->moving = 1;
|
|
if (!strcmp(r->guest, "/proc/self") || !strcmp(r->guest, "/proc/thread-self")) {
|
|
long t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)text, PATH_MAX - 1, 0);
|
|
if (t <= 0 || t >= PATH_MAX - 1) return FP_SLOW;
|
|
text[t] = 0;
|
|
return t;
|
|
}
|
|
if (!r->libc && final && fd_link(r->guest)) return 0;
|
|
long t = proc_readlink(r, text, 0, &changed);
|
|
return t <= 0 ? FP_SLOW : t;
|
|
}
|
|
|
|
static int pop(char *g) {
|
|
char *slash = strrchr(g, '/');
|
|
if (!slash) return -1;
|
|
if (slash == g) g[1] = 0;
|
|
else *slash = 0;
|
|
return 0;
|
|
}
|
|
|
|
#define UNSEEN 2
|
|
#define MAY_BE_UNSEEN 2
|
|
#define GLUE 3
|
|
|
|
static long walk(struct res *r, const char *text, int follow, int last) {
|
|
char rest[PATH_MAX];
|
|
size_t len = strlen(text);
|
|
int links = 0, stated = 0;
|
|
if (len >= sizeof rest) return FP_SLOW;
|
|
memcpy(rest, text, len + 1);
|
|
char *c = rest;
|
|
r->exists = 0;
|
|
for (;;) {
|
|
while (*c == '/') c++;
|
|
if (!*c) break;
|
|
char *e = strchrnul(c, '/');
|
|
size_t n = e - c;
|
|
char *after = e;
|
|
while (*after == '/') after++;
|
|
int final = *after == 0;
|
|
stated = 0;
|
|
if (n == 1 && c[0] == '.') { c = e; continue; }
|
|
if (n == 2 && c[0] == '.' && c[1] == '.') { pop(r->guest); c = e; continue; }
|
|
size_t glen = strlen(r->guest);
|
|
if (glen + n + 2 >= PATH_MAX) return FP_SLOW;
|
|
if (glen > 1) r->guest[glen++] = '/';
|
|
memcpy(r->guest + glen, c, n);
|
|
r->guest[glen + n] = 0;
|
|
if (reserved(r->guest)) return FP_SLOW;
|
|
struct bind *through;
|
|
if (to_host(r->guest, r->host, &through) < 0) return FP_SLOW;
|
|
long s = sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, AT_SYMLINK_NOFOLLOW, 0);
|
|
if (s < 0) {
|
|
int glue = glue_above_binding(r->guest, glen + n);
|
|
if (glue < 0) return FP_SLOW;
|
|
if (glue) {
|
|
if (final) return GLUE;
|
|
c = e;
|
|
continue;
|
|
}
|
|
if (!final || !last) return r->libc ? s : -ENOENT;
|
|
return s == -ENOENT ? 1 : UNSEEN;
|
|
}
|
|
if (S_ISLNK(r->st.st_mode) && (!final || follow)) {
|
|
char target[PATH_MAX];
|
|
long t;
|
|
if (++links > MAX_LINKS || binding_below(r->guest)) return FP_SLOW;
|
|
if (under(r->guest, "/proc", 5)) {
|
|
t = proc_link(r, target, final && last);
|
|
if (t == FP_SLOW) return FP_SLOW;
|
|
if (t == 0) {
|
|
if (sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, 0, 0) < 0) return FP_SLOW;
|
|
r->magic = 1;
|
|
r->exists = 1;
|
|
return 0;
|
|
}
|
|
} else {
|
|
t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)target, sizeof target - 1, 0);
|
|
if (t <= 0 || t >= (long)sizeof target - 1) return FP_SLOW;
|
|
target[t] = 0;
|
|
if (detranslated(target, r->host) == FP_SLOW) return FP_SLOW;
|
|
t = strlen(target);
|
|
}
|
|
if (target[0] == '/') strcpy(r->guest, "/");
|
|
else pop(r->guest);
|
|
size_t tail = strlen(e);
|
|
if ((size_t)t + 1 + tail >= sizeof rest) return FP_SLOW;
|
|
memmove(rest + t + 1, e, tail + 1);
|
|
memcpy(rest, target, t);
|
|
rest[t] = '/';
|
|
c = rest;
|
|
continue;
|
|
}
|
|
if (!final && !S_ISDIR(r->st.st_mode)) return -ENOTDIR;
|
|
stated = 1;
|
|
c = e;
|
|
}
|
|
if (!stated) {
|
|
if (to_host(r->guest, r->host, NULL) < 0) return FP_SLOW;
|
|
long s = sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, AT_SYMLINK_NOFOLLOW, 0);
|
|
if (s < 0 && strcmp(r->guest, "/") != 0 && glue_above_binding(r->guest, strlen(r->guest))) return FP_SLOW;
|
|
if (s < 0) return s == -ENOENT ? FP_SLOW : s;
|
|
}
|
|
r->exists = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int base_of(int dirfd, char *h) {
|
|
char link[40];
|
|
if (dirfd == AT_FDCWD) snprintf(link, sizeof link, "/proc/self/cwd");
|
|
else snprintf(link, sizeof link, "/proc/self/fd/%d", dirfd);
|
|
long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)h, PATH_MAX - 1, 0);
|
|
if (n <= 0 || n >= PATH_MAX - 1) return -1;
|
|
if (h[0] != '/') return dirfd == AT_FDCWD ? -1 : -ENOTDIR;
|
|
h[n] = 0;
|
|
return gone(h, n) ? -1 : 0;
|
|
}
|
|
|
|
static long bound_literal(const char *literal, struct res *r) {
|
|
struct bind *bd;
|
|
if (reserved(literal) || bind_of(literal, &bd) < 0 || !bd || bd->glen != strlen(literal) || bd->glen < 2) return FP_SLOW;
|
|
if (snprintf(r->host, PATH_MAX, "%s", bd->host) >= PATH_MAX) return FP_SLOW;
|
|
if (sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, AT_SYMLINK_NOFOLLOW, 0) < 0 || S_ISLNK(r->st.st_mode)) return FP_SLOW;
|
|
snprintf(r->guest, PATH_MAX, "%s", literal);
|
|
r->exists = 1;
|
|
r->moving = 0;
|
|
r->magic = 0;
|
|
return 0;
|
|
}
|
|
|
|
static long resolve_in(const char *base, const char *path, int follow, struct res *r) {
|
|
char norm[PATH_MAX];
|
|
size_t o = 0;
|
|
int dotdot = 0;
|
|
size_t plen = strlen(path), end = plen;
|
|
while (end > 1 && path[end - 1] == '/') end--;
|
|
int trailing = (plen > 1 && path[plen - 1] == '/') || (end > 0 && path[end - 1] == '.' && (end == 1 || path[end - 2] == '/'));
|
|
for (int part = base ? 0 : 1; part < 2; part++) {
|
|
for (const char *c = part ? path : base; *c;) {
|
|
while (*c == '/') c++;
|
|
if (!*c) break;
|
|
const char *e = strchrnul(c, '/');
|
|
size_t len = e - c;
|
|
c = e;
|
|
if (len == 1 && e[-1] == '.') continue;
|
|
if (len == 2 && e[-1] == '.' && e[-2] == '.') dotdot = 1;
|
|
if (o + len + 2 >= sizeof norm) return FP_SLOW;
|
|
norm[o++] = '/';
|
|
memcpy(norm + o, e - len, len);
|
|
o += len;
|
|
}
|
|
}
|
|
norm[o] = 0;
|
|
strcpy(r->guest, "/");
|
|
r->moving = 0;
|
|
r->magic = 0;
|
|
long w;
|
|
char *slash = strrchr(norm, '/');
|
|
if (o == 0 || dotdot || slash == norm) {
|
|
w = walk(r, o ? norm : "/", follow || trailing, 1);
|
|
} else {
|
|
*slash = 0;
|
|
unsigned h = hash_of(norm);
|
|
if (!cached(norm, h, r->guest)) {
|
|
w = walk(r, norm, 1, 0);
|
|
if (w == FP_SLOW && !trailing) {
|
|
*slash = '/';
|
|
return bound_literal(norm, r);
|
|
}
|
|
if (w < 0) return w;
|
|
if (w != GLUE && !S_ISDIR(r->st.st_mode)) return -ENOTDIR;
|
|
if (!r->moving) remember(norm, h, r->guest);
|
|
}
|
|
*slash = '/';
|
|
w = walk(r, slash + 1, follow || trailing, 1);
|
|
}
|
|
if (w < 0) return w;
|
|
if (w == GLUE || (trailing && w != 1 && (w != 0 || !S_ISDIR(r->st.st_mode)))) return FP_SLOW;
|
|
return w;
|
|
}
|
|
|
|
static __attribute__((noinline)) long same_from_other_names(const char *h, const char *path, int follow,
|
|
long rs, const struct res *r) {
|
|
char base[PATH_MAX];
|
|
struct res other;
|
|
other.libc = r->libc;
|
|
for (int k = 1; guest_name(h, k, base) == 0; k++) {
|
|
if (resolve_in(base, path, follow, &other) != rs) return FP_SLOW;
|
|
if (rs >= 0 && strcmp(other.host, r->host) != 0) return FP_SLOW;
|
|
}
|
|
return rs;
|
|
}
|
|
|
|
static long resolve_as(int dirfd, const char *path, int how, int libc, struct res *r) {
|
|
int follow = how & 1;
|
|
long rs;
|
|
char h[PATH_MAX], base[PATH_MAX];
|
|
if (!path || !path[0] || !fp_ready()) return FP_SLOW;
|
|
r->named = 1;
|
|
r->libc = libc;
|
|
if (path[0] == '/') {
|
|
rs = resolve_in(NULL, path, follow, r);
|
|
} else {
|
|
int b = base_of(dirfd, h);
|
|
if (b == -ENOTDIR) return b;
|
|
if (b < 0 || guest_name(h, 0, base) < 0) return FP_SLOW;
|
|
rs = resolve_in(base, path, follow, r);
|
|
if (rs != FP_SLOW && guest_name(h, 1, base) == 0) {
|
|
r->named = 0;
|
|
rs = same_from_other_names(h, path, follow, rs, r);
|
|
}
|
|
}
|
|
return rs == UNSEEN && !(how & MAY_BE_UNSEEN) ? FP_SLOW : rs;
|
|
}
|
|
|
|
static long resolve(int dirfd, const char *path, int how, struct res *r) {
|
|
return resolve_as(dirfd, path, how, 0, r);
|
|
}
|
|
|
|
static int trailing_slash(const char *path) {
|
|
size_t len = path ? strlen(path) : 0;
|
|
return len > 1 && path[len - 1] == '/';
|
|
}
|
|
|
|
static int ret(long r) {
|
|
if (r < 0) { errno = (int)-r; return -1; }
|
|
return (int)r;
|
|
}
|
|
|
|
#define REAL(name, type) \
|
|
static __typeof__(type) real_##name; \
|
|
if (!real_##name) real_##name = (type)dlsym(RTLD_NEXT, #name)
|
|
|
|
#define ANSWER(r, dirfd, path, real_call) \
|
|
do { long r_ = (r); if (answered(r_)) { hits++; return ret(r_); } missed(__func__, dirfd, path); return real_call; } while (0)
|
|
|
|
|
|
static long fp_open(int dirfd, const char *path, int flags, mode_t mode) {
|
|
struct res r;
|
|
if ((flags & __O_TMPFILE) == __O_TMPFILE) return FP_SLOW;
|
|
int follow = !(flags & O_NOFOLLOW) && !((flags & O_CREAT) && (flags & O_EXCL));
|
|
if ((flags & O_CREAT) && trailing_slash(path)) return FP_SLOW;
|
|
long rs = resolve(dirfd, path, follow | MAY_BE_UNSEEN, &r);
|
|
if (rs < 0) return rs;
|
|
if (rs == 1 && !(flags & O_CREAT)) return -ENOENT;
|
|
if (rs == UNSEEN) return sc(SYS_openat, AT_FDCWD, (long)r.host, flags, (flags & O_CREAT) ? mode : 0, 0);
|
|
return sc(SYS_openat, AT_FDCWD, (long)r.host, flags | (follow && !r.magic ? O_NOFOLLOW : 0), (flags & O_CREAT) ? mode : 0, 0);
|
|
}
|
|
|
|
static int do_openat(int dirfd, const char *path, int flags, mode_t mode, int (*real)(int, const char *, int, ...)) {
|
|
long r = fp_open(dirfd, path, flags, mode);
|
|
if (answered(r)) { hits++; return ret(r); }
|
|
missed(__func__, dirfd, path);
|
|
return real(dirfd, path, flags, mode);
|
|
}
|
|
|
|
int openat(int dirfd, const char *path, int flags, ...) {
|
|
REAL(openat, int (*)(int, const char *, int, ...));
|
|
mode_t mode = 0;
|
|
if (flags & (O_CREAT | __O_TMPFILE)) { va_list ap; va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); }
|
|
return do_openat(dirfd, path, flags, mode, real_openat);
|
|
}
|
|
int openat64(int dirfd, const char *path, int flags, ...) __attribute__((alias("openat")));
|
|
|
|
int open(const char *path, int flags, ...) {
|
|
REAL(openat, int (*)(int, const char *, int, ...));
|
|
mode_t mode = 0;
|
|
if (flags & (O_CREAT | __O_TMPFILE)) { va_list ap; va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); }
|
|
return do_openat(AT_FDCWD, path, flags, mode, real_openat);
|
|
}
|
|
int open64(const char *path, int flags, ...) __attribute__((alias("open")));
|
|
int __open_2(const char *path, int flags) { return open(path, flags); }
|
|
int __open64_2(const char *path, int flags) { return open(path, flags); }
|
|
int __openat_2(int d, const char *path, int flags) { return openat(d, path, flags); }
|
|
int __openat64_2(int d, const char *path, int flags) { return openat(d, path, flags); }
|
|
int creat(const char *path, mode_t mode) { return open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); }
|
|
int creat64(const char *path, mode_t mode) __attribute__((alias("creat")));
|
|
|
|
|
|
static long fp_stat(int dirfd, const char *path, int flags, struct stat *st) {
|
|
struct res r;
|
|
if ((flags & AT_EMPTY_PATH) && path && !path[0] && fp_ready())
|
|
return sc(SYS_newfstatat, dirfd, (long)"", (long)st, flags, 0);
|
|
if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT)) return FP_SLOW;
|
|
long rs = resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
|
|
if (rs < 0) return rs;
|
|
if (rs == 1) return -ENOENT;
|
|
if (rs == UNSEEN) return sc(SYS_newfstatat, AT_FDCWD, (long)r.host, (long)st, flags, 0);
|
|
*st = r.st;
|
|
return 0;
|
|
}
|
|
|
|
static int stat_common(int dirfd, const char *path, struct stat *st, int flags,
|
|
int (*real)(int, const char *, struct stat *, int)) {
|
|
ANSWER(fp_stat(dirfd, path, flags, st), dirfd, path, real(dirfd, path, st, flags));
|
|
}
|
|
|
|
int fstatat(int dirfd, const char *path, struct stat *st, int flags) {
|
|
REAL(fstatat, int (*)(int, const char *, struct stat *, int));
|
|
return stat_common(dirfd, path, st, flags, real_fstatat);
|
|
}
|
|
int fstatat64(int d, const char *p, struct stat64 *st, int f) { return fstatat(d, p, (struct stat *)st, f); }
|
|
int stat(const char *path, struct stat *st) {
|
|
REAL(fstatat, int (*)(int, const char *, struct stat *, int));
|
|
return stat_common(AT_FDCWD, path, st, 0, real_fstatat);
|
|
}
|
|
int stat64(const char *p, struct stat64 *st) { return stat(p, (struct stat *)st); }
|
|
int lstat(const char *path, struct stat *st) {
|
|
REAL(fstatat, int (*)(int, const char *, struct stat *, int));
|
|
return stat_common(AT_FDCWD, path, st, AT_SYMLINK_NOFOLLOW, real_fstatat);
|
|
}
|
|
int lstat64(const char *p, struct stat64 *st) { return lstat(p, (struct stat *)st); }
|
|
|
|
int fstat(int fd, struct stat *st) {
|
|
REAL(fstat, int (*)(int, struct stat *));
|
|
if (!fp_ready()) return real_fstat(fd, st);
|
|
long r = sc(SYS_fstat, fd, (long)st, 0, 0, 0);
|
|
if (!answered(r)) return real_fstat(fd, st);
|
|
hits++;
|
|
return ret(r);
|
|
}
|
|
int fstat64(int fd, struct stat64 *st) { return fstat(fd, (struct stat *)st); }
|
|
|
|
#define FP_STAT_VER 0
|
|
static int xstat_common(int ver, int dirfd, const char *path, struct stat *st, int flags) {
|
|
REAL(fstatat, int (*)(int, const char *, struct stat *, int));
|
|
if (ver != FP_STAT_VER) { errno = EINVAL; return -1; }
|
|
return stat_common(dirfd, path, st, flags, real_fstatat);
|
|
}
|
|
int __xstat(int ver, const char *p, struct stat *st) { return xstat_common(ver, AT_FDCWD, p, st, 0); }
|
|
int __xstat64(int ver, const char *p, struct stat64 *st) { return xstat_common(ver, AT_FDCWD, p, (struct stat *)st, 0); }
|
|
int __lxstat(int ver, const char *p, struct stat *st) { return xstat_common(ver, AT_FDCWD, p, st, AT_SYMLINK_NOFOLLOW); }
|
|
int __lxstat64(int ver, const char *p, struct stat64 *st) { return xstat_common(ver, AT_FDCWD, p, (struct stat *)st, AT_SYMLINK_NOFOLLOW); }
|
|
int __fxstatat(int ver, int d, const char *p, struct stat *st, int f) { return xstat_common(ver, d, p, st, f); }
|
|
int __fxstatat64(int ver, int d, const char *p, struct stat64 *st, int f) { return xstat_common(ver, d, p, (struct stat *)st, f); }
|
|
int __fxstat(int ver, int fd, struct stat *st) {
|
|
if (ver != FP_STAT_VER) { errno = EINVAL; return -1; }
|
|
return fstat(fd, st);
|
|
}
|
|
int __fxstat64(int ver, int fd, struct stat64 *st) { return __fxstat(ver, fd, (struct stat *)st); }
|
|
|
|
#ifndef STATX__RESERVED
|
|
#define STATX__RESERVED 0x80000000U
|
|
#endif
|
|
|
|
static int statx_missing;
|
|
|
|
static unsigned int dev_major(unsigned long long dev) {
|
|
return ((dev >> 8) & 0xfff) | ((unsigned int)(dev >> 32) & ~0xfffU);
|
|
}
|
|
static unsigned int dev_minor(unsigned long long dev) {
|
|
return (dev & 0xff) | ((unsigned int)(dev >> 12) & ~0xffU);
|
|
}
|
|
|
|
static void fill_statx(struct statx *out, const struct stat *st) {
|
|
memset(out, 0, sizeof *out);
|
|
out->stx_mask = STATX_BASIC_STATS;
|
|
out->stx_blksize = st->st_blksize;
|
|
out->stx_nlink = st->st_nlink;
|
|
out->stx_uid = st->st_uid;
|
|
out->stx_gid = st->st_gid;
|
|
out->stx_mode = st->st_mode;
|
|
out->stx_ino = st->st_ino;
|
|
out->stx_size = st->st_size;
|
|
out->stx_blocks = st->st_blocks;
|
|
out->stx_atime.tv_sec = st->st_atim.tv_sec;
|
|
out->stx_atime.tv_nsec = st->st_atim.tv_nsec;
|
|
out->stx_ctime.tv_sec = st->st_ctim.tv_sec;
|
|
out->stx_ctime.tv_nsec = st->st_ctim.tv_nsec;
|
|
out->stx_mtime.tv_sec = st->st_mtim.tv_sec;
|
|
out->stx_mtime.tv_nsec = st->st_mtim.tv_nsec;
|
|
out->stx_rdev_major = dev_major(st->st_rdev);
|
|
out->stx_rdev_minor = dev_minor(st->st_rdev);
|
|
out->stx_dev_major = dev_major(st->st_dev);
|
|
out->stx_dev_minor = dev_minor(st->st_dev);
|
|
}
|
|
|
|
static long statx_call(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) {
|
|
struct stat st;
|
|
long rs;
|
|
if ((flags & ~(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE)) ||
|
|
(flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE)
|
|
return -EINVAL;
|
|
if (!statx_missing) {
|
|
rs = sc(SYS_statx, dirfd, (long)path, flags, mask, (long)out);
|
|
if (rs != -ENOSYS) return rs;
|
|
statx_missing = 1;
|
|
}
|
|
if (flags & AT_STATX_SYNC_TYPE) return -ENOSYS;
|
|
rs = sc(SYS_newfstatat, dirfd, (long)path, (long)&st,
|
|
flags & (AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT), 0);
|
|
if (rs < 0) return rs;
|
|
fill_statx(out, &st);
|
|
return 0;
|
|
}
|
|
|
|
static long fp_statx(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) {
|
|
struct res r;
|
|
long rs;
|
|
if (mask & STATX__RESERVED) return -EINVAL;
|
|
if (!out) return -EFAULT;
|
|
if (!path) return flags & AT_EMPTY_PATH ? FP_SLOW : -EFAULT;
|
|
if ((flags & AT_EMPTY_PATH) && !path[0] && fp_ready())
|
|
return statx_call(dirfd, "", flags, mask, out);
|
|
rs = (flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE)) ? FP_SLOW
|
|
: resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN)
|
|
rs = statx_call(AT_FDCWD, r.host, flags | (rs == 0 && !r.magic ? AT_SYMLINK_NOFOLLOW : 0), mask, out);
|
|
return rs;
|
|
}
|
|
|
|
int statx(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) {
|
|
REAL(statx, int (*)(int, const char *, int, unsigned, struct statx *));
|
|
ANSWER(fp_statx(dirfd, path, flags, mask, out), dirfd, path,
|
|
real_statx ? real_statx(dirfd, path, flags, mask, out) : ret(sc(SYS_statx, dirfd, (long)path, flags, mask, (long)out)));
|
|
}
|
|
|
|
|
|
static int effective_is_real(void) {
|
|
return !getauxval(AT_SECURE) && getuid() == geteuid() && getgid() == getegid();
|
|
}
|
|
|
|
static long fp_access(int dirfd, const char *path, int mode, int flags) {
|
|
struct res r;
|
|
if ((flags & AT_EACCESS) && effective_is_real()) flags &= ~AT_EACCESS;
|
|
long rs = (flags & ~AT_SYMLINK_NOFOLLOW) == 0
|
|
? resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r) : FP_SLOW;
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 && S_ISLNK(r.st.st_mode)) rs = FP_SLOW;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_faccessat, AT_FDCWD, (long)r.host, mode, 0, 0);
|
|
return rs;
|
|
}
|
|
|
|
static int access_common(int dirfd, const char *path, int mode, int flags) {
|
|
REAL(faccessat, int (*)(int, const char *, int, int));
|
|
long rs = fp_access(dirfd, path, mode, flags);
|
|
if (answered(rs)) { hits++; return ret(rs); }
|
|
missed(__func__, dirfd, path);
|
|
if (flags == 0) return (int)syscall(SYS_faccessat, dirfd, path, mode);
|
|
return real_faccessat(dirfd, path, mode, flags);
|
|
}
|
|
int faccessat(int dirfd, const char *path, int mode, int flags) { return access_common(dirfd, path, mode, flags); }
|
|
int access(const char *path, int mode) { return access_common(AT_FDCWD, path, mode, 0); }
|
|
int eaccess(const char *path, int mode) { return access_common(AT_FDCWD, path, mode, AT_EACCESS); }
|
|
int euidaccess(const char *path, int mode) __attribute__((alias("eaccess")));
|
|
|
|
|
|
static long link_text(const struct res *r, char *text, size_t size, int *changed) {
|
|
int in_proc = under(r->guest, "/proc", 5);
|
|
*changed = 0;
|
|
if (in_proc != under(r->host, "/proc", 5)) return FP_SLOW;
|
|
if (in_proc) return proc_readlink(r, text, size, changed);
|
|
long t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)text, PATH_MAX - 1, 0);
|
|
if (t < 0) return t;
|
|
if (t >= PATH_MAX - 1) return FP_SLOW;
|
|
if (size && (size_t)t > size) t = size;
|
|
text[t] = 0;
|
|
long d = detranslated(text, r->host);
|
|
if (d == FP_SLOW) return FP_SLOW;
|
|
*changed = (int)d;
|
|
return (long)strlen(text);
|
|
}
|
|
|
|
static long fp_readlink(int dirfd, const char *path, char *buf, size_t size, size_t *written) {
|
|
struct res r;
|
|
char text[PATH_MAX];
|
|
int changed;
|
|
long rs = size ? resolve(dirfd, path, 0, &r) : FP_SLOW;
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0) rs = link_text(&r, text, size, &changed);
|
|
if (rs < 0) return rs;
|
|
size_t n = rs;
|
|
if (!changed) {
|
|
n = n < size ? n : size;
|
|
memcpy(buf, text, n);
|
|
if (written) *written = n;
|
|
return n;
|
|
}
|
|
size_t max = size < PATH_MAX ? size : PATH_MAX;
|
|
if (n + 1 < max) {
|
|
memcpy(buf, text, n + 1);
|
|
if (written) *written = n + 1;
|
|
return n;
|
|
}
|
|
memcpy(buf, text, max);
|
|
if (written) *written = max;
|
|
return max;
|
|
}
|
|
|
|
static ssize_t readlink_common(int dirfd, const char *path, char *buf, size_t size) {
|
|
REAL(readlinkat, ssize_t (*)(int, const char *, char *, size_t));
|
|
ANSWER(fp_readlink(dirfd, path, buf, size, NULL), dirfd, path, real_readlinkat(dirfd, path, buf, size));
|
|
}
|
|
ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t size) { return readlink_common(dirfd, path, buf, size); }
|
|
ssize_t readlink(const char *path, char *buf, size_t size) { return readlink_common(AT_FDCWD, path, buf, size); }
|
|
|
|
ssize_t __readlink_chk(const char *path, char *buf, size_t size, size_t room) {
|
|
REAL(__readlink_chk, ssize_t (*)(const char *, char *, size_t, size_t));
|
|
if (size > room) return real___readlink_chk(path, buf, size, room);
|
|
return readlink_common(AT_FDCWD, path, buf, size);
|
|
}
|
|
ssize_t __readlinkat_chk(int dirfd, const char *path, char *buf, size_t size, size_t room) {
|
|
REAL(__readlinkat_chk, ssize_t (*)(int, const char *, char *, size_t, size_t));
|
|
if (size > room) return real___readlinkat_chk(dirfd, path, buf, size, room);
|
|
return readlink_common(dirfd, path, buf, size);
|
|
}
|
|
|
|
char *realpath(const char *path, char *resolved) {
|
|
REAL(realpath, char *(*)(const char *, char *));
|
|
struct res r;
|
|
long rs = resolve_as(AT_FDCWD, path, 1, 1, &r);
|
|
if (rs == 0 && !r.named) rs = FP_SLOW;
|
|
if (rs == FP_SLOW) { missed(__func__, AT_FDCWD, path); return real_realpath(path, resolved); }
|
|
hits++;
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs < 0) { errno = (int)-rs; return NULL; }
|
|
size_t n = strlen(r.guest) + 1;
|
|
if (!resolved) {
|
|
resolved = malloc(n);
|
|
if (!resolved) return NULL;
|
|
}
|
|
memcpy(resolved, r.guest, n);
|
|
return resolved;
|
|
}
|
|
char *__realpath_chk(const char *path, char *resolved, size_t size) {
|
|
REAL(__realpath_chk, char *(*)(const char *, char *, size_t));
|
|
if (resolved && size < PATH_MAX) return real___realpath_chk(path, resolved, size);
|
|
return realpath(path, resolved);
|
|
}
|
|
char *canonicalize_file_name(const char *path) { return realpath(path, NULL); }
|
|
|
|
char *getcwd(char *buf, size_t size) {
|
|
REAL(getcwd, char *(*)(char *, size_t));
|
|
char g[PATH_MAX];
|
|
if (!fp_ready() || own_cwd(g) == FP_SLOW || refused()) { missed(__func__, AT_FDCWD, "."); return real_getcwd(buf, size); }
|
|
hits++;
|
|
size_t need = strlen(g) + 1;
|
|
if (buf && size == 0) { errno = EINVAL; return NULL; }
|
|
if (buf && size < need) { errno = ERANGE; return NULL; }
|
|
if (!buf) {
|
|
if (size && size < need) { errno = ERANGE; return NULL; }
|
|
buf = malloc(size ? size : need);
|
|
if (!buf) return NULL;
|
|
}
|
|
memcpy(buf, g, need);
|
|
return buf;
|
|
}
|
|
char *__getcwd_chk(char *buf, size_t size, size_t room) {
|
|
REAL(__getcwd_chk, char *(*)(char *, size_t, size_t));
|
|
if (size > room) return real___getcwd_chk(buf, size, room);
|
|
return getcwd(buf, size);
|
|
}
|
|
|
|
|
|
static long fp_mkdir(int dirfd, const char *path, mode_t mode) {
|
|
struct res r;
|
|
long rs = resolve(dirfd, path, MAY_BE_UNSEEN, &r);
|
|
if (rs == 0 || rs == 1 || rs == UNSEEN) rs = sc(SYS_mkdirat, AT_FDCWD, (long)r.host, mode, 0, 0);
|
|
return rs;
|
|
}
|
|
int mkdirat(int dirfd, const char *path, mode_t mode) {
|
|
REAL(mkdirat, int (*)(int, const char *, mode_t));
|
|
ANSWER(fp_mkdir(dirfd, path, mode), dirfd, path, real_mkdirat(dirfd, path, mode));
|
|
}
|
|
int mkdir(const char *path, mode_t mode) { return mkdirat(AT_FDCWD, path, mode); }
|
|
|
|
static int ends_in_dots(const char *path) {
|
|
size_t n = strlen(path);
|
|
while (n > 1 && path[n - 1] == '/') n--;
|
|
size_t b = n;
|
|
while (b > 0 && path[b - 1] != '/') b--;
|
|
return (n - b == 1 && path[b] == '.') || (n - b == 2 && path[b] == '.' && path[b + 1] == '.');
|
|
}
|
|
|
|
static long fp_unlink(int dirfd, const char *path, int flags) {
|
|
struct res r;
|
|
char bare[PATH_MAX];
|
|
int slash = trailing_slash(path);
|
|
if ((flags & ~AT_REMOVEDIR) || !path || ends_in_dots(path) || (slash && !(flags & AT_REMOVEDIR))) return FP_SLOW;
|
|
if (slash) {
|
|
size_t n = strlen(path);
|
|
if (n >= sizeof bare) return FP_SLOW;
|
|
while (n > 1 && path[n - 1] == '/') n--;
|
|
memcpy(bare, path, n);
|
|
bare[n] = 0;
|
|
path = bare;
|
|
}
|
|
long rs = resolve(dirfd, path, MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (slash && rs == UNSEEN) rs = FP_SLOW;
|
|
if (slash && rs == 0 && !S_ISDIR(r.st.st_mode)) rs = S_ISLNK(r.st.st_mode) ? FP_SLOW : -ENOTDIR;
|
|
if (rs == 0 && (flags & AT_REMOVEDIR) && strcmp(r.guest, "/") == 0) rs = FP_SLOW;
|
|
if (rs == 0 || rs == UNSEEN) {
|
|
int changes_paths = (flags & AT_REMOVEDIR) || rs == UNSEEN || S_ISLNK(r.st.st_mode);
|
|
rs = sc(SYS_unlinkat, AT_FDCWD, (long)r.host, flags, 0, 0);
|
|
if (rs == 0 && changes_paths) forget();
|
|
}
|
|
return rs;
|
|
}
|
|
|
|
static int unlink_common(int dirfd, const char *path, int flags) {
|
|
REAL(unlinkat, int (*)(int, const char *, int));
|
|
ANSWER(fp_unlink(dirfd, path, flags), dirfd, path, real_unlinkat(dirfd, path, flags));
|
|
}
|
|
int unlinkat(int dirfd, const char *path, int flags) { return unlink_common(dirfd, path, flags); }
|
|
int unlink(const char *path) { return unlink_common(AT_FDCWD, path, 0); }
|
|
int rmdir(const char *path) { return unlink_common(AT_FDCWD, path, AT_REMOVEDIR); }
|
|
|
|
static long fp_rename(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) {
|
|
struct res from, to;
|
|
long rs = flags & ~RENAME_NOREPLACE || trailing_slash(old) || trailing_slash(new) ? FP_SLOW : resolve(olddirfd, old, 0, &from);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 && S_ISDIR(from.st.st_mode)) rs = FP_SLOW;
|
|
if (rs == 0) {
|
|
long rt = resolve(newdirfd, new, 0, &to);
|
|
if (rt < 0) rs = rt;
|
|
else if (rt == 0 && S_ISDIR(to.st.st_mode)) rs = FP_SLOW;
|
|
else {
|
|
rs = flags ? sc(SYS_renameat2, AT_FDCWD, (long)from.host, AT_FDCWD, (long)to.host, flags)
|
|
: sc(SYS_renameat, AT_FDCWD, (long)from.host, AT_FDCWD, (long)to.host, 0);
|
|
if (rs == 0) forget();
|
|
}
|
|
}
|
|
return rs;
|
|
}
|
|
static int rename_common(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) {
|
|
REAL(renameat2, int (*)(int, const char *, int, const char *, unsigned));
|
|
long rs = fp_rename(olddirfd, old, newdirfd, new, flags);
|
|
if (answered(rs)) { hits++; return ret(rs); }
|
|
missed(__func__, olddirfd, old);
|
|
if (!flags) return (int)syscall(SYS_renameat, olddirfd, old, newdirfd, new);
|
|
return real_renameat2(olddirfd, old, newdirfd, new, flags);
|
|
}
|
|
int renameat2(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) {
|
|
return rename_common(olddirfd, old, newdirfd, new, flags);
|
|
}
|
|
int renameat(int olddirfd, const char *old, int newdirfd, const char *new) { return rename_common(olddirfd, old, newdirfd, new, 0); }
|
|
int rename(const char *old, const char *new) { return rename_common(AT_FDCWD, old, AT_FDCWD, new, 0); }
|
|
|
|
static long fp_symlink(const char *target, int dirfd, const char *path) {
|
|
struct res r;
|
|
long rs = trailing_slash(path) ? FP_SLOW : resolve(dirfd, path, MAY_BE_UNSEEN, &r);
|
|
if (rs == 0 || rs == 1 || rs == UNSEEN) {
|
|
rs = sc(SYS_symlinkat, (long)target, AT_FDCWD, (long)r.host, 0, 0);
|
|
if (rs == 0) forget();
|
|
}
|
|
return rs;
|
|
}
|
|
int symlinkat(const char *target, int dirfd, const char *path) {
|
|
REAL(symlinkat, int (*)(const char *, int, const char *));
|
|
ANSWER(fp_symlink(target, dirfd, path), dirfd, path, real_symlinkat(target, dirfd, path));
|
|
}
|
|
int symlink(const char *target, const char *path) { return symlinkat(target, AT_FDCWD, path); }
|
|
|
|
static long fp_chmod(int dirfd, const char *path, mode_t mode, int flags) {
|
|
struct res r;
|
|
long rs = flags ? FP_SLOW : resolve(dirfd, path, 1 | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_fchmodat, AT_FDCWD, (long)r.host, mode, 0, 0);
|
|
return rs;
|
|
}
|
|
int fchmodat(int dirfd, const char *path, mode_t mode, int flags) {
|
|
REAL(fchmodat, int (*)(int, const char *, mode_t, int));
|
|
ANSWER(fp_chmod(dirfd, path, mode, flags), dirfd, path, real_fchmodat(dirfd, path, mode, flags));
|
|
}
|
|
int chmod(const char *path, mode_t mode) { return fchmodat(AT_FDCWD, path, mode, 0); }
|
|
|
|
static long fp_chown(int dirfd, const char *path, uid_t uid, gid_t gid, int flags) {
|
|
struct res r;
|
|
long rs = flags & ~AT_SYMLINK_NOFOLLOW ? FP_SLOW : resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_fchownat, AT_FDCWD, (long)r.host, (long)uid, (long)gid, flags);
|
|
return rs;
|
|
}
|
|
int fchownat(int dirfd, const char *path, uid_t uid, gid_t gid, int flags) {
|
|
REAL(fchownat, int (*)(int, const char *, uid_t, gid_t, int));
|
|
ANSWER(fp_chown(dirfd, path, uid, gid, flags), dirfd, path, real_fchownat(dirfd, path, uid, gid, flags));
|
|
}
|
|
int chown(const char *path, uid_t uid, gid_t gid) { return fchownat(AT_FDCWD, path, uid, gid, 0); }
|
|
int lchown(const char *path, uid_t uid, gid_t gid) { return fchownat(AT_FDCWD, path, uid, gid, AT_SYMLINK_NOFOLLOW); }
|
|
|
|
static long fp_utimens(int dirfd, const char *path, const struct timespec times[2], int flags) {
|
|
struct res r;
|
|
long rs;
|
|
if (!path && fp_ready()) {
|
|
rs = sc(SYS_utimensat, dirfd, 0, (long)times, flags, 0);
|
|
} else {
|
|
rs = flags & ~AT_SYMLINK_NOFOLLOW ? FP_SLOW : resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_utimensat, AT_FDCWD, (long)r.host, (long)times, flags, 0);
|
|
}
|
|
return rs;
|
|
}
|
|
static int utimens_common(int dirfd, const char *path, const struct timespec times[2], int flags) {
|
|
REAL(utimensat, int (*)(int, const char *, const struct timespec[2], int));
|
|
ANSWER(fp_utimens(dirfd, path, times, flags), dirfd, path, real_utimensat(dirfd, path, times, flags));
|
|
}
|
|
int utimensat(int dirfd, const char *path, const struct timespec times[2], int flags) {
|
|
return utimens_common(dirfd, path, times, flags);
|
|
}
|
|
int futimens(int fd, const struct timespec times[2]) { return utimens_common(fd, NULL, times, 0); }
|
|
|
|
static int utimes_common(int dirfd, const char *path, const struct timeval tv[2], int flags) {
|
|
struct timespec ts[2];
|
|
if (tv) {
|
|
for (int i = 0; i < 2; i++) {
|
|
if (tv[i].tv_usec < 0 || tv[i].tv_usec >= 1000000) { errno = EINVAL; return -1; }
|
|
ts[i].tv_sec = tv[i].tv_sec;
|
|
ts[i].tv_nsec = tv[i].tv_usec * 1000;
|
|
}
|
|
}
|
|
return utimens_common(dirfd, path, tv ? ts : NULL, flags);
|
|
}
|
|
int utimes(const char *path, const struct timeval tv[2]) { return utimes_common(AT_FDCWD, path, tv, 0); }
|
|
int lutimes(const char *path, const struct timeval tv[2]) { return utimes_common(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW); }
|
|
int futimes(int fd, const struct timeval tv[2]) { return utimes_common(fd, NULL, tv, 0); }
|
|
int utime(const char *path, const struct utimbuf *times) {
|
|
if (!times) return utimens_common(AT_FDCWD, path, NULL, 0);
|
|
struct timespec ts[2] = { { times->actime, 0 }, { times->modtime, 0 } };
|
|
return utimens_common(AT_FDCWD, path, ts, 0);
|
|
}
|
|
|
|
static long fp_truncate(const char *path, off_t length) {
|
|
struct res r;
|
|
long rs = resolve(AT_FDCWD, path, 1 | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_truncate, (long)r.host, length, 0, 0, 0);
|
|
return rs;
|
|
}
|
|
int truncate(const char *path, off_t length) {
|
|
REAL(truncate, int (*)(const char *, off_t));
|
|
ANSWER(fp_truncate(path, length), AT_FDCWD, path, real_truncate(path, length));
|
|
}
|
|
int truncate64(const char *path, off_t length) __attribute__((alias("truncate")));
|
|
|
|
static long fp_statfs(const char *path, struct statfs *buf) {
|
|
struct res r;
|
|
long rs = resolve(AT_FDCWD, path, 1, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 && under(r.guest, "/dev/shm", 8)) rs = FP_SLOW;
|
|
if (rs == 0) rs = sc(SYS_statfs, (long)r.host, (long)buf, 0, 0, 0);
|
|
return rs;
|
|
}
|
|
static int statfs_common(const char *path, struct statfs *buf, int (*real)(const char *, struct statfs *)) {
|
|
ANSWER(fp_statfs(path, buf), AT_FDCWD, path, real(path, buf));
|
|
}
|
|
int statfs(const char *path, struct statfs *buf) {
|
|
REAL(statfs, int (*)(const char *, struct statfs *));
|
|
return statfs_common(path, buf, real_statfs);
|
|
}
|
|
int statfs64(const char *path, struct statfs64 *buf) {
|
|
REAL(statfs64, int (*)(const char *, struct statfs *));
|
|
return statfs_common(path, (struct statfs *)buf, real_statfs64);
|
|
}
|
|
|
|
#define FP_ST_VALID 0x0020
|
|
|
|
|
|
static int statvfs_common(const char *path, struct statvfs *buf, int (*real)(const char *, struct statvfs *)) {
|
|
struct res r;
|
|
struct statfs fs;
|
|
long rs = resolve(AT_FDCWD, path, 1, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 && under(r.guest, "/dev/shm", 8)) rs = FP_SLOW;
|
|
if (rs == 0) rs = sc(SYS_statfs, (long)r.host, (long)&fs, 0, 0, 0);
|
|
if (rs == 0 && !(fs.f_flags & FP_ST_VALID)) rs = FP_SLOW;
|
|
if (rs == 0) {
|
|
memset(buf, 0, sizeof *buf);
|
|
buf->f_bsize = fs.f_bsize;
|
|
buf->f_frsize = fs.f_frsize ? fs.f_frsize : fs.f_bsize;
|
|
buf->f_blocks = fs.f_blocks;
|
|
buf->f_bfree = fs.f_bfree;
|
|
buf->f_bavail = fs.f_bavail;
|
|
buf->f_files = fs.f_files;
|
|
buf->f_ffree = fs.f_ffree;
|
|
buf->f_favail = fs.f_ffree;
|
|
_Static_assert(sizeof buf->f_fsid == sizeof fs.f_fsid, "fsid layout");
|
|
memcpy(&buf->f_fsid, &fs.f_fsid, sizeof fs.f_fsid);
|
|
buf->f_flag = fs.f_flags ^ FP_ST_VALID;
|
|
buf->f_namemax = fs.f_namelen;
|
|
}
|
|
ANSWER(rs, AT_FDCWD, path, real(path, buf));
|
|
}
|
|
int statvfs(const char *path, struct statvfs *buf) {
|
|
REAL(statvfs, int (*)(const char *, struct statvfs *));
|
|
return statvfs_common(path, buf, real_statvfs);
|
|
}
|
|
int statvfs64(const char *path, struct statvfs64 *buf) {
|
|
REAL(statvfs64, int (*)(const char *, struct statvfs *));
|
|
return statvfs_common(path, (struct statvfs *)buf, real_statvfs64);
|
|
}
|
|
|
|
static long fp_getxattr(const char *path, const char *name, void *value, size_t size, int follow) {
|
|
struct res r;
|
|
long rs = resolve(AT_FDCWD, path, follow | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(follow ? SYS_getxattr : SYS_lgetxattr, (long)r.host, (long)name, (long)value, size, 0);
|
|
return rs;
|
|
}
|
|
static ssize_t getxattr_common(const char *path, const char *name, void *value, size_t size, int follow,
|
|
ssize_t (*real)(const char *, const char *, void *, size_t)) {
|
|
long rs = fp_getxattr(path, name, value, size, follow);
|
|
if (answered(rs)) { hits++; return rs < 0 ? ret(rs) : rs; }
|
|
missed(__func__, AT_FDCWD, path);
|
|
return real(path, name, value, size);
|
|
}
|
|
ssize_t getxattr(const char *path, const char *name, void *value, size_t size) {
|
|
REAL(getxattr, ssize_t (*)(const char *, const char *, void *, size_t));
|
|
return getxattr_common(path, name, value, size, 1, real_getxattr);
|
|
}
|
|
ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size) {
|
|
REAL(lgetxattr, ssize_t (*)(const char *, const char *, void *, size_t));
|
|
return getxattr_common(path, name, value, size, 0, real_lgetxattr);
|
|
}
|
|
|
|
static long fp_listxattr(const char *path, char *list, size_t size, int follow) {
|
|
struct res r;
|
|
long rs = resolve(AT_FDCWD, path, follow | MAY_BE_UNSEEN, &r);
|
|
if (rs == 1) rs = -ENOENT;
|
|
if (rs == 0 || rs == UNSEEN) rs = sc(follow ? SYS_listxattr : SYS_llistxattr, (long)r.host, (long)list, size, 0, 0);
|
|
return rs;
|
|
}
|
|
static ssize_t listxattr_common(const char *path, char *list, size_t size, int follow,
|
|
ssize_t (*real)(const char *, char *, size_t)) {
|
|
long rs = fp_listxattr(path, list, size, follow);
|
|
if (answered(rs)) { hits++; return rs < 0 ? ret(rs) : rs; }
|
|
missed(__func__, AT_FDCWD, path);
|
|
return real(path, list, size);
|
|
}
|
|
ssize_t listxattr(const char *path, char *list, size_t size) {
|
|
REAL(listxattr, ssize_t (*)(const char *, char *, size_t));
|
|
return listxattr_common(path, list, size, 1, real_listxattr);
|
|
}
|
|
ssize_t llistxattr(const char *path, char *list, size_t size) {
|
|
REAL(llistxattr, ssize_t (*)(const char *, char *, size_t));
|
|
return listxattr_common(path, list, size, 0, real_llistxattr);
|
|
}
|
|
|
|
static long fp_inotify(int fd, const char *path, uint32_t mask) {
|
|
struct res r;
|
|
long rs = resolve(AT_FDCWD, path, (mask & IN_DONT_FOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
|
|
if (rs == 0 || rs == 1 || rs == UNSEEN) rs = sc(SYS_inotify_add_watch, fd, (long)r.host, mask, 0, 0);
|
|
return rs;
|
|
}
|
|
int inotify_add_watch(int fd, const char *path, uint32_t mask) {
|
|
REAL(inotify_add_watch, int (*)(int, const char *, uint32_t));
|
|
ANSWER(fp_inotify(fd, path, mask), AT_FDCWD, path, real_inotify_add_watch(fd, path, mask));
|
|
}
|
|
|
|
|
|
enum { LONG_SELF, LONG_PEER };
|
|
static struct { unsigned long ino; int kind; char name[sizeof ((struct sockaddr_un *)0)->sun_path]; char guest[sizeof ((struct sockaddr_un *)0)->sun_path]; } long_names[64];
|
|
static unsigned long_next;
|
|
static pthread_mutex_t long_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static const char *fd_name(const char *path) {
|
|
if (strncmp(path, "/proc/self/fd/", 14) != 0) return NULL;
|
|
const char *p = path + 14;
|
|
if (*p < '0' || *p > '9') return NULL;
|
|
while (*p >= '0' && *p <= '9') p++;
|
|
return *p == '/' && p[1] ? p + 1 : NULL;
|
|
}
|
|
|
|
static unsigned long socket_ino(int fd) {
|
|
struct stat st;
|
|
return sc(SYS_newfstatat, fd, (long)"", (long)&st, AT_EMPTY_PATH, 0) == 0 ? (unsigned long)st.st_ino : 0;
|
|
}
|
|
|
|
static void remember_long(int fd, int kind, const char *name, const char *guest) {
|
|
unsigned long ino = socket_ino(fd);
|
|
if (!ino || !name || strlen(name) >= sizeof long_names[0].name || strlen(guest) >= sizeof long_names[0].guest) return;
|
|
unsigned count = sizeof long_names / sizeof long_names[0], i = count;
|
|
pthread_mutex_lock(&long_lock);
|
|
for (unsigned n = 0; n < count && i == count; n++)
|
|
if (long_names[n].ino == ino && long_names[n].kind == kind) i = n;
|
|
if (i == count) i = long_next++ % count;
|
|
long_names[i].ino = ino;
|
|
long_names[i].kind = kind;
|
|
snprintf(long_names[i].name, sizeof long_names[i].name, "%s", name);
|
|
snprintf(long_names[i].guest, sizeof long_names[i].guest, "%s", guest);
|
|
pthread_mutex_unlock(&long_lock);
|
|
}
|
|
|
|
static int long_entry(int fd, int kind, const char *name, char *guest, size_t size) {
|
|
unsigned long ino = socket_ino(fd);
|
|
int found = -1;
|
|
if (!ino || !name) return -1;
|
|
pthread_mutex_lock(&long_lock);
|
|
for (unsigned n = 0; n < sizeof long_names / sizeof long_names[0] && found < 0; n++)
|
|
if (long_names[n].ino == ino && long_names[n].kind == kind && strcmp(long_names[n].name, name) == 0
|
|
&& strlen(long_names[n].guest) < size) {
|
|
memcpy(guest, long_names[n].guest, strlen(long_names[n].guest) + 1);
|
|
found = 0;
|
|
}
|
|
pthread_mutex_unlock(&long_lock);
|
|
return found;
|
|
}
|
|
|
|
static void remember_long_address(int fd, int kind, const struct sockaddr *addr, socklen_t len, const struct sockaddr_un *used) {
|
|
const struct sockaddr_un *un = (const struct sockaddr_un *)addr;
|
|
char guest[sizeof un->sun_path + 1];
|
|
size_t glen = strnlen(un->sun_path, len - offsetof(struct sockaddr_un, sun_path));
|
|
memcpy(guest, un->sun_path, glen);
|
|
guest[glen] = 0;
|
|
remember_long(fd, kind, fd_name(used->sun_path), guest);
|
|
}
|
|
|
|
static long socket_address(const struct sockaddr *addr, socklen_t len, int follow, struct sockaddr_un *out, socklen_t *out_len, long *dir) {
|
|
const struct sockaddr_un *un = (const struct sockaddr_un *)addr;
|
|
*dir = -1;
|
|
size_t room = sizeof un->sun_path;
|
|
if (!addr || len <= offsetof(struct sockaddr_un, sun_path) || addr->sa_family != AF_UNIX || un->sun_path[0] == 0)
|
|
return 0;
|
|
size_t plen = strnlen(un->sun_path, len - offsetof(struct sockaddr_un, sun_path));
|
|
char path[sizeof un->sun_path + 1];
|
|
if (plen > room) return FP_SLOW;
|
|
memcpy(path, un->sun_path, plen);
|
|
path[plen] = 0;
|
|
if (trailing_slash(path)) return FP_SLOW;
|
|
struct res r;
|
|
long rs = resolve(AT_FDCWD, path, follow, &r);
|
|
if (rs < 0) return rs;
|
|
if (rs == 1 && follow) return -ENOENT;
|
|
if (r.magic) return FP_SLOW;
|
|
size_t hlen = strlen(r.host);
|
|
memset(out, 0, sizeof *out);
|
|
out->sun_family = AF_UNIX;
|
|
if (hlen >= room) {
|
|
char *slash = strrchr(r.host, '/');
|
|
if (!slash || slash == r.host) return FP_SLOW;
|
|
*slash = 0;
|
|
*dir = sc(SYS_openat, AT_FDCWD, (long)r.host, O_PATH | O_DIRECTORY | O_CLOEXEC, 0, 0);
|
|
if (*dir < 0) return FP_SLOW;
|
|
hlen = snprintf(out->sun_path, room, "/proc/self/fd/%ld/%s", *dir, slash + 1);
|
|
if (hlen >= room) { sc(SYS_close, *dir, 0, 0, 0, 0); *dir = -1; return FP_SLOW; }
|
|
} else {
|
|
memcpy(out->sun_path, r.host, hlen + 1);
|
|
}
|
|
*out_len = offsetof(struct sockaddr_un, sun_path) + hlen + 1;
|
|
return 1;
|
|
}
|
|
|
|
static int nonblocking(int fd) {
|
|
long fl = sc(SYS_fcntl, fd, F_GETFL, 0, 0, 0);
|
|
return fl >= 0 && (fl & O_NONBLOCK);
|
|
}
|
|
|
|
int connect(int fd, const struct sockaddr *addr, socklen_t len) {
|
|
REAL(connect, int (*)(int, const struct sockaddr *, socklen_t));
|
|
struct sockaddr_un un;
|
|
socklen_t un_len;
|
|
long dir = -1;
|
|
long rs = fp_ready() ? socket_address(addr, len, 1, &un, &un_len, &dir) : FP_SLOW;
|
|
if (rs == 0 && !(addr && addr->sa_family == AF_UNIX) && !nonblocking(fd)) rs = FP_SLOW;
|
|
if (rs == 0) rs = sc(SYS_connect, fd, (long)addr, len, 0, 0);
|
|
else if (rs == 1) rs = sc(SYS_connect, fd, (long)&un, un_len, 0, 0);
|
|
if (dir >= 0) {
|
|
sc(SYS_close, dir, 0, 0, 0, 0);
|
|
if (rs == 0 || rs == -EINPROGRESS) remember_long_address(fd, LONG_PEER, addr, len, &un);
|
|
}
|
|
ANSWER(rs, AT_FDCWD, "(connect)", real_connect(fd, addr, len));
|
|
}
|
|
|
|
int bind(int fd, const struct sockaddr *addr, socklen_t len) {
|
|
REAL(bind, int (*)(int, const struct sockaddr *, socklen_t));
|
|
struct sockaddr_un un;
|
|
socklen_t un_len;
|
|
long dir = -1;
|
|
long rs = fp_ready() ? socket_address(addr, len, 0, &un, &un_len, &dir) : FP_SLOW;
|
|
if (rs == 0) rs = sc(SYS_bind, fd, (long)addr, len, 0, 0);
|
|
else if (rs == 1) rs = sc(SYS_bind, fd, (long)&un, un_len, 0, 0);
|
|
if (dir >= 0) {
|
|
sc(SYS_close, dir, 0, 0, 0, 0);
|
|
if (rs == 0) remember_long_address(fd, LONG_SELF, addr, len, &un);
|
|
}
|
|
ANSWER(rs, AT_FDCWD, "(bind)", real_bind(fd, addr, len));
|
|
}
|
|
|
|
static void report_address(int fd, int kind, const struct sockaddr_storage *got, socklen_t got_len, struct sockaddr *addr, socklen_t *len) {
|
|
struct sockaddr_storage shown = *got;
|
|
socklen_t shown_len = got_len;
|
|
const struct sockaddr_un *un = (const struct sockaddr_un *)got;
|
|
if (got_len > offsetof(struct sockaddr_un, sun_path) && un->sun_family == AF_UNIX && un->sun_path[0]) {
|
|
char g[PATH_MAX], h[sizeof un->sun_path + 1];
|
|
size_t hl = strnlen(un->sun_path, got_len - offsetof(struct sockaddr_un, sun_path));
|
|
memcpy(h, un->sun_path, hl);
|
|
h[hl] = 0;
|
|
struct sockaddr_un *out = (struct sockaddr_un *)&shown;
|
|
if ((long_entry(fd, kind, fd_name(h), g, sizeof g) == 0 || to_guest(h, g, sizeof g) == 0) && strlen(g) < sizeof out->sun_path) {
|
|
memset(out->sun_path, 0, sizeof out->sun_path);
|
|
strcpy(out->sun_path, g);
|
|
shown_len = offsetof(struct sockaddr_un, sun_path) + strlen(g) + 1;
|
|
}
|
|
}
|
|
memcpy(addr, &shown, *len < shown_len ? *len : shown_len);
|
|
*len = shown_len;
|
|
}
|
|
|
|
|
|
static void inherit_long_name(int listener, int accepted) {
|
|
unsigned long ino = socket_ino(listener);
|
|
char name[sizeof long_names[0].name], guest[sizeof long_names[0].guest];
|
|
int found = -1;
|
|
if (!ino) return;
|
|
pthread_mutex_lock(&long_lock);
|
|
for (unsigned n = 0; n < sizeof long_names / sizeof long_names[0] && found < 0; n++)
|
|
if (long_names[n].ino == ino && long_names[n].kind == LONG_SELF) {
|
|
snprintf(name, sizeof name, "%s", long_names[n].name);
|
|
snprintf(guest, sizeof guest, "%s", long_names[n].guest);
|
|
found = 0;
|
|
}
|
|
pthread_mutex_unlock(&long_lock);
|
|
if (found == 0) remember_long(accepted, LONG_SELF, name, guest);
|
|
}
|
|
|
|
static int pending(int fd) {
|
|
struct pollfd p = { .fd = fd, .events = POLLIN };
|
|
struct timespec now = { 0, 0 };
|
|
return sc(SYS_ppoll, (long)&p, 1, (long)&now, 0, 0) == 1 && (p.revents & POLLIN);
|
|
}
|
|
|
|
static int accept_common(int fd, struct sockaddr *addr, socklen_t *len, int flags) {
|
|
REAL(accept4, int (*)(int, struct sockaddr *, socklen_t *, int));
|
|
if (!fp_ready() || (addr && !len) || (!nonblocking(fd) && !pending(fd))) { missed(__func__, AT_FDCWD, "(accept)"); return real_accept4(fd, addr, len, flags); }
|
|
hits++;
|
|
struct sockaddr_storage got;
|
|
socklen_t got_len = sizeof got;
|
|
long r = sc(SYS_accept4, fd, addr ? (long)&got : 0, addr ? (long)&got_len : 0, flags, 0);
|
|
if (refused()) return real_accept4(fd, addr, len, flags);
|
|
if (r >= 0) inherit_long_name(fd, (int)r);
|
|
if (!addr) return ret(r);
|
|
if (r >= 0) report_address((int)r, LONG_PEER, &got, got_len, addr, len);
|
|
return ret(r);
|
|
}
|
|
int accept4(int fd, struct sockaddr *addr, socklen_t *len, int flags) { return accept_common(fd, addr, len, flags); }
|
|
int accept(int fd, struct sockaddr *addr, socklen_t *len) { return accept_common(fd, addr, len, 0); }
|
|
|
|
static int name_common(long nr, int fd, struct sockaddr *addr, socklen_t *len, int (*real)(int, struct sockaddr *, socklen_t *)) {
|
|
if (!fp_ready() || !addr || !len) { missed(__func__, AT_FDCWD, "(name)"); return real(fd, addr, len); }
|
|
hits++;
|
|
struct sockaddr_storage got;
|
|
socklen_t got_len = sizeof got;
|
|
long r = sc(nr, fd, (long)&got, (long)&got_len, 0, 0);
|
|
if (refused()) return real(fd, addr, len);
|
|
if (r == 0) report_address(fd, nr == SYS_getsockname ? LONG_SELF : LONG_PEER, &got, got_len, addr, len);
|
|
return ret(r);
|
|
}
|
|
int getsockname(int fd, struct sockaddr *addr, socklen_t *len) {
|
|
REAL(getsockname, int (*)(int, struct sockaddr *, socklen_t *));
|
|
return name_common(SYS_getsockname, fd, addr, len, real_getsockname);
|
|
}
|
|
int getpeername(int fd, struct sockaddr *addr, socklen_t *len) {
|
|
REAL(getpeername, int (*)(int, struct sockaddr *, socklen_t *));
|
|
return name_common(SYS_getpeername, fd, addr, len, real_getpeername);
|
|
}
|
|
|
|
|
|
static int shm_path(const char *name, char *path) {
|
|
while (*name == '/') name++;
|
|
size_t n = strlen(name);
|
|
if (n == 0 || n > NAME_MAX || strchr(name, '/')) return -1;
|
|
memcpy(path, "/dev/shm/", 9);
|
|
memcpy(path + 9, name, n + 1);
|
|
return 0;
|
|
}
|
|
|
|
int shm_open(const char *name, int flags, mode_t mode) {
|
|
REAL(shm_open, int (*)(const char *, int, mode_t));
|
|
char path[sizeof "/dev/shm/" + NAME_MAX];
|
|
long rs = shm_path(name, path) == 0 ? fp_open(AT_FDCWD, path, flags | O_NOFOLLOW | O_CLOEXEC, mode) : FP_SLOW;
|
|
if (rs == -EISDIR) rs = -EINVAL;
|
|
ANSWER(rs, AT_FDCWD, name, real_shm_open(name, flags, mode));
|
|
}
|
|
|
|
int shm_unlink(const char *name) {
|
|
REAL(shm_unlink, int (*)(const char *));
|
|
char path[sizeof "/dev/shm/" + NAME_MAX];
|
|
long rs = shm_path(name, path) == 0 ? fp_unlink(AT_FDCWD, path, 0) : FP_SLOW;
|
|
if (rs == -EPERM) rs = -EACCES;
|
|
ANSWER(rs, AT_FDCWD, name, real_shm_unlink(name));
|
|
}
|
|
|
|
DIR *opendir(const char *path) {
|
|
REAL(opendir, DIR *(*)(const char *));
|
|
long fd = fp_open(AT_FDCWD, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NONBLOCK, 0);
|
|
if (fd == FP_SLOW) { missed(__func__, AT_FDCWD, path); return real_opendir(path); }
|
|
hits++;
|
|
if (fd < 0) { errno = (int)-fd; return NULL; }
|
|
DIR *d = fdopendir((int)fd);
|
|
if (!d) close((int)fd);
|
|
return d;
|
|
}
|
|
|
|
static FILE *fopen_common(const char *path, const char *m, FILE *(*real)(const char *, const char *)) {
|
|
int flags;
|
|
switch (m[0]) {
|
|
case 'r': flags = strchr(m, '+') ? O_RDWR : O_RDONLY; break;
|
|
case 'w': flags = (strchr(m, '+') ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC; break;
|
|
case 'a': flags = (strchr(m, '+') ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND; break;
|
|
default: return real(path, m);
|
|
}
|
|
if (strchr(m, 'e')) flags |= O_CLOEXEC;
|
|
if (strchr(m, 'x')) flags |= O_EXCL;
|
|
long fd = fp_open(AT_FDCWD, path, flags, 0666);
|
|
if (fd == FP_SLOW) { missed(__func__, AT_FDCWD, path); return real(path, m); }
|
|
hits++;
|
|
if (fd < 0) { errno = (int)-fd; return NULL; }
|
|
FILE *f = fdopen((int)fd, m);
|
|
if (!f) close((int)fd);
|
|
return f;
|
|
}
|
|
FILE *fopen(const char *path, const char *m) { REAL(fopen, FILE *(*)(const char *, const char *)); return fopen_common(path, m, real_fopen); }
|
|
FILE *fopen64(const char *path, const char *m) { REAL(fopen64, FILE *(*)(const char *, const char *)); return fopen_common(path, m, real_fopen64); }
|
|
|
|
static int online_cpus(void) {
|
|
long fd = fp_open(AT_FDCWD, "/sys/devices/system/cpu/online", O_RDONLY | O_CLOEXEC, 0);
|
|
if (fd < 0) return -1;
|
|
char buf[256];
|
|
long n = sc(SYS_read, fd, (long)buf, sizeof buf - 1, 0, 0);
|
|
sc(SYS_close, fd, 0, 0, 0, 0);
|
|
if (n <= 0) return -1;
|
|
buf[n] = 0;
|
|
int count = 0;
|
|
for (char *p = buf; *p && *p != '\n';) {
|
|
char *end;
|
|
long lo = strtol(p, &end, 10), hi = lo;
|
|
if (end == p) return -1;
|
|
if (*end == '-') { p = end + 1; hi = strtol(p, &end, 10); if (end == p || hi < lo) return -1; }
|
|
count += (int)(hi - lo + 1);
|
|
p = *end == ',' ? end + 1 : end;
|
|
}
|
|
return count > 0 ? count : -1;
|
|
}
|
|
int get_nprocs(void) {
|
|
REAL(get_nprocs, int (*)(void));
|
|
int n = fp_ready() ? online_cpus() : -1;
|
|
if (n > 0 && !refused()) { hits++; return n; }
|
|
missed(__func__, AT_FDCWD, "(nprocs)");
|
|
return real_get_nprocs();
|
|
}
|
|
long sysconf(int name) {
|
|
REAL(sysconf, long (*)(int));
|
|
if (name == _SC_NPROCESSORS_ONLN) return get_nprocs();
|
|
return real_sysconf(name);
|
|
}
|
|
|
|
|
|
static struct utsname uts;
|
|
static int uts_known;
|
|
static long own_uname(struct utsname *u) {
|
|
if (!fp_ready()) return FP_SLOW;
|
|
if (!__atomic_load_n(&uts_known, __ATOMIC_ACQUIRE)) {
|
|
struct utsname got;
|
|
if (syscall(SYS_uname, &got) != 0) return FP_SLOW;
|
|
static pthread_mutex_t uts_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
if (pthread_mutex_trylock(&uts_lock) == 0) {
|
|
if (!uts_known) {
|
|
uts = got;
|
|
__atomic_store_n(&uts_known, 1, __ATOMIC_RELEASE);
|
|
}
|
|
pthread_mutex_unlock(&uts_lock);
|
|
}
|
|
*u = got;
|
|
return 0;
|
|
}
|
|
*u = uts;
|
|
return 0;
|
|
}
|
|
|
|
int uname(struct utsname *u) {
|
|
REAL(uname, int (*)(struct utsname *));
|
|
if (!u) return real_uname(u);
|
|
ANSWER(own_uname(u), AT_FDCWD, "(uname)", real_uname(u));
|
|
}
|
|
|
|
int gethostname(char *name, size_t len) {
|
|
REAL(gethostname, int (*)(char *, size_t));
|
|
struct utsname u;
|
|
if (own_uname(&u) == FP_SLOW) { missed(__func__, AT_FDCWD, "(gethostname)"); return real_gethostname(name, len); }
|
|
hits++;
|
|
size_t n = strlen(u.nodename) + 1;
|
|
memcpy(name, u.nodename, len < n ? len : n);
|
|
if (n > len) { errno = ENAMETOOLONG; return -1; }
|
|
return 0;
|
|
}
|
|
|
|
int isatty(int fd) {
|
|
REAL(isatty, int (*)(int));
|
|
struct termios k;
|
|
if (!fp_ready()) return real_isatty(fd);
|
|
hits++;
|
|
long r = sc(SYS_ioctl, fd, TCGETS, (long)&k, 0, 0);
|
|
if (refused()) return real_isatty(fd);
|
|
if (r < 0) { errno = (int)-r; return 0; }
|
|
return r == 0;
|
|
}
|
|
|
|
int memfd_create(const char *name, unsigned flags) {
|
|
REAL(memfd_create, int (*)(const char *, unsigned));
|
|
if (!name || !strncmp(name, "JITCode:", 8) || !strcmp(name, "opcache_lock") || !strncmp(name, "lib/apk/exec/", 13)
|
|
|| !fp_ready()) return real_memfd_create(name, flags);
|
|
long r = sc(SYS_memfd_create, (long)name, flags, 0, 0, 0);
|
|
if (r == -ENOSYS) return real_memfd_create(name, flags);
|
|
hits++;
|
|
return ret(r);
|
|
}
|
|
|
|
|
|
static long read_small(const char *path, char *buf, size_t size) {
|
|
long fd = fp_open(AT_FDCWD, path, O_RDONLY | O_CLOEXEC, 0);
|
|
if (fd < 0) return -1;
|
|
long have = 0, r = 0;
|
|
while ((size_t)have < size && (r = sc(SYS_read, fd, (long)buf + have, size - have, 0, 0)) > 0) have += r;
|
|
sc(SYS_close, fd, 0, 0, 0, 0);
|
|
return (size_t)have < size && r == 0 ? have : -1;
|
|
}
|
|
|
|
static int passwd_is_files(char *conf, size_t size) {
|
|
long n = read_small("/etc/nsswitch.conf", conf, size - 1);
|
|
if (n < 0) return 0;
|
|
conf[n] = 0;
|
|
int found = 0;
|
|
for (char *line = conf; line;) {
|
|
char *end = strchr(line, '\n');
|
|
if (end) *end = 0;
|
|
if (strncmp(line, "passwd", 6) == 0 && strchr(" \t:", line[6])) {
|
|
char *p = line + 6;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
if (*p++ != ':' || found++ || strchr(p, '#')) return 0;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
if (strncmp(p, "files", 5) != 0 || (p[5] && p[5] != ' ' && p[5] != '\t')) return 0;
|
|
for (p += 5; *p == ' ' || *p == '\t'; p++) {}
|
|
if (*p == '[') return 0;
|
|
} else if (strstr(line, "passwd") && line[0] != '#') {
|
|
return 0;
|
|
}
|
|
line = end ? end + 1 : NULL;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static int passwd_line(char *file, const char *name, uid_t uid, char **f, size_t *longest) {
|
|
*longest = 0;
|
|
for (char *line = file; line && *line;) {
|
|
char *end = strchr(line, '\n');
|
|
if (end) *end = 0;
|
|
size_t len = strlen(line);
|
|
if (len > *longest) *longest = len;
|
|
if (line[0] && line[0] != '#') {
|
|
int k = 0;
|
|
f[k++] = line;
|
|
for (char *p = line; *p && k < 8; p++)
|
|
if (*p == ':') { *p = 0; if (k < 7) f[k] = p + 1; k++; }
|
|
if (k != 7 || !f[0][0] || strchr("+- \t", f[0][0])) return 0;
|
|
for (int i = 2; i < 4; i++) {
|
|
size_t d = strspn(f[i], "0123456789");
|
|
if (d == 0 || d > 10 || f[i][d] || strtoul(f[i], NULL, 10) > 0xffffffffUL) return 0;
|
|
}
|
|
if (name ? strcmp(f[0], name) == 0 : (uid_t)strtoul(f[2], NULL, 10) == uid) return 1;
|
|
}
|
|
line = end ? end + 1 : NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int own_passwd(const char *name, uid_t uid, struct passwd *pw, char *buf, size_t buflen) {
|
|
char file[16384], *f[7];
|
|
size_t longest;
|
|
if (!fp_ready() || !passwd_is_files(file, sizeof file)) return 0;
|
|
long n = read_small("/etc/passwd", file, sizeof file - 1);
|
|
if (n < 0) return 0;
|
|
file[n] = 0;
|
|
if (passwd_line(file, name, uid, f, &longest) != 1 || longest + 3 > buflen) return 0;
|
|
size_t off = 0;
|
|
char *s[7];
|
|
for (int i = 0; i < 7; i++) {
|
|
size_t len = strlen(f[i]) + 1;
|
|
s[i] = buf + off;
|
|
memcpy(s[i], f[i], len);
|
|
off += len;
|
|
}
|
|
pw->pw_name = s[0];
|
|
pw->pw_passwd = s[1];
|
|
pw->pw_uid = (uid_t)strtoul(s[2], NULL, 10);
|
|
pw->pw_gid = (gid_t)strtoul(s[3], NULL, 10);
|
|
pw->pw_gecos = s[4];
|
|
pw->pw_dir = s[5];
|
|
pw->pw_shell = s[6];
|
|
return 1;
|
|
}
|
|
|
|
static int passwd_r(const char *name, uid_t uid, struct passwd *pw, char *buf, size_t buflen, struct passwd **result,
|
|
int (*real_name)(const char *, struct passwd *, char *, size_t, struct passwd **),
|
|
int (*real_uid)(uid_t, struct passwd *, char *, size_t, struct passwd **)) {
|
|
if (!own_passwd(name, uid, pw, buf, buflen)) {
|
|
missed(name ? "getpwnam" : "getpwuid", AT_FDCWD, "(passwd)");
|
|
return name ? real_name(name, pw, buf, buflen, result) : real_uid(uid, pw, buf, buflen, result);
|
|
}
|
|
hits++;
|
|
*result = pw;
|
|
errno = 0;
|
|
return 0;
|
|
}
|
|
|
|
int getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t buflen, struct passwd **result) {
|
|
REAL(getpwnam_r, int (*)(const char *, struct passwd *, char *, size_t, struct passwd **));
|
|
return passwd_r(name, 0, pw, buf, buflen, result, real_getpwnam_r, NULL);
|
|
}
|
|
|
|
int getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t buflen, struct passwd **result) {
|
|
REAL(getpwuid_r, int (*)(uid_t, struct passwd *, char *, size_t, struct passwd **));
|
|
return passwd_r(NULL, uid, pw, buf, buflen, result, NULL, real_getpwuid_r);
|
|
}
|
|
|
|
static struct passwd static_pw;
|
|
static char static_pw_buf[4096];
|
|
static pthread_mutex_t static_pw_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static struct passwd *passwd_static(const char *name, uid_t uid) {
|
|
pthread_mutex_lock(&static_pw_lock);
|
|
int own = own_passwd(name, uid, &static_pw, static_pw_buf, sizeof static_pw_buf);
|
|
pthread_mutex_unlock(&static_pw_lock);
|
|
if (!own) return NULL;
|
|
hits++;
|
|
errno = 0;
|
|
return &static_pw;
|
|
}
|
|
|
|
struct passwd *getpwnam(const char *name) {
|
|
REAL(getpwnam, struct passwd *(*)(const char *));
|
|
struct passwd *pw = passwd_static(name, 0);
|
|
if (pw) return pw;
|
|
missed(__func__, AT_FDCWD, "(passwd)");
|
|
return real_getpwnam(name);
|
|
}
|
|
|
|
struct passwd *getpwuid(uid_t uid) {
|
|
REAL(getpwuid, struct passwd *(*)(uid_t));
|
|
struct passwd *pw = passwd_static(NULL, uid);
|
|
if (pw) return pw;
|
|
missed(__func__, AT_FDCWD, "(passwd)");
|
|
return real_getpwuid(uid);
|
|
}
|
|
|
|
#include "svc-patch.h"
|