mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
318 lines
13 KiB
C
318 lines
13 KiB
C
/*
|
|
* Opt-in observations for file allocation and writes to the selected Steam library.
|
|
*
|
|
* Steam's allocation failure and any later fallback are left entirely to the caller. These
|
|
* wrappers only forward the same arguments, record the result and restore errno after logging.
|
|
* The session enables them only when storage diagnostics are turned on. Counters are local to
|
|
* each process, allocation details are capped, writes are sampled, and storage.log has a global
|
|
* size limit because every Steam child shares it.
|
|
*
|
|
* LD_PRELOAD cannot see direct syscalls or libc-internal calls that bypass public symbols. A
|
|
* missing entry therefore does not prove the operation did not happen.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
static int (*next_fstat)(int, struct stat *);
|
|
static int (*next_fallocate)(int, int, off_t, off_t);
|
|
static int (*next_posix_fallocate)(int, off_t, off_t);
|
|
static ssize_t (*next_pwrite)(int, const void *, size_t, off_t);
|
|
static int (*next_ftruncate)(int, off_t);
|
|
static int (*next_fsync)(int);
|
|
|
|
struct counters {
|
|
volatile uint64_t alloc_calls, alloc_failures, alloc_elapsed_ns;
|
|
volatile uint64_t pwrite_samples, pwrite_bytes, pwrite_failures, pwrite_elapsed_ns, pwrite_max_ns;
|
|
volatile uint64_t ftruncate_calls, ftruncate_failures, ftruncate_elapsed_ns, ftruncate_max_ns;
|
|
volatile uint64_t fstat_calls, fstat_elapsed_ns, fstat_max_ns;
|
|
volatile uint64_t fsync_calls, fsync_failures, fsync_elapsed_ns, fsync_max_ns;
|
|
};
|
|
|
|
static struct counters stats;
|
|
static volatile uint64_t pwrite_seen;
|
|
static volatile unsigned int alloc_details;
|
|
static long long selected_device;
|
|
static int init_state;
|
|
static int enabled;
|
|
|
|
static uint64_t add(volatile uint64_t *slot, uint64_t value) {
|
|
return __atomic_add_fetch(slot, value, __ATOMIC_RELAXED);
|
|
}
|
|
|
|
static uint64_t get(const volatile uint64_t *slot) {
|
|
return __atomic_load_n(slot, __ATOMIC_RELAXED);
|
|
}
|
|
|
|
static void update_max(volatile uint64_t *slot, uint64_t value) {
|
|
uint64_t old = get(slot);
|
|
while (old < value && !__atomic_compare_exchange_n(slot, &old, value, 0,
|
|
__ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
|
|
}
|
|
}
|
|
|
|
static void initialize(void) {
|
|
int state = __atomic_load_n(&init_state, __ATOMIC_ACQUIRE);
|
|
if (state == 2) return;
|
|
int expected = 0;
|
|
if (!__atomic_compare_exchange_n(&init_state, &expected, 1, 0,
|
|
__ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
|
while (__atomic_load_n(&init_state, __ATOMIC_ACQUIRE) != 2) { }
|
|
return;
|
|
}
|
|
const char *on = getenv("BL_STORAGE_DIAGNOSTICS");
|
|
const char *device = getenv("BL_STORAGE_DEVICE");
|
|
if (on != NULL && strcmp(on, "1") == 0 && device != NULL && *device != '\0') {
|
|
char *end = NULL;
|
|
errno = 0;
|
|
long long parsed = strtoll(device, &end, 10);
|
|
if (errno == 0 && end != device && *end == '\0') {
|
|
selected_device = parsed;
|
|
enabled = 1;
|
|
}
|
|
}
|
|
__atomic_store_n(&init_state, 2, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
static uint64_t now_ns(void) {
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;
|
|
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
|
|
}
|
|
|
|
static int on_selected_device(int fd, const struct stat *known) {
|
|
struct stat local;
|
|
initialize();
|
|
if (!enabled) return 0;
|
|
if (known == NULL) {
|
|
if (next_fstat == NULL) next_fstat = dlsym(RTLD_NEXT, "fstat");
|
|
if (next_fstat == NULL || next_fstat(fd, &local) != 0) return 0;
|
|
known = &local;
|
|
}
|
|
return (long long)known->st_dev == selected_device;
|
|
}
|
|
|
|
static void comm_name(char *out, size_t size) {
|
|
const char *name = program_invocation_short_name;
|
|
size_t i = 0;
|
|
if (name == NULL || *name == '\0') name = "unknown";
|
|
while (name[i] && i + 1 < size && i < 40) {
|
|
unsigned char c = (unsigned char)name[i];
|
|
out[i] = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
(c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.') ? (char)c : '_';
|
|
i++;
|
|
}
|
|
out[i] = '\0';
|
|
}
|
|
|
|
static void append_line(const char *line, size_t length) {
|
|
const char *path = getenv("BL_STORAGE_LOG");
|
|
struct stat st;
|
|
int saved = errno;
|
|
if (path == NULL || *path == '\0' || length == 0 || length > 2048) goto done;
|
|
int fd = (int)syscall(SYS_openat, (long)AT_FDCWD, (long)path,
|
|
(long)(O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC), 0600L, 0L, 0L);
|
|
if (fd < 0) goto done;
|
|
if (syscall(SYS_fstat, (long)fd, (long)&st, 0L, 0L, 0L, 0L) != 0 || st.st_size < 0 ||
|
|
(uint64_t)st.st_size + length > 524288ULL) {
|
|
syscall(SYS_close, (long)fd, 0L, 0L, 0L, 0L, 0L);
|
|
goto done;
|
|
}
|
|
/* One small append write per complete line; no per-write log flood. */
|
|
(void)syscall(SYS_write, (long)fd, (long)line, (long)length, 0L, 0L, 0L);
|
|
syscall(SYS_close, (long)fd, 0L, 0L, 0L, 0L, 0L);
|
|
done:
|
|
errno = saved;
|
|
}
|
|
|
|
static void allocation_line(const char *api, int result, int error, int flags,
|
|
off_t offset, off_t length, uint64_t elapsed) {
|
|
char line[512], comm[48];
|
|
int api_error = api[0] == 'f' ? (result < 0 ? error : 0) : result;
|
|
unsigned int detail = __atomic_add_fetch(&alloc_details, 1, __ATOMIC_RELAXED);
|
|
add(&stats.alloc_calls, 1);
|
|
add(&stats.alloc_elapsed_ns, elapsed);
|
|
if ((api[0] == 'f' && result < 0) || (api[0] == 'p' && result != 0))
|
|
add(&stats.alloc_failures, 1);
|
|
if (detail > 64) return;
|
|
comm_name(comm, sizeof(comm));
|
|
int n = snprintf(line, sizeof(line),
|
|
"allocation pid=%ld comm=%s api=%s result=%d api_error=%d errno_after=%d flags=0x%x offset=%lld length=%lld elapsed_us=%llu\n",
|
|
(long)getpid(), comm, api, result, api_error, error, flags,
|
|
(long long)offset, (long long)length,
|
|
(unsigned long long)(elapsed / 1000ULL));
|
|
if (n > 0 && (size_t)n < sizeof(line)) append_line(line, (size_t)n);
|
|
}
|
|
|
|
int fallocate(int fd, int mode, off_t offset, off_t length) {
|
|
int entry_errno = errno;
|
|
if (next_fallocate == NULL) next_fallocate = dlsym(RTLD_NEXT, "fallocate");
|
|
if (next_fallocate == NULL) { errno = ENOSYS; return -1; }
|
|
int selected = on_selected_device(fd, NULL);
|
|
errno = entry_errno;
|
|
if (!selected) return next_fallocate(fd, mode, offset, length);
|
|
uint64_t start = now_ns();
|
|
errno = entry_errno;
|
|
int result = next_fallocate(fd, mode, offset, length);
|
|
int call_errno = errno;
|
|
uint64_t elapsed = now_ns() - start;
|
|
allocation_line("fallocate", result, call_errno, mode, offset, length, elapsed);
|
|
errno = call_errno;
|
|
return result;
|
|
}
|
|
|
|
int posix_fallocate(int fd, off_t offset, off_t length) {
|
|
int entry_errno = errno;
|
|
if (next_posix_fallocate == NULL) next_posix_fallocate = dlsym(RTLD_NEXT, "posix_fallocate");
|
|
if (next_posix_fallocate == NULL) { errno = entry_errno; return ENOSYS; }
|
|
int selected = on_selected_device(fd, NULL);
|
|
errno = entry_errno;
|
|
if (!selected) {
|
|
int result = next_posix_fallocate(fd, offset, length);
|
|
errno = entry_errno;
|
|
return result;
|
|
}
|
|
uint64_t start = now_ns();
|
|
errno = entry_errno;
|
|
int result = next_posix_fallocate(fd, offset, length);
|
|
int call_errno = errno;
|
|
uint64_t elapsed = now_ns() - start;
|
|
allocation_line("posix_fallocate", result, call_errno, 0, offset, length, elapsed);
|
|
errno = entry_errno; /* POSIX requires the error to be returned, leaving errno unchanged. */
|
|
return result;
|
|
}
|
|
|
|
ssize_t pwrite(int fd, const void *buffer, size_t count, off_t offset) {
|
|
int entry_errno = errno;
|
|
if (next_pwrite == NULL) next_pwrite = dlsym(RTLD_NEXT, "pwrite");
|
|
if (next_pwrite == NULL) { errno = ENOSYS; return -1; }
|
|
initialize();
|
|
uint64_t sequence = enabled ? add(&pwrite_seen, 1) : 1;
|
|
int sample = enabled && (sequence % 32ULL) == 0;
|
|
int selected = sample && on_selected_device(fd, NULL);
|
|
errno = entry_errno;
|
|
if (!selected)
|
|
return next_pwrite(fd, buffer, count, offset);
|
|
uint64_t start = now_ns();
|
|
errno = entry_errno;
|
|
ssize_t result = next_pwrite(fd, buffer, count, offset);
|
|
int call_errno = errno;
|
|
uint64_t elapsed = now_ns() - start;
|
|
add(&stats.pwrite_samples, 1);
|
|
add(&stats.pwrite_bytes, result > 0 ? (uint64_t)result : 0);
|
|
add(&stats.pwrite_elapsed_ns, elapsed);
|
|
if (result < 0) add(&stats.pwrite_failures, 1);
|
|
update_max(&stats.pwrite_max_ns, elapsed);
|
|
errno = call_errno;
|
|
return result;
|
|
}
|
|
|
|
int ftruncate(int fd, off_t length) {
|
|
int entry_errno = errno;
|
|
if (next_ftruncate == NULL) next_ftruncate = dlsym(RTLD_NEXT, "ftruncate");
|
|
if (next_ftruncate == NULL) { errno = ENOSYS; return -1; }
|
|
int selected = on_selected_device(fd, NULL);
|
|
errno = entry_errno;
|
|
if (!selected) return next_ftruncate(fd, length);
|
|
uint64_t start = now_ns();
|
|
errno = entry_errno;
|
|
int result = next_ftruncate(fd, length);
|
|
int call_errno = errno;
|
|
uint64_t elapsed = now_ns() - start;
|
|
add(&stats.ftruncate_calls, 1);
|
|
if (result != 0) add(&stats.ftruncate_failures, 1);
|
|
add(&stats.ftruncate_elapsed_ns, elapsed);
|
|
update_max(&stats.ftruncate_max_ns, elapsed);
|
|
errno = call_errno;
|
|
return result;
|
|
}
|
|
|
|
int fstat(int fd, struct stat *out) {
|
|
int entry_errno = errno;
|
|
if (next_fstat == NULL) next_fstat = dlsym(RTLD_NEXT, "fstat");
|
|
if (next_fstat == NULL) { errno = ENOSYS; return -1; }
|
|
initialize();
|
|
errno = entry_errno;
|
|
if (!enabled) return next_fstat(fd, out);
|
|
uint64_t start = now_ns();
|
|
errno = entry_errno;
|
|
int result = next_fstat(fd, out);
|
|
int call_errno = errno;
|
|
uint64_t elapsed = now_ns() - start;
|
|
if (result == 0 && on_selected_device(fd, out)) {
|
|
add(&stats.fstat_calls, 1);
|
|
add(&stats.fstat_elapsed_ns, elapsed);
|
|
update_max(&stats.fstat_max_ns, elapsed);
|
|
}
|
|
errno = call_errno;
|
|
return result;
|
|
}
|
|
|
|
int fsync(int fd) {
|
|
int entry_errno = errno;
|
|
if (next_fsync == NULL) next_fsync = dlsym(RTLD_NEXT, "fsync");
|
|
if (next_fsync == NULL) { errno = ENOSYS; return -1; }
|
|
int selected = on_selected_device(fd, NULL);
|
|
errno = entry_errno;
|
|
if (!selected) return next_fsync(fd);
|
|
uint64_t start = now_ns();
|
|
errno = entry_errno;
|
|
int result = next_fsync(fd);
|
|
int call_errno = errno;
|
|
uint64_t elapsed = now_ns() - start;
|
|
add(&stats.fsync_calls, 1);
|
|
if (result != 0) add(&stats.fsync_failures, 1);
|
|
add(&stats.fsync_elapsed_ns, elapsed);
|
|
update_max(&stats.fsync_max_ns, elapsed);
|
|
errno = call_errno;
|
|
return result;
|
|
}
|
|
|
|
static void flush_summary(void) __attribute__((destructor));
|
|
static void flush_summary(void) {
|
|
char line[1024], comm[48];
|
|
initialize();
|
|
if (!enabled) return;
|
|
uint64_t alloc = get(&stats.alloc_calls), pwrite_samples = get(&stats.pwrite_samples);
|
|
uint64_t truncates = get(&stats.ftruncate_calls), fstats = get(&stats.fstat_calls), syncs = get(&stats.fsync_calls);
|
|
if (alloc == 0 && pwrite_samples == 0 && truncates == 0 && fstats == 0 && syncs == 0) return;
|
|
comm_name(comm, sizeof(comm));
|
|
int n = snprintf(line, sizeof(line),
|
|
"summary pid=%ld comm=%s allocation_calls=%llu allocation_failures=%llu allocation_total_us=%llu pwrite_sample_every=32 pwrite_samples=%llu pwrite_bytes=%llu pwrite_failures=%llu pwrite_sample_total_us=%llu pwrite_sample_max_us=%llu ftruncate_calls=%llu ftruncate_failures=%llu ftruncate_total_us=%llu ftruncate_max_us=%llu fstat_calls=%llu fstat_total_us=%llu fstat_max_us=%llu fsync_calls=%llu fsync_failures=%llu fsync_total_us=%llu fsync_max_us=%llu\n",
|
|
(long)getpid(), comm,
|
|
(unsigned long long)alloc, (unsigned long long)get(&stats.alloc_failures),
|
|
(unsigned long long)(get(&stats.alloc_elapsed_ns) / 1000ULL),
|
|
(unsigned long long)pwrite_samples, (unsigned long long)get(&stats.pwrite_bytes),
|
|
(unsigned long long)get(&stats.pwrite_failures),
|
|
(unsigned long long)(get(&stats.pwrite_elapsed_ns) / 1000ULL),
|
|
(unsigned long long)(get(&stats.pwrite_max_ns) / 1000ULL),
|
|
(unsigned long long)truncates, (unsigned long long)get(&stats.ftruncate_failures),
|
|
(unsigned long long)(get(&stats.ftruncate_elapsed_ns) / 1000ULL),
|
|
(unsigned long long)(get(&stats.ftruncate_max_ns) / 1000ULL),
|
|
(unsigned long long)fstats,
|
|
(unsigned long long)(get(&stats.fstat_elapsed_ns) / 1000ULL),
|
|
(unsigned long long)(get(&stats.fstat_max_ns) / 1000ULL),
|
|
(unsigned long long)syncs, (unsigned long long)get(&stats.fsync_failures),
|
|
(unsigned long long)(get(&stats.fsync_elapsed_ns) / 1000ULL),
|
|
(unsigned long long)(get(&stats.fsync_max_ns) / 1000ULL));
|
|
if (n > 0 && (size_t)n < sizeof(line)) append_line(line, (size_t)n);
|
|
}
|
|
|
|
#if defined(__USE_LARGEFILE64)
|
|
int fallocate64(int fd, int mode, off64_t offset, off64_t length) __attribute__((alias("fallocate")));
|
|
int posix_fallocate64(int fd, off64_t offset, off64_t length) __attribute__((alias("posix_fallocate")));
|
|
ssize_t pwrite64(int fd, const void *buffer, size_t count, off64_t offset) __attribute__((alias("pwrite")));
|
|
int ftruncate64(int fd, off64_t length) __attribute__((alias("ftruncate")));
|
|
int fstat64(int fd, struct stat64 *out) __attribute__((alias("fstat")));
|
|
#endif
|