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

file src/state.c

#define _DEFAULT_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "state.h"
#include "stage.h"
#include "cache.h"
#include "preview.h"
#include "verify.h"
#include "extract.h"
#include "package.h"
#include "install.h"
#include "config.h"
#include "resolve.h"
#include "scan.h"
#include "deps.h"
#include "provides.h"
#include "source.h"
#include "change.h"

#include <archive.h>
#include <archive_entry.h>
#include <openssl/evp.h>
#include <dirent.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/openat2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>

#ifndef O_PATH
#define O_PATH 010000000
#endif

static int set_journal_present(int dir);
static int update_pending(int dir);
static int completed_update(int transactions, const char *name);
static int remove_record(int transactions, const char *digest,
                         unsigned long long generation, int broken, int create);
static int remove_workdir(int transactions, const char *digest,
                          unsigned long long generation, int broken);
static int commit_remove_record(int transactions, const char *digest,
                                unsigned long long generation, int broken);
static char *update_record(int dir, const char *name);
static int installed_fields(int item, const char *const *keys,
                             const char *const *values, size_t fields);
static int valid_owner_path(const char *path);
static int loader_directories(const struct holy_elf_info *elf, const char *consumer,
                              char ***directories, size_t *count);
static void free_loader_directories(char **directories, size_t count);
static int loader_file_match(const char *directory, const char *path,
                             const char *name);
static int write_all(int fd, const void *data, size_t length);
static int set_generation(int dir, unsigned long long generation);

static int native_architecture(const char *host, const char *target)
{
    return !strcmp(target, "noarch") ||
        (!strcmp(target, "x86_64") && !strcmp(host, "x86_64")) ||
        (!strcmp(target, "x86") && !strcmp(host, "i686"));
}

static int architecture_valid(const char *text)
{
    const char *space = strchr(text, ' ');
    size_t length;
    if (!space || space == text || (length = (size_t)(space - text)) > 64 ||
        strspn(text, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-") != length) return 0;
    return !strcmp(space + 1, "x86") || !strcmp(space + 1, "x86_64");
}

static int state_architecture(const char *state, char output[96])
{
    const char *start = strstr(state, "\narchitecture "), *end;
    output[0] = 0;
    if (!start) return 1;
    start += 14;
    end = strchr(start, '\n');
    if (!end || end == start || end - start >= 96) return 0;
    memcpy(output, start, (size_t)(end - start));
    output[end - start] = 0;
    return architecture_valid(output);
}

static int instance_architecture(int item, char output[96])
{
    char state[1024];
    ssize_t got;
    int fd = openat(item, "state", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (fd < 0) return 0;
    got = read(fd, state, sizeof state - 1);
    close(fd);
    if (got < 1 || got == (ssize_t)sizeof state - 1 || memchr(state, 0, (size_t)got)) return 0;
    state[got] = 0;
    return state_architecture(state, output);
}

static int instance_architecture_matches(int item, const char *expected)
{
    char actual[96];
    return instance_architecture(item, actual) && !strcmp(actual, expected);
}

static int safe_directory(int fd)
{
    struct stat st;
    return !fstat(fd, &st) && S_ISDIR(st.st_mode) &&
           (st.st_uid == 0 || st.st_uid == geteuid()) &&
           !(st.st_mode & 0022);
}

static int valid_digest(const char *digest)
{
    size_t i;
    if (!digest || strlen(digest) != 64) return 0;
    for (i = 0; i < 64; ++i)
        if (!((digest[i] >= '0' && digest[i] <= '9') ||
              (digest[i] >= 'a' && digest[i] <= 'f'))) return 0;
    return 1;
}

static int child_dir(int parent, const char *name, int create)
{
    int fd;
    if (create) {
        if (mkdirat(parent, name, 0700)) {
            if (errno != EEXIST) return -1;
        } else if (fsync(parent)) return -1;
    }
    fd = openat(parent, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (fd < 0) return -1;
    if (!safe_directory(fd)) { close(fd); errno = EPERM; return -1; }
    return fd;
}

static DIR *directory_stream(int fd)
{
    int copy = openat(fd, ".", O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    DIR *stream;
    if (copy < 0) return NULL;
    stream = fdopendir(copy);
    if (!stream) close(copy);
    return stream;
}

static int state_dir_at(int root, int create)
{
    static const char *const path[] = { "var", "lib", "holypkg" };
    int fd = dup(root);
    size_t i;
    if (fd < 0) return -1;
    if (!safe_directory(fd)) { close(fd); errno = EPERM; return -1; }
    for (i = 0; i < sizeof path / sizeof *path; ++i) {
        int next = child_dir(fd, path[i], create);
        close(fd);
        if (next < 0) return -1;
        fd = next;
    }
    return fd;
}

static int state_dir(const char *root_path, int create)
{
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, create);
    if (root >= 0) close(root);
    return dir;
}

static int read_generation(int dir, unsigned long long *generation)
{
    char buffer[32];
    struct stat st;
    ssize_t got;
    size_t i;
    unsigned long long n = 0;
    int fd = openat(dir, "generation", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) return 0;
    if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 2 ||
        st.st_size > 21 || (st.st_mode & 0022) ||
        (st.st_uid != 0 && st.st_uid != geteuid())) { close(fd); return 0; }
    got = read(fd, buffer, sizeof buffer);
    close(fd);
    if (got != st.st_size || buffer[got - 1] != '\n' ||
        (got > 2 && buffer[0] == '0')) return 0;
    for (i = 0; i + 1 < (size_t)got; ++i) {
        unsigned digit = (unsigned char)buffer[i] - '0';
        if (digit > 9 || n > (ULLONG_MAX - digit) / 10) return 0;
        n = n * 10 + digit;
    }
    *generation = n;
    return 1;
}

static int state_layout(int dir, int create)
{
    static const char *const names[] = { "installed", "transactions", "index" };
    size_t i;
    for (i = 0; i < sizeof names / sizeof *names; ++i) {
        int child = child_dir(dir, names[i], create);
        if (child < 0) return 0;
        close(child);
    }
    return 1;
}

static int empty_child(int dir, const char *name)
{
    int fd = child_dir(dir, name, 0);
    DIR *entries;
    struct dirent *entry;
    int empty = 1;
    if (fd < 0) return 0;
    entries = fdopendir(fd);
    if (!entries) { close(fd); return 0; }
    errno = 0;
    while ((entry = readdir(entries)) != NULL) {
        if (!strcmp(name, "transactions") && completed_update(fd, entry->d_name)) {
            errno = 0; continue;
        }
        if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) {
            empty = 0;
            break;
        }
        errno = 0;
    }
    if (!entry && errno) empty = 0;
    if (closedir(entries)) empty = 0;
    if (!empty) fprintf(stderr, "holypkg: unrecognized database entries in %s\n", name);
    return empty;
}

static int instance_record_digest(int item, const char *name, char output[65])
{
    unsigned char buffer[8192], digest[32];
    unsigned int length;
    size_t total = 0, i;
    ssize_t got;
    struct stat st;
    EVP_MD_CTX *hash = EVP_MD_CTX_new();
    int fd = openat(item, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    int ok = 0;
    if (fd < 0 || !hash || fstat(fd, &st) || !S_ISREG(st.st_mode) ||
        (st.st_mode & 0022) || (st.st_uid != 0 && st.st_uid != geteuid()) ||
        (st.st_size < 1 && strcmp(name, "provides")) || st.st_size < 0 || st.st_size > 16 * 1024 * 1024 ||
        EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1) goto done;
    while ((got = read(fd, buffer, sizeof buffer)) != 0) {
        if (got < 0) { if (errno == EINTR) continue; goto done; }
        total += (size_t)got;
        if (total > 16 * 1024 * 1024 ||
            EVP_DigestUpdate(hash, buffer, (size_t)got) != 1) goto done;
    }
    if (total != (size_t)st.st_size ||
        EVP_DigestFinal_ex(hash, digest, &length) != 1 || length != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(output + i * 2, 3, "%02x", digest[i]);
    ok = 1;
done:
    if (fd >= 0) close(fd);
    EVP_MD_CTX_free(hash);
    return ok;
}

static int config_state_valid(int item, const char *artifact)
{
    char source[65], raw[65], installed[65], plan[65], actual[65];
    char *record = update_record(item, "config-state");
    int ok = 0;
    if (!record) return 0;
    if (sscanf(record,
               "format holy-config-transform-1\nsource %64[0-9a-f]\nraw %64[0-9a-f]\ninstalled %64[0-9a-f]\nplan %64[0-9a-f]\n",
               source, raw, installed, plan) != 4 ||
        !valid_digest(source) || !valid_digest(raw) || !valid_digest(installed) ||
        !valid_digest(plan) ||
        strcmp(source, artifact) ||
        !instance_record_digest(item, "package-files", actual) || strcmp(actual, raw) ||
        !instance_record_digest(item, "files", actual) || strcmp(actual, installed)) goto done;
    ok = 1;
done:
    free(record);
    return ok;
}

static int graph_digest(int item, char output[65])
{
    return instance_record_digest(item, "graph", output);
}

static int instance_state_generation(int item, const char *digest,
                                     unsigned long long *recorded)
{
    char buffer[1024], prefix[960], graph[65], source[65], source_hash[65], claims[65], architecture[96], *end;
    struct stat st;
    unsigned long long generation;
    ssize_t got;
    size_t length;
    const char *reason = "explicit";
    int version, fd = openat(item, "state", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) return 0;
    if (fstat(fd, &st) || !S_ISREG(st.st_mode) ||
        (st.st_mode & 0022) || (st.st_uid != 0 && st.st_uid != geteuid()) ||
        st.st_size < 24 || st.st_size >= (off_t)sizeof buffer) {
        close(fd);
        return 0;
    }
    got = read(fd, buffer, sizeof buffer - 1);
    close(fd);
    if (got != st.st_size || buffer[got - 1] != '\n' || memchr(buffer, 0, (size_t)got)) return 0;
    buffer[got] = '\0';
    version = !strncmp(buffer, "format holy-instance-7\n", 23) ? 7 :
              !strncmp(buffer, "format holy-instance-6\n", 23) ? 6 :
              !strncmp(buffer, "format holy-instance-5\n", 23) ? 5 :
              !strncmp(buffer, "format holy-instance-4\n", 23) ? 4 :
              !strncmp(buffer, "format holy-instance-3\n", 23) ? 3 :
              !strncmp(buffer, "format holy-instance-2\n", 23) ? 2 : 1;
    if (strstr(buffer, "\nreason dependency\n")) reason = "dependency";
    if (version < 3 && (!fstatat(item, "source", &st, AT_SYMLINK_NOFOLLOW) || errno != ENOENT)) return 0;
    if (version < 4 && (!fstatat(item, "provides", &st, AT_SYMLINK_NOFOLLOW) || errno != ENOENT)) return 0;
    if (version >= 4) {
        if (!graph_digest(item, graph) || !instance_record_digest(item, "provides", claims)) return 0;
        if (!fstatat(item, "source", &st, AT_SYMLINK_NOFOLLOW)) {
            if (!holy_source_instance(item, source, source_hash)) return 0;
        } else {
            if (errno != ENOENT) return 0;
            strcpy(source, "-"); strcpy(source_hash, "-");
        }
        if (!state_architecture(buffer, architecture) ||
            (version == 5 || version == 7) != !!architecture[0]) return 0;
        if (architecture[0]) {
            const char *keys[] = {"arch"}, *values[] = {strchr(architecture, ' ') + 1};
            if (installed_fields(item, keys, values, 1) != 1) return 0;
        }
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-instance-%d\nsource-id %s\nsource-record %s\ndelivery local\nreason %s\nartifact %s\ngraph %s\nprovides %s\n%s%s%s%s%s%sgeneration ",
            version, source, source_hash, reason, digest, graph, claims,
            architecture[0] ? "architecture " : "", architecture, architecture[0] ? "\n" : "",
            version >= 6 ? "privileged " : "", version >= 6 ? digest : "",
            version >= 6 ? "\n" : "");
    } else if (version == 3) {
        if (!graph_digest(item, graph) || !holy_source_instance(item, source, source_hash)) return 0;
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-instance-3\nsource-id %s\nsource-record %s\ndelivery local\nreason %s\nartifact %s\ngraph %s\ngeneration ",
            source, source_hash, reason, digest, graph);
    } else if (version == 2) {
        if (!graph_digest(item, graph)) return 0;
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-instance-2\nsource-id -\ndelivery local\nreason %s\nartifact %s\ngraph %s\ngeneration ",
            reason, digest, graph);
    } else {
        if (!fstatat(item, "graph", &st, AT_SYMLINK_NOFOLLOW) || errno != ENOENT) return 0;
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-instance-1\nsource-id -\ndelivery local\nreason explicit\nartifact %s\ngeneration ", digest);
    }
    if (length >= sizeof prefix || (size_t)got <= length + 1 ||
        memcmp(buffer, prefix, length)) return 0;
    buffer[got - 1] = '\0';
    if (buffer[length] < '0' || buffer[length] > '9') return 0;
    errno = 0;
    generation = strtoull(buffer + length, &end, 10);
    if (errno || *end || (buffer[length] == '0' && buffer[length + 1])) return 0;
    if (recorded) *recorded = generation;
    return 1;
}

static int installed_valid(int dir)
{
    static const char *const required[] = { "meta", "files", "deps", "origin", "state", "graph", "source", "provides", "hooks", "transform", "hooks-state", "package-files", "config-state" };
    int installed = child_dir(dir, "installed", 0), ok = 1;
    DIR *list;
    struct dirent *entry;
    if (installed < 0) return 0;
    list = directory_stream(installed);
    if (!list) { close(installed); return 0; }
    errno = 0;
    while ((entry = readdir(list))) {
        int item;
        size_t i;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        if (!valid_digest(entry->d_name)) { ok = 0; break; }
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) { ok = 0; break; }
        for (i = 0; i < 5; ++i) {
            struct stat st;
            if (fstatat(item, required[i], &st, AT_SYMLINK_NOFOLLOW) ||
                !S_ISREG(st.st_mode) || (st.st_mode & 0022) ||
                (st.st_uid != 0 && st.st_uid != geteuid())) { ok = 0; break; }
        }
        if (ok && !instance_state_generation(item, entry->d_name, NULL)) ok = 0;
        if (ok) {
            struct stat transformed, source;
            int has_transform, has_source;
            errno = 0;
            has_transform = !fstatat(item, "config-state", &transformed, AT_SYMLINK_NOFOLLOW);
            if (!has_transform && errno != ENOENT) ok = 0;
            errno = 0;
            has_source = !fstatat(item, "package-files", &source, AT_SYMLINK_NOFOLLOW);
            if (!has_source && errno != ENOENT) ok = 0;
            if (has_transform != has_source || (has_transform &&
                (!S_ISREG(transformed.st_mode) || !S_ISREG(source.st_mode) ||
                 !config_state_valid(item, entry->d_name)))) ok = 0;
        }
        if (ok) {
            DIR *members = fdopendir(dup(item));
            struct dirent *member;
            unsigned seen = 0;
            if (!members) ok = 0;
            else {
                errno = 0;
                while ((member = readdir(members))) {
                    if (!strcmp(member->d_name, ".") ||
                        !strcmp(member->d_name, "..")) continue;
                    for (i = 0; i < sizeof required / sizeof *required; ++i)
                        if (!strcmp(member->d_name, required[i])) break;
                    if (i == sizeof required / sizeof *required ||
                        (seen & (1u << i))) { ok = 0; break; }
                    seen |= 1u << i;
                    errno = 0;
                }
                if (!member && errno) ok = 0;
                {
                    unsigned base = seen & ((1u << 8) - 1u);
                    if (base != (1u << 5) - 1u && base != (1u << 6) - 1u &&
                        base != (1u << 7) - 1u && base != ((1u << 6) - 1u + (1u << 7)) &&
                        base != (1u << 8) - 1u) ok = 0;
                }
                closedir(members);
            }
        }
        if (ok && (fstatat(item, "hooks", &(struct stat){0}, AT_SYMLINK_NOFOLLOW) == 0 ||
                   fstatat(item, "hooks-state", &(struct stat){0}, AT_SYMLINK_NOFOLLOW) == 0)) {
            char expected[96], completed[96], actual[96], digest[65];
            struct stat hooks_stat, state_stat;
            int fd;
            if (fstatat(item, "hooks", &hooks_stat, AT_SYMLINK_NOFOLLOW) ||
                !S_ISREG(hooks_stat.st_mode) || (hooks_stat.st_mode & 0022) ||
                (hooks_stat.st_uid != 0 && hooks_stat.st_uid != geteuid()) ||
                (fstatat(item, "hooks-state", &state_stat, AT_SYMLINK_NOFOLLOW) == 0) !=
                (hooks_stat.st_size > 0)) ok = 0;
            if (ok && hooks_stat.st_size > 0) {
                if (!instance_record_digest(item, "hooks", digest)) ok = 0;
                if (ok) {
                    int length = snprintf(expected, sizeof expected, "skipped sha256 %s\n", digest);
                    int completed_length = snprintf(completed, sizeof completed,
                                                    "completed sha256 %s\n", digest);
                    ssize_t got;
                    fd = openat(item, "hooks-state", O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
                    if (fd < 0 || fstat(fd, &state_stat) || !S_ISREG(state_stat.st_mode) ||
                        (state_stat.st_mode & 0022) ||
                        (state_stat.st_uid != 0 && state_stat.st_uid != geteuid()) ||
                        length >= (int)sizeof expected ||
                        completed_length >= (int)sizeof completed) ok = 0;
                    else {
                        got = read(fd, actual, sizeof actual);
                        if ((got != length || memcmp(actual, expected, (size_t)length)) &&
                            (got != completed_length ||
                             memcmp(actual, completed, (size_t)completed_length))) ok = 0;
                    }
                    if (fd >= 0) close(fd);
                }
            }
        }
        if (ok && !fstatat(item, "transform", &(struct stat){0}, AT_SYMLINK_NOFOLLOW)) {
            struct stat transform_stat;
            if (fstatat(item, "transform", &transform_stat, AT_SYMLINK_NOFOLLOW) ||
                !S_ISREG(transform_stat.st_mode) || (transform_stat.st_mode & 0022) ||
                (transform_stat.st_uid != 0 && transform_stat.st_uid != geteuid())) ok = 0;
        }
        close(item);
        if (!ok) break;
        errno = 0;
    }
    if (!entry && errno) ok = 0;
    closedir(list);
    close(installed);
    if (!ok) fprintf(stderr, "holypkg: invalid installed entries\n");
    return ok;
}

static int hook_skipped(int item)
{
    char prefix[8];
    int fd = openat(item, "hooks-state", O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    ssize_t got;
    if (fd < 0) return 0;
    got = read(fd, prefix, sizeof prefix);
    close(fd);
    return got == (ssize_t)sizeof prefix && !memcmp(prefix, "skipped ", sizeof prefix);
}

static int journal_exists(int dir)
{
    int transactions = child_dir(dir, "transactions", 0);
    struct stat st;
    int present;
    if (transactions < 0) return -1;
    present = fstatat(transactions, "journal", &st, AT_SYMLINK_NOFOLLOW) == 0;
    if (present && (!S_ISREG(st.st_mode) || (st.st_mode & 0022) ||
                    (st.st_uid != 0 && st.st_uid != geteuid()))) present = -1;
    if (!present && errno != ENOENT) present = -1;
    close(transactions);
    return present;
}

static int journal_valid(int dir, unsigned long long generation,
                         unsigned long long *original,
                         char artifact[65], char plan_digest[65], int *removing)
{
    int transactions = child_dir(dir, "transactions", 0), fd = -1, result = -1;
    struct stat st;
    char buffer[256], prefix[96], digest[65], plan[65];
    ssize_t got;
    size_t length;
    unsigned long long recorded = 0;
    int is_removing = 0, attempt;
    if (transactions < 0) return -1;
    fd = openat(transactions, "journal", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) { result = errno == ENOENT ? 0 : -1; goto done; }
    if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 140 ||
        st.st_size > (off_t)sizeof buffer || (st.st_mode & 0022) ||
        (st.st_uid != 0 && st.st_uid != geteuid())) goto done;
    got = read(fd, buffer, sizeof buffer);
    if (got != st.st_size) goto done;
    for (attempt = 0; attempt < 6; ++attempt) {
        if (attempt >= 3 && !generation) break;
        recorded = generation - (unsigned long long)(attempt / 3);
        is_removing = attempt % 3;
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-journal-1\nstage %s\ngeneration %llu\nartifact ",
            is_removing == 2 ? "repairing" : is_removing ? "removing" : "applying", recorded);
        if (length >= sizeof prefix) goto done;
        if (!memcmp(buffer, prefix, length)) break;
    }
    if (attempt == 6 || (attempt >= 3 && !generation)) goto done;
    if (st.st_size != (off_t)(length + 65 + 5 + 65) ||
        buffer[length + 64] != '\n' ||
        memcmp(buffer + length + 65, "plan ", 5) ||
        buffer[length + 65 + 5 + 64] != '\n') goto done;
    memcpy(digest, buffer + length, 64);
    digest[64] = '\0';
    memcpy(plan, buffer + length + 70, 64);
    plan[64] = '\0';
    if (!valid_digest(digest) || !valid_digest(plan)) goto done;
    if (original) *original = recorded;
    if (artifact) memcpy(artifact, digest, 65);
    if (plan_digest) memcpy(plan_digest, plan, 65);
    if (removing) *removing = is_removing;
    result = 1;
done:
    if (fd >= 0) close(fd);
    close(transactions);
    return result;
}

static int transaction_pending(int dir, unsigned long long generation)
{
    int transactions = child_dir(dir, "transactions", 0);
    struct stat hook;
    int hook_pending;
    if (transactions < 0) return -1;
    hook_pending = !fstatat(transactions, "hook-journal", &hook, AT_SYMLINK_NOFOLLOW);
    if (!hook_pending && errno != ENOENT) hook_pending = -1;
    if (hook_pending > 0 && (!S_ISREG(hook.st_mode) || (hook.st_mode & 0022) ||
        (hook.st_uid != 0 && hook.st_uid != geteuid()))) hook_pending = -1;
    close(transactions);
    if (hook_pending) return hook_pending;
    int result = update_pending(dir);
    if (result) return result;
    result = set_journal_present(dir);
    return result ? result : journal_valid(dir, generation, NULL, NULL, NULL, NULL);
}

static int read_reservation(int transactions, const char *name,
                            unsigned long long generation, char digest[65],
                            char approved[65])
{
    char buffer[256], prefix[96];
    struct stat st;
    ssize_t got;
    size_t length, i;
    int is_approved = 0;
    int fd = openat(transactions, name, O_RDONLY | O_NOFOLLOW |
                    O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) return errno == ENOENT ? 0 : -1;
    length = (size_t)snprintf(prefix, sizeof prefix,
        "format holy-reservation-1\nstage prepared\ngeneration %llu\nartifact ",
        generation);
    if (length >= sizeof prefix || fstat(fd, &st) || !S_ISREG(st.st_mode) ||
        (st.st_size != (off_t)(length + 65) &&
         st.st_size != (off_t)(length + 65 + 70)) || (st.st_mode & 0022) ||
        (st.st_uid != 0 && st.st_uid != geteuid())) { close(fd); return -1; }
    got = read(fd, buffer, sizeof buffer);
    close(fd);
    if (got != st.st_size || buffer[length + 64] != '\n') return -1;
    if (memcmp(buffer, prefix, length)) {
        is_approved = 1;
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-reservation-1\nstage approved\ngeneration %llu\nartifact ",
            generation);
        if (length >= sizeof prefix || memcmp(buffer, prefix, length) ||
            st.st_size != (off_t)(length + 65 + 70)) return -1;
    }
    for (i = 0; i < 64; ++i)
        if (!((buffer[length + i] >= '0' && buffer[length + i] <= '9') ||
              (buffer[length + i] >= 'a' && buffer[length + i] <= 'f')))
            return -1;
    memcpy(digest, buffer + length, 64);
    digest[64] = '\0';
    approved[0] = '\0';
    if (is_approved != (st.st_size != (off_t)(length + 65))) return -1;
    if (st.st_size != (off_t)(length + 65)) {
        if (memcmp(buffer + length + 65, "plan ", 5) ||
            buffer[length + 65 + 5 + 64] != '\n') return -1;
        memcpy(approved, buffer + length + 70, 64);
        approved[64] = '\0';
        if (!valid_digest(approved)) return -1;
    }
    return 1;
}

static int pending_child(int dir, unsigned long long generation, char digest[65],
                         char approved[65])
{
    int child = child_dir(dir, "transactions", 0), result = -1;
    DIR *listing;
    struct dirent *entry;
    size_t count = 0;
    if (child < 0) return -1;
    listing = directory_stream(child);
    if (!listing) { close(child); return -1; }
    errno = 0;
    while ((entry = readdir(listing))) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            continue;
        if (completed_update(child, entry->d_name)) { errno = 0; continue; }
        if (strcmp(entry->d_name, "pending") || ++count > 1) break;
        errno = 0;
    }
    if (!entry && !errno && count <= 1)
        result = read_reservation(child, "pending", generation, digest, approved);
    closedir(listing);
    close(child);
    if (result < 0) fprintf(stderr, "holypkg: unrecognized database entries in transactions\n");
    return result;
}

int holy_state_init(const char *root_path)
{
    char temp_name[43];
    unsigned long long generation;
    struct stat st;
    int dir = state_dir(root_path, 1), temp = -1, ok = 0;
    if (dir < 0) goto done;
    if (flock(dir, LOCK_EX)) goto done;
    if (!state_layout(dir, 1) ||
        !empty_child(dir, "installed") ||
        !empty_child(dir, "transactions") ||
        !empty_child(dir, "index")) goto done;
    if (fstatat(dir, "generation", &st, AT_SYMLINK_NOFOLLOW) == 0) {
        if (!read_generation(dir, &generation)) goto done;
    } else if (errno != ENOENT) goto done;
    temp = holy_temporary_at(dir, temp_name);
    if (temp < 0) goto done;
    if (write(temp, "0\n", 2) != 2 || fsync(temp)) goto done;
    if (linkat(dir, temp_name, dir, "generation", 0) && errno != EEXIST)
        goto done;
    if (fsync(dir) || !read_generation(dir, &generation)) goto done;
    printf("generation %llu\n", generation);
    ok = 1;
done:
    if (!ok) fprintf(stderr, "holypkg: database init failed\n");
    if (temp >= 0) { close(temp); unlinkat(dir, temp_name, 0); }
    if (dir >= 0) close(dir);
    return ok;
}

int holy_state_status(const char *root_path, int json)
{
    unsigned long long generation;
    char digest[65], approved[65];
    int dir = state_dir(root_path, 0), pending, result = 1;
    if (dir < 0) goto done;
    if (flock(dir, LOCK_SH) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) goto done;
    pending = transaction_pending(dir, generation);
    if (pending < 0) goto done;
    if (pending) {
        if (json) printf("{\"schema\":\"holy-db-status-1\",\"type\":\"incomplete\",\"generation\":%llu}\n", generation);
        else printf("generation %llu\nincomplete transaction; inspect journal\n", generation);
        result = 5;
        goto done;
    }
    if (!installed_valid(dir)) goto done;
    pending = pending_child(dir, generation, digest, approved);
    if (pending < 0) goto done;
    if (json) {
        printf("{\"schema\":\"holy-db-status-1\",\"type\":\"state\",\"generation\":%llu,\"pending\":",
               generation);
        if (pending) {
            printf("{\"stage\":\"%s\",\"sha256\":\"%s\"",
                   approved[0] ? "approved" : "prepared", digest);
            if (approved[0]) printf(",\"plan\":\"%s\"", approved);
            putchar('}');
        }
        else fputs("null", stdout);
        puts("}");
    } else {
        printf("generation %llu\n", generation);
        if (pending) printf("pending %s%s%s\n", digest,
                            approved[0] ? " approved " : "", approved);
    }
    if (pending) {
        result = 5;
    } else result = 0;
done:
    if (result == 1) {
        fprintf(stderr, "holypkg: database status unavailable\n");
        if (json) puts("{\"schema\":\"holy-db-status-1\",\"type\":\"error\",\"code\":\"invalid-state\"}");
    }
    if (dir >= 0) close(dir);
    return result;
}

static int compare_instance_names(const void *a, const void *b)
{
    return strcmp(*(const char *const *)a, *(const char *const *)b);
}

int holy_state_lock(const char *root_path, int exclusive,
                     unsigned long long *generation, int *status)
{
    unsigned long long current;
    char digest[65], approved[65];
    int dir = state_dir(root_path, 0), pending;
    *status = 1;
    if (dir < 0 || flock(dir, exclusive ? LOCK_EX : LOCK_SH) ||
        !state_layout(dir, 0) || !read_generation(dir, &current)) goto failed;
    pending = transaction_pending(dir, current);
    if (pending < 0) goto failed;
    if (pending) { *status = 5; goto failed; }
    pending = pending_child(dir, current, digest, approved);
    if (pending < 0) goto failed;
    if (pending) { *status = 5; goto failed; }
    if (!installed_valid(dir)) goto failed;
    *generation = current;
    *status = 0;
    return dir;
failed:
    if (dir >= 0) close(dir);
    return -1;
}

