git mirror - github.com/owenewans/holy - branch master
clone: https://src.holypkg.eu/holy/

file tests/update-fault.c

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef HOLY_STATIC_FAULT
extern int __real_fsync(int);
extern int __real_renameat(int, const char *, int, const char *);
extern ssize_t __real_write(int, const void *, size_t);
extern int __real_linkat(int, const char *, int, const char *, int);
extern int __real_unlinkat(int, const char *, int);
#define next_fsync __real_fsync
#define next_renameat __real_renameat
#define next_write __real_write
#define next_linkat __real_linkat
#define next_unlinkat __real_unlinkat
#define fsync __wrap_fsync
#define renameat __wrap_renameat
#define write __wrap_write
#define linkat __wrap_linkat
#define unlinkat __wrap_unlinkat
#else
#include <dlfcn.h>
static int (*next_fsync)(int);
static int (*next_renameat)(int, const char *, int, const char *);
static ssize_t (*next_write)(int, const void *, size_t);
static int (*next_linkat)(int, const char *, int, const char *, int);
static int (*next_unlinkat)(int, const char *, int);
#endif

static int fault(const char *name)
{
    const char *mode = getenv("HOLY_UPDATE_FAULT");
    return mode && !strcmp(mode, name);
}

static void stop(void)
{
    kill(getpid(), SIGKILL);
    _exit(99);
}

static int fd_path(int fd, char path[4096])
{
    char proc[64];
    ssize_t length;
    snprintf(proc, sizeof proc, "/proc/self/fd/%d", fd);
    length = readlink(proc, path, 4095);
    if (length < 0) return 0;
    path[length] = 0;
    return 1;
}

int linkat(int from, const char *old, int to, const char *name, int flags)
{
    int result;
#ifndef HOLY_STATIC_FAULT
    if (!next_linkat) *(void **)(&next_linkat) = dlsym(RTLD_NEXT, "linkat");
#endif
    if (fault("hardlink-before")) stop();
    if (fault("hardlink-no-space")) { errno = ENOSPC; return -1; }
    result = next_linkat(from, old, to, name, flags);
    if (!result && fault("hardlink-after")) stop();
    return result;
}

int unlinkat(int dir, const char *name, int flags)
{
    int result;
#ifndef HOLY_STATIC_FAULT
    if (!next_unlinkat) *(void **)(&next_unlinkat) = dlsym(RTLD_NEXT, "unlinkat");
#endif
    result = next_unlinkat(dir, name, flags);
    if (!result && fault("hardlink-remove") && !strcmp(name, "a-first")) stop();
    if (!result && fault("group-cleanup") && strlen(name) > 6 &&
        !strcmp(name + strlen(name) - 6, "-group")) stop();
    return result;
}

int renameat(int from, const char *old, int to, const char *name)
{
    int result;
#ifndef HOLY_STATIC_FAULT
    if (!next_renameat) *(void **)(&next_renameat) = dlsym(RTLD_NEXT, "renameat");
#endif
    if (!strncmp(old, ".holy-update-", 13)) {
        if (fault("payload-before")) stop();
        if (fault("no-space")) { errno = ENOSPC; return -1; }
    }
    result = next_renameat(from, old, to, name);
    if (!result) {
        if (fault("payload-after") && !strncmp(old, ".holy-update-", 13)) stop();
        if (fault("intent-after") && !strcmp(name, "journal")) stop();
        if (fault("generation-after") && !strcmp(name, "generation")) stop();
    }
    return result;
}

int fsync(int fd)
{
    char path[4096], proc[64], text[256], child[96];
    const char *digest = getenv("HOLY_UPDATE_NEW");
    int result, copy;
    ssize_t length;
    struct stat st;
#ifndef HOLY_STATIC_FAULT
    if (!next_fsync) *(void **)(&next_fsync) = dlsym(RTLD_NEXT, "fsync");
#endif
    result = next_fsync(fd);
    if (result || !getenv("HOLY_UPDATE_FAULT") || !fd_path(fd, path)) return result;
    if (fault("set-journal") && !fstatat(fd, "set-journal", &st, AT_SYMLINK_NOFOLLOW)) stop();
    if (fault("set-instance") && digest && strstr(path, "/installed/") && strstr(path, digest) &&
        !fstatat(fd, "state", &st, AT_SYMLINK_NOFOLLOW)) stop();
    if (fault("directory-ready") && strstr(path, "/.holy-dir-")) stop();
    if (fault("payload-written") && strstr(path, "/opt/apps/deep/data")) stop();
    if (getenv("HOLY_DIRECTORY_PARENT") && !strcmp(path, getenv("HOLY_DIRECTORY_PARENT"))) {
        if (fault("directory-created") && getenv("HOLY_DIRECTORY_NAME") &&
            !fstatat(fd, getenv("HOLY_DIRECTORY_NAME"), &st, AT_SYMLINK_NOFOLLOW)) stop();
        if (fault("directory-partial")) {
            int listing = openat(fd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
            DIR *stream = listing < 0 ? NULL : fdopendir(listing);
            struct dirent *entry;
            if (stream) {
                while ((entry = readdir(stream)))
                    if (!strncmp(entry->d_name, ".holy-dir-", 10)) stop();
                closedir(stream);
            } else if (listing >= 0) close(listing);
        }
    }
    if (getenv("HOLY_UPDATE_STEP") && strstr(path, "/transactions/update/")) {
        static unsigned long step;
        snprintf(proc, sizeof proc, "/proc/self/fd/%d", fd);
        copy = open(proc, O_RDONLY | O_CLOEXEC);
        length = copy < 0 ? -1 : pread(copy, text, sizeof text - 1, 0);
        if (copy >= 0) close(copy);
        if (length > 0) {
            text[length] = 0;
            if (!strncmp(text, "stage applying\n", 15) && strstr(text, "\nresult done\n") &&
                ++step == strtoul(getenv("HOLY_UPDATE_STEP"), NULL, 10)) stop();
        }
    }
    if (fault("database-before")) {
        snprintf(proc, sizeof proc, "/proc/self/fd/%d", fd);
        copy = open(proc, O_RDONLY | O_CLOEXEC);
        length = copy < 0 ? -1 : pread(copy, text, sizeof text - 1, 0);
        if (copy >= 0) close(copy);
        if (length > 0) {
            text[length] = 0;
            if (!strcmp(text, "stage publishing\n")) stop();
        }
    }
    if (fault("database-after") && digest && strlen(digest) == 64 &&
        strlen(path) >= 16 && !strcmp(path + strlen(path) - 16, "/var/lib/holypkg")) {
        snprintf(child, sizeof child, "installed/%s", digest);
        if (!fstatat(fd, child, &st, AT_SYMLINK_NOFOLLOW)) stop();
    }
    if (fault("committed") && strstr(path, "/transactions/update") &&
        !fstatat(fd, "committed", &st, AT_SYMLINK_NOFOLLOW)) stop();
    return result;
}

ssize_t write(int fd, const void *data, size_t size)
{
    char path[4096];
#ifndef HOLY_STATIC_FAULT
    if (!next_write) *(void **)(&next_write) = dlsym(RTLD_NEXT, "write");
#endif
    if (fault("staging-partial") && size && fd_path(fd, path) && strstr(path, "/.holy-update-")) {
        next_write(fd, data, size / 2);
        stop();
    }
    return next_write(fd, data, size);
}