int holy_state_visit(const char *root_path, holy_instance_visit visit, void *context,
                     unsigned long long *generation)
{
    int root = -1, dir = -1, installed = -1, result = 1, pending;
    char **names = NULL, digest[65], approved[65];
    size_t count = 0, i;
    DIR *list = NULL;
    struct dirent *entry;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0 || (dir = state_dir_at(root, 0)) < 0 || flock(dir, LOCK_SH) ||
        !state_layout(dir, 0) || !read_generation(dir, generation)) goto done;
    pending = transaction_pending(dir, *generation);
    if (pending < 0) goto done;
    if (pending) { result = 5; goto done; }
    pending = pending_child(dir, *generation, digest, approved);
    if (pending < 0) goto done;
    if (pending) { result = 5; goto done; }
    if (!installed_valid(dir) || (installed = child_dir(dir, "installed", 0)) < 0 ||
        !(list = directory_stream(installed))) goto done;
    errno = 0;
    while ((entry = readdir(list))) {
        char **grown;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        if (count >= SIZE_MAX / sizeof *names ||
            !(grown = realloc(names, (count + 1) * sizeof *names))) goto done;
        names = grown;
        names[count] = strdup(entry->d_name);
        if (!names[count]) goto done;
        ++count;
        errno = 0;
    }
    if (errno) goto done;
    if (count) qsort(names, count, sizeof *names, compare_instance_names);
    for (i = 0; i < count; ++i) {
        int item = child_dir(installed, names[i], 0), rc;
        if (item < 0) goto done;
        rc = visit(context, root, item, names[i]);
        close(item);
        if (rc) { result = rc; goto done; }
    }
    result = 0;
done:
    for (i = 0; i < count; ++i) free(names[i]);
    free(names);
    if (list) closedir(list);
    if (installed >= 0) close(installed);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_reserve(const char *digest, const char *root_path)
{
    unsigned long long generation;
    char existing[65], approved[65], temp_name[43] = {0}, record[192];
    int dir = -1, transactions = -1, temp = -1, result = 1;
    size_t length;
    if (!valid_digest(digest)) {
        fprintf(stderr, "holypkg: expected a lowercase SHA-256 digest\n");
        return 2;
    }
    dir = state_dir(root_path, 0);
    if (!holy_cache_object(digest, root_path)) { result = 6; goto done; }
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !installed_valid(dir) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation)) goto done;
    result = update_pending(dir);
    if (result) { result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    result = pending_child(dir, generation, existing, approved);
    if (result < 0) { result = 1; goto done; }
    if (result) { result = 5; goto done; }
    result = 1;
    transactions = child_dir(dir, "transactions", 0);
    if (transactions < 0) goto done;
    length = (size_t)snprintf(record, sizeof record,
        "format holy-reservation-1\nstage prepared\ngeneration %llu\nartifact %s\n",
        generation, digest);
    if (length >= sizeof record) goto done;
    temp = holy_temporary_at(transactions, temp_name);
    if (temp < 0 || write(temp, record, length) != (ssize_t)length || fsync(temp) ||
        linkat(transactions, temp_name, transactions, "pending", 0)) goto done;
    if (close(temp)) { temp = -1; goto done; }
    temp = -1;
    if (unlinkat(transactions, temp_name, 0) || fsync(transactions)) goto done;
    printf("reserved %s generation %llu\n", digest, generation);
    result = 0;
done:
    if (result && result != 5) fprintf(stderr, "holypkg: reservation failed\n");
    if (temp >= 0) { close(temp); unlinkat(transactions, temp_name, 0); }
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    return result;
}

int holy_state_cancel(const char *root_path)
{
    unsigned long long generation;
    char digest[65], approved[65];
    int dir = state_dir(root_path, 0), transactions = -1, result = 1;
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !installed_valid(dir) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation)) goto done;
    result = update_pending(dir);
    if (result) { result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    if (pending_child(dir, generation, digest, approved) != 1) goto done;
    transactions = child_dir(dir, "transactions", 0);
    if (transactions < 0 || unlinkat(transactions, "pending", 0) ||
        fsync(transactions)) goto done;
    printf("cancelled %s\n", digest);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: reservation cancellation failed\n");
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    return result;
}

static int temporary_name(const char *name)
{
    size_t i;
    if (strlen(name) != 42 || strncmp(name, ".holy-tmp-", 10)) return 0;
    for (i = 10; i < 42; ++i)
        if (!((name[i] >= '0' && name[i] <= '9') ||
              (name[i] >= 'a' && name[i] <= 'f'))) return 0;
    return 1;
}

int holy_state_recover(const char *root_path)
{
    unsigned long long generation;
    char temp_name[43] = {0}, pending_digest[65], temp_digest[65];
    char pending_approved[65], temp_approved[65];
    struct stat pending_st, temp_st;
    DIR *listing = NULL;
    struct dirent *entry;
    int dir = state_dir(root_path, 0), transactions = -1;
    int has_pending = 0, result = 1;
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) goto done;
    result = transaction_pending(dir, generation);
    if (result < 0) { result = 1; goto done; }
    if (result) {
        fprintf(stderr, "holypkg: incomplete file transaction requires manual inspection\n");
        result = 5;
        goto done;
    }
    result = 1;
    if (!installed_valid(dir)) goto done;
    transactions = child_dir(dir, "transactions", 0);
    if (transactions < 0) goto done;
    listing = directory_stream(transactions);
    if (!listing) goto done;
    errno = 0;
    while ((entry = readdir(listing))) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            continue;
        if (completed_update(transactions, entry->d_name)) { errno = 0; continue; }
        if (!strcmp(entry->d_name, "pending")) {
            if (has_pending++) goto done;
        } else if (temporary_name(entry->d_name) && !temp_name[0]) {
            memcpy(temp_name, entry->d_name, 43);
        } else goto done;
        errno = 0;
    }
    if (errno) goto done;
    if (has_pending && (read_reservation(transactions, "pending", generation,
                                        pending_digest, pending_approved) != 1 ||
                        fstatat(transactions, "pending", &pending_st,
                                AT_SYMLINK_NOFOLLOW))) goto done;
    if (!temp_name[0]) { result = has_pending ? 5 : 0; goto done; }
    if (read_reservation(transactions, temp_name, generation,
                         temp_digest, temp_approved) != 1 ||
        fstatat(transactions, temp_name, &temp_st, AT_SYMLINK_NOFOLLOW)) goto done;
    if (has_pending && (strcmp(pending_digest, temp_digest) ||
        ((pending_st.st_dev != temp_st.st_dev ||
          pending_st.st_ino != temp_st.st_ino) &&
         !(temp_approved[0] && !pending_approved[0])))) goto done;
    if (unlinkat(transactions, temp_name, 0) || fsync(transactions)) goto done;
    printf("recovered temporary reservation %s\n", temp_digest);
    result = has_pending ? 5 : 0;
done:
    if (result == 1) fprintf(stderr, "holypkg: reservation recovery failed\n");
    if (listing) closedir(listing);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    return result;
}

int holy_state_preflight(const char *root_path, int json)
{
    unsigned long long generation;
    char digest[65], approved[65], *snapshot = NULL;
    int dir = state_dir(root_path, 0), pending, result = 1;
    int inspected = 0;
    const char *code = "invalid-state";
    if (dir < 0 || flock(dir, LOCK_SH) || !state_layout(dir, 0) ||
        !installed_valid(dir) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation)) goto done;
    result = update_pending(dir);
    if (result) { code = "incomplete-transaction"; result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    pending = pending_child(dir, generation, digest, approved);
    if (pending < 0) goto done;
    if (!pending) { result = 6; code = "unavailable-reservation"; goto done; }
    snapshot = holy_cache_snapshot(digest, root_path);
    if (!snapshot) { result = 6; code = "unavailable-artifact"; goto done; }
    inspected = 1;
    result = holy_preview_local_format(snapshot, root_path, json);
    if (result == 0 || result == 3 || result == 4) {
        if (json)
            printf("{\"schema\":\"holy-preview-1\",\"type\":\"reservation\",\"generation\":%llu,\"artifact\":\"%s\"}\n",
                   generation, digest);
        else printf("reservation generation %llu artifact %s\n", generation, digest);
    }
done:
    if (result == 6) fprintf(stderr, "holypkg: reservation preflight unavailable\n");
    if (json && !inspected && result != 0)
        printf("{\"schema\":\"holy-preview-1\",\"type\":\"error\",\"code\":\"%s\"}\n", code);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    if (dir >= 0) close(dir);
    return result;
}

struct plan_hash {
    int completed;
    int accepted_privileged;
    EVP_MD_CTX *hash;
    size_t count;
    int dir;
    int claim_error;
};

static int path_available(int dir, const char *path, int directory)
{
    int installed = child_dir(dir, "installed", 0), result = -1;
    DIR *list;
    struct dirent *entry;
    if (installed < 0) return -1;
    list = directory_stream(installed);
    if (!list) { close(installed); return -1; }
    errno = 0;
    while ((entry = readdir(list))) {
        int item, files, kind;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) goto done;
        files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
        close(item);
        if (files < 0) goto done;
        kind = holy_install_manifest_owns(files, path);
        close(files);
        if (kind < 0) goto done;
        if (kind && (kind == 1 || !directory)) {
            fprintf(stderr, "holypkg: path already claimed by %s\n", entry->d_name);
            result = 0;
            goto done;
        }
        errno = 0;
    }
    if (!errno) result = 1;
done:
    closedir(list);
    close(installed);
    return result;
}

static int hash_text(EVP_MD_CTX *hash, const char *text)
{
    char length[32];
    size_t size = strlen(text);
    int written = snprintf(length, sizeof length, "%zu:", size);
    return written > 0 && (size_t)written < sizeof length &&
           EVP_DigestUpdate(hash, length, (size_t)written) == 1 &&
           EVP_DigestUpdate(hash, text, size) == 1;
}

static int plan_entry(void *context, const struct holy_manifest_entry *entry)
{
    struct plan_hash *plan = context;
    char attributes[128];
    int written, available;
    if ((entry->link && (entry->mode != 0777 || entry->link[0] == '/')) ||
        !entry->path[0] || entry->path[0] == '/' ||
        !strcmp(entry->path, "var/lib/holypkg") ||
        !strncmp(entry->path, "var/lib/holypkg/", 16) ||
        !strcmp(entry->path, "var/cache/holypkg") ||
        !strncmp(entry->path, "var/cache/holypkg/", 18) ||
        ((entry->mode & 07000) &&
         (!plan->accepted_privileged || entry->directory || entry->link ||
          entry->hardlink || (entry->mode & 03000))) ||
        entry->uid != (long long)geteuid() ||
        entry->gid != (long long)getegid()) {
        fprintf(stderr, "holypkg: plan requires files or relative symlinks and local ownership; unsupported path: %s\n",
                entry->path);
        return 0;
    }
    available = plan->completed ? 1 : path_available(plan->dir, entry->path, entry->directory);
    if (available != 1) {
        plan->claim_error = available == 0 ? 4 : 1;
        return 0;
    }
    written = snprintf(attributes, sizeof attributes, "%c:%o:%lld:%lld:%lld:",
                       entry->directory ? 'd' : entry->link ? 'l' : entry->hardlink ? 'h' : 'f', entry->mode,
                       entry->uid, entry->gid, entry->size);
    if (written <= 0 || (size_t)written >= sizeof attributes ||
        !hash_text(plan->hash, entry->path) ||
        !hash_text(plan->hash, attributes) ||
        (entry->link && !hash_text(plan->hash, entry->link)) ||
        (entry->hardlink && !hash_text(plan->hash, entry->hardlink)) ||
        (entry->group && !hash_text(plan->hash, entry->group)) ||
        (!entry->directory && !entry->link &&
         EVP_DigestUpdate(plan->hash, entry->hash, 32) != 1)) return 0;
    ++plan->count;
    return 1;
}

static int recorded_transform(const char *snapshot)
{
    struct archive *archive = archive_read_new();
    struct archive_entry *entry;
    int status, ok = 0, found = 0;
    if (!archive) return 0;
    if (archive_read_support_filter_lz4(archive) != ARCHIVE_OK ||
        archive_read_support_format_tar(archive) != ARCHIVE_OK ||
        archive_read_open_filename(archive, snapshot, 8192) != ARCHIVE_OK)
        goto done;
    while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
        if (!strcmp(archive_entry_pathname(entry), "HOLY/transform")) {
            if (found++ || archive_entry_filetype(entry) != AE_IFREG) goto done;
        }
        if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
    }
    ok = status == ARCHIVE_EOF && found == 1;
done:
    archive_read_free(archive);
    return ok;
}

static int same_root(const char *root_path, const struct stat *before)
{
    struct stat after;
    int fd = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int ok = fd >= 0 && !fstat(fd, &after) &&
             before->st_dev == after.st_dev && before->st_ino == after.st_ino;
    if (fd >= 0) close(fd);
    return ok;
}

static int installed_fields(int item, const char *const *keys,
                             const char *const *values, size_t fields)
{
    struct stat st;
    char *line = NULL;
    size_t capacity = 0, number = 0;
    ssize_t length;
    int fd = openat(item, "meta", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    unsigned seen = 0;
    int matches = 1, result = -1;
    FILE *input;
    if (fd < 0) return -1;
    if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 16 * 1024 * 1024) { close(fd); return -1; }
    input = fdopen(fd, "r");
    if (!input) { close(fd); return -1; }
    while ((length = getline(&line, &capacity, input)) >= 0) {
        char **v = NULL, *error = NULL;
        size_t count = 0;
        ++number;
        if (memchr(line, '\0', (size_t)length) ||
            !holy_lex(line, (size_t)length, &v, &count, "installed/meta", number, &error)) {
            free(error);
            goto done;
        }
        if (count && count != 2) { holy_tokens_free(v, count); goto done; }
        if (count) {
            size_t i;
            for (i = 0; i < fields; ++i) if (!strcmp(v[0], keys[i])) {
                if (seen & (1u << i)) { holy_tokens_free(v, count); goto done; }
                seen |= 1u << i;
                if (strcmp(v[1], values[i])) matches = 0;
            }
        }
        holy_tokens_free(v, count);
    }
    if (!ferror(input) && seen == (1u << fields) - 1u && ftello(input) == st.st_size) result = matches;
done:
    free(line);
    fclose(input);
    return result;
}

static int installed_name(int item, const char *name)
{
    const char *keys[] = {"name"}, *values[] = {name};
    return installed_fields(item, keys, values, 1);
}

static int installed_source_id(int item, char source[65])
{
    struct stat st;
    char digest[65];
    if (!fstatat(item, "source", &st, AT_SYMLINK_NOFOLLOW))
        return holy_source_instance(item, source, digest);
    if (errno != ENOENT) return 0;
    strcpy(source, "-");
    return 1;
}

int holy_state_find_slot(const char *root_path, const char *source_id,
                         const char *name, const char *arch, const char *libc,
                         char digest[65])
{
    unsigned long long generation;
    int database = -1, installed = -1, result = 1;
    DIR *list = NULL;
    struct dirent *entry;
    size_t found = 0;
    digest[0] = 0;
    if ((!valid_digest(source_id) && strcmp(source_id, "-")) || !name || !*name ||
        (arch && !*arch) || (libc && !*libc)) return 2;
    database = holy_state_lock(root_path, 0, &generation, &result);
    if (database < 0) return result;
    result = 1;
    installed = child_dir(database, "installed", 0);
    list = installed < 0 ? NULL : directory_stream(installed);
    if (!list) goto done;
    errno = 0;
    while ((entry = readdir(list))) {
        const char *arch_key[] = {"arch"}, *libc_key[] = {"libc"};
        const char *arch_value[] = {arch}, *libc_value[] = {libc};
        char actual[65];
        int item, match;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) goto done;
        match = installed_name(item, name);
        if (match > 0 && arch) match = installed_fields(item, arch_key, arch_value, 1);
        if (match > 0 && libc) match = installed_fields(item, libc_key, libc_value, 1);
        if (match > 0) match = installed_source_id(item, actual) ?
            !strcmp(actual, source_id) : -1;
        close(item);
        if (match < 0) goto done;
        if (match) {
            if (!valid_digest(entry->d_name)) goto done;
            ++found;
            if (found == 1) strcpy(digest, entry->d_name);
        }
        errno = 0;
    }
    if (errno) goto done;
    result = found > 1 ? 3 : found ? 0 : 6;
done:
    if (result) fprintf(stderr, "holypkg: installed source slot %s for %s\n",
                        result == 3 ? "requires arch/libc choice" : "unavailable", name);
    if (list) closedir(list);
    if (installed >= 0) close(installed);
    if (database >= 0) close(database);
    if (result) digest[0] = 0;
    return result;
}

struct hook_step {
    char *interpreter;
    char *path;
    char *body;
    size_t length;
};

struct hook_plan {
    struct hook_step steps[64];
    size_t count;
    char hash[65];
};

static void free_hook_plan(struct hook_plan *plan)
{
    size_t i;
    for (i = 0; i < plan->count; ++i) {
        free(plan->steps[i].interpreter);
        free(plan->steps[i].path);
        free(plan->steps[i].body);
    }
}

static int hook_body(int root, int files, const char *path, const char *expected,
                     char **body, size_t *length)
{
    struct open_how how = {0};
    struct stat st;
    unsigned char digest[32];
    unsigned int n;
    char actual[65];
    int fd = -1, ok = 0;
    size_t used = 0, i;
    if (!valid_owner_path(path) || holy_install_manifest_owns(files, path) != 1 ||
        holy_install_check_path(files, root, path) != 1) return 0;
    how.flags = O_RDONLY | O_CLOEXEC;
    how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS;
    fd = (int)syscall(SYS_openat2, root, path, &how, sizeof how);
    if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 1024 * 1024) goto done;
    *body = malloc((size_t)st.st_size + 1);
    if (!*body) goto done;
    while (used < (size_t)st.st_size) {
        ssize_t got = read(fd, *body + used, (size_t)st.st_size - used);
        if (got < 0 && errno == EINTR) continue;
        if (got <= 0) goto done;
        used += (size_t)got;
    }
    for (i = 0; i < used; ++i)
        if (((unsigned char)(*body)[i] < 32 && (*body)[i] != '\n' && (*body)[i] != '\t') ||
            (unsigned char)(*body)[i] == 127) goto done;
    (*body)[used] = 0;
    if (EVP_Digest(*body, used, digest, &n, EVP_sha256(), NULL) != 1 || n != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(actual + i * 2, 3, "%02x", digest[i]);
    if (strcmp(actual, expected)) goto done;
    *length = used;
    ok = 1;
done:
    if (!ok) { free(*body); *body = NULL; }
    if (fd >= 0) close(fd);
    return ok;
}

static int hook_interpreter_digest(int root, const char *path, char output[65])
{
    struct open_how how = {0};
    struct stat st;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    unsigned char buffer[8192], digest[32];
    unsigned int size;
    ssize_t got;
    int fd, ok = 0;
    size_t i;
    if (!ctx) return 0;
    how.flags = O_RDONLY | O_CLOEXEC;
    how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS;
    fd = (int)syscall(SYS_openat2, root, path, &how, sizeof how);
    if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || !(st.st_mode & 0111) ||
        EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) goto done;
    for (;;) {
        got = read(fd, buffer, sizeof buffer);
        if (got < 0 && errno == EINTR) continue;
        if (got < 0) goto done;
        if (!got) break;
        if (EVP_DigestUpdate(ctx, buffer, (size_t)got) != 1) goto done;
    }
    if (EVP_DigestFinal_ex(ctx, digest, &size) != 1 || size != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(output + i * 2, 3, "%02x", digest[i]);
    ok = 1;
done:
    if (fd >= 0) close(fd);
    EVP_MD_CTX_free(ctx);
    return ok;
}

static int prepare_hook_plan(int root, int item, const char *artifact,
                             unsigned long long generation, struct hook_plan *plan)
{
    char *hooks = update_record(item, "hooks"), *cursor;
    char number[32], device[96];
    struct stat st;
    EVP_MD_CTX *hash = EVP_MD_CTX_new();
    unsigned char bytes[32];
    unsigned int n;
    int files = -1, ok = 0;
    size_t i, line = 0;
    if (!hooks || !*hooks || !hash || fstat(root, &st)) goto done;
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    if (files < 0) goto done;
    snprintf(device, sizeof device, "%ju:%ju", (uintmax_t)st.st_dev, (uintmax_t)st.st_ino);
    snprintf(number, sizeof number, "%llu", generation);
    if (EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1 ||
        !hash_text(hash, "holy-hook-plan-1") || !hash_text(hash, device) ||
        !hash_text(hash, number) || !hash_text(hash, artifact) || !hash_text(hash, hooks)) goto done;
    for (cursor = hooks; *cursor; ) {
        char *end = strchr(cursor, '\n'), **v = NULL, *error = NULL;
        char interpreter_hash[65];
        size_t count = 0;
        struct hook_step *step;
        if (!end || plan->count >= 64 ||
            !holy_lex(cursor, (size_t)(end - cursor), &v, &count,
                      "installed/hooks", ++line, &error)) {
            free(error); holy_tokens_free(v, count); goto done;
        }
        if (!count) { holy_tokens_free(v, count); cursor = end + 1; continue; }
        if (count != 6 || strcmp(v[0], "hook") || strcmp(v[1], "postinstall") ||
            v[2][0] != '/' || !valid_owner_path(v[2] + 1) ||
            !valid_owner_path(v[3]) || strcmp(v[4], "sha256") ||
            !valid_digest(v[5])) {
            holy_tokens_free(v, count); goto done;
        }
        step = &plan->steps[plan->count];
        step->interpreter = strdup(v[2]);
        step->path = strdup(v[3]);
        if (!step->interpreter || !step->path ||
            !hook_interpreter_digest(root, step->interpreter, interpreter_hash) ||
            !hook_body(root, files, step->path, v[5], &step->body, &step->length) ||
            !hash_text(hash, v[2]) || !hash_text(hash, v[3]) ||
            !hash_text(hash, v[5]) || !hash_text(hash, interpreter_hash) ||
            !hash_text(hash, step->body)) {
            free(step->interpreter); free(step->path); free(step->body);
            memset(step, 0, sizeof *step);
            holy_tokens_free(v, count); goto done;
        }
        ++plan->count;
        holy_tokens_free(v, count);
        cursor = end + 1;
    }
    if (!plan->count || EVP_DigestFinal_ex(hash, bytes, &n) != 1 || n != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(plan->hash + i * 2, 3, "%02x", bytes[i]);
    ok = 1;
done:
    if (files >= 0) close(files);
    EVP_MD_CTX_free(hash);
    free(hooks);
    return ok;
}

static int hook_journal_write(int transactions, unsigned long long generation,
                              const char *artifact, const char *plan,
                              const char *stage, size_t next)
{
    char record[320], temporary[43] = {0};
    int fd, ok = 0;
    int length = snprintf(record, sizeof record,
        "format holy-hook-journal-1\ngeneration %llu\nartifact %s\nplan %s\nstage %s\nnext %zu\n",
        generation, artifact, plan, stage, next);
    if (length < 0 || length >= (int)sizeof record) return 0;
    fd = holy_temporary_at(transactions, temporary);
    if (fd < 0) return 0;
    if (write_all(fd, record, (size_t)length) && !fsync(fd) && !close(fd)) {
        fd = -1;
        if (!renameat(transactions, temporary, transactions, "hook-journal") &&
            !fsync(transactions)) ok = 1;
    }
    if (fd >= 0) close(fd);
    if (!ok) unlinkat(transactions, temporary, 0);
    return ok;
}

static int hook_journal_read(int transactions, unsigned long long *generation,
                             char artifact[65], char plan[65],
                             char stage[8], size_t *next)
{
    char *record = update_record(transactions, "hook-journal"), canonical[320];
    unsigned long long saved;
    unsigned long index;
    char digest[65], checksum[65], phase[8], extra;
    int n, result = 0;
    if (!record || strlen(record) > 319 ||
        sscanf(record, "format holy-hook-journal-1\ngeneration %llu\nartifact %64[0-9a-f]\nplan %64[0-9a-f]\nstage %7[a-z]\nnext %lu\n%c",
               &saved, digest, checksum, phase, &index, &extra) != 5 ||
        !valid_digest(digest) || !valid_digest(checksum) ||
        (strcmp(phase, "ready") && strcmp(phase, "running"))) goto done;
    n = snprintf(canonical, sizeof canonical,
        "format holy-hook-journal-1\ngeneration %llu\nartifact %s\nplan %s\nstage %s\nnext %lu\n",
        saved, digest, checksum, phase, index);
    if (n < 0 || n >= (int)sizeof canonical || strcmp(record, canonical)) goto done;
    *generation = saved; *next = (size_t)index;
    strcpy(artifact, digest); strcpy(plan, checksum); strcpy(stage, phase);
    result = 1;
done:
    free(record);
    return result;
}

static int hook_journal_clean_temps(int transactions)
{
    DIR *list = directory_stream(transactions);
    struct dirent *entry;
    int ok = 1;
    if (!list) return 0;
    errno = 0;
    while ((entry = readdir(list))) {
        struct stat st;
        if (!temporary_name(entry->d_name)) { errno = 0; continue; }
        if (fstatat(transactions, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) ||
            !S_ISREG(st.st_mode) || (st.st_mode & 0022) ||
            (st.st_uid != 0 && st.st_uid != geteuid()) ||
            unlinkat(transactions, entry->d_name, 0)) { ok = 0; break; }
        errno = 0;
    }
    if (!entry && errno) ok = 0;
    closedir(list);
    return ok && !fsync(transactions);
}

static int run_hook_step(int root, const struct hook_step *step, int *exit_status)
{
    pid_t child;
    int status;
    char *script = malloc(strlen(step->path) + 2);
    *exit_status = -1;
    if (!script) return 0;
    script[0] = '/';
    strcpy(script + 1, step->path);
    child = fork();
    if (child < 0) { free(script); return 0; }
    if (!child) {
        char *const argv[] = {step->interpreter, script, NULL};
        char *const env[] = {"PATH=/usr/bin:/bin", "HOME=/", "LANG=C", NULL};
        if (fchdir(root) || chroot(".") || chdir("/")) _exit(127);
        execve(step->interpreter, argv, env);
        _exit(127);
    }
    free(script);
    while (waitpid(child, &status, 0) < 0) if (errno != EINTR) return 0;
    if (WIFEXITED(status)) *exit_status = WEXITSTATUS(status);
    else if (WIFSIGNALED(status)) *exit_status = 128 + WTERMSIG(status);
    return *exit_status == 0;
}

static int complete_hooks(int item, int transactions, int dir,
                          unsigned long long generation)
{
    char hooks_hash[65], completed[96], existing[96];
    struct stat st;
    int fd, n;
    if (!instance_record_digest(item, "hooks", hooks_hash)) return 0;
    n = snprintf(completed, sizeof completed, "completed sha256 %s\n", hooks_hash);
    if (n < 0 || n >= (int)sizeof completed) return 0;
    fd = openat(transactions, "hook-state-new", O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
    if (fd >= 0) {
        int written = write_all(fd, completed, (size_t)n) && !fsync(fd);
        if (close(fd)) written = 0;
        if (!written || fsync(transactions)) return 0;
    } else if (errno == EEXIST) {
        fd = openat(transactions, "hook-state-new", O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
        if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) ||
            st.st_size != n || (st.st_mode & 0022) ||
            (st.st_uid != 0 && st.st_uid != geteuid()) ||
            read(fd, existing, (size_t)n) != n || memcmp(existing, completed, (size_t)n)) {
            if (fd >= 0) close(fd);
            return 0;
        }
        close(fd);
    } else return 0;
    if (renameat(transactions, "hook-state-new", item, "hooks-state") ||
        fsync(transactions) || fsync(item)) return 0;
    if (!set_generation(dir, generation + 1) ||
        unlinkat(transactions, "hook-journal", 0) || fsync(transactions)) return 0;
    return 1;
}

int holy_state_configure(const char *digest, const char *approved,
                         const char *root_path, int retry)
{
    struct hook_plan plan = {0};
    unsigned long long generation = 0, saved_generation = 0;
    char saved_artifact[65], saved_plan[65], stage[8];
    int root = -1, dir = -1, installed = -1, item = -1, transactions = -1;
    int result = 1;
    size_t i, next = 0;
    if (!valid_digest(digest) || (approved && !valid_digest(approved))) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0) goto done;
    if (!retry) {
        dir = holy_state_lock(root_path, approved != NULL, &generation, &result);
        if (dir < 0) goto done;
    } else {
        dir = state_dir_at(root, 0);
        if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
            !read_generation(dir, &generation) || !installed_valid(dir)) goto done;
    }
    result = 1;
    installed = child_dir(dir, "installed", 0);
    transactions = child_dir(dir, "transactions", 0);
    item = installed < 0 ? -1 : child_dir(installed, digest, 0);
    if (transactions < 0 || item < 0) { result = 6; goto done; }
    if (retry) {
        if (!hook_journal_read(transactions, &saved_generation, saved_artifact,
                               saved_plan, stage, &next) ||
            (saved_generation != generation && saved_generation + 1 != generation) ||
            strcmp(saved_artifact, digest)) {
            result = 5; goto done;
        }
        if (!hook_journal_clean_temps(transactions)) { result = 5; goto done; }
        if (saved_generation + 1 == generation) {
            char *record = update_record(item, "hooks-state");
            int completed = record && !strncmp(record, "completed sha256 ", 17) &&
                !strcmp(stage, "ready");
            free(record);
            if (!completed || unlinkat(transactions, "hook-journal", 0) || fsync(transactions)) {
                result = 5; goto done;
            }
            printf("configured %s generation %llu recovered\n", digest, generation);
            result = 0; goto done;
        }
    } else if (!hook_skipped(item)) { result = 6; goto done; }
    result = 6;
    if (!prepare_hook_plan(root, item, digest, generation, &plan)) goto done;
    if (retry) {
        if (strcmp(saved_plan, plan.hash) || next > plan.count) { result = 5; goto done; }
    } else if (!approved) {
        printf("configure-plan generation %llu artifact %s hooks %zu sha256 %s read-only\n",
               generation, digest, plan.count, plan.hash);
        for (i = 0; i < plan.count; ++i) {
            printf("hook %zu postinstall interpreter %s script /%s uid 0 root %s\n",
                   i, plan.steps[i].interpreter, plan.steps[i].path, root_path);
            fwrite(plan.steps[i].body, 1, plan.steps[i].length, stdout);
            if (!plan.steps[i].length || plan.steps[i].body[plan.steps[i].length - 1] != '\n') putchar('\n');
        }
        result = ferror(stdout) ? 1 : 0;
        goto done;
    } else if (strcmp(approved, plan.hash)) { result = 3; goto done; }
    if (geteuid() != 0) { result = 6; goto done; }
    if (!retry) {
        if (!hook_journal_write(transactions, generation, digest, plan.hash, "ready", 0)) {
            result = 1; goto done;
        }
    } else if (!strcmp(stage, "running")) {
        fprintf(stderr, "holypkg: retrying hook %zu after unknown result by explicit request\n", next);
    }
    result = 5;
    for (i = next; i < plan.count; ++i) {
        int succeeded, exit_status;
        if (!hook_journal_write(transactions, generation, digest, plan.hash, "running", i)) goto done;
        if (flock(dir, LOCK_UN)) goto done;
        succeeded = run_hook_step(root, &plan.steps[i], &exit_status);
        if (flock(dir, LOCK_EX)) goto done;
        {
            unsigned long long current;
            if (!read_generation(dir, &current) || current != generation ||
                !installed_valid(dir)) goto done;
        }
        if (!succeeded) {
            fprintf(stderr, "holypkg: hook %zu exit=%d, external effects unknown; explicit retry required\n",
                    i, exit_status);
            goto done;
        }
        if (!hook_journal_write(transactions, generation, digest, plan.hash, "ready", i + 1)) goto done;
    }
    if (!complete_hooks(item, transactions, dir, generation)) goto done;
    printf("configured %s generation %llu hooks %zu\n", digest, generation + 1, plan.count);
    result = 0;
done:
    if (result && result != 3 && result != 6 && result != 5)
        fprintf(stderr, "holypkg: hook configuration failed (status %d)\n", result);
    free_hook_plan(&plan);
    if (item >= 0) close(item);
    if (transactions >= 0) close(transactions);
    if (installed >= 0) close(installed);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

static int same_slot(const struct holy_package_identity *a, const char *a_source,
                     const struct holy_package_identity *b, const char *b_source)
{
    return !strcmp(a_source, b_source) && !strcmp(a->name, b->name) &&
           !strcmp(a->os, b->os) && !strcmp(a->arch, b->arch) && !strcmp(a->libc, b->libc);
}

static int slot_available_except(int dir, const struct holy_package_identity *identity,
                                 const char *source_id, const char *replaced)
{
    const char *keys[] = {"name", "os", "arch", "libc"};
    const char *values[] = {identity->name, identity->os, identity->arch, identity->libc};
    int installed = child_dir(dir, "installed", 0), available = -1;
    DIR *list;
    struct dirent *entry;
    if (installed < 0) return -1;
    list = directory_stream(installed);
    if (!list) { close(installed); return -1; }
    errno = 0;
    while ((entry = readdir(list))) {
        int item, match;
        char source[65];
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        if (replaced && !strcmp(entry->d_name, replaced)) { errno = 0; continue; }
        if (!strcmp(entry->d_name, identity->digest)) { available = 0; goto done; }
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) goto done;
        match = installed_fields(item, keys, values, 4);
        if (match > 0) {
            if (!installed_source_id(item, source)) match = -1;
            else match = !strcmp(source_id, source);
        }
        close(item);
        if (match < 0) goto done;
        if (match) { available = 0; goto done; }
        errno = 0;
    }
    if (!errno) available = 1;
done:
    closedir(list);
    close(installed);
    return available;
}

static int slot_available(int dir, const struct holy_package_identity *identity,
                          const char *source_id)
{
    return slot_available_except(dir, identity, source_id, NULL);
}

static int inspect_plan(const char *root_path, int root, int dir,
                        unsigned long long generation, const char *digest,
                        char output[65], size_t *paths, char **graph_output, size_t *graph_length)
{
    struct holy_package_identity identity = {0};
    struct plan_hash plan = {0};
    struct holy_resolution resolution = {0};
    char *graph = NULL;
    size_t graph_size = 0;
    char generation_text[32], root_id[128];
    unsigned char checksum[32];
    unsigned int checksum_size;
    size_t i;
    struct stat root_st;
    struct utsname host;
    int result = 1;
    char *snapshot = NULL;
    if (fstat(root, &root_st)) goto done;
    snapshot = holy_cache_snapshot(digest, root_path);
    if (!snapshot) { result = 6; goto done; }
    result = holy_preview_local_format(snapshot, root_path, -1);
    if (result) goto done;
    result = 6;
    if (!holy_extract_preflight(snapshot) || !recorded_transform(snapshot) ||
        !holy_package_identity(snapshot, &identity) ||
        strcmp(identity.os, "linux") ||
        strcmp(identity.libc, "nolibc") || strcmp(identity.digest, digest)) goto done;
    if (strcmp(identity.arch, "noarch") &&
        (uname(&host) ||
         !((!strcmp(identity.arch, "x86_64") && !strcmp(host.machine, "x86_64")) ||
           (!strcmp(identity.arch, "x86") && !strcmp(host.machine, "i686"))))) {
        fprintf(stderr, "holypkg: plan requires native host architecture for static executables\n");
        goto done;
    }
    {
        const char *inputs[] = {snapshot};
        result = holy_resolve_collect(inputs, 1, NULL, &resolution);
        if (result) goto done;
        result = 1;
        if (!holy_resolution_record(&resolution, &graph, &graph_size)) goto done;
    }
    plan.hash = EVP_MD_CTX_new();
    plan.dir = dir;
    if (!plan.hash) { result = 1; goto done; }
    snprintf(generation_text, sizeof generation_text, "%llu", generation);
    if (snprintf(root_id, sizeof root_id, "%ju:%ju",
                 (uintmax_t)root_st.st_dev, (uintmax_t)root_st.st_ino) >=
        (int)sizeof root_id) { result = 1; goto done; }
    result = 6;
    if (EVP_DigestInit_ex(plan.hash, EVP_sha256(), NULL) != 1 ||
        !hash_text(plan.hash, "holy-readonly-plan-2") ||
        !hash_text(plan.hash, root_id) ||
        !hash_text(plan.hash, generation_text) || !hash_text(plan.hash, digest) ||
        !hash_text(plan.hash, graph) ||
        !holy_verify_visit(snapshot, plan_entry, &plan)) {
        if (plan.claim_error) result = plan.claim_error;
        goto done;
    }
    if (!holy_install_preflight(snapshot, root, 0)) { result = 4; goto done; }
    result = slot_available(dir, &identity, "-");
    if (result < 0) { result = 1; goto done; }
    if (!result) {
        fprintf(stderr, "holypkg: installed package slot already active: %s\n", identity.name);
        result = 4;
        goto done;
    }
    if (EVP_DigestFinal_ex(plan.hash, checksum, &checksum_size) != 1 ||
        checksum_size != sizeof checksum) { result = 1; goto done; }
    if (!same_root(root_path, &root_st)) { result = 4; goto done; }
    for (i = 0; i < sizeof checksum; ++i)
        snprintf(output + i * 2, 3, "%02x", checksum[i]);
    output[64] = '\0';
    *paths = plan.count;
    if (graph_output) {
        *graph_output = graph;
        *graph_length = graph_size;
        graph = NULL;
    }
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: cannot form install plan (status %d)\n", result);
    free(graph);
    holy_resolution_free(&resolution);
    EVP_MD_CTX_free(plan.hash);
    holy_package_identity_free(&identity);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    return result;
}

int holy_state_plan(const char *root_path)
{
    struct stat root_st;
    unsigned long long generation;
    char digest[65], hash[65], approved[65];
    size_t paths;
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, 0), pending, result = 1;
    if (root < 0 || dir < 0 || flock(dir, LOCK_SH) ||
        !state_layout(dir, 0) || !installed_valid(dir) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) goto done;
    result = update_pending(dir);
    if (result) { result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    pending = pending_child(dir, generation, digest, approved);
    if (pending < 0) goto done;
    if (!pending) { result = 6; goto done; }
    result = inspect_plan(root_path, root, dir, generation, digest, hash, &paths, NULL, NULL);
    if (result) goto done;
    if (fstat(root, &root_st)) { result = 1; goto done; }
    printf("plan root %ju:%ju generation %llu artifact %s paths %zu sha256 %s read-only\n",
           (uintmax_t)root_st.st_dev, (uintmax_t)root_st.st_ino,
           generation, digest, paths, hash);
done:
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_approve(const char *hash, const char *root_path)
{
    unsigned long long generation;
    char digest[65], approved[65], actual[65], record[256];
    char temp_name[43] = {0};
    size_t paths, length;
    int root, dir = -1, transactions = -1, temp = -1, result = 1;
    if (!valid_digest(hash)) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0) goto done;
    dir = state_dir_at(root, 0);
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !installed_valid(dir) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation)) goto done;
    result = update_pending(dir);
    if (result) { result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    result = pending_child(dir, generation, digest, approved);
    if (result < 0) { result = 1; goto done; }
    if (!result) { result = 6; goto done; }
    if (approved[0]) { result = 5; goto done; }
    result = inspect_plan(root_path, root, dir, generation, digest, actual, &paths, NULL, NULL);
    if (result) goto done;
    if (strcmp(hash, actual)) { result = 3; goto done; }
    result = 1;
    transactions = child_dir(dir, "transactions", 0);
    if (transactions < 0) goto done;
    length = (size_t)snprintf(record, sizeof record,
        "format holy-reservation-1\nstage approved\ngeneration %llu\nartifact %s\nplan %s\n",
        generation, digest, actual);
    if (length >= sizeof record) goto done;
    temp = holy_temporary_at(transactions, temp_name);
    if (temp < 0 || write(temp, record, length) != (ssize_t)length ||
        fsync(temp)) goto done;
    if (close(temp)) { temp = -1; goto done; }
    temp = -1;
    if (renameat(transactions, temp_name, transactions, "pending") ||
        fsync(transactions)) goto done;
    printf("approved %s generation %llu artifact %s\n", actual, generation, digest);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: plan approval failed (status %d)\n", result);
    if (temp >= 0) close(temp);
    if (temp_name[0] && transactions >= 0) unlinkat(transactions, temp_name, 0);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_recheck(const char *root_path)
{
    unsigned long long generation;
    char digest[65], approved[65], actual[65];
    size_t paths;
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, 0), pending, result = 1;
    if (dir < 0 || flock(dir, LOCK_SH) || !state_layout(dir, 0) ||
        !installed_valid(dir) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation)) goto done;
    result = update_pending(dir);
    if (result) { result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    pending = pending_child(dir, generation, digest, approved);
    if (pending < 0) goto done;
    if (!pending || !approved[0]) { result = 5; goto done; }
    result = inspect_plan(root_path, root, dir, generation, digest, actual, &paths, NULL, NULL);
    if (result) goto done;
    if (strcmp(approved, actual)) { result = 3; goto done; }
    printf("rechecked %s generation %llu artifact %s paths %zu\n",
           approved, generation, digest, paths);
done:
    if (result) fprintf(stderr, "holypkg: approved plan recheck failed (status %d)\n", result);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

static int write_all(int fd, const void *data, size_t length)
{
    const char *p = data;
    while (length) {
        ssize_t sent = write(fd, p, length);
        if (sent < 0 && errno == EINTR) continue;
        if (sent <= 0) return 0;
        p += sent;
        length -= (size_t)sent;
    }
    return 1;
}

static int record_file(int dir, const char *name, const void *data, size_t length)
{
    int fd = openat(dir, name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
    int ok;
    if (fd < 0) return 0;
    ok = write_all(fd, data, length) && !fsync(fd);
    if (close(fd)) ok = 0;
    return ok && !fsync(dir);
}

static int instance_preflight(const char *snapshot)
{
    static const char *const names[] = {
        "HOLY/meta", "HOLY/files", "HOLY/deps", "HOLY/origin", "HOLY/provides"
    };
    struct archive *archive = archive_read_new();
    struct archive_entry *entry;
    int status, ok = 0;
    unsigned seen = 0;
    size_t i;
    if (!archive) return 0;
    if (archive_read_support_filter_lz4(archive) != ARCHIVE_OK ||
        archive_read_support_format_tar(archive) != ARCHIVE_OK ||
        archive_read_open_filename(archive, snapshot, 8192) != ARCHIVE_OK) goto done;
    while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
        const char *path = archive_entry_pathname(entry);
        for (i = 0; i < sizeof names / sizeof *names; ++i)
            if (path && !strcmp(path, names[i])) break;
        if (i < sizeof names / sizeof *names) {
            if (seen & (1u << i) || archive_entry_filetype(entry) != AE_IFREG ||
                archive_entry_size(entry) < 0 ||
                archive_entry_size(entry) > 16 * 1024 * 1024) goto done;
            seen |= 1u << i;
        }
        if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
    }
    ok = status == ARCHIVE_EOF && seen == (1u << 5) - 1u;
done:
    archive_read_free(archive);
    return ok;
}

static char *read_hooks(const char *snapshot, size_t *length)
{
    struct archive *archive = archive_read_new();
    struct archive_entry *entry;
    char *text = NULL;
    int status;
    *length = 0;
    if (!archive || archive_read_support_filter_lz4(archive) != ARCHIVE_OK ||
        archive_read_support_format_tar(archive) != ARCHIVE_OK ||
        archive_read_open_filename(archive, snapshot, 8192) != ARCHIVE_OK) goto done;
    while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
        const char *path = archive_entry_pathname(entry);
        la_int64_t size = archive_entry_size(entry);
        if (!path || strcmp(path, "HOLY/hooks")) {
            if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
            continue;
        }
        if (text || archive_entry_filetype(entry) != AE_IFREG || size < 0 ||
            size > 1024 * 1024) goto done;
        text = malloc((size_t)size + 1);
        if (!text) goto done;
        {
            size_t used = 0;
            while (used < (size_t)size) {
                la_ssize_t got = archive_read_data(archive, text + used, (size_t)size - used);
                if (got <= 0) goto done;
                used += (size_t)got;
            }
        }
        {
            size_t i;
            for (i = 0; i < (size_t)size; ++i)
                if (((unsigned char)text[i] < 32 && text[i] != '\n' && text[i] != '\t') ||
                    (unsigned char)text[i] == 127) goto done;
        }
        text[size] = 0;
        *length = (size_t)size;
    }
    if (status == ARCHIVE_EOF && text) {
        archive_read_free(archive);
        return text;
    }
done:
    free(text);
    if (archive) archive_read_free(archive);
    return NULL;
}

static int save_instance(int installed, const char *digest, const char *snapshot,
                         unsigned long long generation, const char *graph, size_t graph_length,
                         const char *reason, const char *source_record,
                         const char *architecture, int privileged, int skip_hooks)
{
    static const char *const names[] = { "meta", "files", "deps", "origin", "provides", "hooks", "transform" };
    struct archive *archive = NULL;
    struct archive_entry *entry;
    char buffer[65536], state[1024], graph_hash[65], source[65], source_hash[65], claims[65];
    int item = -1, status, ok = 0;
    unsigned seen = 0;
    size_t i;
    if (architecture && !architecture_valid(architecture)) return 0;
    if (mkdirat(installed, digest, 0700)) return 0;
    if (fsync(installed)) return 0;
    item = child_dir(installed, digest, 0);
    if (item < 0) goto done;
    archive = archive_read_new();
    if (!archive || archive_read_support_filter_lz4(archive) != ARCHIVE_OK ||
        archive_read_support_format_tar(archive) != ARCHIVE_OK ||
        archive_read_open_filename(archive, snapshot, 8192) != ARCHIVE_OK) goto done;
    while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
        const char *path = archive_entry_pathname(entry);
        int fd;
        la_ssize_t got;
        la_int64_t count = 0;
        for (i = 0; i < sizeof names / sizeof *names; ++i)
            if (path && !strncmp(path, "HOLY/", 5) && !strcmp(path + 5, names[i])) break;
        if (i == sizeof names / sizeof *names) {
            if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
            continue;
        }
        if (seen & (1u << i) || archive_entry_filetype(entry) != AE_IFREG ||
            archive_entry_size(entry) < 0 || archive_entry_size(entry) > 16 * 1024 * 1024)
            goto done;
        fd = openat(item, names[i], O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
        if (fd < 0) goto done;
        while ((got = archive_read_data(archive, buffer, sizeof buffer)) > 0) {
            if (count > archive_entry_size(entry) - got ||
                !write_all(fd, buffer, (size_t)got)) break;
            count += got;
        }
        if (got != 0 || count != archive_entry_size(entry) || fsync(fd)) {
            close(fd);
            goto done;
        }
        if (close(fd)) goto done;
        seen |= 1u << i;
    }
    if (status != ARCHIVE_EOF || seen != (1u << 7) - 1u) goto done;
    {
        char hooks_hash[65], hook_state[96];
        struct stat hooks_stat;
        int hooks = openat(item, "hooks", O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
        int has_hooks = hooks >= 0 && !fstat(hooks, &hooks_stat) && hooks_stat.st_size > 0;
        if (hooks >= 0) close(hooks);
        if (has_hooks != !!skip_hooks) goto done;
        if (has_hooks) {
            size_t n;
            if (!instance_record_digest(item, "hooks", hooks_hash)) goto done;
            n = (size_t)snprintf(hook_state, sizeof hook_state, "skipped sha256 %s\n", hooks_hash);
            if (n >= sizeof hook_state || !record_file(item, "hooks-state", hook_state, n)) goto done;
        }
    }
    if (!record_file(item, "graph", graph, graph_length) ||
        !graph_digest(item, graph_hash)) goto done;
    if (!instance_record_digest(item, "provides", claims)) goto done;
    strcpy(source, "-"); strcpy(source_hash, "-");
    if (source_record && (!record_file(item, "source", source_record, strlen(source_record)) ||
        !holy_source_instance(item, source, source_hash))) goto done;
    i = (size_t)snprintf(state, sizeof state,
        "format holy-instance-%d\nsource-id %s\nsource-record %s\ndelivery local\nreason %s\nartifact %s\ngraph %s\nprovides %s\n%s%s%s%s%s%sgeneration %llu\n",
        privileged ? architecture ? 7 : 6 : architecture ? 5 : 4,
        source, source_hash, reason, digest, graph_hash, claims,
        architecture ? "architecture " : "", architecture ? architecture : "", architecture ? "\n" : "",
        privileged ? "privileged " : "", privileged ? digest : "", privileged ? "\n" : "",
        generation + 1);
    if (i >= sizeof state || !record_file(item, "state", state, i) || fsync(item)) goto done;
    ok = 1;
done:
    if (archive) archive_read_free(archive);
    if (item >= 0) close(item);
    return ok;
}

int holy_state_apply(const char *root_path)
{
    char digest[65], approved[65], actual[65], journal[256], generation_record[32];
    char temp_name[43] = {0};
    char *snapshot = NULL, *graph = NULL;
    size_t graph_length = 0;
    unsigned long long generation;
    struct stat st;
    size_t paths, length;
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, 0);
    int transactions = -1, installed = -1, temp = -1, journaled = 0, result = 1;
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !installed_valid(dir) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation) || generation == ULLONG_MAX) goto done;
    result = update_pending(dir);
    if (result) { result = result > 0 ? 5 : 1; goto done; }
    result = 1;
    result = pending_child(dir, generation, digest, approved);
    if (result < 0) { result = 1; goto done; }
    if (!result || !approved[0]) { result = 5; goto done; }
    result = inspect_plan(root_path, root, dir, generation, digest, actual, &paths, &graph, &graph_length);
    if (result) goto done;
    if (strcmp(actual, approved)) { result = 3; goto done; }
    snapshot = holy_cache_snapshot(digest, root_path);
    if (!snapshot) { result = 6; goto done; }
    if (!instance_preflight(snapshot)) { result = 6; goto done; }
    if (!holy_install_preflight(snapshot, root, 0)) { result = 4; goto done; }
    installed = child_dir(dir, "installed", 0);
    transactions = child_dir(dir, "transactions", 0);
    if (installed < 0 || transactions < 0 ||
        !fstatat(installed, digest, &st, AT_SYMLINK_NOFOLLOW) ||
        errno != ENOENT) { result = 4; goto done; }
    length = (size_t)snprintf(journal, sizeof journal,
        "format holy-journal-1\nstage applying\ngeneration %llu\nartifact %s\nplan %s\n",
        generation, digest, approved);
    if (length >= sizeof journal || !record_file(transactions, "journal", journal, length)) {
        result = journal_exists(dir) ? 5 : 1;
        goto done;
    }
    journaled = 1;
    result = 5;
    if (!holy_install_payload(snapshot, root, 0) ||
        !save_instance(installed, digest, snapshot, generation, graph, graph_length, "explicit", NULL, NULL, 0, 0)) goto done;
    length = (size_t)snprintf(generation_record, sizeof generation_record,
                              "%llu\n", generation + 1);
    if (length >= sizeof generation_record) goto done;
    temp = holy_temporary_at(dir, temp_name);
    if (temp < 0 || !write_all(temp, generation_record, length) || fsync(temp)) goto done;
    if (close(temp)) { temp = -1; goto done; }
    temp = -1;
    if (renameat(dir, temp_name, dir, "generation") || fsync(dir) ||
        unlinkat(transactions, "pending", 0) || fsync(transactions) ||
        unlinkat(transactions, "journal", 0) || fsync(transactions)) goto done;
    printf("installed %s generation %llu paths %zu\n", digest, generation + 1, paths);
    result = 0;
done:
    free(graph);
    if (result) fprintf(stderr, "holypkg: apply failed (status %d)%s\n", result,
                        journaled ? "; inspect incomplete transaction" : "");
    if (temp >= 0) close(temp);
    if (temp_name[0] && dir >= 0) unlinkat(dir, temp_name, 0);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    if (installed >= 0) close(installed);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_abort_empty(const char *root_path)
{
    unsigned long long generation, recorded;
    char digest[65], plan[65], reserved[65], approved[65];
    struct stat st, root_st;
    char *snapshot = NULL;
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, 0);
    int transactions = -1, installed = -1, result = 1, removing = 0;
    if (dir < 0 || fstat(root, &root_st) || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) goto done;
    result = journal_valid(dir, generation, &recorded, digest, plan, &removing);
    if (result < 0) { result = 1; goto done; }
    if (!result) { result = 5; goto done; }
    result = 5;
    if (removing || recorded != generation || !installed_valid(dir)) goto done;
    transactions = child_dir(dir, "transactions", 0);
    installed = child_dir(dir, "installed", 0);
    if (transactions < 0 || installed < 0 ||
        read_reservation(transactions, "pending", generation, reserved, approved) != 1 ||
        strcmp(reserved, digest) || strcmp(approved, plan) ||
        !fstatat(installed, digest, &st, AT_SYMLINK_NOFOLLOW) || errno != ENOENT) goto done;
    snapshot = holy_cache_snapshot(digest, root_path);
    if (!snapshot || !holy_install_preflight(snapshot, root, 0) ||
        !same_root(root_path, &root_st)) goto done;
    if (unlinkat(transactions, "journal", 0) || fsync(transactions)) goto done;
    printf("aborted empty apply %s; approval retained\n", digest);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: cannot abort incomplete transaction without manual review\n");
    if (snapshot) { unlink(snapshot); free(snapshot); }
    if (installed >= 0) close(installed);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

struct check_finding {
    char *path;
    char *code;
    char *target;
};

struct check_result {
    char digest[65];
    char architecture[96];
    int intact;
    struct check_finding *findings;
    size_t count;
};

static void free_check_result(struct check_result *record)
{
    size_t i;
    for (i = 0; i < record->count; ++i) {
        free(record->findings[i].path);
        free(record->findings[i].code);
        free(record->findings[i].target);
    }
    free(record->findings);
}

static int collect_finding(void *context, const char *path, const char *code,
                           const char *target)
{
    struct check_result *record = context;
    struct check_finding *grown;
    char *copy, *code_copy, *target_copy = target ? strdup(target) : NULL;
    if (record->count >= SIZE_MAX / sizeof *grown) return 0;
    if (target && !target_copy) return 0;
    copy = strdup(path);
    if (!copy) { free(target_copy); return 0; }
    code_copy = strdup(code);
    if (!code_copy) { free(copy); free(target_copy); return 0; }
    grown = realloc(record->findings, (record->count + 1) * sizeof *grown);
    if (!grown) { free(copy); free(code_copy); free(target_copy); return 0; }
    record->findings = grown;
    grown[record->count].path = copy;
    grown[record->count].code = code_copy;
    grown[record->count++].target = target_copy;
    return 1;
}

static int check_status(int checked, const struct check_result *record)
{
    size_t i;
    if (checked > 0) return 1;
    if (checked < 0 || !record->count) return checked;
    for (i = 0; i < record->count; ++i)
        if (strcmp(record->findings[i].code, "unknown-interpreter") &&
            strcmp(record->findings[i].code, "unavailable-path-resolution") &&
            strcmp(record->findings[i].code, "unknown-loader-context")) return 0;
    return 2;
}

static int check_unavailable(const struct check_result *record)
{
    size_t i;
    for (i = 0; i < record->count; ++i)
        if (!strcmp(record->findings[i].code, "unavailable-path-resolution")) return 1;
    return 0;
}

static void print_check_string(const char *value)
{
    const unsigned char *p = (const unsigned char *)value;
    putchar('"');
    for (; *p; ++p) {
        if (*p == '"' || *p == '\\') printf("\\%c", *p);
        else if (*p < 32 || *p >= 127) printf("\\u%04x", (unsigned)*p);
        else putchar(*p);
    }
    putchar('"');
}

static int print_check_result(const struct check_result *record,
                               unsigned long long generation)
{
    size_t i, primary = 0;
    if (record->intact == 0)
        for (i = 0; i < record->count; ++i)
            if (strcmp(record->findings[i].code, "unknown-interpreter") &&
                strcmp(record->findings[i].code, "unavailable-path-resolution")) {
                primary = i;
                break;
            }
    printf("{\"schema\":\"holy-installed-check-1\",\"type\":\"artifact\",\"artifact\":\"%s\",\"state\":\"%s\",\"code\":",
           record->digest, record->intact == 1 ? "pass" : record->intact == 2 ? "unknown" : "fail");
    if (record->intact == 1 || !record->count) fputs("null", stdout);
    else print_check_string(record->findings[primary].code);
    printf(",\"generation\":%llu,\"findings\":[", generation);
    for (i = 0; i < record->count; ++i) {
        const struct check_finding *finding = &record->findings[i];
        int unknown = !strcmp(finding->code, "unknown-interpreter") ||
                      !strcmp(finding->code, "unavailable-path-resolution") ||
                      !strcmp(finding->code, "unknown-loader-context") ||
                      !strcmp(finding->code, "skipped-hook");
        printf("%s{\"code\":\"%s\",\"severity\":\"%s\",\"path\":",
               i ? "," : "", finding->code, unknown ? "warning" : "error");
        print_check_string(finding->path);
        if (finding->target) {
            fputs(",\"target\":", stdout);
            print_check_string(finding->target);
        }
        fputs("}", stdout);
    }
    fputs("]", stdout);
    if (record->architecture[0]) {
        const char *space = strchr(record->architecture, ' ');
        printf(",\"architecture\":{\"code\":\"accepted-arch-mismatch\",\"host\":\"%.*s\",\"target\":\"%s\",\"execution\":\"unverified\",\"scope\":\"artifact\"}",
               (int)(space - record->architecture), record->architecture, space + 1);
    }
    puts("}");
    return !ferror(stdout);
}

static int compare_check_result(const void *a, const void *b)
{
    const struct check_result *left = a, *right = b;
    return strcmp(left->digest, right->digest);
}

struct graph_elf {
    struct holy_elf_info info;
    int seen;
};

struct graph_soname {
    const struct holy_elf_info *consumer;
    const char *consumer_path;
    const char *name;
    int root, files;
    int found, arch, versions, path_ok;
};

static int graph_alias_match(int root, int files, const char *directory,
                              const char *name, int regular)
{
    struct open_how how = {0};
    struct stat source, target;
    size_t prefix = strlen(directory + 1), n = strlen(name);
    char *path;
    int alias, ok = 0;
    if (prefix > SIZE_MAX - n - 2) return 0;
    path = malloc(prefix + n + 2);
    if (!path) return 0;
    memcpy(path, directory + 1, prefix);
    path[prefix] = '/';
    memcpy(path + prefix + 1, name, n + 1);
    if (holy_install_check_path(files, root, path) != 1) goto done;
    how.flags = O_PATH | O_CLOEXEC;
    how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS;
    alias = (int)syscall(SYS_openat2, root, path, &how, sizeof how);
    if (alias < 0) goto done;
    ok = !fstat(alias, &target) && !fstat(regular, &source) &&
         S_ISREG(target.st_mode) && target.st_dev == source.st_dev &&
         target.st_ino == source.st_ino;
    close(alias);
done:
    free(path);
    return ok;
}

static int graph_consumer_elf(void *context, const char *path, int fd)
{
    struct graph_elf *captured = context;
    (void)path;
    if (captured->seen || holy_elf_read_fd(fd, &captured->info)) return 0;
    captured->seen = 1;
    return 1;
}

static int graph_provider_elf(void *context, const char *path, int fd)
{
    struct graph_soname *match = context;
    struct holy_elf_info info = {0};
    size_t i, j;
    int status = holy_elf_read_fd(fd, &info), versions = 1;
    if (status == 1) { holy_elf_free(&info); return 1; }
    if (status) { holy_elf_free(&info); return 0; }
    if (info.type != ET_DYN || (info.flags1 & DF_1_PIE) ||
        !info.soname || strcmp(info.soname, match->name)) goto done;
    match->found = 1;
    if (!match->consumer) { match->arch = 1; match->versions = 1; goto done; }
    if (info.elf_class != match->consumer->elf_class ||
        info.machine != match->consumer->machine) goto done;
    match->arch = 1;
    for (i = 0; i < match->consumer->version_count; ++i) {
        const struct holy_elf_version *want = &match->consumer->versions[i];
        if (want->weak || strcmp(want->provider, match->name)) continue;
        for (j = 0; j < info.defined_version_count; ++j)
            if (!strcmp(info.defined_versions[j].name, want->name)) break;
        if (j == info.defined_version_count) { versions = 0; break; }
    }
    if (versions) {
        char **directories = NULL;
        size_t count = 0, directory;
        match->versions = 1;
        if (loader_directories(match->consumer, match->consumer_path,
                               &directories, &count)) {
            for (directory = 0; directory < count; ++directory) {
                struct open_how how = {0};
                char *alias;
                int opened;
                size_t a = strlen(directories[directory] + 1), b = strlen(match->name);
                if (a > SIZE_MAX - b - 2) break;
                alias = malloc(a + b + 2);
                if (!alias) break;
                memcpy(alias, directories[directory] + 1, a);
                alias[a] = '/';
                memcpy(alias + a + 1, match->name, b + 1);
                how.flags = O_PATH | O_CLOEXEC;
                how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS;
                opened = (int)syscall(SYS_openat2, match->root, alias, &how, sizeof how);
                free(alias);
                if (opened < 0) continue;
                close(opened);
                if (loader_file_match(directories[directory], path, match->name) ||
                    graph_alias_match(match->root, match->files,
                                      directories[directory], match->name, fd))
                    match->path_ok = 1;
                break;
            }
        }
        free_loader_directories(directories, count);
    }
done:
    holy_elf_free(&info);
    return 1;
}

static int check_soname_edge(int installed, int root, const char *consumer,
                             int provider, const char *path, const char *name,
                             const char **code)
{
    struct graph_elf captured = {0};
    struct graph_soname match = {0};
    int item = -1, files = -1, provider_files = -1, status, result = -1;
    size_t i;
    *code = "broken-provider";
    if (strcmp(path, "-")) {
        item = child_dir(installed, consumer, 0);
        files = item < 0 ? -1 : openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
        if (files < 0) goto done;
        status = holy_install_visit_regular(files, root, path, graph_consumer_elf, &captured);
        if (status < 0) goto done;
        if (!status || !captured.seen) { result = 0; goto done; }
        for (i = 0; i < captured.info.needed_count; ++i)
            if (!strcmp(captured.info.needed[i], name)) break;
        if (i == captured.info.needed_count) goto done;
        match.consumer = &captured.info;
        match.consumer_path = path;
    }
    match.name = name;
    provider_files = openat(provider, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (provider_files < 0) goto done;
    match.root = root;
    match.files = provider_files;
    status = holy_install_visit_regular(provider_files, root, NULL, graph_provider_elf, &match);
    if (status < 0) goto done;
    if (!status) { result = 0; goto done; }
    if (!match.found || !match.arch) {
        *code = "missing-soname";
        result = 0;
    } else if (!match.versions) {
        *code = "missing-symbol-version";
        result = 0;
    } else {
        *code = "unknown-loader-context";
        result = match.consumer && !match.path_ok ? 2 : 1;
    }
done:
    holy_elf_free(&captured.info);
    if (provider_files >= 0) close(provider_files);
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    return result;
}

static int check_graph(int installed, int root, const char *digest,
                        struct check_result *record)
{
    int item = child_dir(installed, digest, 0), fd, result = 1;
    FILE *stream;
    char *line = NULL;
    size_t capacity = 0, number = 0;
    ssize_t length;
    if (item < 0) return -1;
    fd = openat(item, "graph", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    close(item);
    if (fd < 0) return errno == ENOENT ? 1 : -1;
    stream = fdopen(fd, "r");
    if (!stream) { close(fd); return -1; }
    while ((length = getline(&line, &capacity, stream)) >= 0) {
        char **v = NULL, *error = NULL;
        size_t count = 0;
        int provider, intact = 1, detailed = 0;
        ++number;
        if (memchr(line, 0, (size_t)length) ||
            !holy_lex(line, (size_t)length, &v, &count, "installed/graph", number, &error)) {
            free(error); result = -1; break;
        }
        if (!count || strcmp(v[0], "edge")) { holy_tokens_free(v, count); continue; }
        if (count != 7 || !valid_digest(v[1]) || !valid_digest(v[3])) {
            holy_tokens_free(v, count); result = -1; break;
        }
        if (strcmp(v[1], digest)) { holy_tokens_free(v, count); continue; }
        provider = child_dir(installed, v[3], 0);
        if (provider < 0) intact = errno == ENOENT ? 0 : -1;
        else if (!strcmp(v[5], "soname")) {
            const char *code;
            intact = check_soname_edge(installed, root, digest, provider,
                                       v[4], v[6], &code);
            if (intact == 2) {
                if (record && !collect_finding(record, v[4], code, v[6])) result = -1;
                else if (record && result == 1) result = 0;
            } else if (!intact && record) {
                if (!collect_finding(record, v[4], code, v[6])) result = -1;
                else detailed = 1;
            }
        } else if (!strcmp(v[5], "interpreter") || !strcmp(v[5], "needed-path") ||
                 !strcmp(v[5], "shebang") || !strcmp(v[5], "path-alias")) {
            int files = openat(provider, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
            intact = files < 0 || v[6][0] != '/' ? -1 : holy_install_check_path(files, root, v[6] + 1);
            if (files >= 0) close(files);
        }
        if (provider >= 0) close(provider);
        if (intact < 0) result = -1;
        else if (!intact) {
            fprintf(stderr, "holypkg: broken-provider consumer=%s requirement=%s provider=%s target=%s\n",
                    digest, v[2], v[3], v[6]);
            if (result == 1) result = 0;
            if (record && !detailed &&
                !collect_finding(record, v[6], "broken-provider", NULL)) result = -1;
        }
        holy_tokens_free(v, count);
        if (result < 0) break;
    }
    if (ferror(stream)) result = -1;
    free(line);
    fclose(stream);
    return result;
}

static int check_all(int installed, int root, unsigned long long generation, int json)
{
    struct check_result *records = NULL;
    DIR *list = NULL;
    struct dirent *entry;
    size_t count = 0, capacity = 0, i, passed = 0, failed = 0, unknown = 0;
    int result = 1, unavailable = 0;
    list = directory_stream(installed);
    if (!list) return 1;
    errno = 0;
    while ((entry = readdir(list))) {
        int item, files, status, skipped = 0;
        struct check_result *grown;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        if (count == capacity) {
            size_t next = capacity ? capacity * 2 : 8;
            if (next < capacity || next > SIZE_MAX / sizeof *records) goto done;
            grown = realloc(records, next * sizeof *records);
            if (!grown) goto done;
            records = grown;
            capacity = next;
        }
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) goto done;
        files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
        memset(&records[count], 0, sizeof records[count]);
        if (files < 0 || !instance_architecture(item, records[count].architecture)) {
            if (files >= 0) close(files);
            close(item); goto done;
        }
        skipped = hook_skipped(item);
        if (skipped && !collect_finding(&records[count], "HOLY/hooks", "skipped-hook", NULL)) {
            close(files); close(item); goto done;
        }
        close(item);
        memcpy(records[count].digest, entry->d_name, 65);
        ++count;
        status = holy_install_check_report(files, root,
                    collect_finding, &records[count - 1]);
        close(files);
        if (status < 0) goto done;
        if (skipped) status = 0;
        {
            int graph = check_graph(installed, root, entry->d_name, &records[count - 1]);
            if (graph < 0) goto done;
            if (!graph) status = 0;
        }
        records[count - 1].intact = check_status(status, &records[count - 1]);
        if (check_unavailable(&records[count - 1])) unavailable = 1;
        errno = 0;
    }
    if (errno) goto done;
    if (count) qsort(records, count, sizeof *records, compare_check_result);
    result = 0;
    for (i = 0; i < count; ++i) {
        int written;
        if (records[i].intact == 1) ++passed;
        else if (records[i].intact == 2) { ++unknown; result = 4; }
        else { ++failed; result = 4; }
        if (json)
            written = print_check_result(&records[i], generation) ? 0 : -1;
        else
            written = printf("%s %s generation %llu\n",
                             records[i].intact == 1 ? "intact" :
                             records[i].intact == 2 ? "unknown" : "changed",
                             records[i].digest, generation);
        if (written < 0) { result = 1; break; }
        if (!json && records[i].architecture[0] &&
            printf("accepted-arch-mismatch %s %s execution unverified scope artifact\n",
                   records[i].digest, records[i].architecture) < 0) { result = 1; break; }
        if (!json) {
            size_t j;
            for (j = 0; j < records[i].count; ++j)
                if (!strcmp(records[i].findings[j].code, "skipped-hook") &&
                    printf("skipped-hook %s installed-unconfigured\n", records[i].digest) < 0) {
                    result = 1; break;
                }
            if (result == 1) break;
        }
    }
    if (result != 1) {
        if (json) {
            if (printf("{\"schema\":\"holy-installed-check-1\",\"type\":\"summary\",\"pass\":%zu,\"fail\":%zu,\"unknown\":%zu,\"coverage\":\"data-manifest-and-direct-shebang\"}\n",
                       passed, failed, unknown) < 0) result = 1;
        } else if (count == 0 && puts("checked 0 installed packages") == EOF)
            result = 1;
    }
    if (result == 4 && unavailable) result = 6;
done:
    for (i = 0; i < count; ++i) free_check_result(&records[i]);
    free(records);
    closedir(list);
    return result;
}

int holy_state_check(const char *digest, const char *root_path, int json)
{
    struct check_result record = {0};
    unsigned long long generation;
    int root, dir = -1, installed = -1, item = -1, files = -1, result = 1, reported = 0;
    int checked, all = !strcmp(digest, "--all");
    if (!all && !valid_digest(digest)) {
        if (json) puts("{\"schema\":\"holy-installed-check-1\",\"type\":\"error\",\"code\":\"invalid-argument\",\"status\":2}");
        return 2;
    }
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0) goto done;
    dir = state_dir_at(root, 0);
    if (dir < 0 || flock(dir, LOCK_SH) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) goto done;
    checked = transaction_pending(dir, generation);
    if (checked < 0) goto done;
    if (checked) { result = 5; goto done; }
    if (!installed_valid(dir)) goto done;
    installed = child_dir(dir, "installed", 0);
    if (installed < 0) goto done;
    if (all) {
        result = check_all(installed, root, generation, json);
        reported = result == 0 || result == 4 || result == 6;
        goto done;
    }
    item = child_dir(installed, digest, 0);
    if (item < 0) { result = 6; goto done; }
    if (!instance_architecture(item, record.architecture)) goto done;
    if (hook_skipped(item) &&
        !collect_finding(&record, "HOLY/hooks", "skipped-hook", NULL)) goto done;
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (files < 0) goto done;
    checked = holy_install_check_report(files, root,
                collect_finding, &record);
    if (checked >= 0) {
        int graph = check_graph(installed, root, digest, &record);
        if (graph < 0) checked = -1;
        else if (!graph) checked = 0;
    }
    if (checked > 0 && record.count) checked = 0;
    checked = check_status(checked, &record);
    result = checked > 0 && checked != 2 ? 0 : checked >= 0 ? 4 : 1;
    if (result == 4 && check_unavailable(&record)) result = 6;
    if (json && checked >= 0) {
        memcpy(record.digest, digest, 65);
        record.intact = checked;
        if (!print_check_result(&record, generation)) { result = 1; goto done; }
        printf("{\"schema\":\"holy-installed-check-1\",\"type\":\"summary\",\"pass\":%d,\"fail\":%d,\"unknown\":%d,\"coverage\":\"data-manifest-and-direct-shebang\"}\n",
               checked == 1, checked == 0, checked == 2);
        reported = 1;
    } else if (checked >= 0 && !json)
        printf("%s %s generation %llu\n",
               checked == 1 ? "intact" : checked == 2 ? "unknown" : "changed", digest, generation);
    if (!json && (result == 0 || result == 4) && record.architecture[0])
        printf("accepted-arch-mismatch %s %s execution unverified scope artifact\n", digest, record.architecture);
    if (!json && (result == 0 || result == 4) && record.count) {
        size_t i;
        for (i = 0; i < record.count; ++i)
            if (!strcmp(record.findings[i].code, "skipped-hook"))
                printf("skipped-hook %s installed-unconfigured\n", digest);
    }
done:
    free_check_result(&record);
    if (result) fprintf(stderr, "holypkg: installed check failed (status %d)\n", result);
    if (json && result != 0 && !reported) {
        const char *code = result == 5 ? "incomplete-transaction" :
                           result == 6 ? "unavailable-instance" : "invalid-state";
        printf("{\"schema\":\"holy-installed-check-1\",\"type\":\"error\",\"code\":\"%s\",\"status\":%d}\n",
               code, result);
    }
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

static int compare_installed_path(const void *left, const void *right)
{
    const char *const *a = left, *const *b = right;
    return strcmp(*a, *b);
}

int holy_state_files(const char *digest, const char *root_path)
{
    unsigned long long generation;
    char **paths = NULL, *line = NULL;
    size_t count = 0, capacity = 0, length = 0, number = 0, i;
    int database = -1, installed = -1, item = -1, fd = -1, result = 1;
    struct stat st;
    FILE *input = NULL;
    ssize_t got;
    if (!valid_digest(digest)) return 2;
    database = holy_state_lock(root_path, 0, &generation, &result);
    if (database < 0) return result;
    result = 1;
    if (!installed_valid(database) ||
        (installed = child_dir(database, "installed", 0)) < 0 ||
        (item = child_dir(installed, digest, 0)) < 0) {
        if (item < 0 && installed >= 0 && errno == ENOENT) result = 6;
        goto done;
    }
    fd = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) ||
        st.st_size < 0 || st.st_size > 16 * 1024 * 1024) goto done;
    input = fdopen(fd, "r");
    if (!input) goto done;
    fd = -1;
    while ((got = getline(&line, &length, input)) >= 0) {
        char **fields = NULL, *error = NULL, *copy;
        size_t fields_count = 0;
        ++number;
        if (memchr(line, '\0', (size_t)got) ||
            !holy_lex(line, (size_t)got, &fields, &fields_count,
                      "installed/files", number, &error)) {
            free(error); holy_tokens_free(fields, fields_count); goto done;
        }
        if (fields_count) {
            int link = !strcmp(fields[0], "symlink") ||
                       !strcmp(fields[0], "hardlink");
            if ((link && fields_count != 13) ||
                (!link && fields_count != 12) ||
                (!link && strcmp(fields[0], "dir") && strcmp(fields[0], "file")) ||
                !valid_owner_path(fields[1]) || count >= 100000) {
                holy_tokens_free(fields, fields_count); goto done;
            }
            copy = strdup(fields[1]);
            if (!copy) { holy_tokens_free(fields, fields_count); goto done; }
            if (count == capacity) {
                size_t next = capacity ? capacity * 2 : 32;
                char **grown = realloc(paths, next * sizeof *grown);
                if (!grown) { free(copy); holy_tokens_free(fields, fields_count); goto done; }
                paths = grown; capacity = next;
            }
            paths[count++] = copy;
        }
        holy_tokens_free(fields, fields_count);
    }
    if (ferror(input) || ftello(input) != st.st_size) goto done;
    qsort(paths, count, sizeof *paths, compare_installed_path);
    for (i = 1; i < count; ++i) if (!strcmp(paths[i - 1], paths[i])) goto done;
    for (i = 0; i < count; ++i) {
        char *absolute = malloc(strlen(paths[i]) + 2);
        if (!absolute) goto done;
        absolute[0] = '/';
        strcpy(absolute + 1, paths[i]);
        print_check_string(absolute);
        putchar('\n');
        free(absolute);
    }
    if (ferror(stdout)) goto done;
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: installed file list failed (status %d)\n", result);
    for (i = 0; i < count; ++i) free(paths[i]);
    free(paths); free(line);
    if (input) fclose(input);
    if (fd >= 0) close(fd);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    if (database >= 0) close(database);
    return result;
}

static int finish_remove_record(int dir, int installed, int item, int transactions,
                                const char *digest, unsigned long long generation,
                                int broken, int retired)
{
    char generation_record[32], temp_name[43] = {0};
    size_t length;
    int temp = -1, work = -1, ok = 0;
    struct stat old, observed;
    work = remove_workdir(transactions, digest, generation, broken);
    if (work < 0 || fstat(item, &old)) goto done;
    if (!retired) {
        if (!fstatat(work, "old-instance", &observed, AT_SYMLINK_NOFOLLOW) ||
            errno != ENOENT || fsync(item) ||
            renameat(installed, digest, work, "old-instance")) goto done;
    }
    if (fstatat(work, "old-instance", &observed, AT_SYMLINK_NOFOLLOW) ||
        !S_ISDIR(observed.st_mode) || old.st_dev != observed.st_dev ||
        old.st_ino != observed.st_ino || fsync(work) || fsync(installed) ||
        fsync(transactions)) goto done;
    length = (size_t)snprintf(generation_record, sizeof generation_record,
                              "%llu\n", generation + 1);
    if (length >= sizeof generation_record) goto done;
    temp = holy_temporary_at(dir, temp_name);
    if (temp < 0 || !write_all(temp, generation_record, length) || fsync(temp)) goto done;
    if (close(temp)) { temp = -1; goto done; }
    temp = -1;
    if (renameat(dir, temp_name, dir, "generation") || fsync(dir) ||
        !commit_remove_record(transactions, digest, generation, broken) ||
        unlinkat(transactions, "journal", 0) || fsync(transactions)) goto done;
    ok = 1;
done:
    if (work >= 0) close(work);
    if (temp >= 0) close(temp);
    if (temp_name[0]) unlinkat(dir, temp_name, 0);
    return ok;
}

static int exclusive_claims(int installed, const char *digest, int files)
{
    DIR *list = directory_stream(installed);
    struct dirent *entry;
    int result = -1;
    if (!list) return -1;
    errno = 0;
    while ((entry = readdir(list))) {
        int item, other, conflict;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") ||
            !strcmp(entry->d_name, digest)) continue;
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) goto done;
        other = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
        close(item);
        if (other < 0) goto done;
        conflict = holy_install_manifests_conflict(files, other);
        close(other);
        if (conflict < 0) goto done;
        if (conflict) {
            fprintf(stderr, "holypkg: conflicting installed ownership with %s\n", entry->d_name);
            result = 0;
            goto done;
        }
        errno = 0;
    }
    if (!errno) result = 1;
done:
    closedir(list);
    return result;
}

static int dependent_consumer(int installed, const char *digest)
{
    DIR *list = directory_stream(installed);
    struct dirent *entry;
    int result = 0, found = 0;
    if (!list) return -1;
    errno = 0;
    while ((entry = readdir(list))) {
        FILE *stream;
        char *line = NULL;
        size_t capacity = 0, number = 0;
        ssize_t length;
        int item, fd;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) { result = -1; break; }
        fd = openat(item, "graph", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
        close(item);
        if (fd < 0) {
            if (errno == ENOENT) { errno = 0; continue; }
            result = -1; break;
        }
        stream = fdopen(fd, "r");
        if (!stream) { close(fd); result = -1; break; }
        while ((length = getline(&line, &capacity, stream)) >= 0) {
            char **v = NULL, *error = NULL;
            size_t count = 0;
            struct stat st;
            ++number;
            if (memchr(line, 0, (size_t)length) ||
                !holy_lex(line, (size_t)length, &v, &count, "installed/graph", number, &error)) {
                free(error); result = -1; break;
            }
            if (count && !strcmp(v[0], "edge")) {
                if (count != 7 || !valid_digest(v[1]) || !valid_digest(v[3])) result = -1;
                else if (!strcmp(v[3], digest) && strcmp(v[1], digest)) {
                    if (!fstatat(installed, v[1], &st, AT_SYMLINK_NOFOLLOW)) {
                        if (!S_ISDIR(st.st_mode)) result = -1;
                        else {
                            fprintf(stderr, "holypkg: provider %s still required by %s requirement %s\n",
                                    digest, v[1], v[2]);
                            found = 1;
                        }
                    } else if (errno != ENOENT) result = -1;
                }
            }
            holy_tokens_free(v, count);
            if (result) break;
        }
        if (ferror(stream)) result = -1;
        free(line);
        fclose(stream);
        if (result) break;
        errno = 0;
    }
    if (!entry && errno) result = -1;
    closedir(list);
    return result < 0 ? -1 : found;
}

int holy_state_remove(const char *digest, const char *root_path, int accept_broken)
{
    unsigned long long generation;
    char journal[256];
    char reserved[65], approved[65];
    size_t length;
    int root, dir = -1, installed = -1, item = -1, files = -1;
    int transactions = -1, journaled = 0, result = 1, pending, broken;
    if (!valid_digest(digest)) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0) goto done;
    dir = state_dir_at(root, 0);
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation) ||
        generation == ULLONG_MAX) goto done;
    pending = transaction_pending(dir, generation);
    if (pending < 0) goto done;
    if (pending) { result = 5; goto done; }
    if (!installed_valid(dir)) goto done;
    pending = pending_child(dir, generation, reserved, approved);
    if (pending < 0) goto done;
    if (pending) { result = 5; goto done; }
    installed = child_dir(dir, "installed", 0);
    transactions = child_dir(dir, "transactions", 0);
    if (installed < 0 || transactions < 0) goto done;
    pending = dependent_consumer(installed, digest);
    if (pending < 0) goto done;
    if (pending && !accept_broken) { result = 3; goto done; }
    if (pending) fprintf(stderr, "holypkg: accepted broken dependents for %s\n", digest);
    broken = pending;
    item = child_dir(installed, digest, 0);
    if (item < 0) { result = 6; goto done; }
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (files < 0) goto done;
    pending = holy_install_check_manifest(files, root);
    if (pending != 1) { result = pending == 0 ? 4 : 1; goto done; }
    pending = exclusive_claims(installed, digest, files);
    if (pending != 1) { result = pending == 0 ? 4 : 1; goto done; }
    length = (size_t)snprintf(journal, sizeof journal,
        "format holy-journal-1\nstage removing\ngeneration %llu\nartifact %s\nplan %064d\n",
        generation, digest, broken ? 1 : 0);
    if (length >= sizeof journal || !record_file(transactions, "journal", journal, length)) {
        result = journal_exists(dir) ? 5 : 1;
        goto done;
    }
    journaled = 1;
    result = 5;
    if (!remove_record(transactions, digest, generation, broken, 1)) goto done;
    if (!holy_install_remove_manifest(files, root)) goto done;
    if (close(files)) { files = -1; goto done; }
    files = -1;
    if (!finish_remove_record(dir, installed, item, transactions, digest, generation,
                              broken, 0)) goto done;
    printf("removed %s generation %llu\n", digest, generation + 1);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: remove failed (status %d)%s\n", result,
                        journaled ? "; inspect incomplete transaction" : "");
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_continue_remove(const char *root_path)
{
    unsigned long long generation, recorded;
    char digest[65], plan[65], reserved[65], approved[65];
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, 0);
    int installed = -1, item = -1, transactions = -1, files = -1;
    int result = 5, removing = 0, found, retired = 0, work = -1;
    struct stat removed_st;
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation) ||
        generation == ULLONG_MAX) { result = 1; goto done; }
    found = journal_valid(dir, generation, &recorded, digest, plan, &removing);
    if (found < 0) { result = 1; goto done; }
    if (!found || removing != 1 ||
        (strspn(plan, "0") != 64 && strcmp(plan,
         "0000000000000000000000000000000000000000000000000000000000000001")) ||
        !installed_valid(dir)) goto done;
    transactions = child_dir(dir, "transactions", 0);
    installed = child_dir(dir, "installed", 0);
    if (transactions < 0 || installed < 0) { result = 1; goto done; }
    if (read_reservation(transactions, "pending", generation, reserved, approved) != 0)
        goto done;
    if (recorded != generation) {
        if (generation != recorded + 1 || !remove_record(transactions, digest, recorded,
            plan[63] == '1', 0) || fstatat(installed, digest, &removed_st, AT_SYMLINK_NOFOLLOW) == 0 ||
            errno != ENOENT || !commit_remove_record(transactions, digest, recorded,
            plan[63] == '1') || unlinkat(transactions, "journal", 0) || fsync(transactions)) goto done;
        printf("recovered removal %s generation %llu\n", digest, generation);
        result = 0;
        goto done;
    }
    item = child_dir(installed, digest, 0);
    if (item < 0) {
        if (errno != ENOENT || !remove_record(transactions, digest, generation,
                                              plan[63] == '1', 0)) goto done;
        work = remove_workdir(transactions, digest, generation, plan[63] == '1');
        item = work < 0 ? -1 : child_dir(work, "old-instance", 0);
        if (item < 0) goto done;
        retired = 1;
    }
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (files < 0 || exclusive_claims(installed, digest, files) != 1 ||
        !remove_record(transactions, digest, generation, plan[63] == '1', 1) ||
        !holy_install_finish_remove_manifest(files, root) ||
        !finish_remove_record(dir, installed, item, transactions, digest, generation,
                              plan[63] == '1', retired)) goto done;
    printf("recovered removal %s generation %llu\n", digest, generation + 1);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: removal recovery requires manual inspection (status %d)\n", result);
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (work >= 0) close(work);
    if (installed >= 0) close(installed);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_finish_apply(const char *root_path)
{
    unsigned long long generation, recorded, instance_generation;
    char digest[65], plan[65], reserved[65], approved[65];
    int root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    int dir = root < 0 ? -1 : state_dir_at(root, 0);
    int transactions = -1, installed = -1, item = -1, files = -1;
    int result = 5, removing = 0, found;
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) {
        result = 1;
        goto done;
    }
    found = journal_valid(dir, generation, &recorded, digest, plan, &removing);
    if (found < 0) { result = 1; goto done; }
    if (!found || removing || !generation || recorded != generation - 1 ||
        !installed_valid(dir)) goto done;
    transactions = child_dir(dir, "transactions", 0);
    installed = child_dir(dir, "installed", 0);
    if (transactions < 0 || installed < 0) { result = 1; goto done; }
    found = read_reservation(transactions, "pending", recorded, reserved, approved);
    if (found < 0 || (found && (strcmp(reserved, digest) || strcmp(approved, plan))))
        goto done;
    item = child_dir(installed, digest, 0);
    if (item < 0 || !instance_state_generation(item, digest, &instance_generation) ||
        instance_generation != generation) goto done;
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
    if (files < 0 || holy_install_check_manifest(files, root) != 1) goto done;
    if (found && (unlinkat(transactions, "pending", 0) || fsync(transactions))) goto done;
    if (unlinkat(transactions, "journal", 0) || fsync(transactions)) goto done;
    printf("recovered install %s generation %llu\n", digest, generation);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: install recovery requires manual inspection (status %d)\n", result);
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

static int valid_owner_path(const char *path)
{
    const char *part = path;
    if (!*path) return 0;
    while (*part) {
        const char *end = strchr(part, '/');
        size_t length = end ? (size_t)(end - part) : strlen(part), i;
        if (!length || (length == 1 && part[0] == '.') ||
            (length == 2 && part[0] == '.' && part[1] == '.')) return 0;
        for (i = 0; i < length; ++i)
            if ((unsigned char)part[i] < 32 ||
                (unsigned char)part[i] == 127) return 0;
        if (!end) break;
        part = end + 1;
    }
    return 1;
}

static int compare_owner(const void *a, const void *b)
{
    return strcmp((const char *)a, (const char *)b);
}

int holy_state_owner(const char *input, const char *root_path)
{
    const char *path = *input == '/' ? input + 1 : input;
    int root = -1, dir = -1, installed = -1, result = 1, found = 0;
    unsigned long long generation;
    char (*owners)[65] = NULL;
    size_t count = 0, capacity = 0, i;
    DIR *list = NULL;
    struct dirent *entry;
    if (!valid_owner_path(path)) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0) goto done;
    dir = state_dir_at(root, 0);
    if (dir < 0 || flock(dir, LOCK_SH) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) goto done;
    found = transaction_pending(dir, generation);
    if (found < 0) goto done;
    if (found) { result = 5; goto done; }
    if (!installed_valid(dir)) goto done;
    found = 0;
    installed = child_dir(dir, "installed", 0);
    if (installed < 0) goto done;
    list = directory_stream(installed);
    if (!list) goto done;
    errno = 0;
    while ((entry = readdir(list))) {
        int item, files, kind;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        item = child_dir(installed, entry->d_name, 0);
        if (item < 0) goto done;
        files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
        close(item);
        if (files < 0) goto done;
        kind = holy_install_manifest_owns(files, path);
        close(files);
        if (kind < 0) goto done;
        if (kind) {
            char (*grown)[65];
            if (found && (found == 1 || kind == 1)) {
                fprintf(stderr, "holypkg: conflicting installed owners for %s\n", path);
                result = 4;
                goto done;
            }
            if (count == capacity) {
                size_t next = capacity ? capacity * 2 : 4;
                if (next < capacity || next > SIZE_MAX / sizeof *owners) goto done;
                grown = realloc(owners, next * sizeof *owners);
                if (!grown) goto done;
                owners = grown;
                capacity = next;
            }
            memcpy(owners[count], entry->d_name, 65);
            ++count;
            found = kind;
        }
        errno = 0;
    }
    if (errno) goto done;
    result = found ? 0 : 6;
    if (!result) {
        qsort(owners, count, sizeof *owners, compare_owner);
        for (i = 0; i < count; ++i)
            if (printf("%s %s %s\n", owners[i],
                       found == 1 ? "file" : "directory", path) < 0) {
                result = 1;
                break;
            }
    }
done:
    free(owners);
    if (list) closedir(list);
    if (installed >= 0) close(installed);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

struct set_item {
    char *snapshot, *source_record;
    char source_id[65];
    char architecture[96];
    struct holy_package_identity identity;
    int reused;
    int privileged;
    int skipped_hooks;
};

struct privileged_scan {
    char *first;
    unsigned mode;
    size_t count;
    int unsupported;
};

static int scan_privileged(void *context, const struct holy_manifest_entry *entry)
{
    struct privileged_scan *scan = context;
    if (!(entry->mode & 07000)) return 1;
    if (entry->directory || entry->link || entry->hardlink || entry->group ||
        (entry->mode & 03000) || !(entry->mode & 0111)) {
        scan->unsupported = 1;
        return 0;
    }
    if (!scan->first) {
        scan->first = strdup(entry->path);
        if (!scan->first) return 0;
        scan->mode = entry->mode;
    }
    ++scan->count;
    return 1;
}

struct set_claim {
    char *path;
    unsigned mode;
    long long uid, gid;
    int directory;
};

struct install_set {
    struct holy_resolution resolution;
    struct set_item *items;
    size_t count, paths;
    struct set_claim *claims;
    size_t claim_count;
    char *graph;
    size_t graph_length;
    char **bindings;
    size_t binding_count;
    char hash[65];
    char host[65];
    char catalog_index[65];
};

struct set_journal {
    unsigned long long generation;
    char hash[65], root[65];
    char catalog_index[65];
    char *choice;
    char **digests, **bindings;
    size_t count, binding_count;
    char **accepted_arch, host[65];
    size_t accepted_count;
    char **accepted_privileged;
    size_t privileged_count;
    char **skipped_hooks;
    size_t skipped_count;
};

static void free_set(struct install_set *set)
{
    size_t i;
    for (i = 0; i < set->count; ++i) {
        if (set->items[i].snapshot) unlink(set->items[i].snapshot);
        free(set->items[i].snapshot);
        free(set->items[i].source_record);
        holy_package_identity_free(&set->items[i].identity);
    }
    for (i = 0; i < set->claim_count; ++i) free(set->claims[i].path);
    free(set->claims);
    free(set->items);
    free(set->graph);
    for (i = 0; i < set->binding_count; ++i) free(set->bindings[i]);
    free(set->bindings);
    holy_resolution_free(&set->resolution);
    memset(set, 0, sizeof *set);
}

static int set_claim(void *context, const struct holy_manifest_entry *entry)
{
    struct install_set *set = context;
    struct set_claim *claims;
    size_t length = strlen(entry->path);
    if (set->claim_count >= 1000000) return 0;
    claims = realloc(set->claims, (set->claim_count + 1) * sizeof *claims);
    if (!claims) return 0;
    set->claims = claims;
    if (length && entry->path[length - 1] == '/') --length;
    claims = &set->claims[set->claim_count];
    claims->path = strndup(entry->path, length);
    if (!claims->path) return 0;
    claims->directory = entry->directory;
    claims->mode = entry->mode;
    claims->uid = entry->uid;
    claims->gid = entry->gid;
    ++set->claim_count;
    return 1;
}

static int claim_order(const void *left, const void *right)
{
    return strcmp(((const struct set_claim *)left)->path,
                  ((const struct set_claim *)right)->path);
}

static int set_claims_valid(struct install_set *set)
{
    size_t i;
    if (set->claim_count) qsort(set->claims, set->claim_count,
                               sizeof *set->claims, claim_order);
    for (i = 1; i < set->claim_count; ++i) {
        const struct set_claim *a = &set->claims[i - 1], *b = &set->claims[i];
        if (!strcmp(a->path, b->path) &&
            (!a->directory || !b->directory || a->mode != b->mode ||
             a->uid != b->uid || a->gid != b->gid)) {
            fprintf(stderr, "holypkg: selected packages claim the same path: %s\n", a->path);
            return 0;
        }
    }
    return 1;
}

static int loader_directories(const struct holy_elf_info *elf, const char *consumer,
                              char ***directories, size_t *count)
{
    const char *path = elf->runpath ? elf->runpath : elf->rpath;
    const char *start, *end;
    char **list = NULL;
    size_t length, used = 0, capacity = 0;
    *directories = NULL;
    *count = 0;
    if (!path || !*path) return 0;
    for (start = path; ; start = end + 1) {
        char *directory;
        end = strchr(start, ':');
        length = end ? (size_t)(end - start) : strlen(start);
        if (length < 2 || start[length - 1] == '/') goto fail;
        if (start[0] == '/') {
            if (memchr(start, '$', length)) goto fail;
            directory = strndup(start, length);
            if (!directory) goto fail;
            if (!valid_owner_path(directory + 1)) { free(directory); goto fail; }
        } else if (length >= 7 && !memcmp(start, "$ORIGIN", 7) &&
                   (length == 7 || start[7] == '/') && consumer && *consumer) {
            char *relative;
            const char *suffix = start + 7;
            size_t prefix = 0, k;
            for (k = 0; consumer[k]; ++k) if (consumer[k] == '/') prefix = k + 1;
            if (!prefix) goto fail;
            if (length == 7) relative = strndup(consumer, prefix - 1);
            else {
                char *tail = strndup(suffix + 1, length - 8);
                relative = tail ? holy_relative_link_path(consumer, strlen(consumer),
                                                          tail, "") : NULL;
                free(tail);
            }
            if (!relative || !valid_owner_path(relative)) { free(relative); goto fail; }
            directory = malloc(strlen(relative) + 2);
            if (!directory) { free(relative); goto fail; }
            directory[0] = '/';
            strcpy(directory + 1, relative);
            free(relative);
        } else goto fail;
        if (used == capacity) {
            size_t next = capacity ? capacity * 2 : 4;
            char **grown;
            if (next < capacity || next > 1024) { free(directory); goto fail; }
            grown = realloc(list, next * sizeof *list);
            if (!grown) { free(directory); goto fail; }
            list = grown; capacity = next;
        }
        list[used++] = directory;
        if (!end) break;
    }
    *directories = list;
    *count = used;
    return 1;
fail:
    while (used) free(list[--used]);
    free(list);
    return 0;
}

static void free_loader_directories(char **directories, size_t count)
{
    while (count) free(directories[--count]);
    free(directories);
}

static int loader_file_match(const char *directory, const char *path,
                             const char *name)
{
    size_t length = strlen(directory + 1);
    return !strncmp(path, directory + 1, length) && path[length] == '/' &&
           !strcmp(path + length + 1, name);
}

static int scan_loader_alias(const struct holy_scan_result *scan,
                             const char *directory, const char *name,
                             const char *regular)
{
    size_t prefix = strlen(directory + 1), n = strlen(name), hop, i;
    char *current;
    if (prefix > SIZE_MAX - n - 2) return 0;
    current = malloc(prefix + n + 2);
    if (!current) return 0;
    memcpy(current, directory + 1, prefix);
    current[prefix] = '/';
    memcpy(current + prefix + 1, name, n + 1);
    for (hop = 0; hop < 16; ++hop) {
        const char *target = NULL;
        char *next;
        if (!strcmp(current, regular)) { free(current); return 1; }
        for (i = 0; i < scan->symlink_count; ++i)
            if (!strcmp(scan->symlinks[i].path, current)) {
                target = scan->symlinks[i].target; break;
            }
        if (!target) break;
        next = holy_relative_link_path(current, strlen(current), target, "");
        if (!next) break;
        free(current);
        current = next;
    }
    free(current);
    return 0;
}

static int explicit_elf_paths(const char *snapshot, int allow_soname)
{
    struct holy_scan_result scan = {0};
    size_t i, j;
    int result = 6;
    if (!holy_scan_collect(snapshot, &scan)) return 6;
    result = 0;
    for (i = 0; i < scan.count; ++i) {
        const struct holy_scanned_file *file = &scan.files[i];
        if (file->elf.interpreter && file->elf.interpreter[0] != '/') { result = 3; break; }
        for (j = 0; j < file->elf.needed_count; ++j) {
            char **directories = NULL;
            size_t directory_count = 0;
            int known = allow_soname && loader_directories(&file->elf, file->path,
                                                            &directories, &directory_count);
            free_loader_directories(directories, directory_count);
            if (file->elf.needed[j][0] != '/' && !known) {
                fprintf(stderr, "holypkg: unknown-loader-search consumer=%s requirement=%s\n",
                        file->path, file->elf.needed[j]);
                result = 3;
                break;
            }
        }
        if (result) break;
    }
    for (i = 0; !result && i < scan.script_count; ++i)
        if (scan.scripts[i].kind != 1) {
            fprintf(stderr, "holypkg: script-interpreter-decision consumer=%s interpreter=%s\n",
                    scan.scripts[i].path, scan.scripts[i].interpreter);
            result = 3;
        }
    holy_scan_free(&scan);
    return result;
}

static int selected_soname_paths(const struct holy_resolution *resolution,
                                  const char *const *digests,
                                  const char *const *snapshots, size_t count, int root,
                                  const struct set_claim *claims, size_t claim_count)
{
    size_t i, j, k;
    for (i = 0; i < resolution->edge_count; ++i) {
        const struct holy_resolved_edge *edge = &resolution->edges[i];
        struct holy_scan_result consumer = {0}, provider = {0};
        const struct holy_scanned_file *file = NULL;
        char **directories = NULL;
        size_t directory_count = 0, d;
        int found = 0, ok = 0;
        if (strcmp(edge->kind, "soname") || !strcmp(edge->path, "-")) continue;
        for (j = 0; j < count; ++j)
            if (!strcmp(digests[j], edge->consumer)) break;
        if (j == count || !holy_scan_collect(snapshots[j], &consumer)) goto edge_done;
        for (k = 0; k < consumer.count; ++k)
            if (!strcmp(consumer.files[k].path, edge->path)) {
                file = &consumer.files[k]; break;
            }
        if (!file || !loader_directories(&file->elf, file->path,
                                         &directories, &directory_count)) goto edge_done;
        for (j = 0; j < count; ++j)
            if (!strcmp(digests[j], edge->provider)) break;
        if (j == count || !holy_scan_collect(snapshots[j], &provider)) goto edge_done;
        for (d = 0; d < directory_count; ++d) {
            struct open_how how = {0};
            char *alias;
            size_t a = strlen(directories[d] + 1), b = strlen(edge->target);
            int opened, occupied = 0;
            if (a > SIZE_MAX - b - 2) break;
            alias = malloc(a + b + 2);
            if (!alias) break;
            memcpy(alias, directories[d] + 1, a);
            alias[a] = '/';
            memcpy(alias + a + 1, edge->target, b + 1);
            for (k = 0; k < provider.count; ++k) {
                const struct holy_scanned_file *candidate = &provider.files[k];
                if (candidate->elf.type == ET_DYN && !(candidate->elf.flags1 & DF_1_PIE) &&
                    candidate->elf.soname && !strcmp(candidate->elf.soname, edge->target) &&
                    candidate->elf.elf_class == file->elf.elf_class &&
                    candidate->elf.machine == file->elf.machine &&
                    !strcmp(candidate->runtime, file->runtime) &&
                    scan_loader_alias(&provider, directories[d], edge->target,
                                      candidate->path)) { found = 1; break; }
            }
            if (found) { free(alias); break; }
            {
                struct set_claim key = {0};
                key.path = alias;
                occupied = bsearch(&key, claims, claim_count,
                                   sizeof *claims, claim_order) != NULL;
            }
            how.flags = O_PATH | O_CLOEXEC;
            how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS;
            if (!occupied) {
                opened = (int)syscall(SYS_openat2, root, alias, &how, sizeof how);
                if (opened >= 0) { occupied = 1; close(opened); }
                else if (errno != ENOENT) occupied = 1;
            }
            free(alias);
            if (occupied) break;
        }
        ok = found;
edge_done:
        free_loader_directories(directories, directory_count);
        holy_scan_free(&consumer);
        holy_scan_free(&provider);
        if (!ok) {
            fprintf(stderr, "holypkg: unknown-loader-search consumer=%s requirement=%s provider=%s\n",
                    edge->path, edge->target, edge->provider);
            return 0;
        }
    }
    return 1;
}

static int set_soname_paths(const struct install_set *set, int root)
{
    const char **digests = calloc(set->count, sizeof *digests);
    const char **snapshots = calloc(set->count, sizeof *snapshots);
    size_t i;
    int ok = 0;
    if (!digests || !snapshots) goto done;
    for (i = 0; i < set->count; ++i) {
        digests[i] = set->items[i].identity.digest;
        snapshots[i] = set->items[i].snapshot;
    }
    ok = selected_soname_paths(&set->resolution, digests, snapshots, set->count,
                               root, set->claims, set->claim_count);
done:
    free(digests); free(snapshots);
    return ok;
}

static int instance_matches_snapshot(int item, const char *snapshot);

static int reuse_instance(int dir, int root, struct set_item *candidate,
                          unsigned long long generation, int completed,
                          const char *graph, EVP_MD_CTX *hash)
{
    int installed = child_dir(dir, "installed", 0), item = -1, files = -1, fd = -1;
    int result = -1;
    unsigned long long recorded;
    char state[640], prefix[80], *line = NULL;
    size_t capacity = 0, old_count = 0, new_count = 0;
    ssize_t got;
    FILE *stream = NULL;
    const char *part;
    if (installed < 0) goto done;
    item = child_dir(installed, candidate->identity.digest, 0);
    if (item < 0) { if (errno == ENOENT) result = 0; goto done; }
    if (!instance_state_generation(item, candidate->identity.digest, &recorded)) goto done;
    if (recorded > generation) {
        if (completed && recorded == generation + 1) result = 0;
        goto done;
    }
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (files < 0 || !instance_matches_snapshot(item, candidate->snapshot) ||
        exclusive_claims(installed, candidate->identity.digest, files) != 1 ||
        holy_install_check_manifest(files, root) != 1 ||
        check_graph(installed, root, candidate->identity.digest, NULL) != 1) goto done;
    fd = openat(item, "graph", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (fd < 0 || !(stream = fdopen(fd, "r"))) goto done;
    fd = -1;
    snprintf(prefix, sizeof prefix, "edge \"%s\" ", candidate->identity.digest);
    for (part = graph; *part; ) {
        if (!strncmp(part, prefix, strlen(prefix))) ++new_count;
        part = strchr(part, '\n');
        if (!part) goto done;
        ++part;
    }
    while ((got = getline(&line, &capacity, stream)) >= 0) {
        const char *match;
        if (strncmp(line, prefix, strlen(prefix))) continue;
        ++old_count;
        match = strstr(graph, line);
        if (!match || (match != graph && match[-1] != '\n')) goto done;
    }
    if (ferror(stream) || old_count != new_count) goto done;
    fd = openat(item, "state", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (fd < 0 || (got = read(fd, state, sizeof state - 1)) <= 0) goto done;
    state[got] = 0;
    if (!hash_text(hash, "reuse-installed") || !hash_text(hash, state)) goto done;
    {
        char marker[96];
        snprintf(marker, sizeof marker, "\nprivileged %s\n", candidate->identity.digest);
        candidate->privileged = strstr(state, marker) != NULL;
    }
    if (!installed_source_id(item, candidate->source_id)) goto done;
    candidate->reused = 1;
    result = 1;
done:
    if (stream) fclose(stream);
    free(line);
    if (fd >= 0) close(fd);
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    return result;
}

struct installed_candidates {
    int installed;
    char **digests;
    size_t count;
    const char *root;
};

struct named_claim { const char *name; int matched; };

static int match_named_claim(void *opaque, const char *kind, const char *name,
    const char *arch, const char *libc, const char *version, const char *evidence)
{
    struct named_claim *claim = opaque;
    (void)arch; (void)libc; (void)version; (void)evidence;
    if (!strcmp(kind, "package") && !strcmp(name, claim->name)) claim->matched = 1;
    return 1;
}

static int installed_package_claim(struct installed_candidates *catalog, int item,
                                    const char *digest, const char *name)
{
    struct named_claim claim = {name, 0};
    int fd, ok, matches = installed_name(item, name);
    if (matches) return matches;
    fd = openat(item, "provides", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (fd >= 0) {
        ok = holy_provides_visit_fd(fd, match_named_claim, &claim);
        close(fd);
    } else {
        char *snapshot;
        if (errno != ENOENT) return -1;
        snapshot = holy_cache_snapshot(digest, catalog->root);
        if (!snapshot) {
            fprintf(stderr, "holypkg: legacy capability metadata needs cached artifact %s\n", digest);
            return -1;
        }
        ok = holy_provides_visit(snapshot, match_named_claim, &claim);
        unlink(snapshot); free(snapshot);
    }
    return ok ? claim.matched : -1;
}

static int installed_command_claim(int item, const char *name)
{
    static const char *const dirs[] = {"usr/bin/", "bin/", "usr/sbin/", "sbin/"};
    size_t i, length = strlen(name);
    int files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    int result = 0;
    if (files < 0) return -1;
    for (i = 0; i < sizeof dirs / sizeof *dirs; ++i) {
        size_t prefix = strlen(dirs[i]);
        char *path;
        if (length > SIZE_MAX - prefix - 1) { result = -1; break; }
        path = malloc(prefix + length + 1);
        if (!path) { result = -1; break; }
        memcpy(path, dirs[i], prefix);
        memcpy(path + prefix, name, length + 1);
        result = holy_install_manifest_executable(files, path);
        free(path);
        if (result) break;
    }
    close(files);
    return result;
}

static int installed_soname_claim(struct installed_candidates *catalog,
                                  int item, const char *digest, const char *name,
                                  const char *arch, const char *libc)
{
    struct holy_scan_result scan = {0};
    const char *keys[] = {"arch", "libc"}, *values[] = {arch, libc};
    char *snapshot;
    size_t i, fields = 0;
    int result = -1;
    if (strcmp(arch, "any")) { keys[fields] = "arch"; values[fields++] = arch; }
    if (strcmp(libc, "any")) { keys[fields] = "libc"; values[fields++] = libc; }
    if (fields && (result = installed_fields(item, keys, values, fields)) <= 0) return result;
    snapshot = holy_cache_snapshot(digest, catalog->root);
    if (!snapshot) return -1;
    if (!holy_scan_collect(snapshot, &scan)) goto done;
    result = 0;
    for (i = 0; i < scan.count; ++i) {
        const struct holy_scanned_file *file = &scan.files[i];
        if (file->elf.type == ET_DYN && !(file->elf.flags1 & DF_1_PIE) &&
            file->elf.soname && !strcmp(file->elf.soname, name) &&
            (!strcmp(arch, "any") || !strcmp(arch, holy_elf_machine(&file->elf))) &&
            (!strcmp(libc, "any") || !strcmp(libc, file->runtime))) {
            result = 1;
            break;
        }
    }
done:
    holy_scan_free(&scan);
    unlink(snapshot); free(snapshot);
    return result;
}

enum installed_query { QUERY_PACKAGE, QUERY_PATH, QUERY_COMMAND, QUERY_SONAME };

static int add_installed_candidates(struct installed_candidates *catalog,
                                    enum installed_query query, const char *needle,
                                    const char *arch, const char *libc)
{
    DIR *list = directory_stream(catalog->installed);
    struct dirent *entry;
    int ok = 0;
    if (!list) return 0;
    errno = 0;
    while ((entry = readdir(list))) {
        int item, matches;
        size_t i;
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        for (i = 0; i < catalog->count; ++i)
            if (!strcmp(entry->d_name, catalog->digests[i])) break;
        if (i != catalog->count) { errno = 0; continue; }
        item = child_dir(catalog->installed, entry->d_name, 0);
        if (item < 0) goto done;
        if (query == QUERY_PACKAGE)
            matches = installed_package_claim(catalog, item, entry->d_name, needle);
        else if (query == QUERY_COMMAND)
            matches = installed_command_claim(item, needle);
        else if (query == QUERY_SONAME)
            matches = installed_soname_claim(catalog, item, entry->d_name, needle, arch, libc);
        else {
            int files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
            matches = files < 0 ? -1 : holy_install_manifest_owns(files, needle);
            if (files >= 0) close(files);
        }
        close(item);
        if (matches < 0) goto done;
        if (matches) {
            if (catalog->count == 10000 ||
                !(catalog->digests[catalog->count] = strdup(entry->d_name))) goto done;
            ++catalog->count;
        }
        errno = 0;
    }
    ok = errno == 0;
done:
    closedir(list);
    return ok;
}

static int installed_or_provider(void *context, const char *name,
                                 const char *relation, const char *version)
{
    (void)relation; (void)version;
    return add_installed_candidates(context, QUERY_PACKAGE, name, NULL, NULL);
}

static int installed_requirement(void *context, const char *id,
    const char *consumer, const char *kind, const char *name,
    const char *arch, const char *libc, const char *relation,
    const char *version, const char *original, const char *evidence)
{
    (void)id; (void)consumer; (void)relation;
    (void)version; (void)original; (void)evidence;
    if (!strcmp(kind, "package"))
        return add_installed_candidates(context, QUERY_PACKAGE, name, NULL, NULL);
    if (!strcmp(kind, "package-or"))
        return holy_package_or_each(name, installed_or_provider, context);
    if (!strcmp(kind, "file") && name[0] == '/')
        return add_installed_candidates(context, QUERY_PATH, name + 1, NULL, NULL);
    if (!strcmp(kind, "command"))
        return add_installed_candidates(context, QUERY_COMMAND, name, NULL, NULL);
    if (!strcmp(kind, "soname") && !strcmp(relation, "any"))
        return add_installed_candidates(context, QUERY_SONAME, name, arch, libc);
    return 1;
}

static int installed_link_step(int root, const char *path, size_t *alias_length,
                               char **target)
{
    const char *end = path;
    struct open_how how = {0};
    *target = NULL;
    how.flags = O_PATH | O_NOFOLLOW | O_CLOEXEC;
    how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS;
    while (*end) {
        char *prefix;
        struct stat st;
        int fd;
        ssize_t length;
        end = strchr(end, '/');
        if (!end) end = path + strlen(path);
        prefix = strndup(path, (size_t)(end - path));
        if (!prefix) return 1;
        fd = (int)syscall(SYS_openat2, root, prefix, &how, sizeof how);
        free(prefix);
        if (fd < 0) return errno == ENOENT ? 0 : errno == ENOSYS ? 6 : 1;
        if (fstat(fd, &st)) { close(fd); return 1; }
        if (S_ISLNK(st.st_mode)) {
            *target = malloc(65537);
            if (!*target) { close(fd); return 1; }
            length = readlinkat(fd, "", *target, 65536);
            close(fd);
            if (length <= 0 || length == 65536) {
                free(*target); *target = NULL; return 3;
            }
            (*target)[length] = 0;
            *alias_length = (size_t)(end - path);
            return 2;
        }
        close(fd);
        if (!*end) break;
        ++end;
    }
    return 0;
}

static int candidate_link_step(const struct holy_scan_result *scans, size_t count,
                                const char *path, size_t *alias_length,
                                const char **target)
{
    size_t i, j;
    *alias_length = 0;
    *target = NULL;
    for (i = 0; i < count; ++i) for (j = 0; j < scans[i].symlink_count; ++j) {
        const struct holy_scanned_symlink *link = &scans[i].symlinks[j];
        size_t length = strlen(link->path);
        if (strncmp(path, link->path, length) ||
            (path[length] && path[length] != '/')) continue;
        if (length > *alias_length) {
            *alias_length = length;
            *target = link->target;
        } else if (length == *alias_length && strcmp(*target, link->target)) {
            return 3;
        }
    }
    return *target ? 2 : 0;
}

static int installed_script_candidates(struct installed_candidates *catalog,
                                        int root, const char *interpreter,
                                        const struct holy_scan_result *initial,
                                        size_t initial_count)
{
    char *path = strdup(interpreter + 1), *visited[16] = {0};
    size_t hop, i;
    int result = 1;
    if (!path) return 1;
    for (hop = 0; hop < 16; ++hop) {
        size_t alias_length = 0;
        char *target = NULL, *next;
        const char *candidate_target = NULL;
        size_t candidate_length = 0;
        int step, candidate_step;
        for (i = 0; i < hop; ++i) if (!strcmp(visited[i], path)) {
            result = 3; goto done;
        }
        visited[hop] = strdup(path);
        if (!visited[hop]) goto done;
        step = installed_link_step(root, path, &alias_length, &target);
        if (step == 6 || step == 3 || step == 1) { result = step; goto done; }
        candidate_step = candidate_link_step(initial, initial_count, path,
                                             &candidate_length, &candidate_target);
        if (candidate_step == 3) { free(target); result = 3; goto done; }
        if (step == 0 && candidate_step == 2) {
            next = holy_relative_link_path(path, candidate_length,
                                            candidate_target, path + candidate_length);
            if (!next) { result = 3; goto done; }
            free(path);
            path = next;
            continue;
        }
        if (step == 0) {
            result = add_installed_candidates(catalog, QUERY_PATH, path, NULL, NULL) ? 0 : 1;
            break;
        }
        next = holy_relative_link_path(path, alias_length, target,
                                       path + alias_length);
        free(target);
        if (!next) { result = 3; goto done; }
        path[alias_length] = 0;
        if (!add_installed_candidates(catalog, QUERY_PATH, path, NULL, NULL)) {
            free(next); goto done;
        }
        free(path);
        path = next;
    }
    if (hop == 16) result = 3;
done:
    for (i = 0; i < 16; ++i) free(visited[i]);
    free(path);
    return result;
}

static int discover_installed(const char *root_path, int dir,
                              const char *const *digests, size_t *count, char ***output)
{
    struct installed_candidates catalog = {-1, NULL, 0, root_path};
    struct holy_scan_result *initial = NULL;
    size_t i, j, k, initial_count = *count;
    int root = -1, result = 1;
    catalog.digests = calloc(10000, sizeof *catalog.digests);
    if (!catalog.digests) return 1;
    catalog.installed = child_dir(dir, "installed", 0);
    if (catalog.installed < 0) goto done;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0) goto done;
    initial = calloc(initial_count, sizeof *initial);
    if (!initial) goto done;
    for (i = 0; i < *count; ++i) {
        char *snapshot;
        catalog.digests[i] = strdup(digests[i]);
        if (!catalog.digests[i]) goto done;
        ++catalog.count;
        snapshot = holy_cache_snapshot(catalog.digests[i], root_path);
        if (!snapshot) { result = 6; goto done; }
        j = holy_scan_collect(snapshot, &initial[i]);
        unlink(snapshot); free(snapshot);
        if (!j) { result = 6; goto done; }
    }
    for (i = 0; i < catalog.count; ++i) {
        struct holy_scan_result extra = {0};
        struct holy_scan_result *scan = i < initial_count ? &initial[i] : &extra;
        char *snapshot = holy_cache_snapshot(catalog.digests[i], root_path);
        int ok;
        if (!snapshot) { result = 6; goto done; }
        ok = holy_deps_visit(snapshot, installed_requirement, &catalog) &&
             (i < initial_count || holy_scan_collect(snapshot, scan));
        unlink(snapshot);
        free(snapshot);
        for (j = 0; ok && j < scan->count; ++j) {
            const struct holy_elf_info *elf = &scan->files[j].elf;
            if (elf->interpreter && elf->interpreter[0] == '/')
                ok = add_installed_candidates(&catalog, QUERY_PATH, elf->interpreter + 1, NULL, NULL);
            for (k = 0; ok && k < elf->needed_count; ++k)
                if (elf->needed[k][0] == '/')
                    ok = add_installed_candidates(&catalog, QUERY_PATH, elf->needed[k] + 1, NULL, NULL);
                else
                    ok = add_installed_candidates(&catalog, QUERY_SONAME, elf->needed[k],
                        holy_elf_machine(elf), scan->files[j].runtime);
        }
        for (j = 0; ok && j < scan->script_count; ++j)
            if (scan->scripts[j].kind == 1) {
                int status = installed_script_candidates(&catalog, root,
                                                         scan->scripts[j].interpreter,
                                                         initial, initial_count);
                if (status) { result = status; ok = 0; }
            }
        if (i >= initial_count) holy_scan_free(&extra);
        if (!ok) { if (result == 1) result = 6; goto done; }
    }
    result = 0;
done:
    if (initial) {
        for (i = 0; i < initial_count; ++i) holy_scan_free(&initial[i]);
        free(initial);
    }
    if (root >= 0) close(root);
    if (catalog.installed >= 0) close(catalog.installed);
    *count = catalog.count;
    *output = catalog.digests;
    return result;
}

static int binding_valid(const char *binding)
{
    return strlen(binding) == 129 && binding[64] == '=' &&
           strspn(binding, "0123456789abcdef") == 64 && valid_digest(binding + 65);
}

static int instance_source_matches(int instance, const char *record)
{
    char id[65], actual[65], expected[65];
    unsigned char bytes[32];
    unsigned int size;
    size_t i;
    struct stat st;
    if (!record) return fstatat(instance, "source", &st, AT_SYMLINK_NOFOLLOW) && errno == ENOENT;
    if (!holy_source_instance(instance, id, actual) ||
        EVP_Digest(record, strlen(record), bytes, &size, EVP_sha256(), NULL) != 1 || size != 32) return 0;
    for (i = 0; i < 32; ++i) snprintf(expected + i * 2, 3, "%02x", bytes[i]);
    return !strcmp(expected, actual);
}

static int build_set(const char *root_path, int root, int dir,
                      unsigned long long generation, const char *const *digests,
                      size_t count, const char *choice, int completed,
                      const char *const *bindings, size_t binding_count,
                      const char *default_source_id,
                      const char *catalog_index,
                      const char *const *accepted_arch, size_t accepted_count,
                      const char *const *accepted_privileged, size_t privileged_count,
                      const char *const *skipped_hooks, size_t skipped_count,
                      struct install_set *set)
{
    char **snapshots = NULL;
    char **candidates = NULL;
    struct plan_hash plan = {0};
    struct stat st;
    struct utsname host;
    char number[128];
    unsigned char digest[32];
    unsigned int length;
    size_t i, j, initial_count = count;
    int result = 1;
    if (!count || count > 10000 || binding_count > 10000 ||
        accepted_count > 10000 || privileged_count > 10000 || skipped_count > 10000) return 2;
    if ((default_source_id && !valid_digest(default_source_id)) ||
        (catalog_index && (!valid_digest(catalog_index) ||
                           (!default_source_id && !completed)))) return 2;
    for (i = 0; i < accepted_count; ++i) {
        if (!valid_digest(accepted_arch[i])) return 2;
        for (j = 0; j < i; ++j) if (!strcmp(accepted_arch[i], accepted_arch[j])) return 2;
    }
    for (i = 0; i < privileged_count; ++i) {
        if (!valid_digest(accepted_privileged[i])) return 2;
        for (j = 0; j < i; ++j)
            if (!strcmp(accepted_privileged[i], accepted_privileged[j])) return 2;
    }
    for (i = 0; i < skipped_count; ++i) {
        if (!valid_digest(skipped_hooks[i])) return 2;
        for (j = 0; j < i; ++j) if (!strcmp(skipped_hooks[i], skipped_hooks[j])) return 2;
    }
    for (i = 0; i < count; ++i) if (!valid_digest(digests[i])) return 2;
    for (i = 0; i < binding_count; ++i) {
        if (!binding_valid(bindings[i])) return 2;
        for (j = 0; j < i; ++j) if (!strncmp(bindings[i], bindings[j], 64)) return 2;
        for (j = 0; j < count; ++j)
            if (!strncmp(bindings[i], digests[j], 64)) break;
        if (j == count) return 2;
    }
    if (!completed) {
        result = discover_installed(root_path, dir, digests, &count, &candidates);
        if (result) goto done;
        digests = (const char *const *)candidates;
        result = 1;
    }
    snapshots = calloc(count, sizeof *snapshots);
    if (!snapshots || fstat(root, &st) || uname(&host)) goto done;
    if (strlen(host.machine) >= sizeof set->host) goto done;
    strcpy(set->host, host.machine);
    for (i = 0; i < count; ++i) {
        snapshots[i] = holy_cache_snapshot(digests[i], root_path);
        if (!snapshots[i]) { result = 6; goto done; }
    }
    result = holy_resolve_collect((const char *const *)snapshots, count, choice,
                                  &set->resolution);
    if (result) goto done;
    for (i = 0; i < accepted_count; ++i) {
        for (j = 0; j < set->resolution.artifact_count; ++j)
            if (!strcmp(accepted_arch[i], set->resolution.artifacts[j])) break;
        if (j == set->resolution.artifact_count) { result = 3; goto done; }
    }
    for (i = 0; i < privileged_count; ++i) {
        for (j = 0; j < set->resolution.artifact_count; ++j)
            if (!strcmp(accepted_privileged[i], set->resolution.artifacts[j])) break;
        if (j == set->resolution.artifact_count) { result = 3; goto done; }
    }
    for (i = 0; i < skipped_count; ++i) {
        for (j = 0; j < set->resolution.artifact_count; ++j)
            if (!strcmp(skipped_hooks[i], set->resolution.artifacts[j])) break;
        if (j == set->resolution.artifact_count) { result = 3; goto done; }
    }
    /* a caller binding is a decision about one artifact, so it must land in the set.
       a source binding instead offers every staged candidate, and the resolver may
       drop one, so those are not checked here. */
    if (!catalog_index)
        for (i = 0; i < binding_count; ++i) {
            for (j = 0; j < set->resolution.artifact_count; ++j)
                if (!strncmp(bindings[i], set->resolution.artifacts[j], 64)) break;
            if (j == set->resolution.artifact_count) { result = 3; goto done; }
        }
    result = 1;
    set->items = calloc(set->resolution.artifact_count, sizeof *set->items);
    if (!set->items || !holy_resolution_record(&set->resolution, &set->graph,
                                               &set->graph_length)) goto done;
    set->count = set->resolution.artifact_count;
    plan.hash = EVP_MD_CTX_new();
    plan.dir = dir;
    plan.completed = completed;
    if (!plan.hash || EVP_DigestInit_ex(plan.hash, EVP_sha256(), NULL) != 1 ||
        !hash_text(plan.hash, "holy-set-plan-1") ||
        !hash_text(plan.hash, choice ? choice : "-") ||
        !hash_text(plan.hash, set->graph)) goto done;
    if (catalog_index) {
        memcpy(set->catalog_index, catalog_index, 65);
        if (!hash_text(plan.hash, "catalog-index") ||
            !hash_text(plan.hash, catalog_index)) goto done;
    }
    snprintf(number, sizeof number, "%ju:%ju", (uintmax_t)st.st_dev, (uintmax_t)st.st_ino);
    if (!hash_text(plan.hash, number)) goto done;
    snprintf(number, sizeof number, "%llu", generation);
    if (!hash_text(plan.hash, number)) goto done;
    for (i = 0; i < set->count; ++i) {
        struct set_item *item = &set->items[i];
        for (j = 0; j < count; ++j)
            if (!strcmp(digests[j], set->resolution.artifacts[i])) break;
        if (j == count) goto done;
        item->snapshot = snapshots[j];
        snapshots[j] = NULL;
        result = 6;
        if (!holy_package_identity(item->snapshot, &item->identity) ||
            strcmp(item->identity.digest, set->resolution.artifacts[i]) ||
            strcmp(item->identity.os, "linux") ||
            (strcmp(item->identity.libc, "nolibc") && strcmp(item->identity.libc, "glibc") &&
             strcmp(item->identity.libc, "musl")) ||
            !recorded_transform(item->snapshot) || !instance_preflight(item->snapshot)) goto done;
        strcpy(item->source_id, "-");
        result = explicit_elf_paths(item->snapshot, 1);
        if (result) goto done;
        result = reuse_instance(dir, root, item, generation, completed, set->graph, plan.hash);
        if (result < 0) { result = 4; goto done; }
        {
            size_t hook_length;
            char *hooks = read_hooks(item->snapshot, &hook_length);
            int accepted = 0;
            if (!hooks) { result = 6; goto done; }
            for (j = 0; j < skipped_count; ++j)
                if (!strcmp(skipped_hooks[j], item->identity.digest)) accepted = 1;
            if (item->reused) result = accepted ? 3 : 0;
            else if (accepted && !hook_length) result = 2;
            else if (hook_length && !accepted) {
                fprintf(stderr, "holypkg: decision-required hooks artifact=%s; --skip-hooks %s records installed-unconfigured\n",
                        item->identity.digest, item->identity.digest);
                fwrite(hooks, 1, hook_length, stderr);
                if (hooks[hook_length - 1] != '\n') fputc('\n', stderr);
                result = 3;
            } else {
                item->skipped_hooks = accepted;
                result = 0;
                if (accepted && (!hash_text(plan.hash, "skipped-hooks") ||
                                 !hash_text(plan.hash, item->identity.digest) ||
                                 !hash_text(plan.hash, hooks))) result = 1;
            }
            free(hooks);
            if (result) goto done;
        }
        {
            struct privileged_scan scan = {0};
            int accepted = 0, scanned = holy_verify_visit(item->snapshot, scan_privileged, &scan);
            for (j = 0; j < privileged_count; ++j)
                if (!strcmp(accepted_privileged[j], item->identity.digest)) accepted = 1;
            if (!scanned) {
                result = scan.unsupported ? 6 : 1;
                free(scan.first); goto done;
            }
            if (item->reused) {
                result = accepted ? 3 : item->privileged != !!scan.count ? 4 : 0;
            } else if (accepted && !scan.count) result = 2;
            else if (scan.count && !accepted) {
                fprintf(stderr, "holypkg: decision-required privileged artifact=%s path=%s mode=%04o; --accept-privileged %s permits setuid placement\n",
                        item->identity.digest, scan.first, scan.mode, item->identity.digest);
                result = 3;
            } else {
                item->privileged = accepted;
                result = 0;
                if (accepted && (!hash_text(plan.hash, "accepted-privileged") ||
                                 !hash_text(plan.hash, item->identity.digest))) result = 1;
            }
            free(scan.first);
            if (result) goto done;
        }
        for (j = 0; j < accepted_count; ++j)
            if (!strcmp(accepted_arch[j], item->identity.digest)) break;
        if (j < accepted_count) {
            if (item->reused) { result = 3; goto done; }
            if (native_architecture(host.machine, item->identity.arch)) { result = 2; goto done; }
            snprintf(item->architecture, sizeof item->architecture, "%s %s", host.machine, item->identity.arch);
            if (!architecture_valid(item->architecture)) { result = 2; goto done; }
            if (!hash_text(plan.hash, "accepted-architecture") ||
                !hash_text(plan.hash, item->identity.digest) ||
                !hash_text(plan.hash, item->architecture)) { result = 1; goto done; }
        } else if (item->reused) {
            int installed = child_dir(dir, "installed", 0);
            int existing = installed < 0 ? -1 : child_dir(installed, item->identity.digest, 0);
            int valid = existing >= 0 && instance_architecture(existing, item->architecture);
            if (existing >= 0) close(existing);
            if (installed >= 0) close(installed);
            if (!valid) { result = 4; goto done; }
        }
        if (!native_architecture(host.machine, item->identity.arch)) {
            char expected[96];
            snprintf(expected, sizeof expected, "%s %s", host.machine, item->identity.arch);
            if (strcmp(expected, item->architecture)) {
                fprintf(stderr, "holypkg: decision-required architecture artifact=%s host=%s target=%s; --accept-arch %s permits placement without proving execution\n",
                        item->identity.digest, host.machine, item->identity.arch, item->identity.digest);
                result = 3; goto done;
            }
        }
        for (j = 0; j < binding_count; ++j) if (!strncmp(bindings[j], item->identity.digest, 64)) {
            char registry[65];
            if (item->reused) { result = 3; goto done; }
            result = holy_source_record(dir, bindings[j] + 65, &item->source_record, registry);
            if (result) goto done;
            memcpy(item->source_id, bindings[j] + 65, 65);
            if (!hash_text(plan.hash, "source-binding") || !hash_text(plan.hash, registry) ||
                !hash_text(plan.hash, item->source_record)) { result = 1; goto done; }
            break;
        }
        if (default_source_id && j == binding_count && !item->reused) {
            char registry[65];
            for (j = 0; j < initial_count; ++j)
                if (!strcmp(digests[j], item->identity.digest)) break;
            if (j == initial_count) { result = 6; goto done; }
            result = holy_source_record(dir, default_source_id,
                                        &item->source_record, registry);
            if (result) goto done;
            memcpy(item->source_id, default_source_id, 65);
            if (!hash_text(plan.hash, "source-binding") || !hash_text(plan.hash, registry) ||
                !hash_text(plan.hash, item->source_record)) { result = 1; goto done; }
        }
        if (item->reused && !strcmp(item->identity.digest, set->resolution.root)) {
            result = 3; goto done;
        }
        result = holy_preview_resolved(item->snapshot, root_path, completed || item->reused,
                                       item->privileged, item->skipped_hooks || item->reused);
        if (result) goto done;
        for (j = 0; j < i; ++j)
            if (same_slot(&set->items[j].identity, set->items[j].source_id,
                          &item->identity, item->source_id)) {
                result = 4; goto done;
            }
        if (!completed && !item->reused) {
            result = slot_available(dir, &item->identity, item->source_id);
            if (result != 1) { result = result < 0 ? 1 : 4; goto done; }
            if (!holy_install_preflight(item->snapshot, root, item->privileged)) { result = 4; goto done; }
        }
        result = 6;
        plan.completed = completed || item->reused;
        plan.accepted_privileged = item->privileged;
        if (!hash_text(plan.hash, item->identity.digest) ||
            !holy_verify_visit(item->snapshot, plan_entry, &plan)) {
            if (plan.claim_error) result = plan.claim_error;
            goto done;
        }
        result = 1;
        if (!holy_verify_visit(item->snapshot, set_claim, set)) goto done;
    }
    if (!set_claims_valid(set)) { result = 4; goto done; }
    if (!set_soname_paths(set, root)) { result = 3; goto done; }
    if (!same_root(root_path, &st)) { result = 4; goto done; }
    set->bindings = calloc(set->count, sizeof *set->bindings);
    if (!set->bindings) goto done;
    for (i = 0; i < set->count; ++i) if (set->items[i].source_record) {
        char *binding = malloc(130);
        if (!binding) goto done;
        snprintf(binding, 130, "%s=%s", set->items[i].identity.digest,
                 set->items[i].source_id);
        set->bindings[set->binding_count++] = binding;
    }
    if (EVP_DigestFinal_ex(plan.hash, digest, &length) != 1 || length != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(set->hash + i * 2, 3, "%02x", digest[i]);
    set->paths = plan.count;
    result = 0;
done:
    if (snapshots) for (i = 0; i < count; ++i) {
        if (snapshots[i]) unlink(snapshots[i]);
        free(snapshots[i]);
    }
    free(snapshots);
    if (candidates) {
        for (i = 0; i < count; ++i) free(candidates[i]);
        free(candidates);
    }
    EVP_MD_CTX_free(plan.hash);
    return result;
}

static void free_set_journal(struct set_journal *journal)
{
    size_t i;
    for (i = 0; i < journal->count; ++i) free(journal->digests[i]);
    free(journal->digests);
    for (i = 0; i < journal->binding_count; ++i) free(journal->bindings[i]);
    free(journal->bindings);
    free(journal->choice);
    for (i = 0; i < journal->accepted_count; ++i) free(journal->accepted_arch[i]);
    free(journal->accepted_arch);
    for (i = 0; i < journal->privileged_count; ++i) free(journal->accepted_privileged[i]);
    free(journal->accepted_privileged);
    for (i = 0; i < journal->skipped_count; ++i) free(journal->skipped_hooks[i]);
    free(journal->skipped_hooks);
    memset(journal, 0, sizeof *journal);
}

static int set_choice_valid(const char *choice)
{
    const char *equal;
    size_t i;
    if (!strcmp(choice, "-")) return 1;
    equal = strchr(choice, '=');
    if (!equal || equal == choice || !valid_digest(equal + 1) ||
        (size_t)(equal - choice) > 65536) return 0;
    for (i = 0; choice + i < equal; ++i)
        if (!((choice[i] >= 'a' && choice[i] <= 'z') ||
              (choice[i] >= 'A' && choice[i] <= 'Z') ||
              (choice[i] >= '0' && choice[i] <= '9') ||
              choice[i] == '-' || choice[i] == '_' || choice[i] == '.')) return 0;
    return 1;
}

static int read_set_journal(int dir, struct set_journal *journal)
{
    int transactions = child_dir(dir, "transactions", 0), fd = -1, result = -1, version = 1;
    struct stat st;
    FILE *stream = NULL;
    char *line = NULL;
    size_t capacity = 0, number = 0, bytes = 0, roots = 0;
    ssize_t got;
    if (transactions < 0) return -1;
    {
        DIR *list = directory_stream(transactions);
        struct dirent *entry;
        int valid = 1;
        if (!list) goto done;
        errno = 0;
        while ((entry = readdir(list))) {
            if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
            if (completed_update(transactions, entry->d_name)) { errno = 0; continue; }
            if (strcmp(entry->d_name, "set-journal")) { valid = 0; break; }
            errno = 0;
        }
        if (!entry && errno) valid = 0;
        closedir(list);
        if (!valid) {
            struct stat other;
            if (fstatat(transactions, "set-journal", &other, AT_SYMLINK_NOFOLLOW) &&
                errno == ENOENT) result = 0;
            goto done;
        }
    }
    fd = openat(transactions, "set-journal", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (fd < 0) { result = errno == ENOENT ? 0 : -1; goto done; }
    if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 1 ||
        st.st_size > 4 * 1024 * 1024 || (st.st_mode & 0022) ||
        (st.st_uid != 0 && st.st_uid != geteuid())) goto done;
    stream = fdopen(fd, "r");
    if (!stream) goto done;
    fd = -1;
    while ((got = getline(&line, &capacity, stream)) >= 0) {
        char *end;
        bytes += (size_t)got;
        if (bytes > 4 * 1024 * 1024 || !got || line[got - 1] != '\n' ||
            memchr(line, 0, (size_t)got)) goto done;
        line[got - 1] = 0;
        if (number == 0) {
            if (!strcmp(line, "format holy-set-journal-6")) version = 6;
            else if (!strcmp(line, "format holy-set-journal-5")) version = 5;
            else if (!strcmp(line, "format holy-set-journal-4")) version = 4;
            else if (!strcmp(line, "format holy-set-journal-3")) version = 3;
            else if (!strcmp(line, "format holy-set-journal-2")) version = 2;
            else if (strcmp(line, "format holy-set-journal-1")) goto done;
        } else if (number == 1) {
            if (strncmp(line, "generation ", 11) || line[11] < '0' || line[11] > '9' ||
                (line[11] == '0' && line[12])) goto done;
            errno = 0;
            journal->generation = strtoull(line + 11, &end, 10);
            if (errno || *end || journal->generation == ULLONG_MAX) goto done;
        } else if (number == 2) {
            if (strncmp(line, "plan ", 5) || !valid_digest(line + 5)) goto done;
            memcpy(journal->hash, line + 5, 65);
        } else if (number == 3) {
            if (strncmp(line, "root ", 5) || !valid_digest(line + 5)) goto done;
            memcpy(journal->root, line + 5, 65);
        } else if (number == 4) {
            if (strncmp(line, "choice ", 7) || !set_choice_valid(line + 7)) goto done;
            journal->choice = strdup(line + 7);
            if (!journal->choice) goto done;
        } else if (version >= 3 && number == 5) {
            char architecture[96];
            if (strncmp(line, "host ", 5) || strlen(line + 5) > 64) goto done;
            snprintf(architecture, sizeof architecture, "%s x86", line + 5);
            if (!architecture_valid(architecture)) goto done;
            strcpy(journal->host, line + 5);
        } else if ((version == 5 || version == 6) && number == 6 &&
                   !strncmp(line, "catalog-index ", 14)) {
            if (strncmp(line, "catalog-index ", 14) ||
                !valid_digest(line + 14)) goto done;
            memcpy(journal->catalog_index, line + 14, 65);
        } else if (version >= 3 && !strncmp(line, "accept-arch ", 12)) {
            char **next;
            size_t i;
            if (!valid_digest(line + 12) || journal->accepted_count >= journal->count) goto done;
            for (i = 0; i < journal->count; ++i) if (!strcmp(line + 12, journal->digests[i])) break;
            if (i == journal->count) goto done;
            for (i = 0; i < journal->accepted_count; ++i)
                if (!strcmp(line + 12, journal->accepted_arch[i])) goto done;
            next = realloc(journal->accepted_arch, (journal->accepted_count + 1) * sizeof *next);
            if (!next) goto done;
            journal->accepted_arch = next;
            next[journal->accepted_count] = strdup(line + 12);
            if (!next[journal->accepted_count]) goto done;
            ++journal->accepted_count;
        } else if (version >= 4 && !strncmp(line, "accept-privileged ", 18)) {
            char **next;
            size_t i;
            if (!valid_digest(line + 18) || journal->privileged_count >= journal->count) goto done;
            for (i = 0; i < journal->count; ++i)
                if (!strcmp(line + 18, journal->digests[i])) break;
            if (i == journal->count) goto done;
            for (i = 0; i < journal->privileged_count; ++i)
                if (!strcmp(line + 18, journal->accepted_privileged[i])) goto done;
            next = realloc(journal->accepted_privileged,
                           (journal->privileged_count + 1) * sizeof *next);
            if (!next) goto done;
            journal->accepted_privileged = next;
            next[journal->privileged_count] = strdup(line + 18);
            if (!next[journal->privileged_count]) goto done;
            ++journal->privileged_count;
        } else if (version == 6 && !strncmp(line, "skip-hooks ", 11)) {
            char **next;
            size_t i;
            if (!valid_digest(line + 11) || journal->skipped_count >= journal->count) goto done;
            for (i = 0; i < journal->count; ++i)
                if (!strcmp(line + 11, journal->digests[i])) break;
            if (i == journal->count) goto done;
            for (i = 0; i < journal->skipped_count; ++i)
                if (!strcmp(line + 11, journal->skipped_hooks[i])) goto done;
            next = realloc(journal->skipped_hooks, (journal->skipped_count + 1) * sizeof *next);
            if (!next) goto done;
            journal->skipped_hooks = next;
            next[journal->skipped_count] = strdup(line + 11);
            if (!next[journal->skipped_count]) goto done;
            ++journal->skipped_count;
        } else if (version >= 2 && !strncmp(line, "binding ", 8)) {
            char **next;
            size_t i;
            if (journal->accepted_count || journal->privileged_count || journal->skipped_count ||
                !binding_valid(line + 8) || journal->binding_count >= journal->count) goto done;
            for (i = 0; i < journal->count; ++i)
                if (!strncmp(line + 8, journal->digests[i], 64)) break;
            if (i == journal->count) goto done;
            for (i = 0; i < journal->binding_count; ++i)
                if (!strncmp(line + 8, journal->bindings[i], 64)) goto done;
            next = realloc(journal->bindings, (journal->binding_count + 1) * sizeof *next);
            if (!next) goto done;
            journal->bindings = next;
            next[journal->binding_count] = strdup(line + 8);
            if (!next[journal->binding_count]) goto done;
            ++journal->binding_count;
        } else {
            char **next;
            if (journal->binding_count || journal->accepted_count || journal->privileged_count ||
                journal->skipped_count ||
                strncmp(line, "artifact ", 9) || !valid_digest(line + 9) ||
                journal->count >= 10000 ||
                (journal->count && strcmp(journal->digests[journal->count - 1], line + 9) >= 0))
                goto done;
            next = realloc(journal->digests, (journal->count + 1) * sizeof *next);
            if (!next) goto done;
            journal->digests = next;
            next[journal->count] = strdup(line + 9);
            if (!next[journal->count]) goto done;
            ++journal->count;
            if (!strcmp(line + 9, journal->root)) ++roots;
        }
        ++number;
    }
    if (!ferror(stream) && bytes == (size_t)st.st_size && number >= 6 && roots == 1 &&
        (version == 1 || (version == 2 && journal->binding_count) ||
         (version == 3 && journal->accepted_count) ||
         (version == 4 && journal->privileged_count) ||
         (version == 5 && journal->catalog_index[0] && journal->binding_count) ||
         (version == 6 && journal->skipped_count))) result = 1;
done:
    free(line);
    if (stream) fclose(stream);
    if (fd >= 0) close(fd);
    close(transactions);
    if (result != 1) free_set_journal(journal);
    return result;
}

static int set_journal_present(int dir)
{
    struct set_journal journal = {0};
    unsigned long long generation;
    int result = read_set_journal(dir, &journal);
    if (result == 1 && (!read_generation(dir, &generation) ||
        (generation != journal.generation && generation != journal.generation + 1))) result = -1;
    free_set_journal(&journal);
    return result;
}

static int write_set_journal(int transactions, unsigned long long generation,
                             const struct install_set *set, const char *choice,
                             const char *const *accepted_arch, size_t accepted_count,
                             const char *const *accepted_privileged, size_t privileged_count,
                             const char *const *skipped_hooks, size_t skipped_count)
{
    char *record = NULL;
    size_t length = 0, i;
    FILE *stream = open_memstream(&record, &length);
    int ok = 1;
    if (!stream) return 0;
    if (fprintf(stream, "format holy-set-journal-%d\ngeneration %llu\nplan %s\nroot %s\nchoice %s\n",
                skipped_count ? 6 : set->catalog_index[0] ? 5 : privileged_count ? 4 :
                accepted_count ? 3 : set->binding_count ? 2 : 1,
                generation, set->hash, set->resolution.root, choice ? choice : "-") < 0) ok = 0;
    if ((accepted_count || privileged_count || skipped_count || set->catalog_index[0]) &&
        fprintf(stream, "host %s\n", set->host) < 0) ok = 0;
    if (set->catalog_index[0] &&
        fprintf(stream, "catalog-index %s\n", set->catalog_index) < 0) ok = 0;
    for (i = 0; i < set->count && ok; ++i)
        if (fprintf(stream, "artifact %s\n", set->items[i].identity.digest) < 0) ok = 0;
    for (i = 0; i < set->binding_count && ok; ++i)
        if (fprintf(stream, "binding %s\n", set->bindings[i]) < 0) ok = 0;
    for (i = 0; i < accepted_count && ok; ++i)
        if (fprintf(stream, "accept-arch %s\n", accepted_arch[i]) < 0) ok = 0;
    for (i = 0; i < privileged_count && ok; ++i)
        if (fprintf(stream, "accept-privileged %s\n", accepted_privileged[i]) < 0) ok = 0;
    for (i = 0; i < skipped_count && ok; ++i)
        if (fprintf(stream, "skip-hooks %s\n", skipped_hooks[i]) < 0) ok = 0;
    if (fclose(stream)) ok = 0;
    if (ok) ok = record_file(transactions, "set-journal", record, length);
    free(record);
    return ok;
}

static int set_generation(int dir, unsigned long long generation)
{
    char record[32], temp_name[43] = {0};
    size_t length = (size_t)snprintf(record, sizeof record, "%llu\n", generation);
    int fd = holy_temporary_at(dir, temp_name), ok = 0;
    if (fd < 0) return 0;
    if (length < sizeof record && write_all(fd, record, length) && !fsync(fd) &&
        !renameat(dir, temp_name, dir, "generation") && !fsync(dir)) ok = 1;
    close(fd);
    if (!ok) unlinkat(dir, temp_name, 0);
    return ok;
}

static int state_set(const char *const *digests, size_t count, const char *choice,
                     const char *approved, const char *root_path,
                     const char *const *bindings, size_t binding_count,
                     const char *default_source_id,
                     const char *catalog_index,
                     const char *const *accepted_arch, size_t accepted_count,
                     const char *const *accepted_privileged, size_t privileged_count,
                     const char *const *skipped_hooks, size_t skipped_count,
                     char plan_hash[65], int quiet)
{
    struct install_set set = {0};
    unsigned long long generation;
    int root = -1, dir = -1, installed = -1, transactions = -1, result = 1, journaled = 0;
    size_t i;
    if (plan_hash) plan_hash[0] = 0;
    if ((approved && !valid_digest(approved)) || (choice && !set_choice_valid(choice))) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    dir = root < 0 ? -1 : state_dir_at(root, 0);
    if (dir < 0 || flock(dir, approved ? LOCK_EX : LOCK_SH) ||
        !state_layout(dir, 0) || !read_generation(dir, &generation) ||
        generation == ULLONG_MAX || !empty_child(dir, "index")) goto done;
    if (!empty_child(dir, "transactions")) { result = 5; goto done; }
    if (!installed_valid(dir)) goto done;
    result = build_set(root_path, root, dir, generation, digests, count, choice, 0, bindings, binding_count,
                       default_source_id, catalog_index,
                       accepted_arch, accepted_count, accepted_privileged, privileged_count,
                       skipped_hooks, skipped_count, &set);
    if (result) goto done;
    if (!approved) {
        if (plan_hash) memcpy(plan_hash, set.hash, 65);
        if (quiet) goto done;
        printf("plan-set generation %llu root %s artifacts %zu paths %zu sha256 %s read-only\n",
               generation, set.resolution.root, set.count, set.paths, set.hash);
        if (set.catalog_index[0]) printf("catalog-index %s\n", set.catalog_index);
        for (i = 0; i < set.count; ++i)
            printf("selected %s %s %s\n", set.items[i].identity.digest,
                   set.items[i].identity.name,
                   set.items[i].reused ? "installed" :
                   strcmp(set.items[i].identity.digest, set.resolution.root) ? "dependency" : "explicit");
        for (i = 0; i < set.resolution.edge_count; ++i) {
            const struct holy_resolved_edge *edge = &set.resolution.edges[i];
            printf("requirement %s consumer %s provider %s %s %s\n",
                   edge->id, edge->consumer, edge->provider, edge->kind, edge->target);
        }
        for (i = 0; i < set.count; ++i) if (set.items[i].source_record)
            printf("binding %s %s", set.items[i].identity.digest, set.items[i].source_record);
        for (i = 0; i < set.count; ++i) if (set.items[i].architecture[0])
            printf("architecture %s %s accepted-unverified scope artifact\n",
                   set.items[i].identity.digest, set.items[i].architecture);
        for (i = 0; i < set.count; ++i) if (set.items[i].privileged)
            printf("privileged %s setuid accepted scope artifact\n",
                   set.items[i].identity.digest);
        for (i = 0; i < set.count; ++i) if (set.items[i].skipped_hooks)
            printf("hooks %s skipped installed-unconfigured scope artifact\n",
                   set.items[i].identity.digest);
        goto done;
    }
    if (strcmp(set.hash, approved)) { result = 3; goto done; }
    installed = child_dir(dir, "installed", 0);
    transactions = child_dir(dir, "transactions", 0);
    if (installed < 0 || transactions < 0) { result = 1; goto done; }
    if (!write_set_journal(transactions, generation, &set, choice,
                           accepted_arch, accepted_count,
                           accepted_privileged, privileged_count,
                           skipped_hooks, skipped_count)) {
        struct stat st;
        result = fstatat(transactions, "set-journal", &st, AT_SYMLINK_NOFOLLOW) ? 1 : 5;
        goto done;
    }
    journaled = 1;
    result = 5;
    for (i = 0; i < set.count; ++i) {
        struct set_item *item = &set.items[i];
        if (item->reused) continue;
        if (!holy_install_payload(item->snapshot, root, item->privileged) ||
            !save_instance(installed, item->identity.digest, item->snapshot, generation,
                           set.graph, set.graph_length,
                           strcmp(item->identity.digest, set.resolution.root) ? "dependency" : "explicit",
                           item->source_record, item->architecture[0] ? item->architecture : NULL,
                           item->privileged, item->skipped_hooks))
            goto done;
        printf("applied %s\n", item->identity.digest);
    }
    if (!set_generation(dir, generation + 1) ||
        unlinkat(transactions, "set-journal", 0) || fsync(transactions)) goto done;
    printf("committed-set %s generation %llu artifacts %zu\n", set.hash, generation + 1, set.count);
    result = 0;
done:
    if (result && !quiet) fprintf(stderr, "holypkg: package set failed (status %d)%s\n", result,
                        journaled ? "; incomplete set journal retained" : "");
    free_set(&set);
    if (transactions >= 0) close(transactions);
    if (installed >= 0) close(installed);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_set(const char *const *digests, size_t count, const char *choice,
                   const char *approved, const char *root_path,
                   const char *const *bindings, size_t binding_count,
                   const char *const *accepted_arch, size_t accepted_count,
                   const char *const *accepted_privileged, size_t privileged_count,
                   const char *const *skipped_hooks, size_t skipped_count,
                   char plan_hash[65])
{
    return state_set(digests, count, choice, approved, root_path, bindings,
                     binding_count, NULL, NULL, accepted_arch, accepted_count,
                     accepted_privileged, privileged_count, skipped_hooks, skipped_count, plan_hash, 0);
}

int holy_state_set_source(const char *const *digests, size_t count,
                          const char *source_id, const char *catalog_index,
                          const char *choice,
                          const char *approved, const char *root_path,
                          const char *const *accepted_arch, size_t accepted_count,
                          const char *const *accepted_privileged, size_t privileged_count,
                          char plan_hash[65])
{
    if (!source_id || !catalog_index) return 2;
    return state_set(digests, count, choice, approved, root_path, NULL, 0,
                     source_id, catalog_index, accepted_arch, accepted_count,
                     accepted_privileged, privileged_count, NULL, 0, plan_hash, 0);
}

int holy_state_set_source_bindings(const char *const *digests, size_t count,
                                  const char *source_id, const char *catalog_index,
                                  const char *const *bindings, size_t binding_count,
                                  const char *choice, const char *approved,
                                  const char *root_path,
                                  const char *const *accepted_arch, size_t accepted_count,
                                  const char *const *accepted_privileged, size_t privileged_count,
                                  char plan_hash[65])
{
    if (!source_id || !catalog_index) return 2;
    return state_set(digests, count, choice, approved, root_path,
                     bindings, binding_count, source_id, catalog_index,
                     accepted_arch, accepted_count,
                     accepted_privileged, privileged_count, NULL, 0, plan_hash, 0);
}

int holy_state_probe_source_bindings(const char *const *digests, size_t count,
                                    const char *source_id, const char *catalog_index,
                                    const char *const *bindings, size_t binding_count,
                                    const char *choice, const char *root_path,
                                    const char *const *accepted_arch, size_t accepted_count,
                                    const char *const *accepted_privileged,
                                    size_t privileged_count)
{
    if (!source_id || !catalog_index) return 2;
    return state_set(digests, count, choice, NULL, root_path,
                     bindings, binding_count, source_id, catalog_index,
                     accepted_arch, accepted_count,
                     accepted_privileged, privileged_count, NULL, 0, NULL, 1);
}

static int instance_matches_snapshot(int item, const char *snapshot)
{
    static const char *const names[] = {"meta", "files", "deps", "origin", "provides", "hooks", "transform"};
    struct archive *archive = archive_read_new();
    struct archive_entry *entry;
    struct holy_package_identity identity = {0};
    unsigned seen = 0;
    char incoming[8192], installed[8192];
    int status, ok = 0, has_claims, has_hooks, has_transform, has_config;
    struct stat claims_stat;
    size_t i;
    has_claims = !fstatat(item, "provides", &claims_stat, AT_SYMLINK_NOFOLLOW);
    if (!has_claims && errno != ENOENT) goto done;
    has_hooks = !fstatat(item, "hooks", &claims_stat, AT_SYMLINK_NOFOLLOW);
    if (!has_hooks && errno != ENOENT) goto done;
    has_transform = !fstatat(item, "transform", &claims_stat, AT_SYMLINK_NOFOLLOW);
    if (!has_transform && errno != ENOENT) goto done;
    has_config = !fstatat(item, "config-state", &claims_stat, AT_SYMLINK_NOFOLLOW);
    if (!has_config && errno != ENOENT) goto done;
    if (has_config && (!holy_package_identity(snapshot, &identity) ||
                       !config_state_valid(item, identity.digest))) goto done;
    if (!archive || archive_read_support_filter_lz4(archive) != ARCHIVE_OK ||
        archive_read_support_format_tar(archive) != ARCHIVE_OK ||
        archive_read_open_filename(archive, snapshot, 8192) != ARCHIVE_OK) goto done;
    while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
        const char *path = archive_entry_pathname(entry);
        struct stat st;
        la_ssize_t got;
        int fd;
        for (i = 0; i < sizeof names / sizeof *names; ++i)
            if (path && !strncmp(path, "HOLY/", 5) && !strcmp(path + 5, names[i])) break;
        if (i == sizeof names / sizeof *names || (i == 4 && !has_claims) ||
            (i == 5 && !has_hooks) || (i == 6 && !has_transform)) {
            if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
            continue;
        }
        if (seen & (1u << i)) goto done;
        fd = openat(item, i == 1 && has_config ? "package-files" : names[i],
                    O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
        if (fd < 0) goto done;
        if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size != archive_entry_size(entry)) {
            close(fd); goto done;
        }
        while ((got = archive_read_data(archive, incoming, sizeof incoming)) > 0) {
            size_t used = 0;
            while (used < (size_t)got) {
                ssize_t read_count = read(fd, installed + used, (size_t)got - used);
                if (read_count < 0 && errno == EINTR) continue;
                if (read_count <= 0) { close(fd); goto done; }
                used += (size_t)read_count;
            }
            if (memcmp(incoming, installed, (size_t)got)) { close(fd); goto done; }
        }
        close(fd);
        if (got) goto done;
        seen |= 1u << i;
    }
    ok = status == ARCHIVE_EOF &&
         seen == ((1u << 4) - 1u) + (has_claims ? (1u << 4) : 0) +
                 (has_hooks ? (1u << 5) : 0) + (has_transform ? (1u << 6) : 0);
done:
    holy_package_identity_free(&identity);
    archive_read_free(archive);
    return ok;
}

static int instance_reason_matches(int item, const char *reason)
{
    char buffer[1024], expected[64];
    ssize_t got;
    int fd = openat(item, "state", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    if (fd < 0) return 0;
    got = read(fd, buffer, sizeof buffer - 1);
    close(fd);
    if (got <= 0) return 0;
    buffer[got] = 0;
    snprintf(expected, sizeof expected, "\nreason %s\n", reason);
    return strstr(buffer, expected) != NULL;
}

static int recover_set(const char *root_path, int resume)
{
    struct install_set set = {0};
    struct set_journal journal = {0};
    unsigned long long generation, recorded;
    const char **digests = NULL;
    unsigned char *present = NULL;
    size_t i, j;
    struct utsname host;
    int root = -1, dir = -1, installed = -1, transactions = -1, result = 5;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    dir = root < 0 ? -1 : state_dir_at(root, 0);
    if (dir < 0 || flock(dir, LOCK_EX) || !state_layout(dir, 0) ||
        !empty_child(dir, "index") || !read_generation(dir, &generation)) { result = 1; goto done; }
    if (read_set_journal(dir, &journal) != 1 ||
        (generation != journal.generation && generation != journal.generation + 1) ||
        !installed_valid(dir)) goto done;
    if (journal.host[0] && (uname(&host) || strcmp(host.machine, journal.host))) goto done;
    digests = calloc(journal.count, sizeof *digests);
    present = calloc(journal.count, 1);
    if (!digests || !present) { result = 1; goto done; }
    digests[0] = journal.root;
    for (i = 0, j = 1; i < journal.count; ++i)
        if (strcmp(journal.root, journal.digests[i])) digests[j++] = journal.digests[i];
    if (build_set(root_path, root, dir, journal.generation, digests, journal.count,
                  strcmp(journal.choice, "-") ? journal.choice : NULL, 1,
                  (const char *const *)journal.bindings, journal.binding_count,
                  NULL, journal.catalog_index[0] ? journal.catalog_index : NULL,
                  (const char *const *)journal.accepted_arch, journal.accepted_count,
                  (const char *const *)journal.accepted_privileged, journal.privileged_count,
                  (const char *const *)journal.skipped_hooks, journal.skipped_count,
                  &set) ||
        set.count != journal.count || strcmp(set.hash, journal.hash)) goto done;
    installed = child_dir(dir, "installed", 0);
    transactions = child_dir(dir, "transactions", 0);
    if (installed < 0 || transactions < 0) goto done;
    for (i = 0; i < set.count; ++i) {
        char graph[65], expected[65];
        unsigned char hash[32];
        unsigned int length;
        size_t k;
        struct stat st;
        int item, files, ok;
        const struct set_item *candidate = &set.items[i];
        if (candidate->reused) { present[i] = 1; continue; }
        if (fstatat(installed, candidate->identity.digest, &st, AT_SYMLINK_NOFOLLOW)) {
            struct plan_hash claims = {0};
            if (errno != ENOENT || !resume || generation != journal.generation ||
                slot_available(dir, &candidate->identity, candidate->source_id) != 1 ||
                !holy_install_preflight_resume(candidate->snapshot, root, candidate->privileged)) goto done;
            claims.dir = dir;
            claims.hash = EVP_MD_CTX_new();
            ok = claims.hash && EVP_DigestInit_ex(claims.hash, EVP_sha256(), NULL) == 1 &&
                 holy_verify_visit(candidate->snapshot, plan_entry, &claims);
            EVP_MD_CTX_free(claims.hash);
            if (!ok) goto done;
            continue;
        }
        present[i] = 1;
        item = child_dir(installed, candidate->identity.digest, 0);
        files = item < 0 ? -1 : openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
        ok = files >= 0 && instance_state_generation(item, candidate->identity.digest, &recorded) &&
             instance_source_matches(item, candidate->source_record) &&
             instance_architecture_matches(item, candidate->architecture) &&
             recorded == journal.generation + 1 && graph_digest(item, graph) &&
             instance_reason_matches(item, strcmp(candidate->identity.digest, set.resolution.root) ?
                                     "dependency" : "explicit") &&
             instance_matches_snapshot(item, candidate->snapshot) &&
             EVP_Digest(set.graph, set.graph_length, hash, &length, EVP_sha256(), NULL) == 1 &&
             length == 32;
        if (ok) {
            for (k = 0; k < 32; ++k) snprintf(expected + k * 2, 3, "%02x", hash[k]);
            ok = !strcmp(expected, graph) && exclusive_claims(installed, candidate->identity.digest, files) == 1 &&
                 holy_install_check_manifest(files, root) == 1;
        }
        if (files >= 0) close(files);
        if (item >= 0) close(item);
        if (!ok) goto done;
    }
    for (i = 0; i < set.count; ++i) if (!present[i]) {
        const struct set_item *item = &set.items[i];
        if (!holy_install_payload_missing(item->snapshot, root) ||
            !save_instance(installed, item->identity.digest, item->snapshot, journal.generation,
                           set.graph, set.graph_length,
                           strcmp(item->identity.digest, set.resolution.root) ? "dependency" : "explicit",
                           item->source_record, item->architecture[0] ? item->architecture : NULL,
                           item->privileged, item->skipped_hooks))
            goto done;
        printf("resumed %s\n", item->identity.digest);
    }
    if (generation == journal.generation && !set_generation(dir, generation + 1)) goto done;
    if (fsync(dir) || unlinkat(transactions, "set-journal", 0) || fsync(transactions)) goto done;
    printf("recovered-set %s generation %llu artifacts %zu\n",
           set.hash, journal.generation + 1, set.count);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: set recovery requires inspection (status %d)\n", result);
    free(present);
    free(digests);
    free_set(&set);
    free_set_journal(&journal);
    if (transactions >= 0) close(transactions);
    if (installed >= 0) close(installed);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

int holy_state_finish_set(const char *root_path)
{
    return recover_set(root_path, 0);
}

int holy_state_continue_set(const char *root_path)
{
    return recover_set(root_path, 1);
}

static int repair_hash(int root, unsigned long long generation, const char *digest,
                        const char *graph, const char *manifest, char output[65])
{
    struct stat st;
    char identity[128];
    unsigned char hash[32];
    unsigned int length;
    size_t i;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    int ok = 0;
    if (!ctx || fstat(root, &st) || EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1 ||
        !hash_text(ctx, "holy-repair-missing-2") || !hash_text(ctx, digest) ||
        !hash_text(ctx, graph) || !hash_text(ctx, manifest)) goto done;
    snprintf(identity, sizeof identity, "%ju:%ju:%llu", (uintmax_t)st.st_dev,
             (uintmax_t)st.st_ino, generation);
    if (!hash_text(ctx, identity) || EVP_DigestFinal_ex(ctx, hash, &length) != 1 || length != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(output + i * 2, 3, "%02x", hash[i]);
    ok = 1;
done:
    EVP_MD_CTX_free(ctx);
    return ok;
}

int holy_state_repair(const char *digest, const char *approved, const char *root_path)
{
    int root = -1, dir = -1, installed = -1, item = -1, files = -1, transactions = -1;
    int result = 1, stage = 0, journaled = 0, resume = digest == NULL, transformed = 0;
    char artifact[65], expected[65], actual[65], graph[65], manifest[65], journal[256];
    char *snapshot = NULL;
    unsigned long long generation, recorded;
    struct stat root_st;
    size_t length;
    if (!resume && (!valid_digest(digest) || (approved && !valid_digest(approved)))) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    dir = root < 0 ? -1 : state_dir_at(root, 0);
    if (dir < 0 || fstat(root, &root_st) || flock(dir, approved || resume ? LOCK_EX : LOCK_SH) ||
        !state_layout(dir, 0) || !empty_child(dir, "index") ||
        !read_generation(dir, &generation) || generation == ULLONG_MAX) goto done;
    recorded = generation;
    if (resume) {
        result = 5;
        if (set_journal_present(dir) ||
            journal_valid(dir, generation, &recorded, artifact, expected, &stage) != 1 ||
            stage != 2) goto done;
        {
            DIR *list;
            struct dirent *entry;
            int valid = 1, journal_dir = child_dir(dir, "transactions", 0);
            if (journal_dir < 0) goto done;
            list = directory_stream(journal_dir);
            if (!list) { close(journal_dir); goto done; }
            errno = 0;
            while ((entry = readdir(list))) {
                if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
                if (completed_update(journal_dir, entry->d_name)) { errno = 0; continue; }
                if (strcmp(entry->d_name, "journal")) { valid = 0; break; }
                errno = 0;
            }
            if (!entry && errno) valid = 0;
            closedir(list);
            close(journal_dir);
            if (!valid) goto done;
        }
        journaled = 1;
        digest = artifact;
        approved = expected;
    } else if (!empty_child(dir, "transactions")) { result = 5; goto done; }
    if (!installed_valid(dir)) goto done;
    installed = child_dir(dir, "installed", 0);
    item = installed < 0 ? -1 : child_dir(installed, digest, 0);
    if (item < 0) { result = 6; goto done; }
    {
        struct stat st;
        transformed = !fstatat(item, "config-state", &st, AT_SYMLINK_NOFOLLOW);
        if (!transformed && errno != ENOENT) goto done;
    }
    files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    snapshot = holy_cache_snapshot(digest, root_path);
    if (!snapshot) { result = 6; goto done; }
    if (files < 0 || !instance_matches_snapshot(item, snapshot) ||
        !graph_digest(item, graph) || !instance_record_digest(item, "files", manifest) ||
        !repair_hash(root, recorded, digest, graph, manifest, actual)) goto done;
    {
        struct plan_hash claims = {0};
        int valid;
        claims.completed = 1;
        claims.hash = EVP_MD_CTX_new();
        valid = claims.hash && EVP_DigestInit_ex(claims.hash, EVP_sha256(), NULL) == 1 &&
                holy_verify_visit(snapshot, plan_entry, &claims);
        EVP_MD_CTX_free(claims.hash);
        if (!valid) { result = resume ? 5 : 4; goto done; }
    }
    if (approved && strcmp(approved, actual)) { result = 3; goto done; }
    if (exclusive_claims(installed, digest, files) != 1 ||
        (transformed ? holy_install_check_repair_transformed(files, root) :
                       holy_install_check_or_missing(files, root)) != 1) {
        result = resume ? 5 : 4; goto done;
    }
    if (!same_root(root_path, &root_st)) { result = 4; goto done; }
    if (!approved) {
        printf("repair-plan generation %llu artifact %s sha256 %s missing-only read-only\n",
               generation, digest, actual);
        result = 0;
        goto done;
    }
    transactions = child_dir(dir, "transactions", 0);
    if (transactions < 0) goto done;
    result = 5;
    if (!resume) {
        length = (size_t)snprintf(journal, sizeof journal,
            "format holy-journal-1\nstage repairing\ngeneration %llu\nartifact %s\nplan %s\n",
            recorded, digest, actual);
        if (length >= sizeof journal || !record_file(transactions, "journal", journal, length)) goto done;
        journaled = 1;
    }
    if (!(transformed ? holy_install_payload_missing_mapped(snapshot, root, files) :
                         holy_install_payload_missing(snapshot, root)) ||
        holy_install_check_manifest(files, root) != 1 ||
        (generation == recorded && !set_generation(dir, recorded + 1)) || fsync(dir) ||
        unlinkat(transactions, "journal", 0) || fsync(transactions)) goto done;
    printf("repaired %s generation %llu missing-only\n", digest, recorded + 1);
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: missing-file repair failed (status %d)%s\n", result,
                        journaled ? "; repair journal retained" : "");
    if (snapshot) { unlink(snapshot); free(snapshot); }
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    if (transactions >= 0) close(transactions);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    return result;
}

struct update_journal {
    unsigned long long generation;
    char old[65], next[65], plan[65];
    int architecture, privileged;
};

static char *update_record(int dir, const char *name)
{
    struct stat st;
    int fd = openat(dir, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
    char *bytes = NULL;
    size_t used = 0;
    if (fd < 0) return NULL;
    errno = 0;
    if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 64 * 1024 * 1024 || (st.st_mode & 0022) ||
        (st.st_uid != 0 && st.st_uid != geteuid())) goto done;
    bytes = malloc((size_t)st.st_size + 1);
    if (!bytes) goto done;
    while (used < (size_t)st.st_size) {
        ssize_t got = read(fd, bytes + used, (size_t)st.st_size - used);
        if (got < 0 && errno == EINTR) continue;
        if (got <= 0) { free(bytes); bytes = NULL; goto done; }
        used += (size_t)got;
    }
    if (memchr(bytes, 0, used)) { free(bytes); bytes = NULL; goto done; }
    bytes[used] = 0;
done:
    close(fd);
    return bytes;
}

static int remove_identity(const char *digest, unsigned long long generation,
                           int broken, char plan[192], char identity[65])
{
    static const char digits[] = "0123456789abcdef";
    unsigned char hash[32];
    unsigned length = 0;
    size_t i;
    int n = snprintf(plan, 192,
        "format holy-remove-1\ngeneration %llu\nartifact %s\naccept-broken %s\n",
        generation, digest, broken ? "yes" : "no");
    if (n < 0 || n >= 192 || EVP_Digest(plan, (size_t)n, hash, &length,
        EVP_sha256(), NULL) != 1 || length != 32) return 0;
    for (i = 0; i < 32; ++i) {
        identity[i * 2] = digits[hash[i] >> 4];
        identity[i * 2 + 1] = digits[hash[i] & 15];
    }
    identity[64] = 0;
    return 1;
}

static int remove_workdir(int transactions, const char *digest,
                          unsigned long long generation, int broken)
{
    char plan[192], identity[65];
    if (!remove_identity(digest, generation, broken, plan, identity)) return -1;
    return child_dir(transactions, identity, 0);
}

static int remove_record(int transactions, const char *digest,
                         unsigned long long generation, int broken, int create)
{
    char plan[192], identity[65], decisions[32];
    char *saved = NULL;
    int child = -1, ok = 0;
    if (!remove_identity(digest, generation, broken, plan, identity)) return 0;
    if (create && mkdirat(transactions, identity, 0700) && errno != EEXIST) return 0;
    child = child_dir(transactions, identity, 0);
    if (child < 0) return 0;
    saved = update_record(child, "plan");
    if (!saved && create && errno == ENOENT) {
        if (!record_file(child, "plan", plan, strlen(plan))) goto done;
        saved = update_record(child, "plan");
    }
    if (!saved || strcmp(saved, plan)) goto done;
    free(saved); saved = NULL;
    snprintf(decisions, sizeof decisions, "accept-broken %s\n", broken ? "yes" : "no");
    saved = update_record(child, "decisions");
    if (!saved && create && errno == ENOENT) {
        if (!record_file(child, "decisions", decisions, strlen(decisions))) goto done;
        saved = update_record(child, "decisions");
    }
    ok = saved && !strcmp(saved, decisions) && !fsync(child) && !fsync(transactions);
done:
    free(saved);
    close(child);
    return ok;
}

static int commit_remove_record(int transactions, const char *digest,
                                unsigned long long generation, int broken)
{
    char plan[192], identity[65], line[66];
    char *saved;
    int child, ok;
    if (!remove_identity(digest, generation, broken, plan, identity) ||
        !remove_record(transactions, digest, generation, broken, 0)) return 0;
    child = child_dir(transactions, identity, 0);
    if (child < 0) return 0;
    snprintf(line, sizeof line, "%s\n", identity);
    saved = update_record(child, "committed");
    if (!saved && errno == ENOENT) {
        if (!record_file(child, "committed", line, strlen(line))) {
            close(child); return 0;
        }
        saved = update_record(child, "committed");
    }
    ok = saved && !strcmp(saved, line) && !fsync(transactions);
    free(saved);
    close(child);
    return ok;
}

static int completed_update(int transactions, const char *name)
{
    int child, ok;
    char *record, *plan;
    if (!valid_digest(name)) return 0;
    child = child_dir(transactions, name, 0);
    if (child < 0) return 0;
    record = update_record(child, "committed");
    ok = record && strlen(record) == 65 && !memcmp(record, name, 64) && record[64] == '\n';
    free(record);
    plan = ok ? update_record(child, "plan") : NULL;
    if (!plan) ok = 0;
    else if (!strncmp(plan, "format holy-remove-1\n", 21)) {
        unsigned long long generation;
        char artifact[65], choice[4], canonical[192], identity[65];
        char *decision = update_record(child, "decisions");
        ok = sscanf(plan,
            "format holy-remove-1\ngeneration %llu\nartifact %64[0-9a-f]\naccept-broken %3s",
            &generation, artifact, choice) == 3 && valid_digest(artifact) &&
            (!strcmp(choice, "yes") || !strcmp(choice, "no")) &&
            remove_identity(artifact, generation, !strcmp(choice, "yes"),
                            canonical, identity) && !strcmp(plan, canonical) &&
            !strcmp(name, identity) && decision &&
            !strcmp(decision, !strcmp(choice, "yes") ?
                    "accept-broken yes\n" : "accept-broken no\n");
        free(decision);
    }
    free(plan); close(child);
    return ok;
}

static int update_pending(int dir)
{
    struct stat st;
    int transactions = child_dir(dir, "transactions", 0), result = -1;
    if (transactions < 0) return -1;
    if (fstatat(transactions, "update", &st, AT_SYMLINK_NOFOLLOW)) {
        if (errno == ENOENT) result = 0;
    } else if (S_ISDIR(st.st_mode) && !(st.st_mode & 0022) &&
               (st.st_uid == 0 || st.st_uid == geteuid())) result = 1;
    close(transactions);
    return result;
}

static int update_replace(int dir, const char *name, const char *record)
{
    char temporary[43] = {0};
    int fd = holy_temporary_at(dir, temporary), ok = 0;
    if (fd < 0) return 0;
    if (!write_all(fd, record, strlen(record)) || fsync(fd)) goto done;
    if (renameat(dir, temporary, dir, name) || fsync(dir)) goto done;
    ok = 1;
done:
    close(fd);
    if (!ok) unlinkat(dir, temporary, 0);
    return ok;
}

static int read_update_journal(int work, unsigned long long current, struct update_journal *journal)
{
    char *record = update_record(work, "journal"), prefix[128];
    size_t length, record_length;
    int attempt, version, ok = 0;
    if (!record) return 0;
    record_length = strlen(record);
    for (attempt = 0; attempt < 2 && !ok; ++attempt) for (version = 1; version <= 4; ++version) {
        const char *p;
        size_t tail = version >= 3 ? 77 : 0;
        if (version == 2 || version == 4) tail += 83;
        if (attempt && !current) break;
        journal->generation = current - (unsigned)attempt;
        length = (size_t)snprintf(prefix, sizeof prefix,
            "format holy-update-journal-%d\ngeneration %llu\nold ",
            version, journal->generation);
        if (length >= sizeof prefix || record_length != length + 204 + tail ||
            memcmp(record, prefix, length)) continue;
        p = record + length;
        if (p[64] != '\n' || memcmp(p + 65, "new ", 4) || p[133] != '\n' ||
            memcmp(p + 134, "plan ", 5) || p[203] != '\n') continue;
        memcpy(journal->old, p, 64); journal->old[64] = 0;
        memcpy(journal->next, p + 69, 64); journal->next[64] = 0;
        memcpy(journal->plan, p + 139, 64); journal->plan[64] = 0;
        journal->architecture = version >= 3;
        journal->privileged = version == 2 || version == 4;
        if (journal->architecture &&
            (memcmp(p + 204, "accept-arch ", 12) ||
             memcmp(p + 216, journal->next, 64) || p[280] != '\n')) continue;
        if (journal->privileged &&
            (memcmp(p + 204 + (journal->architecture ? 77 : 0), "accept-privileged ", 18) ||
             memcmp(p + 222 + (journal->architecture ? 77 : 0), journal->next, 64) ||
             p[286 + (journal->architecture ? 77 : 0)] != '\n')) continue;
        ok = valid_digest(journal->old) && valid_digest(journal->next) &&
             valid_digest(journal->plan) && strcmp(journal->old, journal->next) &&
             journal->generation != ULLONG_MAX;
        if (ok) break;
    }
    free(record);
    return ok;
}

static int instance_graph(const struct holy_resolution *full, const char *digest,
                           char **record, size_t *length)
{
    struct holy_resolution part = {0};
    size_t i, j, count = 1;
    int ok = 0;
    memcpy(part.root, digest, 65);
    part.artifacts = calloc(full->artifact_count, sizeof *part.artifacts);
    part.edges = calloc(full->edge_count ? full->edge_count : 1, sizeof *part.edges);
    if (!part.artifacts || !part.edges) goto done;
    part.artifacts[0] = (char *)digest;
    for (i = 0; i < full->edge_count; ++i) if (!strcmp(full->edges[i].consumer, digest)) {
        part.edges[part.edge_count++] = full->edges[i];
        for (j = 0; j < count; ++j)
            if (!strcmp(part.artifacts[j], full->edges[i].provider)) break;
        if (j == count) {
            if (count == full->artifact_count) goto done;
            part.artifacts[count++] = full->edges[i].provider;
        }
    }
    part.artifact_count = count;
    qsort(part.artifacts, count, sizeof *part.artifacts, compare_instance_names);
    ok = holy_resolution_record(&part, record, length);
done:
    free(part.artifacts); free(part.edges);
    return ok;
}

static int clear_update_installed(int parent, const struct holy_resolution *resolution)
{
    static const char *const files[] = {"meta", "files", "deps", "origin", "graph", "state", "source", "provides", "hooks", "transform", "hooks-state", "package-files", "config-state"};
    int installed = child_dir(parent, "installed", 1), item = -1, ok = 0;
    DIR *list = NULL, *members = NULL;
    struct dirent *entry;
    size_t i;
    if (installed < 0 || !(list = directory_stream(installed))) goto done;
    errno = 0;
    while ((entry = readdir(list))) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        for (i = 0; i < resolution->artifact_count; ++i)
            if (!strcmp(entry->d_name, resolution->artifacts[i])) break;
        if (i == resolution->artifact_count || (item = child_dir(installed, entry->d_name, 0)) < 0 ||
            !(members = directory_stream(item))) goto done;
        errno = 0;
        for (;;) {
            struct stat st;
            struct dirent *member = readdir(members);
            if (!member) { if (errno) goto done; break; }
            if (!strcmp(member->d_name, ".") || !strcmp(member->d_name, "..")) continue;
            for (i = 0; i < sizeof files / sizeof *files; ++i)
                if (!strcmp(member->d_name, files[i])) break;
            if (i == sizeof files / sizeof *files || fstatat(item, member->d_name, &st, AT_SYMLINK_NOFOLLOW) ||
                !S_ISREG(st.st_mode) || st.st_uid != geteuid() || (st.st_mode & 0022)) goto done;
            errno = 0;
        }
        closedir(members); members = NULL;
        for (i = 0; i < sizeof files / sizeof *files; ++i)
            if (unlinkat(item, files[i], 0) && errno != ENOENT) goto done;
        if (fsync(item)) goto done;
        close(item); item = -1;
        if (unlinkat(installed, entry->d_name, AT_REMOVEDIR) || fsync(installed)) goto done;
        errno = 0;
    }
    ok = errno == 0;
done:
    if (members) closedir(members);
    if (list) closedir(list);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    return ok;
}

static int update_config_manifest(int old_item, int proposed,
                                  const struct holy_file_plan *plan,
                                  const char *digest, const char *transaction,
                                  int replaced)
{
    char *raw = NULL, *installed = NULL, *state = NULL;
    char raw_hash[65], installed_hash[65], record[360];
    size_t length = 0, i;
    int transformed = 0, result = 0;
    if (replaced) {
        for (i = 0; i < plan->count; ++i)
            if (plan->changes[i].keep_config) { transformed = 1; break; }
    } else {
        struct stat st;
        transformed = !fstatat(old_item, "config-state", &st, AT_SYMLINK_NOFOLLOW);
        if (!transformed && errno != ENOENT) return 0;
    }
    if (!transformed) return 1;
    raw = update_record(proposed, "files");
    if (!raw || !instance_record_digest(proposed, "files", raw_hash)) goto done;
    if (replaced) {
        if (!holy_file_plan_installed_manifest(plan, raw, strlen(raw),
                                               &installed, &length)) goto done;
    } else {
        char previous_hash[65];
        if (!config_state_valid(old_item, digest) ||
            !instance_record_digest(old_item, "package-files", previous_hash) ||
            strcmp(previous_hash, raw_hash)) goto done;
        installed = update_record(old_item, "files");
        state = update_record(old_item, "config-state");
        if (!installed || !state) goto done;
        length = strlen(installed);
    }
    if (renameat(proposed, "files", proposed, "package-files") || fsync(proposed) ||
        !record_file(proposed, "files", installed, length) ||
        !instance_record_digest(proposed, "files", installed_hash)) goto done;
    if (replaced) {
        int written = snprintf(record, sizeof record,
            "format holy-config-transform-1\nsource %s\nraw %s\ninstalled %s\nplan %s\n",
            digest, raw_hash, installed_hash, transaction);
        if (written < 0 || (size_t)written >= sizeof record ||
            !record_file(proposed, "config-state", record, (size_t)written)) goto done;
    } else if (!record_file(proposed, "config-state", state, strlen(state))) goto done;
    result = config_state_valid(proposed, digest);
done:
    free(raw); free(installed); free(state);
    return result;
}

static int update_instances(int next_db, int before, char **names, char **snapshots,
                             size_t count, size_t replaced, const char *new_digest,
                             const char *new_source, unsigned long long generation,
                             const struct holy_resolution *resolution,
                             const char *new_architecture, int new_privileged,
                             const struct holy_file_plan *file_plan,
                             const char *transaction, int create)
{
    int installed = -1, item = -1, proposed = -1, ok = 0;
    char *source = NULL, *graph = NULL;
    size_t i, length = 0;
    if (create && !clear_update_installed(next_db, resolution)) return 0;
    installed = child_dir(next_db, "installed", 0);
    if (installed < 0) goto done;
    for (i = 0; i < count; ++i) {
        const char *digest = i == replaced ? new_digest : names[i], *reason;
        unsigned long long recorded;
        char expected[65], actual[65], architecture[96];
        unsigned char hash[32];
        unsigned hash_size;
        size_t j;
        item = child_dir(before, names[i], 0);
        if (item < 0) goto done;
        if (!instance_architecture(item, architecture)) goto done;
        if (i == replaced) {
            if (new_architecture && !architecture_valid(new_architecture)) goto done;
            snprintf(architecture, sizeof architecture, "%s",
                     new_architecture ? new_architecture : "");
        }
        reason = instance_reason_matches(item, "dependency") ? "dependency" : "explicit";
        if (i == replaced && new_source) source = strdup(new_source);
        else source = update_record(item, "source");
        if (!source && ((i == replaced && new_source) || errno != ENOENT)) goto done;
        if (!instance_graph(resolution, digest, &graph, &length)) goto done;
        if (create) {
            char *state_record = update_record(item, "state");
            int privileged;
            if (!state_record) goto done;
            privileged = i == replaced ? new_privileged :
                         strstr(state_record, "\nprivileged ") != NULL;
            free(state_record);
            if (!save_instance(installed, digest, snapshots[i], generation, graph, length,
                               reason, source, architecture[0] ? architecture : NULL,
                               privileged, 0)) goto done;
        }
        proposed = child_dir(installed, digest, 0);
        if (proposed >= 0 && create &&
            !update_config_manifest(item, proposed, file_plan, digest, transaction,
                                    i == replaced)) goto done;
        if (proposed < 0 || !instance_state_generation(proposed, digest, &recorded) || recorded != generation + 1 ||
            !instance_reason_matches(proposed, reason) || !instance_source_matches(proposed, source) ||
            !instance_architecture_matches(proposed, architecture) ||
            !instance_matches_snapshot(proposed, snapshots[i]) || !graph_digest(proposed, actual) ||
            EVP_Digest(graph, length, hash, &hash_size, EVP_sha256(), NULL) != 1 || hash_size != 32) goto done;
        if (i == replaced) {
            char marker[96], *state_record = update_record(proposed, "state");
            int observed_privileged;
            if (!state_record) goto done;
            snprintf(marker, sizeof marker, "\nprivileged %s\n", digest);
            observed_privileged = strstr(state_record, marker) != NULL;
            free(state_record);
            if (observed_privileged != new_privileged) goto done;
        }
        for (j = 0; j < 32; ++j) snprintf(expected + j * 2, 3, "%02x", hash[j]);
        if (strcmp(expected, actual)) goto done;
        free(source); source = NULL; free(graph); graph = NULL;
        close(item); item = -1; close(proposed); proposed = -1;
    }
    {
        DIR *list = directory_stream(installed);
        struct dirent *entry;
        size_t found = 0;
        if (!list) goto done;
        errno = 0;
        while ((entry = readdir(list))) {
            if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
            for (i = 0; i < resolution->artifact_count; ++i)
                if (!strcmp(entry->d_name, resolution->artifacts[i])) break;
            if (i == resolution->artifact_count) break;
            ++found; errno = 0;
        }
        ok = !entry && !errno && found == count;
        closedir(list);
    }
    ok = ok && installed_valid(next_db) && !fsync(installed);
done:
    free(source); free(graph);
    if (item >= 0) close(item);
    if (proposed >= 0) close(proposed);
    if (installed >= 0) close(installed);
    return ok;
}

struct update_progress {
    int work;
    const struct holy_file_plan *files;
};

static int update_exchange_available(int work)
{
    int a = child_dir(work, "exchange-a", 1), b = child_dir(work, "exchange-b", 1), ok = 0;
    if (a < 0 || b < 0) goto done;
#ifdef SYS_renameat2
    if (syscall(SYS_renameat2, work, "exchange-a", work, "exchange-b", 2u)) {
        fputs("holypkg: renameat2(RENAME_EXCHANGE) unavailable for installed database\n", stderr);
        goto done;
    }
    ok = !fsync(work) && !unlinkat(work, "exchange-a", AT_REMOVEDIR) &&
         !unlinkat(work, "exchange-b", AT_REMOVEDIR) && !fsync(work);
#else
    fputs("holypkg: renameat2(RENAME_EXCHANGE) unavailable\n", stderr);
#endif
done:
    if (a >= 0) close(a);
    if (b >= 0) close(b);
    return ok;
}

static int update_progress(void *context, size_t index, int completed)
{
    struct update_progress *p = context;
    char record[160];
    snprintf(record, sizeof record, "stage applying\nchange %s\nresult %s\n",
             p->files->changes[index].id, completed ? "done" : "intent");
    return update_replace(p->work, "progress", record);
}

static int finish_update(int root, int db, int transactions, int work, int before,
                         char **names, char **snapshots, size_t count, size_t replaced,
                         const struct update_journal *journal, const char *source,
                         const char *new_architecture,
                         const struct holy_resolution *resolution, const struct holy_file_plan *files,
                         int resume, int swapped, unsigned long long current)
{
    int next_db = -1, result = 5;
    size_t failed = 0;
    char committed[66];
    struct update_progress progress = {work, files};
    struct stat expected, observed;
    next_db = child_dir(work, "next-db", !swapped);
    if (next_db < 0) goto done;
    if (!swapped) {
        if (current != journal->generation || !update_exchange_available(work) ||
            !update_instances(next_db, before, names, snapshots, count, replaced,
                              journal->next, source, journal->generation, resolution,
                              new_architecture,
                              journal->privileged, files, journal->plan, 1) ||
            !update_replace(work, "progress", "stage prepared\n")) goto done;
        if (holy_file_plan_stage(files, snapshots[replaced], root, resume, &failed) ||
            holy_file_plan_apply(files, root, update_progress, &progress, &failed)) {
            fprintf(stderr, "holypkg: incomplete update at file change %zu; inspect reserved staging objects\n", failed);
            goto done;
        }
        if (fstat(before, &expected) || fstatat(db, "installed", &observed, AT_SYMLINK_NOFOLLOW) ||
            expected.st_dev != observed.st_dev || expected.st_ino != observed.st_ino ||
            !update_replace(work, "progress", "stage publishing\n")) goto done;
#ifdef SYS_renameat2
        if (syscall(SYS_renameat2, db, "installed", next_db, "installed", 2u)) {
            if (errno == ENOSYS || errno == EINVAL || errno == EOPNOTSUPP)
                fputs("holypkg: renameat2(RENAME_EXCHANGE) unavailable; update remains incomplete\n", stderr);
            goto done;
        }
#else
        fputs("holypkg: renameat2(RENAME_EXCHANGE) unavailable; update remains incomplete\n", stderr);
        goto done;
#endif
    }
    if (fsync(next_db) || fsync(db)) goto done;
    if (holy_file_plan_finished(files, root) ||
        !update_instances(db, before, names, snapshots, count, replaced,
                          journal->next, source, journal->generation, resolution,
                          new_architecture,
                          journal->privileged, files, journal->plan, 0) ||
        (current == journal->generation && !set_generation(db, journal->generation + 1)) || fsync(db)) goto done;
    snprintf(committed, sizeof committed, "%s\n", journal->plan);
    if (holy_file_plan_cleanup(files, root) ||
        !update_replace(work, "progress", "stage committed\n") ||
        !update_replace(work, "committed", committed)) goto done;
#ifdef SYS_renameat2
    if (syscall(SYS_renameat2, transactions, "update", transactions, journal->plan, 1u) || fsync(transactions)) goto done;
#else
    goto done;
#endif
    printf("updated %s to %s generation %llu plan %s\n", journal->old, journal->next,
           journal->generation + 1, journal->plan);
    result = 0;
done:
    if (next_db >= 0) close(next_db);
    return result;
}

static int state_update(const char *old_digest, const char *new_digest,
                         const char *expected, const char *accepted_arch,
                         const char *accepted_privileged,
                         const char *root_path, int resume,
                         char prepared_hash[65], char **prepared_record)
{
    int root = -1, dir = -1, installed = -1, item = -1, files = -1, result = 1, pending;
    int old_privileged = 0, new_privileged = 0;
    int transactions = -1, work = -1, old_db = -1, reference_db = -1, swapped = 0, journaled = 0;
    struct update_journal journal = {0};
    char *saved = NULL;
    char **names = NULL, **snapshots = NULL, **states = NULL;
    const char **selected_ids = NULL;
    char source[65], registry[65], existing[65], approved[65], checksum[65];
    char new_architecture[96] = {0};
    char *old_snapshot = NULL, *new_snapshot = NULL, *source_record = NULL;
    char *file_record = NULL, *graph_record = NULL, *record = NULL;
    size_t count = 0, i, old_index = 0, file_size = 0, graph_size = 0, record_size = 0, failed;
    unsigned long long generation, recorded, current_generation;
    struct stat root_st, db_st;
    struct holy_package_identity old = {0}, next = {0};
    struct holy_file_plan changes = {0};
    struct holy_resolution resolution = {0};
    struct install_set claims = {0};
    struct plan_hash validation = {0};
    DIR *list = NULL;
    struct dirent *entry;
    FILE *out = NULL;
    unsigned char bytes[32];
    unsigned length;
    if (prepared_record) *prepared_record = NULL;
    if (prepared_hash) prepared_hash[0] = 0;
    if (!resume && (!valid_digest(old_digest) || !valid_digest(new_digest) ||
                    (expected && !valid_digest(expected)) ||
                    (accepted_arch && (!valid_digest(accepted_arch) ||
                                       strcmp(accepted_arch, new_digest))) ||
                    (accepted_privileged && (!valid_digest(accepted_privileged) ||
                                             strcmp(accepted_privileged, new_digest))))) return 2;
    if (!resume && !strcmp(old_digest, new_digest)) return 3;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0 || fstat(root, &root_st) || (dir = state_dir_at(root, 0)) < 0 ||
        flock(dir, expected || resume ? LOCK_EX : LOCK_SH) || fstat(dir, &db_st) || !state_layout(dir, 0) ||
        !read_generation(dir, &generation)) goto done;
    current_generation = generation;
    reference_db = dir;
    if (resume) {
        struct stat a, b;
        int old_present, new_present, live;
        result = 6;
        transactions = child_dir(dir, "transactions", 0);
        work = transactions < 0 ? -1 : child_dir(transactions, "update", 0);
        if (work < 0) goto done;
        journaled = 1; result = 5;
        if (!read_update_journal(work, generation, &journal)) goto done;
        old_digest = journal.old; new_digest = journal.next; expected = journal.plan;
        accepted_arch = journal.architecture ? journal.next : NULL;
        accepted_privileged = journal.privileged ? journal.next : NULL;
        generation = journal.generation;
        saved = update_record(work, "plan");
        if (!saved && errno != ENOENT) goto done;
        live = child_dir(dir, "installed", 0);
        if (live < 0) goto done;
        old_present = !fstatat(live, old_digest, &a, AT_SYMLINK_NOFOLLOW);
        new_present = !fstatat(live, new_digest, &b, AT_SYMLINK_NOFOLLOW);
        close(live);
        if (old_present == new_present) goto done;
        swapped = new_present;
        if (swapped) {
            old_db = child_dir(work, "next-db", 0);
            if (old_db < 0) goto done;
            reference_db = old_db;
        } else if (current_generation != generation) goto done;
    } else {
        pending = transaction_pending(dir, generation);
        if (pending < 0) goto done;
        if (pending) { result = 5; goto done; }
        pending = pending_child(dir, generation, existing, approved);
        if (pending < 0) goto done;
        if (pending) { result = 5; goto done; }
    }
    result = 1;
    if (!installed_valid(reference_db) || (installed = child_dir(reference_db, "installed", 0)) < 0 ||
        !(list = directory_stream(installed))) goto done;
    names = calloc(10000, sizeof *names);
    snapshots = calloc(10000, sizeof *snapshots);
    states = calloc(10000, sizeof *states);
    if (!names || !snapshots || !states) goto done;
    errno = 0;
    while ((entry = readdir(list))) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        if (count == 10000) { result = 6; goto done; }
        names[count] = strdup(entry->d_name);
        if (!names[count]) goto done;
        ++count;
        errno = 0;
    }
    if (errno) goto done;
    if (count) qsort(names, count, sizeof *names, compare_instance_names);
    for (old_index = 0; old_index < count; ++old_index)
        if (!strcmp(names[old_index], old_digest)) break;
    if (old_index == count) { result = 6; goto done; }
    for (i = 0; i < count; ++i)
        if (!strcmp(names[i], new_digest)) { result = 4; goto done; }
    for (i = 0; i < count; ++i) {
        char state[65], architecture[96];
        snapshots[i] = holy_cache_snapshot(names[i], root_path);
        if (!snapshots[i]) { result = 6; goto done; }
        item = child_dir(installed, names[i], 0);
        if (item < 0) goto done;
        if (!instance_architecture(item, architecture)) goto done;
        files = openat(item, "files", O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
        if (files < 0 || !instance_state_generation(item, names[i], &recorded) ||
            recorded > generation || !instance_matches_snapshot(item, snapshots[i]) ||
            !instance_record_digest(item, "state", state)) goto done;
        if (i == old_index) {
            char *state_record = update_record(item, "state"), marker[96];
            if (!state_record) goto done;
            snprintf(marker, sizeof marker, "\nprivileged %s\n", old_digest);
            old_privileged = strstr(state_record, marker) != NULL;
            free(state_record);
        }
        states[i] = strdup(state);
        if (!states[i]) goto done;
        if (exclusive_claims(installed, names[i], files) != 1 ||
            ((!resume || i != old_index) &&
             (i == old_index ? holy_install_check_manifest_except_configs(files, root) :
                               holy_install_check_manifest(files, root)) != 1) ||
            (!resume && check_graph(installed, root, names[i], NULL) != 1)) { result = 4; goto done; }
        if (i == old_index && !installed_source_id(item, source)) goto done;
        close(files); files = -1;
        close(item); item = -1;
    }
    strcpy(registry, "-");
    if (strcmp(source, "-")) {
        result = holy_source_record(dir, source, &source_record, registry);
        if (result) goto done;
    }
    result = 6;
    new_snapshot = holy_cache_snapshot(new_digest, root_path);
    if (!new_snapshot || !holy_package_identity(snapshots[old_index], &old) ||
        !holy_package_identity(new_snapshot, &next) ||
        !recorded_transform(new_snapshot) || !instance_preflight(new_snapshot)) goto done;
    if (!same_slot(&old, source, &next, source)) { result = 4; goto done; }
    {
        struct privileged_scan scan = {0};
        int scanned = holy_verify_visit(new_snapshot, scan_privileged, &scan);
        if (!scanned) {
            result = scan.unsupported ? 6 : 1;
            free(scan.first); goto done;
        }
        new_privileged = scan.count != 0;
        if (new_privileged && !accepted_privileged) {
            fprintf(stderr, "holypkg: decision-required privileged update artifact=%s path=%s mode=%04o; --accept-privileged %s permits setuid placement\n",
                    new_digest, scan.first, scan.mode, new_digest);
            result = 3;
            free(scan.first); goto done;
        }
        free(scan.first);
        if (accepted_privileged && !new_privileged) { result = 2; goto done; }
    }
    {
        struct utsname host;
        if (uname(&host)) { result = 1; goto done; }
        if (!native_architecture(host.machine, next.arch)) {
            if (!accepted_arch) {
                fprintf(stderr, "holypkg: decision-required architecture artifact=%s host=%s target=%s; --accept-arch %s permits placement without proving execution\n",
                        new_digest, host.machine, next.arch, new_digest);
                result = 3; goto done;
            }
            snprintf(new_architecture, sizeof new_architecture, "%s %s", host.machine, next.arch);
            if (!architecture_valid(new_architecture)) { result = 2; goto done; }
        } else if (accepted_arch) {
            result = 2; goto done;
        }
    }
    pending = slot_available_except(reference_db, &next, source, old_digest);
    if (pending != 1) { result = pending < 0 ? 1 : 4; goto done; }
    result = holy_preview_resolved(new_snapshot, root_path, 1, new_privileged, 0);
    if (result) goto done;
    result = explicit_elf_paths(new_snapshot, 1);
    if (result) goto done;
    validation.completed = 1;
    validation.accepted_privileged = new_privileged;
    validation.hash = EVP_MD_CTX_new();
    result = 6;
    if (!validation.hash || EVP_DigestInit_ex(validation.hash, EVP_sha256(), NULL) != 1 ||
        !holy_verify_visit(new_snapshot, plan_entry, &validation) ||
        !holy_file_plan_collect(snapshots[old_index], new_snapshot, &changes)) goto done;
    result = holy_file_plan_preserve_configs(&changes, root, resume ? saved : NULL);
    if (result) goto done;
    result = 6;
    if (!holy_file_plan_record(&changes, &file_record, &file_size)) goto done;
    changes.before_privileged = old_privileged;
    changes.after_privileged = new_privileged;
    result = holy_file_plan_check(&changes, root, resume, &failed);
    if (result) {
        fprintf(stderr, "holypkg: update file preflight failed at change %zu\n", failed);
        goto done;
    }
    old_snapshot = snapshots[old_index];
    snapshots[old_index] = new_snapshot;
    new_snapshot = NULL;
    for (i = 0; i < count; ++i)
        if (!holy_verify_visit(snapshots[i], set_claim, &claims)) { result = 6; goto done; }
    for (i = 0; i < changes.count; ++i)
        if (changes.changes[i].keep_config &&
            !set_claim(&claims, changes.changes[i].after)) { result = 1; goto done; }
    if (!set_claims_valid(&claims)) { result = 4; goto done; }
    result = holy_resolve_collect_set((const char *const *)snapshots, count, &resolution);
    if (result) goto done;
    selected_ids = calloc(count, sizeof *selected_ids);
    if (!selected_ids) { result = 1; goto done; }
    for (i = 0; i < count; ++i)
        selected_ids[i] = i == old_index ? new_digest : names[i];
    if (!selected_soname_paths(&resolution, selected_ids,
                               (const char *const *)snapshots, count, root,
                               claims.claims, claims.claim_count)) {
        result = 3; goto done;
    }
    result = 1;
    if (!holy_resolution_record(&resolution, &graph_record, &graph_size)) goto done;
    out = open_memstream(&record, &record_size);
    if (!out) goto done;
    fprintf(out, "[update]\nformat holy-update-plan-1\ngeneration %llu\n"
            "root %ju %ju\ndatabase %ju %ju\nold %s\nnew %s\nregistry %s\n",
            generation, (uintmax_t)root_st.st_dev, (uintmax_t)root_st.st_ino,
            (uintmax_t)db_st.st_dev, (uintmax_t)db_st.st_ino, old_digest, new_digest, registry);
    if (accepted_arch) fprintf(out, "accept-arch %s\narchitecture %s\n", new_digest, new_architecture);
    if (new_privileged) fprintf(out, "accept-privileged %s\n", new_digest);
    if (source_record) fputs(source_record, out);
    else fputs("source - local\n", out);
    fputs("delivery local\n[installed]\n", out);
    for (i = 0; i < count; ++i) fprintf(out, "instance %s %s\n", names[i], states[i]);
    fputs("[files]\n", out);
    fwrite(file_record, 1, file_size, out);
    fputs("[graph]\n", out);
    fwrite(graph_record, 1, graph_size, out);
    pending = ferror(out);
    if (fclose(out)) pending = 1;
    out = NULL;
    if (pending || !same_root(root_path, &root_st) ||
        EVP_Digest(record, record_size, bytes, &length, EVP_sha256(), NULL) != 1 || length != 32)
        goto done;
    for (i = 0; i < 32; ++i) snprintf(checksum + i * 2, 3, "%02x", bytes[i]);
    if (!expected) {
        if (prepared_record) {
            if (prepared_hash) memcpy(prepared_hash, checksum, 65);
            *prepared_record = record;
            record = NULL;
            result = 0;
            goto done;
        }
        if (printf("plan-update sha256 %s read-only\n", checksum) < 0 ||
            fwrite(record, 1, record_size, stdout) != record_size) goto done;
        result = 0;
        goto done;
    }
    if (strcmp(expected, checksum) || (saved && strcmp(saved, record))) { result = 3; goto done; }
    if (generation == ULLONG_MAX || record_size > 64 * 1024 * 1024) { result = 6; goto done; }
    if (!resume) {
        char header[512];
        int header_size;
        result = holy_file_plan_reservations(&changes, root);
        if (result) goto done;
        result = 1;
        transactions = child_dir(dir, "transactions", 0);
        if (transactions < 0 || mkdirat(transactions, "update", 0700)) goto done;
        journaled = 1;
        if (fsync(transactions) || (work = child_dir(transactions, "update", 0)) < 0) goto done;
        journal.generation = generation;
        memcpy(journal.old, old_digest, 65); memcpy(journal.next, new_digest, 65); memcpy(journal.plan, checksum, 65);
        header_size = snprintf(header, sizeof header,
            "format holy-update-journal-%d\ngeneration %llu\nold %s\nnew %s\nplan %s\n%s%s%s%s%s%s",
            accepted_arch ? (new_privileged ? 4 : 3) : (new_privileged ? 2 : 1),
            generation, old_digest, new_digest, checksum,
            accepted_arch ? "accept-arch " : "", accepted_arch ? new_digest : "",
            accepted_arch ? "\n" : "",
            new_privileged ? "accept-privileged " : "",
            new_privileged ? new_digest : "", new_privileged ? "\n" : "");
        if (header_size < 0 || (size_t)header_size >= sizeof header) goto done;
        journal.architecture = !!accepted_arch;
        journal.privileged = new_privileged;
        if (!update_replace(work, "journal", header)) goto done;
    }
    if (!saved && !update_replace(work, "plan", record)) goto done;
    result = finish_update(root, dir, transactions, work, installed, names, snapshots,
                           count, old_index, &journal, source_record,
                           new_architecture[0] ? new_architecture : NULL, &resolution,
                           &changes, resume, swapped, current_generation);
done:
    if (result) {
        fprintf(stderr, "holypkg: update failed (status %d)%s\n", result, journaled ? "; update journal retained" : "");
        if (journaled) result = 5;
    }
    if (out) fclose(out);
    if (list) closedir(list);
    if (files >= 0) close(files);
    if (item >= 0) close(item);
    if (installed >= 0) close(installed);
    if (old_db >= 0) close(old_db);
    if (work >= 0) close(work);
    if (transactions >= 0) close(transactions);
    free(saved);
    free(selected_ids);
    if (dir >= 0) close(dir);
    if (root >= 0) close(root);
    if (old_snapshot) { unlink(old_snapshot); free(old_snapshot); }
    if (new_snapshot) { unlink(new_snapshot); free(new_snapshot); }
    for (i = 0; i < count; ++i) {
        free(names[i]);
        if (snapshots && snapshots[i]) { unlink(snapshots[i]); free(snapshots[i]); }
        if (states) free(states[i]);
    }
    free(names); free(snapshots); free(states); free(source_record);
    free(file_record); free(graph_record); free(record);
    holy_package_identity_free(&old); holy_package_identity_free(&next);
    holy_file_plan_free(&changes); holy_resolution_free(&resolution); free_set(&claims);
    EVP_MD_CTX_free(validation.hash);
    return result;
}

int holy_state_update_plan(const char *old_digest, const char *new_digest,
                           const char *accepted_arch, const char *accepted_privileged,
                           const char *root_path)
{
    return state_update(old_digest, new_digest, NULL, accepted_arch,
                        accepted_privileged, root_path, 0, NULL, NULL);
}

int holy_state_update_prepare(const char *old_digest, const char *new_digest,
                              const char *accepted_arch, const char *accepted_privileged,
                              const char *root_path, char hash[65], char **record)
{
    if (!hash || !record) return 2;
    return state_update(old_digest, new_digest, NULL, accepted_arch,
                        accepted_privileged, root_path, 0, hash, record);
}

int holy_state_apply_update(const char *plan, const char *old_digest,
                            const char *new_digest, const char *accepted_arch,
                            const char *accepted_privileged,
                            const char *root_path)
{
    return state_update(old_digest, new_digest, plan, accepted_arch,
                        accepted_privileged, root_path, 0, NULL, NULL);
}

int holy_state_recover_update(const char *root_path)
{
    return state_update(NULL, NULL, NULL, NULL, NULL, root_path, 1, NULL, NULL);
}

int holy_state_rollback(const char *transaction, const char *approved,
                        const char *accepted_arch, const char *accepted_privileged,
                        const char *root_path)
{
    struct update_journal journal = {0};
    unsigned long long generation;
    unsigned char digest[32];
    unsigned digest_size;
    char computed[65], hash[65], lineage[160], generation_line[64];
    char *committed = NULL, *source_plan = NULL, *installed_section;
    char *record = NULL, *marker = NULL;
    int root = -1, db = -1, transactions = -1, work = -1, result = 1;
    size_t i;
    if (!valid_digest(transaction) || (approved && !valid_digest(approved)) ||
        (accepted_arch && !valid_digest(accepted_arch)) ||
        (accepted_privileged && !valid_digest(accepted_privileged))) return 2;
    root = open(root_path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (root < 0 || (db = state_dir_at(root, 0)) < 0 || flock(db, LOCK_SH) ||
        !state_layout(db, 0) || (transactions = child_dir(db, "transactions", 0)) < 0 ||
        (work = child_dir(transactions, transaction, 0)) < 0) goto done;
    committed = update_record(work, "committed");
    source_plan = update_record(work, "plan");
    marker = update_record(work, "journal");
    if (!committed || strlen(committed) != 65 ||
        memcmp(committed, transaction, 64) || committed[64] != '\n' ||
        !source_plan || !marker ||
        sscanf(marker, "format holy-update-journal-%*d\ngeneration %llu\n", &generation) != 1 ||
        generation == ULLONG_MAX ||
        !read_update_journal(work, generation + 1, &journal) ||
        strcmp(journal.plan, transaction) ||
        EVP_Digest(source_plan, strlen(source_plan), digest, &digest_size,
                   EVP_sha256(), NULL) != 1 || digest_size != 32) {
        result = 2; goto done;
    }
    for (i = 0; i < 32; ++i) snprintf(computed + i * 2, 3, "%02x", digest[i]);
    if (strcmp(computed, transaction) ||
        strncmp(source_plan, "[update]\nformat holy-update-plan-1\n",
                sizeof "[update]\nformat holy-update-plan-1\n" - 1)) {
        result = 2; goto done;
    }
    snprintf(lineage, sizeof lineage, "\nold %s\nnew %s\n", journal.old, journal.next);
    snprintf(generation_line, sizeof generation_line, "\ngeneration %llu\n",
             journal.generation);
    installed_section = strstr(source_plan, "\n[installed]\n");
    if (!installed_section || !strstr(source_plan, lineage) ||
        strstr(source_plan, lineage) >= installed_section ||
        !strstr(source_plan, generation_line) ||
        strstr(source_plan, generation_line) >= installed_section) {
        result = 2; goto done;
    }
    result = 0;
done:
    free(committed); free(source_plan); free(marker);
    if (work >= 0) close(work);
    if (transactions >= 0) close(transactions);
    if (db >= 0) close(db);
    if (root >= 0) close(root);
    if (result) return result;
    if (approved) {
        result = holy_state_apply_update(approved, journal.next, journal.old,
                                         accepted_arch, accepted_privileged, root_path);
        if (!result) printf("rollback %s restored %s\n", transaction, journal.old);
        return result;
    }
    result = holy_state_update_prepare(journal.next, journal.old,
                                       accepted_arch, accepted_privileged,
                                       root_path, hash, &record);
    if (result) return result;
    if (printf("rollback-plan transaction %s current %s target %s sha256 %s read-only\n",
               transaction, journal.next, journal.old, hash) < 0 ||
        fputs(record, stdout) == EOF) result = 1;
    free(record);
    return result;
}