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

file src/import.c

#define _POSIX_C_SOURCE 200809L
#include "import.h"
#include "pack.h"
#include "package.h"
#include "verify.h"
#include "deps.h"
#include "stage.h"
#include "elf.h"
#include "provides.h"
#include "version.h"
#include "../backends/pacman.h"
#include "../backends/deb-version.h"
#include "../backends/apk-version.h"
#include "../backends/apk.h"
#include "../backends/xbps-version.h"
#include "../backends/rpm-version.h"

#include <archive.h>
#include <archive_entry.h>
#include <openssl/evp.h>
#ifdef HOLY_HAVE_RPM
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
#include <rpm/rpmio.h>
#include <rpm/header.h>
#include <rpm/rpmtag.h>
#include <rpm/rpmtd.h>
#include <rpm/rpmds.h>
#include <rpm/rpmfiles.h>
#include <rpm/rpmfi.h>
#include <rpm/rpmarchive.h>
#include <rpm/rpmpgp.h>
#endif
#ifdef __TINYC__
/* libplist's fallback pragma is an error under tcc's strict warning mode. */
#define __llvm__ 1
#endif
#include <plist/plist.h>
#ifdef __TINYC__
#undef __llvm__
#endif
#include <ctype.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>

struct foreign_entry {
    struct holy_stream_entry stream;
    char *original;
    char *soname;
    unsigned char hash[32];
    int metadata, group, hardlink_group, config;
};

struct foreign_group {
    const char *arch, *libc;
};

struct foreign_input {
    FILE *spool;
    struct foreign_entry *entries;
    size_t count, capacity;
    struct foreign_group groups[7];
    size_t group_count;
    char *pkginfo;
    size_t pkginfo_size;
    int unknown;
};

enum foreign_archive_kind { FOREIGN_PACMAN, FOREIGN_DEB_CONTROL, FOREIGN_DEB_DATA,
                            FOREIGN_SLACKWARE, FOREIGN_APK_SIGNATURE,
                            FOREIGN_APK_CONTROL, FOREIGN_APK_DATA, FOREIGN_XBPS, FOREIGN_RPM };

struct rpm_metadata { char *name, *version, *release, *arch;
#ifdef HOLY_HAVE_RPM
                      Header header;
#endif
};

struct deb_field { char *key, *value; size_t line; };
struct deb_metadata {
    char *name, *version, *arch;
    struct deb_field *fields;
    size_t count;
};

struct slack_metadata { char *name, *version, *arch, *build; int lzma; };

struct apk_field { char *key, *value; size_t line; };
struct apk_metadata {
    char *name, *version, *arch, *datahash;
    struct apk_field *fields;
    size_t count;
};

struct xbps_metadata {
    plist_t props;
    char *name, *version, *release, *arch;
};

static void token(FILE *out, const char *value)
{
    const unsigned char *p = (const unsigned char *)value;
    fputc('"', out);
    for (; *p; ++p) {
        if (*p == '"' || *p == '\\') fprintf(out, "\\%c", *p);
        else if (*p < 32 || *p >= 127) fprintf(out, "\\x%02x", *p);
        else fputc(*p, out);
    }
    fputc('"', out);
}

static char *joined(const char *a, const char *b)
{
    size_t x = strlen(a), y = strlen(b);
    char *out;
    if (x > SIZE_MAX - y - 2) return NULL;
    out = malloc(x + y + 2);
    if (out) { memcpy(out, a, x); out[x] = '/'; memcpy(out + x + 1, b, y + 1); }
    return out;
}

static char *normalized(const char *input, int directory)
{
    char *out, *p;
    size_t n;
    if (!input) return NULL;
    while (!strncmp(input, "./", 2)) input += 2;
    if (!*input || *input == '/') return NULL;
    out = strdup(input);
    if (!out) return NULL;
    n = strlen(out);
    if (directory && n && out[n-1] == '/') out[--n] = 0;
    if (!n) { free(out); return NULL; }
    for (p = out; *p; ) {
        char *slash = strchr(p, '/');
        size_t length = slash ? (size_t)(slash - p) : strlen(p);
        if (!length || (length == 1 && *p == '.') || (length == 2 && !memcmp(p, "..", 2))) {
            free(out); return NULL;
        }
        if (!slash) break;
        p = slash + 1;
        if (!*p) { free(out); return NULL; }
    }
    return out;
}

static struct archive *foreign_reader(const char *snapshot, int *result, int lzma)
{
    unsigned char header[8];
    int fd = open(snapshot, O_RDONLY | O_CLOEXEC), support;
    ssize_t got;
    struct archive *a = NULL;
    if (fd < 0) return NULL;
    got = read(fd, header, sizeof header); close(fd);
    if (got < 0 || !(a = archive_read_new())) return NULL;
    if (lzma) support = archive_read_support_filter_lzma(a);
    else if (got >= 4 && !memcmp(header, "\x28\xb5\x2f\xfd", 4)) support = archive_read_support_filter_zstd(a);
    else if (got >= 6 && !memcmp(header, "\xfd""7zXZ\0", 6)) support = archive_read_support_filter_xz(a);
    else if (got >= 3 && !memcmp(header, "BZh", 3)) support = archive_read_support_filter_bzip2(a);
    else if (got >= 2 && header[0] == 0x1f && header[1] == 0x8b) support = archive_read_support_filter_gzip(a);
    else if (got >= 4 && !memcmp(header, "\x04\x22\x4d\x18", 4)) support = archive_read_support_filter_lz4(a);
    else support = archive_read_support_filter_none(a);
    if (support != ARCHIVE_OK) {
        fputs("holypkg: built-in foreign archive codec unavailable\n", stderr);
        *result = 6; archive_read_free(a); return NULL;
    }
    if ((archive_read_support_format_tar(a) != ARCHIVE_OK) ||
        archive_read_open_filename(a, snapshot, 65536) != ARCHIVE_OK) {
        *result = 2; archive_read_free(a); return NULL;
    }
    return a;
}

static int add_group(struct foreign_input *input, const char *arch, const char *libc)
{
    size_t i;
    for (i = 0; i < input->group_count; ++i)
        if (!strcmp(input->groups[i].arch, arch) && !strcmp(input->groups[i].libc, libc)) return (int)i;
    if (input->group_count == sizeof input->groups / sizeof *input->groups) return -1;
    input->groups[i].arch = arch; input->groups[i].libc = libc;
    ++input->group_count;
    return (int)i;
}

static int metadata_path(const char *path)
{
    static const char *const names[] = {".PKGINFO", ".BUILDINFO", ".MTREE", ".INSTALL", ".CHANGELOG"};
    size_t i;
    for (i = 0; i < sizeof names / sizeof *names; ++i)
        if (!strcmp(path, names[i])) return 1;
    return 0;
}

static int collect_archive(const char *snapshot, struct foreign_input *input,
                           enum foreign_archive_kind kind, int lzma)
{
    struct archive *a = NULL;
    struct archive_entry *entry;
    int status, result = 1;
    if (!input->spool && !(input->spool = tmpfile())) goto done;
    if (!(a = foreign_reader(snapshot, &result, lzma))) goto done;
    while ((status = archive_read_next_header(a, &entry)) == ARCHIVE_OK) {
        struct foreign_entry *e;
        const char *name = archive_entry_pathname(entry), *hardlink = archive_entry_hardlink(entry);
        mode_t type = archive_entry_filetype(entry);
        long long size = archive_entry_size(entry), total = 0;
        char buffer[65536];
        EVP_MD_CTX *hash = NULL;
        unsigned hash_size;
        FILE *elf = NULL;
        la_ssize_t got;
        result = 2;
        if (name && (!strcmp(name, ".") || !strcmp(name, "./")) && type == AE_IFDIR && !size) continue;
        if (input->count == 100000 || size < 0 || size > 1024LL * 1024 * 1024) goto done;
        if (archive_entry_xattr_count(entry) || archive_entry_acl_types(entry) ||
            (type != AE_IFREG && type != AE_IFDIR && type != AE_IFLNK && !(hardlink && type == 0))) {
            result = 6; goto done;
        }
        if ((kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL ||
             kind == FOREIGN_APK_SIGNATURE) && (type != AE_IFREG || hardlink)) goto done;
        if ((type == AE_IFDIR || type == AE_IFLNK || hardlink) && size) goto done;
        if (input->count == input->capacity) {
            size_t capacity = input->capacity ? input->capacity * 2 : 64;
            void *grown = realloc(input->entries, capacity * sizeof *input->entries);
            if (!grown) { result = 1; goto done; }
            input->entries = grown; input->capacity = capacity;
        }
        e = &input->entries[input->count++];
        memset(e, 0, sizeof *e); e->group = -1; e->hardlink_group = -1;
        e->original = normalized(name, type == AE_IFDIR);
        if (!e->original) goto done;
        if ((kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL ||
             kind == FOREIGN_APK_SIGNATURE) && strchr(e->original, '/')) goto done;
        if (kind == FOREIGN_APK_SIGNATURE && strncmp(e->original, ".SIGN.", 6)) goto done;
        if (kind == FOREIGN_APK_CONTROL && e->original[0] != '.') goto done;
        e->metadata = kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL ||
                      kind == FOREIGN_APK_SIGNATURE ||
                      (kind == FOREIGN_XBPS && (!strcmp(e->original, "props.plist") ||
                       !strcmp(e->original, "files.plist") || !strcmp(e->original, "INSTALL") ||
                       !strcmp(e->original, "REMOVE"))) ||
                      (kind == FOREIGN_PACMAN && metadata_path(e->original)) ||
                      (kind == FOREIGN_SLACKWARE &&
                       (!strcmp(e->original, "install") || !strncmp(e->original, "install/", 8)));
        if (e->metadata && (type != AE_IFREG || hardlink) &&
            !(kind == FOREIGN_SLACKWARE && !strcmp(e->original, "install") && type == AE_IFDIR)) goto done;
        if (kind == FOREIGN_DEB_CONTROL) {
            char *original = e->original;
            e->stream.path = joined("HOLY/foreign/deb", original);
            e->original = joined("@control", original);
            free(original);
        } else if (kind == FOREIGN_APK_CONTROL || kind == FOREIGN_APK_SIGNATURE)
            e->stream.path = joined("HOLY/foreign/apk", e->original);
        else if (kind == FOREIGN_XBPS && e->metadata)
            e->stream.path = joined("HOLY/foreign/xbps", e->original);
        else if (kind == FOREIGN_SLACKWARE && e->metadata)
            e->stream.path = joined("HOLY/foreign/slackware",
                                    !strcmp(e->original, "install") ? "" : e->original + 8);
        else e->stream.path = e->metadata ?
            joined("HOLY/foreign/pacman", e->original + 1) : joined("DATA", e->original);
        e->stream.mode = archive_entry_perm(entry);
        e->stream.uid = archive_entry_uid(entry); e->stream.gid = archive_entry_gid(entry);
        e->stream.directory = type == AE_IFDIR;
        e->stream.size = size;
        e->stream.offset = (long long)ftello(input->spool);
        if (!e->stream.path || e->stream.offset < 0 ||
            e->stream.offset > 4LL * 1024 * 1024 * 1024 - size) goto done;
        if (archive_entry_uname(entry)) e->stream.owner = strdup(archive_entry_uname(entry));
        if (archive_entry_gname(entry)) e->stream.group = strdup(archive_entry_gname(entry));
        if ((archive_entry_uname(entry) && !e->stream.owner) ||
            (archive_entry_gname(entry) && !e->stream.group)) { result = 1; goto done; }
        if (type == AE_IFLNK) {
            const char *target = archive_entry_symlink(entry);
            if (!target || !*target || !holy_safe_link(e->original, target)) goto done;
            e->stream.link = strdup(target);
            if (!e->stream.link) { result = 1; goto done; }
        }
        if (hardlink) {
            char *target = normalized(hardlink, 0);
            if (!target) goto done;
            e->stream.hardlink = joined("DATA", target);
            free(target);
            if (!e->stream.hardlink) { result = 1; goto done; }
        }
        hash = EVP_MD_CTX_new();
        if (!hash || EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1) {
            EVP_MD_CTX_free(hash); result = 1; goto done;
        }
        while ((got = archive_read_data(a, buffer, sizeof buffer)) > 0) {
            if (got > size - total || fwrite(buffer, 1, (size_t)got, input->spool) != (size_t)got ||
                EVP_DigestUpdate(hash, buffer, (size_t)got) != 1) break;
            if (!total && !e->metadata) {
                if (got >= 4 && !memcmp(buffer, "\177ELF", 4)) {
                    elf = tmpfile();
                    if (!elf) break;
                } else if ((got >= 8 && !memcmp(buffer, "!<arch>\n", 8)) ||
                           (got >= 2 && !memcmp(buffer, "MZ", 2)) ||
                           ((e->stream.mode & 0111) && (got < 2 || memcmp(buffer, "#!", 2)))) input->unknown = 1;
            }
            if (elf && fwrite(buffer, 1, (size_t)got, elf) != (size_t)got) break;
            total += got;
        }
        if (got || total != size || EVP_DigestFinal_ex(hash, e->hash, &hash_size) != 1 || hash_size != 32) {
            EVP_MD_CTX_free(hash); if (elf) fclose(elf); goto done;
        }
        EVP_MD_CTX_free(hash);
        if (elf) {
            struct holy_elf_info info;
            int parsed;
            if (fflush(elf)) { fclose(elf); result = 1; goto done; }
            parsed = holy_elf_read_fd(fileno(elf), &info);
            if (!parsed && strcmp(holy_elf_machine(&info), "unknown") && strcmp(holy_elf_runtime(&info), "unknown"))
                e->group = add_group(input, holy_elf_machine(&info), holy_elf_runtime(&info));
            else input->unknown = 1;
            if (!parsed && e->group >= 0 && info.type == ET_DYN && info.has_dynamic &&
                info.soname && !strchr(info.soname, '/')) {
                e->soname = strdup(info.soname);
                if (!e->soname) {
                    holy_elf_free(&info); fclose(elf); result = 1; goto done;
                }
            }
            holy_elf_free(&info); fclose(elf);
            if (e->group < 0) input->unknown = 1;
        }
        if (((kind == FOREIGN_PACMAN || kind == FOREIGN_APK_CONTROL) &&
             !strcmp(e->original, ".PKGINFO")) ||
            (kind == FOREIGN_XBPS && !strcmp(e->original, "props.plist")) ||
            (kind == FOREIGN_DEB_CONTROL && !strcmp(e->original, "@control/control"))) {
            if (input->pkginfo || size > 1024 * 1024 || fflush(input->spool)) goto done;
            input->pkginfo = malloc((size_t)size + 1);
            if (!input->pkginfo) { result = 1; goto done; }
            if (pread(fileno(input->spool), input->pkginfo, (size_t)size, (off_t)e->stream.offset) != size) goto done;
            input->pkginfo[size] = 0; input->pkginfo_size = (size_t)size;
        }
    }
    if (status != ARCHIVE_EOF ||
        ((kind == FOREIGN_PACMAN || kind == FOREIGN_DEB_CONTROL ||
          kind == FOREIGN_APK_CONTROL || kind == FOREIGN_XBPS) && !input->pkginfo) ||
        fflush(input->spool) || fsync(fileno(input->spool))) goto done;
    result = 0;
done:
    if (a) archive_read_free(a);
    return result;
}

static int deb_version(struct archive *ar, long long size)
{
    char data[4096];
    size_t used = 0, i;
    if (size < 4 || size > (long long)sizeof data) return 0;
    while (used < (size_t)size) {
        la_ssize_t got = archive_read_data(ar, data + used, (size_t)size - used);
        if (got <= 0) return 0;
        used += (size_t)got;
    }
    if (data[0] != '2' || data[1] != '.' || data[size - 1] != '\n' ||
        memchr(data, 0, (size_t)size)) return 0;
    for (i = 2; i < (size_t)size && data[i] >= '0' && data[i] <= '9'; ++i) {}
    return i > 2 && i < (size_t)size && data[i] == '\n';
}

static int deb_codec_matches(FILE *part, const char *suffix)
{
    unsigned char header[6];
    ssize_t size = pread(fileno(part), header, sizeof header, 0);
    int gzip, xz, zstd, bzip2, lz4;
    if (size < 0) return 0;
    gzip = size >= 2 && header[0] == 0x1f && header[1] == 0x8b;
    xz = size >= 6 && !memcmp(header, "\xfd""7zXZ\0", 6);
    zstd = size >= 4 && !memcmp(header, "\x28\xb5\x2f\xfd", 4);
    bzip2 = size >= 3 && !memcmp(header, "BZh", 3);
    lz4 = size >= 4 && !memcmp(header, "\x04\x22\x4d\x18", 4);
    if (lz4) return 0;
    if (!strcmp(suffix, ".gz")) return gzip;
    if (!strcmp(suffix, ".xz")) return xz;
    if (!strcmp(suffix, ".zst")) return zstd;
    if (!strcmp(suffix, ".bz2")) return bzip2;
    return !gzip && !xz && !zstd && !bzip2;
}

static int collect_deb(const char *snapshot, struct foreign_input *input)
{
    struct archive *ar = archive_read_new();
    struct archive_entry *entry;
    char magic[8];
    int fd = open(snapshot, O_RDONLY | O_CLOEXEC);
    int stage = 0, result = 2, status;
    if (!ar) { if (fd >= 0) close(fd); return 1; }
    if (fd < 0 || read(fd, magic, sizeof magic) != sizeof magic || memcmp(magic, "!<arch>\n", sizeof magic)) {
        if (fd >= 0) close(fd);
        goto done;
    }
    close(fd);
    if (archive_read_support_filter_none(ar) != ARCHIVE_OK ||
        archive_read_support_format_ar(ar) != ARCHIVE_OK ||
        archive_read_open_filename(ar, snapshot, 65536) != ARCHIVE_OK) goto done;
    while ((status = archive_read_next_header(ar, &entry)) == ARCHIVE_OK) {
        const char *name = archive_entry_pathname(entry);
        long long size = archive_entry_size(entry), total = 0;
        FILE *part = NULL;
        char descriptor[64], buffer[65536];
        la_ssize_t got;
        result = 2;
        if (!name || size < 0 || size > 1024LL * 1024 * 1024) goto done;
        if (name[0] == '_' && stage && stage < 3) {
            if (archive_read_data_skip(ar) != ARCHIVE_OK) goto done;
            continue;
        }
        if (stage == 0) {
            if (strcmp(name, "debian-binary") || !deb_version(ar, size)) goto done;
            stage = 1;
            continue;
        }
        if (stage == 3) {
            if (archive_read_data_skip(ar) != ARCHIVE_OK) goto done;
            continue;
        }
        if (stage == 1) {
            if (strncmp(name, "control.tar", 11) ||
                (strcmp(name + 11, "") && strcmp(name + 11, ".gz") &&
                 strcmp(name + 11, ".xz") && strcmp(name + 11, ".zst")) || size > 16 * 1024 * 1024) goto done;
        } else if (stage == 2) {
            if (strncmp(name, "data.tar", 8) ||
                (strcmp(name + 8, "") && strcmp(name + 8, ".gz") &&
                 strcmp(name + 8, ".xz") && strcmp(name + 8, ".zst") &&
                 strcmp(name + 8, ".bz2") && strcmp(name + 8, ".lzma"))) goto done;
        } else goto done;
        part = tmpfile();
        if (!part) { result = 1; goto done; }
        while ((got = archive_read_data(ar, buffer, sizeof buffer)) > 0) {
            if (got > size - total || fwrite(buffer, 1, (size_t)got, part) != (size_t)got) break;
            total += got;
        }
        if (got || total != size || fflush(part) || fsync(fileno(part)) ||
            (strcmp(stage == 1 ? name + 11 : name + 8, ".lzma") &&
             !deb_codec_matches(part, stage == 1 ? name + 11 : name + 8))) {
            fclose(part); goto done;
        }
        snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fileno(part));
        result = collect_archive(descriptor, input, stage == 1 ? FOREIGN_DEB_CONTROL : FOREIGN_DEB_DATA,
                                 stage == 2 && !strcmp(name + 8, ".lzma"));
        fclose(part);
        if (result) goto done;
        ++stage;
    }
    result = status == ARCHIVE_EOF && stage == 3 ? 0 : 2;
done:
    archive_read_free(ar);
    return result;
}


static void free_input(struct foreign_input *input)
{
    size_t i;
    for (i = 0; i < input->count; ++i) {
        struct foreign_entry *e = &input->entries[i];
        free(e->original); free(e->soname);
        free((char *)e->stream.path); free((char *)e->stream.link);
        free((char *)e->stream.hardlink); free((char *)e->stream.owner); free((char *)e->stream.group);
    }
    free(input->entries); free(input->pkginfo);
    if (input->spool) fclose(input->spool);
}

static void free_deb(struct deb_metadata *meta)
{
    size_t i;
    for (i = 0; i < meta->count; ++i) { free(meta->fields[i].key); free(meta->fields[i].value); }
    free(meta->fields);
}

static void free_apk(struct apk_metadata *meta)
{
    size_t i;
    for (i = 0; i < meta->count; ++i) { free(meta->fields[i].key); free(meta->fields[i].value); }
    free(meta->fields);
}

static int parse_apk(const char *data, size_t size, struct apk_metadata *meta)
{
    size_t offset = 0, line = 0;
    if (memchr(data, 0, size)) return 0;
    while (offset < size) {
        const char *start = data + offset, *end = memchr(start, '\n', size - offset), *separator;
        size_t length = end ? (size_t)(end - start) : size - offset, i;
        struct apk_field *field, *grown;
        ++line; offset += length + (end != NULL);
        if (!length || *start == '#') continue;
        separator = memchr(start, '=', length);
        if (!separator || separator < start + 2 || separator[-1] != ' ' ||
            separator + 1 >= start + length || separator[1] != ' ' ||
            meta->count == 4096) return 0;
        for (i = 0; i < (size_t)(separator - start - 1); ++i)
            if (!isalnum((unsigned char)start[i]) && start[i] != '_' && start[i] != '-') return 0;
        grown = realloc(meta->fields, (meta->count + 1) * sizeof *grown);
        if (!grown) return 0;
        meta->fields = grown; field = &meta->fields[meta->count++];
        field->key = strndup(start, (size_t)(separator - start - 1));
        field->value = strndup(separator + 2, (size_t)(start + length - separator - 2));
        field->line = line;
        if (!field->key || !field->value || !*field->value) return 0;
        if (!strcmp(field->key, "pkgname")) { if (meta->name) return 0; meta->name = field->value; }
        if (!strcmp(field->key, "pkgver")) { if (meta->version) return 0; meta->version = field->value; }
        if (!strcmp(field->key, "arch")) { if (meta->arch) return 0; meta->arch = field->value; }
        if (!strcmp(field->key, "datahash")) { if (meta->datahash) return 0; meta->datahash = field->value; }
    }
    if (!meta->name || !meta->version || !meta->arch) return 0;
    if (!isalnum((unsigned char)meta->name[0]) ||
        strspn(meta->name, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+_.-") != strlen(meta->name)) return 0;
    return 1;
}

static int parse_deb(const char *control, size_t length, struct deb_metadata *meta)
{
    size_t offset = 0, line = 0;
    int blank = 0;
    if (memchr(control, 0, length)) return 0;
    while (offset < length) {
        const char *start = control + offset, *end = memchr(start, '\n', length - offset), *colon;
        size_t size = end ? (size_t)(end - start) : length - offset, i;
        struct deb_field *field;
        ++line;
        offset += size + (end != NULL);
        if (size && start[size-1] == '\r') --size;
        if (!size) { if (meta->count) blank = 1; continue; }
        if (blank) return 0;
        if (*start == ' ' || *start == '\t') {
            char *value;
            size_t old;
            if (!meta->count) return 0;
            field = &meta->fields[meta->count - 1]; old = strlen(field->value);
            if (old > SIZE_MAX - size - 2) return 0;
            value = realloc(field->value, old + size + 2);
            if (!value) return 0;
            field->value = value; value[old] = '\n';
            memcpy(value + old + 1, start, size); value[old + size + 1] = 0;
            continue;
        }
        colon = memchr(start, ':', size);
        if (!colon || colon == start || meta->count >= 4096) return 0;
        for (i = 0; i < (size_t)(colon - start); ++i)
            if (!((start[i] >= 'A' && start[i] <= 'Z') ||
                  (start[i] >= 'a' && start[i] <= 'z') || start[i] == '-')) return 0;
        for (i = 0; i < meta->count; ++i)
            if (strlen(meta->fields[i].key) == (size_t)(colon - start) &&
                !strncasecmp(meta->fields[i].key, start, (size_t)(colon - start))) return 0;
        i = (size_t)(colon - start);
        while (colon + 1 < start + size && (colon[1] == ' ' || colon[1] == '\t')) ++colon;
        {
            struct deb_field *grown = realloc(meta->fields, (meta->count + 1) * sizeof *grown);
            if (!grown) return 0;
            meta->fields = grown;
        }
        field = &meta->fields[meta->count++];
        field->key = strndup(start, i);
        field->value = strndup(colon + 1, (size_t)(start + size - colon - 1));
        field->line = line;
        if (!field->key || !field->value) return 0;
    }
    for (offset = 0; offset < meta->count; ++offset) {
        struct deb_field *field = &meta->fields[offset];
        if (!strcasecmp(field->key, "Package")) meta->name = field->value;
        if (!strcasecmp(field->key, "Version")) meta->version = field->value;
        if (!strcasecmp(field->key, "Architecture")) meta->arch = field->value;
    }
    if (!meta->name || !*meta->name || !meta->version || !*meta->version || !meta->arch || !*meta->arch) return 0;
    for (offset = 0; meta->name[offset]; ++offset)
        if (!((meta->name[offset] >= 'a' && meta->name[offset] <= 'z') ||
              (meta->name[offset] >= '0' && meta->name[offset] <= '9') ||
              (offset && (meta->name[offset] == '+' || meta->name[offset] == '-' ||
                          meta->name[offset] == '.')))) return 0;
    for (offset = 0; meta->version[offset]; ++offset)
        if ((unsigned char)meta->version[offset] <= 32 || (unsigned char)meta->version[offset] >= 127) return 0;
    for (offset = 0; meta->arch[offset]; ++offset)
        if (!((meta->arch[offset] >= 'a' && meta->arch[offset] <= 'z') ||
              (meta->arch[offset] >= '0' && meta->arch[offset] <= '9'))) return 0;
    {
        int order;
        if (!holy_deb_version_compare(meta->version, meta->version, &order)) return 0;
    }
    return 1;
}

static void free_slack(struct slack_metadata *meta)
{
    free(meta->name); free(meta->version); free(meta->arch); free(meta->build);
}

static int parse_slack_name(const char *path, struct slack_metadata *meta)
{
    const char *base = strrchr(path, '/');
    char *copy, *part;
    size_t i, length;
    base = base ? base + 1 : path;
    length = strlen(base);
    if (length < 12 ||
        (strcmp(base + length - 4, ".txz") && strcmp(base + length - 4, ".tgz") &&
         strcmp(base + length - 4, ".tbz") && strcmp(base + length - 4, ".tlz"))) return 0;
    meta->lzma = !strcmp(base + length - 4, ".tlz");
    copy = strndup(base, length - 4);
    if (!copy) return 0;
    part = strrchr(copy, '-');
    if (!part || !part[1]) goto bad;
    meta->build = strdup(part + 1); *part = 0;
    part = strrchr(copy, '-');
    if (!part || !part[1]) goto bad;
    meta->arch = strdup(part + 1); *part = 0;
    part = strrchr(copy, '-');
    if (!part || !part[1] || part == copy) goto bad;
    meta->version = strdup(part + 1); *part = 0;
    meta->name = strdup(copy);
    if (!meta->name || !meta->version || !meta->arch || !meta->build) goto bad;
    for (i = 0; meta->name[i]; ++i)
        if (!isalnum((unsigned char)meta->name[i]) &&
            (i == 0 || (meta->name[i] != '-' && meta->name[i] != '_' &&
                        meta->name[i] != '+' && meta->name[i] != '.'))) goto bad;
    for (i = 0; meta->version[i]; ++i)
        if (!isalnum((unsigned char)meta->version[i]) &&
            meta->version[i] != '.' && meta->version[i] != '_' &&
            meta->version[i] != '+' && meta->version[i] != '~') goto bad;
    for (i = 0; meta->arch[i]; ++i)
        if (!isalnum((unsigned char)meta->arch[i]) && meta->arch[i] != '_') goto bad;
    for (i = 0; meta->build[i]; ++i)
        if (!isalnum((unsigned char)meta->build[i]) &&
            meta->build[i] != '_' && meta->build[i] != '.') goto bad;
    free(copy);
    return 1;
bad:
    free(copy);
    return 0;
}

static int slack_codec_matches(const char *snapshot, const char *path)
{
    unsigned char header[6];
    size_t length = strlen(path);
    int fd = open(snapshot, O_RDONLY | O_CLOEXEC);
    ssize_t got = fd >= 0 ? pread(fd, header, sizeof header, 0) : -1;
    if (fd >= 0) close(fd);
    if (got < 0 || length < 4) return 0;
    if (!strcmp(path + length - 4, ".txz"))
        return got >= 6 && !memcmp(header, "\xfd""7zXZ\0", 6);
    if (!strcmp(path + length - 4, ".tgz"))
        return got >= 2 && header[0] == 0x1f && header[1] == 0x8b;
    if (!strcmp(path + length - 4, ".tbz"))
        return got >= 3 && !memcmp(header, "BZh", 3);
    return got > 0;
}

static int input_hash(const char *path, char hex[65])
{
    FILE *file = fopen(path, "rb");
    EVP_MD_CTX *hash = EVP_MD_CTX_new();
    unsigned char digest[32], bytes[65536];
    unsigned length;
    size_t got, i;
    int ok = 0;
    if (!file || !hash || EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1) goto done;
    while ((got = fread(bytes, 1, sizeof bytes, file)))
        if (EVP_DigestUpdate(hash, bytes, got) != 1) goto done;
    if (ferror(file) || EVP_DigestFinal_ex(hash, digest, &length) != 1 || length != 32) goto done;
    for (i = 0; i < 32; ++i) snprintf(hex + i * 2, 3, "%02x", digest[i]);
    ok = 1;
done:
    if (file) fclose(file);
    EVP_MD_CTX_free(hash);
    return ok;
}

static int path_order(const void *a, const void *b)
{
    const struct foreign_entry *const *x = a, *const *y = b;
    return strcmp((*x)->original, (*y)->original);
}

static struct foreign_entry *find_path(struct foreign_entry **sorted, size_t count, const char *path)
{
    struct foreign_entry key = {0}, *pointer = &key;
    struct foreign_entry **found;
    key.original = (char *)path;
    found = bsearch(&pointer, sorted, count, sizeof *sorted, path_order);
    return found ? *found : NULL;
}

static int validate_paths(struct foreign_input *input)
{
    struct foreign_entry **sorted = malloc(input->count * sizeof *sorted);
    size_t i;
    int ok = 0;
    if (!sorted) return 0;
    for (i = 0; i < input->count; ++i) sorted[i] = &input->entries[i];
    qsort(sorted, input->count, sizeof *sorted, path_order);
    for (i = 0; i < input->count; ++i) {
        struct foreign_entry *e = sorted[i];
        char *parent, *slash;
        if (i && !strcmp(sorted[i-1]->original, e->original)) goto done;
        parent = strdup(e->original);
        if (!parent) goto done;
        for (slash = strchr(parent, '/'); slash; slash = strchr(slash + 1, '/')) {
            struct foreign_entry *ancestor;
            *slash = 0;
            ancestor = find_path(sorted, input->count, parent);
            *slash = '/';
            if (ancestor && !ancestor->stream.directory) { free(parent); goto done; }
        }
        free(parent);
        if (e->stream.hardlink) {
            struct foreign_entry *target = find_path(sorted, input->count, e->stream.hardlink + 5);
            if (!target || target->metadata || target->stream.directory || target->stream.link ||
                target->stream.hardlink || e->stream.mode != target->stream.mode ||
                e->stream.uid != target->stream.uid || e->stream.gid != target->stream.gid) goto done;
            e->hardlink_group = (int)(target - input->entries);
            target->hardlink_group = e->hardlink_group;
            e->group = target->group; memcpy(e->hash, target->hash, 32);
        }
    }
    ok = 1;
done:
    free(sorted);
    return ok;
}

static char *xbps_string(plist_t dict, const char *key)
{
    plist_t item = plist_dict_get_item(dict, key);
    char *value = NULL;
    if (item && plist_get_node_type(item) == PLIST_STRING) plist_get_string_val(item, &value);
    return value;
}

static int xbps_label(const char *value)
{
    const unsigned char *p = (const unsigned char *)value;
    if (!value || !*value || strlen(value) > 255 || !isalnum(*p)) return 0;
    for (; *p; ++p)
        if (!isalnum(*p) && *p != '-' && *p != '_' && *p != '.' && *p != '+' && *p != '~') return 0;
    return 1;
}

static int xbps_dependency(const char *expression, char **name,
                           const char **relation, const char **version)
{
    const char *op = strpbrk(expression, "<>");
    int order;
    size_t length;
    if (!op || op == expression) return 0;
    length = (size_t)(op - expression);
    *name = strndup(expression, length);
    if (!*name) return 0;
    if (!xbps_label(*name)) { free(*name); *name = NULL; return 0; }
    if (op[1] == '=') {
        *relation = *op == '<' ? "le" : "ge";
        *version = op + 2;
    } else {
        *relation = *op == '<' ? "lt" : "gt";
        *version = op + 1;
    }
    if (!holy_xbps_version_compare(*version, *version, &order)) {
        free(*name); *name = NULL; return 0;
    }
    return 1;
}

static int xbps_parse(struct foreign_input *input, struct xbps_metadata *meta)
{
    char *pkgver = NULL, *declared_version = NULL;
    const char *suffix;
    size_t i;
    if (input->pkginfo_size > UINT32_MAX ||
        plist_from_xml(input->pkginfo, (uint32_t)input->pkginfo_size, &meta->props) != PLIST_ERR_SUCCESS ||
        !meta->props || plist_get_node_type(meta->props) != PLIST_DICT) return 0;
    meta->name = xbps_string(meta->props, "pkgname");
    meta->arch = xbps_string(meta->props, "architecture");
    pkgver = xbps_string(meta->props, "pkgver");
    declared_version = xbps_string(meta->props, "version");
    if (!xbps_label(meta->name) || !xbps_label(meta->arch) ||
        !pkgver || !declared_version || strlen(pkgver) <= strlen(meta->name) + 1 ||
        strncmp(pkgver, meta->name, strlen(meta->name)) ||
        pkgver[strlen(meta->name)] != '-' ||
        strcmp(pkgver + strlen(meta->name) + 1, declared_version)) goto bad;
    suffix = strrchr(declared_version, '_');
    if (!suffix || !suffix[1] || strspn(suffix + 1, "0123456789") != strlen(suffix + 1)) goto bad;
    meta->version = strndup(declared_version, (size_t)(suffix - declared_version));
    meta->release = strdup(suffix + 1);
    if (!xbps_label(meta->version) || !meta->release) goto bad;
    for (i = 0; i < input->count; ++i)
        if (!strcmp(input->entries[i].original, "files.plist")) break;
    free(pkgver); free(declared_version);
    return i < input->count;
bad:
    free(pkgver); free(declared_version);
    return 0;
}

static char *xbps_link_target(const char *path, const char *target)
{
    if (!target || !holy_safe_link(path, target)) return NULL;
    if (target[0] == '/') return normalized(target + 1, 0);
    return holy_relative_link_path(path, strlen(path), target, "");
}

static int xbps_files(struct foreign_input *input)
{
    plist_t root = NULL;
    struct foreign_entry *control = NULL;
    unsigned char *seen = NULL;
    size_t i, covered = 0;
    int ok = 0;
    for (i = 0; i < input->count; ++i)
        if (!strcmp(input->entries[i].original, "files.plist")) control = &input->entries[i];
    if (!control || control->stream.size > 1024 * 1024) return 0;
    {
        size_t size = (size_t)control->stream.size;
        char *xml = malloc(size + 1);
        if (!xml) return 0;
        if (fflush(input->spool) || pread(fileno(input->spool), xml, size,
                                          (off_t)control->stream.offset) != (ssize_t)size ||
            plist_from_xml(xml, (uint32_t)size, &root) != PLIST_ERR_SUCCESS) {
            free(xml); return 0;
        }
        free(xml);
    }
    if (!root || plist_get_node_type(root) != PLIST_DICT) goto done;
    {
        plist_dict_iter iter = NULL;
        char *key = NULL;
        plist_t value = NULL;
        plist_dict_new_iter(root, &iter);
        if (!iter) goto done;
        for (;;) {
            plist_dict_next_item(root, iter, &key, &value);
            if (!key) break;
            if (strcmp(key, "dirs") && strcmp(key, "files") && strcmp(key, "links")) {
                fprintf(stderr, "holypkg: unsupported XBPS files.plist field %s\n", key);
                free(key); free(iter); goto done;
            }
            free(key);
        }
        free(iter);
    }
    seen = calloc(input->count ? input->count : 1, 1);
    if (!seen) goto done;
    {
        static const char *const keys[] = {"dirs", "files", "links"};
        size_t kind;
        for (kind = 0; kind < 3; ++kind) {
            plist_t array = plist_dict_get_item(root, keys[kind]);
            uint32_t j;
            if (!array) continue;
            if (plist_get_node_type(array) != PLIST_ARRAY) goto done;
            for (j = 0; j < plist_array_get_size(array); ++j) {
                plist_t item = plist_array_get_item(array, j);
                char *path, *digest = NULL;
                struct foreign_entry *entry = NULL;
                uint64_t size = 0;
                plist_t size_node;
                if (!item || plist_get_node_type(item) != PLIST_DICT) goto done;
                path = xbps_string(item, "file");
                if (!path || path[0] != '/' || !path[1]) { free(path); goto done; }
                for (i = 0; i < input->count; ++i)
                    if (!input->entries[i].metadata && !strcmp(input->entries[i].original, path + 1)) {
                        entry = &input->entries[i]; break;
                    }
                free(path);
                if (kind == 0 && !entry) continue;
                if (!entry || seen[i] || (!!entry->stream.directory != (kind == 0)) ||
                    (!!entry->stream.link != (kind == 2))) goto done;
                seen[i] = 1; ++covered;
                if (kind == 2) {
                    char *target = xbps_string(item, "target");
                    char *listed = target ? xbps_link_target(entry->original, target) : NULL;
                    char *payload = entry->stream.link ?
                        xbps_link_target(entry->original, entry->stream.link) : NULL;
                    int matches = listed && payload && !strcmp(listed, payload);
                    free(target); free(listed); free(payload);
                    if (!matches) {
                        fprintf(stderr, "holypkg: XBPS files.plist link target mismatch %s\n",
                                entry->original);
                        goto done;
                    }
                }
                if (kind != 1) continue;
                digest = xbps_string(item, "sha256");
                size_node = plist_dict_get_item(item, "size");
                if (!digest || strlen(digest) != 64 ||
                    strspn(digest, "0123456789abcdefABCDEF") != 64 ||
                    !size_node || plist_get_node_type(size_node) != PLIST_INT) {
                    free(digest); goto done;
                }
                plist_get_uint_val(size_node, &size);
                if (size != (uint64_t)entry->stream.size) { free(digest); goto done; }
                {
                    char actual[65];
                    size_t k;
                    for (k = 0; k < 32; ++k) snprintf(actual + k * 2, 3, "%02x", entry->hash[k]);
                    if (strcasecmp(actual, digest)) { free(digest); goto done; }
                }
                free(digest);
            }
        }
    }
    for (i = 0; i < input->count; ++i)
        if (!input->entries[i].metadata && !seen[i]) goto done;
    ok = covered != 0;
done:
    free(seen); plist_free(root);
    return ok;
}

static int deb_md5_matches(const struct foreign_input *input,
                           const struct foreign_entry *entry, const char *expected)
{
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    unsigned char digest[16], buffer[65536];
    unsigned length;
    long long offset = 0;
    size_t i;
    int ok = 0;
    if (!ctx || EVP_DigestInit_ex(ctx, EVP_md5(), NULL) != 1) goto done;
    while (offset < entry->stream.size) {
        size_t size = (size_t)(entry->stream.size - offset);
        if (size > sizeof buffer) size = sizeof buffer;
        if (pread(fileno(input->spool), buffer, size,
                  (off_t)(entry->stream.offset + offset)) != (ssize_t)size ||
            EVP_DigestUpdate(ctx, buffer, size) != 1) goto done;
        offset += (long long)size;
    }
    if (EVP_DigestFinal_ex(ctx, digest, &length) != 1 || length != sizeof digest) goto done;
    for (i = 0; i < sizeof digest; ++i) {
        char hex[3];
        snprintf(hex, sizeof hex, "%02x", digest[i]);
        if (tolower((unsigned char)expected[i * 2]) != hex[0] ||
            tolower((unsigned char)expected[i * 2 + 1]) != hex[1]) goto done;
    }
    ok = 1;
done:
    EVP_MD_CTX_free(ctx);
    return ok;
}

static int verify_deb_md5sums(const struct foreign_input *input)
{
    const struct foreign_entry *list = NULL;
    struct foreign_entry **sorted = NULL;
    unsigned char *seen = NULL;
    char *data = NULL;
    size_t i, offset = 0;
    int ok = 0;
    for (i = 0; i < input->count; ++i)
        if (!strcmp(input->entries[i].original, "@control/md5sums")) list = &input->entries[i];
    if (!list) return 1;
    if (list->stream.size < 0 || list->stream.size > 16 * 1024 * 1024) goto done;
    data = malloc((size_t)list->stream.size + 1);
    sorted = malloc(input->count * sizeof *sorted);
    seen = calloc(input->count, 1);
    if (!data || !sorted || !seen ||
        pread(fileno(input->spool), data, (size_t)list->stream.size,
              (off_t)list->stream.offset) != list->stream.size) goto done;
    data[list->stream.size] = 0;
    if (memchr(data, 0, (size_t)list->stream.size)) goto done;
    for (i = 0; i < input->count; ++i) sorted[i] = &input->entries[i];
    qsort(sorted, input->count, sizeof *sorted, path_order);
    while (offset < (size_t)list->stream.size) {
        char *line = data + offset, *end = memchr(line, '\n', (size_t)list->stream.size - offset);
        char *path, *canonical;
        struct foreign_entry *entry;
        size_t j, index;
        if (end) { *end = 0; offset = (size_t)(end - data) + 1; }
        else offset = (size_t)list->stream.size;
        if (strlen(line) < 35 || line[32] != ' ' || line[33] != ' ' ||
            !line[34] || line[strlen(line) - 1] == ' ' || line[strlen(line) - 1] == '\t') goto done;
        for (j = 0; j < 32; ++j) if (!isxdigit((unsigned char)line[j])) goto done;
        path = line + 34;
        canonical = normalized(path, 0);
        if (!canonical) goto done;
        j = strcmp(canonical, path);
        free(canonical);
        if (j) goto done;
        entry = find_path(sorted, input->count, path);
        if (!entry || entry->metadata || entry->stream.directory || entry->stream.link) goto done;
        index = (size_t)(entry - input->entries);
        if (seen[index]++) goto done;
        if (entry->stream.hardlink) entry = &input->entries[entry->hardlink_group];
        if (!deb_md5_matches(input, entry, line)) goto done;
    }
    ok = 1;
done:
    if (!ok) fputs("holypkg: Debian md5sums does not match payload\n", stderr);
    free(data); free(sorted); free(seen);
    return ok;
}

static int mark_deb_conffiles(struct foreign_input *input)
{
    struct foreign_entry *list = NULL;
    char *data = NULL;
    size_t i, offset = 0;
    int ok = 0;
    for (i = 0; i < input->count; ++i)
        if (!strcmp(input->entries[i].original, "@control/conffiles")) {
            if (list) return 0;
            list = &input->entries[i];
        }
    if (!list) return 1;
    if (list->stream.size < 0 || list->stream.size > 16 * 1024 * 1024) goto done;
    data = malloc((size_t)list->stream.size + 1);
    if (!data || pread(fileno(input->spool), data, (size_t)list->stream.size,
                       (off_t)list->stream.offset) != list->stream.size ||
        memchr(data, 0, (size_t)list->stream.size)) goto done;
    data[list->stream.size] = 0;
    while (offset < (size_t)list->stream.size) {
        char *line = data + offset, *end = strchr(line, '\n');
        char *canonical;
        struct foreign_entry *entry = NULL;
        if (end) { *end = 0; offset = (size_t)(end - data) + 1; }
        else offset = (size_t)list->stream.size;
        if (line[0] != '/' || !line[1] || line[strlen(line) - 1] == ' ' ||
            line[strlen(line) - 1] == '\r') goto done;
        canonical = normalized(line + 1, 0);
        if (!canonical) goto done;
        if (strcmp(canonical, line + 1)) { free(canonical); goto done; }
        for (i = 0; i < input->count; ++i)
            if (!input->entries[i].metadata && !strcmp(input->entries[i].original, canonical)) {
                entry = &input->entries[i]; break;
            }
        free(canonical);
        if (!entry || entry->config || entry->stream.directory ||
            entry->stream.link || entry->stream.hardlink) goto done;
        entry->config = 1;
    }
    ok = 1;
done:
    if (!ok) fputs("holypkg: Debian conffiles do not match regular payload files\n", stderr);
    free(data);
    return ok;
}

static int append_text(struct foreign_input *input, struct holy_stream_entry *entry,
                        const char *path, const char *text, size_t size)
{
    memset(entry, 0, sizeof *entry);
    entry->path = path; entry->mode = 0644; entry->owner = entry->group = "root";
    entry->offset = (long long)ftello(input->spool); entry->size = (long long)size;
    return entry->offset >= 0 && fwrite(text, 1, size, input->spool) == size;
}

static void hex_hash(FILE *file, const unsigned char hash[32])
{
    size_t i;
    for (i = 0; i < 32; ++i) fprintf(file, "%02x", hash[i]);
}

static int write_manifest(FILE *manifest, const struct foreign_input *input,
                          size_t index, const char *family)
{
    const struct foreign_entry *e = &input->entries[index];
    const struct holy_stream_entry *s = &e->stream;
    long long size = s->hardlink ? input->entries[e->hardlink_group].stream.size : s->size;
    fprintf(manifest, "%s ", s->directory ? "dir" : s->link ? "symlink" : s->hardlink ? "hardlink" : "file");
    token(manifest, e->original);
    fprintf(manifest, " %o ", s->mode); token(manifest, s->owner ? s->owner : "-");
    fputc(' ', manifest); token(manifest, s->group ? s->group : "-");
    fprintf(manifest, " %lld %lld %lld ", s->uid, s->gid, size);
    if (s->directory || s->link) fputc('-', manifest); else hex_hash(manifest, e->hash);
    fprintf(manifest, " %s - ", e->config ? "config" : "none");
    if (e->hardlink_group >= 0) fprintf(manifest, "%s-hardlink-%d", family, e->hardlink_group);
    else fputc('-', manifest);
    if (s->link || s->hardlink) { fputc(' ', manifest); token(manifest, s->link ? s->link : s->hardlink + 5); }
    fputc('\n', manifest);
    return !ferror(manifest);
}

static int belongs(const struct foreign_input *input, size_t index, int group)
{
    const struct foreign_entry *e = &input->entries[index];
    size_t i, length;
    int has_children = 0;
    if (e->metadata) return 1;
    if (!e->stream.directory) return e->group == group;
    length = strlen(e->original);
    for (i = 0; i < input->count; ++i) {
        const struct foreign_entry *child = &input->entries[i];
        if (i == index || child->metadata || child->stream.directory) continue;
        if (!strncmp(child->original, e->original, length) && child->original[length] == '/') {
            has_children = 1;
            if (child->group == group) return 1;
        }
    }
    return !has_children && group == (int)input->group_count - 1;
}

static int soname_entry_order(const void *left, const void *right)
{
    const struct foreign_entry *a = *(const struct foreign_entry *const *)left;
    const struct foreign_entry *b = *(const struct foreign_entry *const *)right;
    int result = strcmp(a->soname, b->soname);
    return result ? result : strcmp(a->original, b->original);
}

static int emit_elf_provides(FILE *out, const struct foreign_input *input,
                             int group, const char *arch, const char *libc)
{
    const struct foreign_entry **items;
    size_t count = 0, i;
    int ok = 0;
    items = calloc(input->count ? input->count : 1, sizeof *items);
    if (!items) return 0;
    for (i = 0; i < input->count; ++i) {
        const struct foreign_entry *entry = &input->entries[i];
        if (entry->soname && belongs(input, i, group) &&
            holy_provides_claim_valid("soname", entry->soname, arch, libc,
                                      "-", entry->original)) items[count++] = entry;
    }
    qsort(items, count, sizeof *items, soname_entry_order);
    for (i = 0; i < count; ++i) {
        if (i && !strcmp(items[i-1]->soname, items[i]->soname)) continue;
        fputs("provide soname ", out); token(out, items[i]->soname);
        fprintf(out, " %s %s - ", arch, libc);
        token(out, items[i]->original); fputc('\n', out);
        {
            off_t position = ftello(out);
            if (ferror(out) || position < 0 || position > 1024 * 1024) goto done;
        }
    }
    ok = 1;
done:
    free(items);
    return ok;
}

static void requirement(FILE *out, 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)
{
    const char *fields[] = {id, consumer, kind, name, arch, libc, relation, version, original, evidence};
    size_t i;
    fputs("require", out);
    for (i = 0; i < sizeof fields / sizeof *fields; ++i) { fputc(' ', out); token(out, fields[i]); }
    fputc('\n', out);
}

#ifdef HOLY_HAVE_RPM
static int rpm_simple_capability(const char *name)
{
    const unsigned char *p = (const unsigned char *)name;
    for (; *p; ++p) if (*p <= 32 || *p == 127) return 0;
    return 1;
}

static int rpm_relations(FILE *deps, FILE *provides, FILE *origin, const struct rpm_metadata *rpm,
                         const char *arch, const char *libc)
{
    static const rpmTagVal names[] = {RPMTAG_REQUIRENAME, RPMTAG_PROVIDENAME,
                                      RPMTAG_CONFLICTNAME, RPMTAG_OBSOLETENAME};
    static const rpmTagVal versions[] = {RPMTAG_REQUIREVERSION, RPMTAG_PROVIDEVERSION,
                                         RPMTAG_CONFLICTVERSION, RPMTAG_OBSOLETEVERSION};
    static const rpmTagVal flags[] = {RPMTAG_REQUIREFLAGS, RPMTAG_PROVIDEFLAGS,
                                      RPMTAG_CONFLICTFLAGS, RPMTAG_OBSOLETEFLAGS};
    size_t k;
    for (k = 0; k < 4; ++k) {
        rpmtd n = rpmtdNew(), v = rpmtdNew(), f = rpmtdNew();
        rpm_count_t count, j;
        int ok = 1;
        if (!n || !v || !f) { ok = 0; goto next; }
        if (!headerGet(rpm->header, names[k], n, HEADERGET_MINMEM)) goto next;
        count = rpmtdCount(n);
        if ((headerGet(rpm->header, versions[k], v, HEADERGET_MINMEM) && rpmtdCount(v) != count) ||
            (headerGet(rpm->header, flags[k], f, HEADERGET_MINMEM) && rpmtdCount(f) != count)) {
            ok = 0; goto next;
        }
        for (j = 0; j < count; ++j) {
            const char *name, *version = "", *relation = "any";
            uint32_t bits = 0, all_flags = 0;
            char id[48], original[1024];
            if (rpmtdSetIndex(n, j) < 0) { ok = 0; break; }
            name = rpmtdGetString(n);
            if (!name || !*name || strlen(name) >= sizeof original / 2) { ok = 0; break; }
            if (rpmtdCount(v)) { if (rpmtdSetIndex(v, j) < 0) { ok = 0; break; } version = rpmtdGetString(v); }
            if (rpmtdCount(f)) { if (rpmtdSetIndex(f, j) < 0) { ok = 0; break; } all_flags = (uint32_t)rpmtdGetNumber(f); }
            if (!version) { ok = 0; break; }
            bits = all_flags;
            bits &= RPMSENSE_LESS | RPMSENSE_GREATER | RPMSENSE_EQUAL;
            if (bits == RPMSENSE_EQUAL) relation = "eq";
            else if (bits == (RPMSENSE_GREATER | RPMSENSE_EQUAL)) relation = "ge";
            else if (bits == (RPMSENSE_LESS | RPMSENSE_EQUAL)) relation = "le";
            else if (bits == RPMSENSE_GREATER) relation = "gt";
            else if (bits == RPMSENSE_LESS) relation = "lt";
            else if (bits) relation = "foreign";
            snprintf(id, sizeof id, "rpm-%zu-%u", k, j);
            if (snprintf(original, sizeof original, "%s %s %s", name, relation, version) >=
                (int)sizeof original) { ok = 0; break; }
            fputs("rpm-relation ", origin);
            token(origin, k == 0 ? "Requires" : k == 1 ? "Provides" :
                          k == 2 ? "Conflicts" : "Obsoletes");
            fputc(' ', origin); token(origin, original);
            fprintf(origin, " %u\n", all_flags);
            if (k == 1) {
                if (rpm_simple_capability(name) && ((!bits && !*version) ||
                    (bits == RPMSENSE_EQUAL && holy_rpm_version_valid(version)))) {
                    fputs("provide package ", provides); token(provides, name);
                    fputs(" any any ", provides); token(provides, *version ? version : "-");
                    fputs(" rpm\n", provides);
                }
            } else if (k == 0 && !strncmp(name, "rpmlib(", 7)) {
                /* rpmlib names describe the archive format, not a runtime dependency. */
            } else if (k == 0 && (all_flags & RPMSENSE_CONFIG) &&
                       !strncmp(name, "config(", 7) &&
                       strlen(name) == strlen(rpm->name) + 8 &&
                       !strncmp(name + 7, rpm->name, strlen(rpm->name)) &&
                       name[strlen(name) - 1] == ')') {
                /* rpm emits a config-file requirement on the package itself. */
            } else if (k == 0 && rpm_simple_capability(name) && strcmp(relation, "foreign") &&
                       ((!bits && !*version) || (bits && *version && holy_rpm_version_valid(version))) &&
                       (name[0] != '/' || !bits) &&
                       !(all_flags & ~(RPMSENSE_LESS | RPMSENSE_GREATER | RPMSENSE_EQUAL)))
                requirement(deps, id, rpm->name, name[0] == '/' ? "file" : "package", name,
                            "any", "any", relation, *version ? version : "-", original,
                            "rpm:Requires");
            else
                requirement(deps, id, rpm->name, "foreign", original, arch, libc, "any", "-",
                            original, k == 0 ? "rpm:Requires" : k == 2 ? "rpm:Conflicts" : "rpm:Obsoletes");
        }
next:
        rpmtdFree(n); rpmtdFree(v); rpmtdFree(f);
        if (!ok) return 0;
    }
    return !ferror(deps) && !ferror(provides) && !ferror(origin);
}
#endif

static int apk_simple_name(const char *name)
{
    const unsigned char *p = (const unsigned char *)name;
    if (!isalnum(*p)) return 0;
    for (; *p; ++p)
        if (!isalnum(*p) && *p != '.' && *p != '_' && *p != '+' && *p != '-') return 0;
    return 1;
}

static int apk_depends(FILE *out, const char *consumer, const struct apk_field *field)
{
    char *copy = strdup(field->value), *save = NULL, *part;
    size_t index = 0;
    if (!copy) return 0;
    for (part = strtok_r(copy, " \t", &save); part; part = strtok_r(NULL, " \t", &save)) {
        const char *kind = "foreign", *name = part;
        const char *relation = "any", *version = "-";
        char *base = NULL;
        char id[64];
        if (++index > 4096) { free(copy); return 0; }
        if (!strncmp(part, "so:", 3) && apk_simple_name(part + 3)) {
            kind = "soname"; name = part + 3;
        } else if (!strncmp(part, "cmd:", 4) && apk_simple_name(part + 4)) {
            kind = "command"; name = part + 4;
        } else if (apk_simple_name(part)) kind = "package";
        else {
            const char *operator = strpbrk(part, "<=>");
            if (operator && operator > part) {
                const char *value = operator + 1;
                int order;
                base = strndup(part, (size_t)(operator - part));
                if (!base) { free(copy); return 0; }
                if ((*operator == '<' || *operator == '>') && *value == '=') ++value;
                if (apk_simple_name(base) && *value &&
                    holy_apk_version_compare(value, value, &order)) {
                    kind = "package"; name = base; version = value;
                    relation = *operator == '<' ? value == operator + 2 ? "le" : "lt" :
                               *operator == '>' ? value == operator + 2 ? "ge" : "gt" : "eq";
                }
            }
        }
        snprintf(id, sizeof id, "apk-%zu-%zu", field->line, index);
        requirement(out, id, consumer, kind, name, "any", "any", relation, version,
                    part, "apk:depend");
        free(base);
    }
    free(copy);
    return index != 0 && !ferror(out);
}

static int deb_term(char *term, char **name, const char **relation, char **version)
{
    char *end, *name_end, *p;
    size_t i;
    int order;
    while (*term == ' ' || *term == '\t' || *term == '\n') ++term;
    end = term + strlen(term);
    while (end > term && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n')) *--end = 0;
    if (end == term) return 0;
    name_end = term;
    while (*name_end && *name_end != ' ' && *name_end != '\t' &&
           *name_end != '\n' && *name_end != '(') ++name_end;
    if (name_end == term) return 0;
    for (i = 0; term + i < name_end; ++i)
        if (!((term[i] >= 'a' && term[i] <= 'z') ||
              (term[i] >= '0' && term[i] <= '9') ||
              (i && (term[i] == '+' || term[i] == '-' || term[i] == '.')))) return 0;
    *name = term;
    *relation = "any";
    *version = NULL;
    if (!*name_end) return 1;
    p = name_end;
    if (*p == '(') *p++ = 0;
    else {
        *p++ = 0;
        while (*p == ' ' || *p == '\t' || *p == '\n') ++p;
        if (*p++ != '(') return 0;
    }
    while (*p == ' ' || *p == '\t') ++p;
    if (p[0] == '<' && p[1] == '<') *relation = "lt";
    else if (p[0] == '<' && p[1] == '=') *relation = "le";
    else if (p[0] == '=') *relation = "eq";
    else if (p[0] == '>' && p[1] == '=') *relation = "ge";
    else if (p[0] == '>' && p[1] == '>') *relation = "gt";
    else return 0;
    p += !strcmp(*relation, "eq") ? 1 : 2;
    while (*p == ' ' || *p == '\t') ++p;
    *version = p;
    while (*p && *p != ' ' && *p != '\t' && *p != ')') ++p;
    if (p == *version) return 0;
    if (*p == ')') { *p++ = 0; if (*p) return 0; }
    else {
        if (!*p) return 0;
        *p++ = 0;
        while (*p == ' ' || *p == '\t') ++p;
        if (*p++ != ')' || *p) return 0;
    }
    return holy_deb_version_compare(*version, *version, &order);
}

static int deb_relations(FILE *out, const char *consumer, const struct deb_field *field,
                         int claims)
{
    char *copy = strdup(field->value), *cursor, *buffer = NULL, *original = NULL;
    const char *seen[4096];
    size_t size = 0, index = 0, seen_count = 0;
    FILE *temporary = NULL;
    int ok = 0;
    if (!copy || !(temporary = open_memstream(&buffer, &size))) goto done;
    cursor = copy;
    while (*cursor) {
        struct { char *name, *version; const char *relation; } terms[64];
        char *segment = cursor, *comma = strchr(cursor, ','), *end, *part;
        char id[64];
        size_t count = 0, i;
        if (comma) { *comma = 0; cursor = comma + 1; if (!*cursor) goto done; }
        else cursor += strlen(cursor);
        while (*segment == ' ' || *segment == '\t' || *segment == '\n') ++segment;
        end = segment + strlen(segment);
        while (end > segment && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n')) *--end = 0;
        if (end == segment || ++index > 4096 ||
            (size_t)(end - segment) > 65536 || !(original = strdup(segment))) goto done;
        part = segment;
        while (part) {
            char *bar = strchr(part, '|');
            if (bar) *bar = 0;
            if (count == 64 || !deb_term(part, &terms[count].name,
                                         &terms[count].relation,
                                         &terms[count].version)) goto done;
            ++count;
            part = bar ? bar + 1 : NULL;
        }
        if (claims) {
            if (count != 1 || (strcmp(terms[0].relation, "any") &&
                               strcmp(terms[0].relation, "eq"))) goto done;
            for (i = 0; i < seen_count; ++i)
                if (!strcmp(seen[i], terms[0].name)) goto done;
            seen[seen_count++] = terms[0].name;
            fputs("provide package ", temporary); token(temporary, terms[0].name);
            fputs(" any any ", temporary); token(temporary, terms[0].version ? terms[0].version : "-");
            fputs(" deb:Provides\n", temporary);
        } else {
            snprintf(id, sizeof id, "deb-%zu-%zu", field->line, index);
            if (count == 1)
                requirement(temporary, id, consumer, "package", terms[0].name,
                            "any", "any", terms[0].relation,
                            terms[0].version ? terms[0].version : "-", original,
                            "deb:Depends");
            else {
                char *expression = NULL;
                size_t expression_size = 0;
                FILE *encoded = open_memstream(&expression, &expression_size);
                if (!encoded) goto done;
                for (i = 0; i < count; ++i)
                    fprintf(encoded, "%s%s@%s@%s", i ? "|" : "", terms[i].name,
                            terms[i].relation, terms[i].version ? terms[i].version : "-");
                {
                    int failed = ferror(encoded);
                    if (fclose(encoded)) failed = 1;
                    if (failed) { free(expression); goto done; }
                }
                if (!holy_package_or_each(expression, NULL, NULL)) {
                    free(expression); goto done;
                }
                requirement(temporary, id, consumer, "package-or", expression,
                            "any", "any", "any", "-", original, "deb:Depends");
                free(expression);
            }
        }
        if (ferror(temporary)) goto done;
        free(original); original = NULL;
    }
    if (!index) goto done;
    if (fclose(temporary)) { temporary = NULL; goto done; }
    temporary = NULL;
    if (fwrite(buffer, 1, size, out) != size) goto done;
    ok = 1;
done:
    if (temporary) fclose(temporary);
    free(original); free(copy); free(buffer);
    return ok;
}

static int write_output(struct foreign_input *input, const struct holy_pacman_metadata *meta,
                         const struct deb_metadata *deb, const struct slack_metadata *slack,
                         const struct apk_metadata *apk, const struct xbps_metadata *xbps,
                         const struct rpm_metadata *rpm,
                         const char *source, const char *hash, const char *output, int output_fd,
                         FILE *receipt, int group, const char *verification,
                         const char *key_hash, const char *signature_hash,
                         const char *index_hash, const char *source_url)
{
    static const char *const names[] = {
        "HOLY/meta", "HOLY/files", "HOLY/deps", "HOLY/provides", "HOLY/hooks", "HOLY/origin", "HOLY/transform"
    };
    char *text[7] = {0}, *path = NULL, *filename = NULL;
    size_t sizes[7] = {0}, i, count = 0, length;
    FILE *files[7] = {0};
    struct holy_stream_entry *entries = NULL;
    int ok = 0, aggregate = input->group_count == 1 || group == (int)input->group_count - 1;
    const char *arch = input->groups[group].arch, *libc = input->groups[group].libc;
    const char *family = rpm ? "rpm" : xbps ? "xbps" : apk ? "apk" : slack ? "slackware" : deb ? "deb" : "pacman";
    const char *name = rpm ? rpm->name : xbps ? xbps->name : apk ? apk->name : slack ? slack->name : deb ? deb->name : meta->name;
    const char *version = rpm ? rpm->version : xbps ? xbps->version : apk ? apk->version : slack ? slack->version : deb ? deb->version : meta->version;
    const char *source_arch = rpm ? rpm->arch : xbps ? xbps->arch : apk ? apk->arch : slack ? slack->arch : deb ? deb->arch : meta->arch;
    for (i = 0; i < 7; ++i) if (!(files[i] = open_memstream(&text[i], &sizes[i]))) goto done;
    fputs("format holy-package-1\nname ", files[0]); token(files[0], name);
    fputs("\nversion ", files[0]); token(files[0], version);
    fputs("\nrelease ", files[0]); token(files[0], rpm ? rpm->release : xbps ? xbps->release : slack ? slack->build : "1");
    fprintf(files[0], "\nos linux\narch %s\nlibc %s\nx-version-family %s\nx-source-arch ", arch, libc, family);
    token(files[0], source_arch); fputc('\n', files[0]);
    if (slack) {
        fputs("x-source-build ", files[0]); token(files[0], slack->build);
        fputc('\n', files[0]);
    }
    fprintf(files[5], "format holy-import-origin-1\nfamily %s\nsource-name ", family);
    token(files[5], source);
    fprintf(files[5], "\noriginal-sha256 %s\nverification %s\nconverter holy-%s-1\noriginal-version ",
            hash, verification ? verification : "unverified", family);
    token(files[5], version); fputc('\n', files[5]);
    if (key_hash && fprintf(files[5], "%s %s\n",
                            deb ? "keyring-sha256" : "public-key-sha256", key_hash) < 0) goto done;
    if (signature_hash && fprintf(files[5], "signature-sha256 %s\n", signature_hash) < 0) goto done;
    if (index_hash && fprintf(files[5], "index-sha256 %s\n", index_hash) < 0) goto done;
    if (source_url) {
        fputs("source-url ", files[5]); token(files[5], source_url);
        fputc('\n', files[5]);
    }
    if (input->group_count > 1) {
        fprintf(files[6], "split %s %s %s %s\n", family, hash, arch, libc);
        for (i = 0; i < input->group_count; ++i) {
            char id[64];
            if ((aggregate && (int)i == group) || (!aggregate && i != input->group_count - 1)) continue;
            snprintf(id, sizeof id, "split-%zu", i);
            requirement(files[2], id, name, "package", name, input->groups[i].arch,
                        input->groups[i].libc, "any", "-", name, "import-output");
        }
    }
    for (i = 0; !deb && !slack && !apk && !xbps && !rpm && i < meta->count; ++i) {
        const struct holy_pacman_field *field = &meta->fields[i];
        char id[64];
        fputs("pkginfo ", files[5]); token(files[5], field->key); fputc(' ', files[5]);
        token(files[5], field->value); fprintf(files[5], " %zu\n", field->line);
        if (!aggregate) continue;
        snprintf(id, sizeof id, "pacman-%zu", field->line);
        if (field->kind == HOLY_PACMAN_DEPEND) {
            const struct holy_pacman_relation *r = &field->relation;
            requirement(files[2], id, meta->name,
                        r->kind == HOLY_PACMAN_PACKAGE ? "package" : "foreign",
                        r->kind == HOLY_PACMAN_PACKAGE ? r->name : field->value,
                        "any", "any", r->kind == HOLY_PACMAN_PACKAGE ? r->comparison : "any",
                        r->kind == HOLY_PACMAN_PACKAGE ? r->version : "-", field->value, "pacman");
        } else if (field->kind == HOLY_PACMAN_PROVIDE && field->relation.kind == HOLY_PACMAN_PACKAGE) {
            fputs("provide package ", files[3]); token(files[3], field->relation.name);
            fputs(" any any ", files[3]); token(files[3], field->relation.version); fputs(" pacman\n", files[3]);
        } else if (field->kind == HOLY_PACMAN_CONFLICT || field->kind == HOLY_PACMAN_REPLACE ||
                   field->kind == HOLY_PACMAN_UNKNOWN ||
                   (field->kind == HOLY_PACMAN_EXTRA && strncmp(field->value, "pkgtype=", 8)) ||
                   (field->kind == HOLY_PACMAN_PROVIDE && field->relation.kind != HOLY_PACMAN_PACKAGE)) {
            requirement(files[2], id, meta->name, "foreign", field->value,
                        "any", "any", "any", "-", field->value, field->key);
        }
    }
    for (i = 0; deb && i < deb->count; ++i) {
        const struct deb_field *field = &deb->fields[i];
        char id[64];
        fputs("control ", files[5]); token(files[5], field->key); fputc(' ', files[5]);
        token(files[5], field->value); fprintf(files[5], " %zu\n", field->line);
        if (!aggregate || !strcasecmp(field->key, "Package") || !strcasecmp(field->key, "Version") ||
            !strcasecmp(field->key, "Architecture") || !strcasecmp(field->key, "Description") ||
            !strcasecmp(field->key, "Maintainer") || !strcasecmp(field->key, "Homepage") ||
            !strcasecmp(field->key, "Section") || !strcasecmp(field->key, "Priority") ||
            !strcasecmp(field->key, "Installed-Size") || !strcasecmp(field->key, "Source")) continue;
        if (!strcasecmp(field->key, "Depends") && deb_relations(files[2], name, field, 0)) continue;
        if (!strcasecmp(field->key, "Provides") && deb_relations(files[3], name, field, 1)) continue;
        snprintf(id, sizeof id, "deb-%zu", field->line);
        requirement(files[2], id, name, "foreign", field->value, "any", "any", "any", "-",
                    field->value, field->key);
    }
    for (i = 0; apk && i < apk->count; ++i) {
        const struct apk_field *field = &apk->fields[i];
        char id[64];
        fputs("pkginfo ", files[5]); token(files[5], field->key); fputc(' ', files[5]);
        token(files[5], field->value); fprintf(files[5], " %zu\n", field->line);
        if (!aggregate) continue;
        if (!strcmp(field->key, "depend")) {
            if (!apk_depends(files[2], name, field)) goto done;
        } else if (!strcmp(field->key, "install_if") ||
                   !strcmp(field->key, "replaces") || !strcmp(field->key, "provides")) {
            snprintf(id, sizeof id, "apk-%zu", field->line);
            requirement(files[2], id, name, "foreign", field->value, "any", "any", "any", "-",
                        field->value, field->key);
        }
    }
    if (xbps && aggregate) {
        static const char *const keys[] = {"run_depends", "shlib-requires", "provides", "conflicts", "replaces"};
        size_t k;
        for (k = 0; k < sizeof keys / sizeof *keys; ++k) {
            plist_t array = plist_dict_get_item(xbps->props, keys[k]);
            uint32_t j;
            if (!array) continue;
            if (plist_get_node_type(array) != PLIST_ARRAY) goto done;
            for (j = 0; j < plist_array_get_size(array); ++j) {
                char *value = NULL, id[80];
                plist_t item = plist_array_get_item(array, j);
                if (!item || plist_get_node_type(item) != PLIST_STRING) goto done;
                plist_get_string_val(item, &value);
                if (!value || !*value) { free(value); goto done; }
                snprintf(id, sizeof id, "xbps-%zu-%u", k, j);
                if (k == 2) {
                    fputs("foreign-provide ", files[5]); token(files[5], value);
                    fputc('\n', files[5]);
                } else if (k == 0) {
                    char *dependency = NULL;
                    const char *relation, *required_version;
                    if (xbps_dependency(value, &dependency, &relation, &required_version))
                        requirement(files[2], id, name, "package", dependency,
                                    "any", "any", relation, required_version, value, keys[k]);
                    else
                        requirement(files[2], id, name, "foreign", value,
                                    "any", "any", "any", "-", value, keys[k]);
                    free(dependency);
                } else
                    requirement(files[2], id, name, k == 1 ? "soname" : "foreign", value,
                                k == 1 ? arch : "any", k == 1 ? libc : "any",
                                "any", "-", value, keys[k]);
                free(value);
            }
        }
    }
#ifdef HOLY_HAVE_RPM
    if (rpm && aggregate && !rpm_relations(files[2], files[3], files[5], rpm, arch, libc)) goto done;
#endif
    if (!emit_elf_provides(files[3], input, group, arch, libc)) goto done;
    if (slack) {
        fputs("package-filename ", files[5]); token(files[5], name);
        fputc(' ', files[5]); token(files[5], version);
        fputc(' ', files[5]); token(files[5], source_arch);
        fputc(' ', files[5]); token(files[5], slack->build);
        fputc('\n', files[5]);
    }
    entries = calloc(input->count + 11, sizeof *entries);
    if (!entries) goto done;
    for (i = 0; i < input->count; ++i) {
        const struct foreign_entry *e = &input->entries[i];
        if (!belongs(input, i, group)) continue;
        if (!e->metadata && !write_manifest(files[1], input, i, family)) goto done;
        if (!apk && !deb && !slack && !xbps && !rpm && aggregate && !strcmp(e->original, ".INSTALL")) {
            fputs("foreign-script pacman /bin/sh HOLY/foreign/pacman/INSTALL sha256 ", files[4]);
            hex_hash(files[4], e->hash);
            fputs(" review-required\n", files[4]);
        }
        if (xbps && aggregate && e->metadata &&
            (!strcmp(e->original, "INSTALL") || !strcmp(e->original, "REMOVE"))) {
            fputs("foreign-script xbps /bin/sh ", files[4]); token(files[4], e->stream.path);
            fputs(" sha256 ", files[4]); hex_hash(files[4], e->hash);
            fputs(" review-required\n", files[4]);
        }
        if (apk && aggregate && e->metadata && strcmp(e->original, ".PKGINFO") &&
            strncmp(e->original, ".SIGN.", 6)) {
            if (strstr(e->original, "install") || strstr(e->original, "upgrade") ||
                strstr(e->original, "deinstall")) {
                fputs("foreign-script apk /bin/sh ", files[4]); token(files[4], e->stream.path);
                fputs(" sha256 ", files[4]); hex_hash(files[4], e->hash);
                fputs(" review-required\n", files[4]);
            } else {
                char id[64];
                snprintf(id, sizeof id, "apk-control-%zu", i);
                requirement(files[2], id, name, "foreign", e->original, "any", "any", "any", "-",
                            e->original, "apk-control-file");
            }
        }
        if (slack && aggregate && !strcmp(e->original, "install/doinst.sh")) {
            fputs("foreign-script slackware /bin/sh HOLY/foreign/slackware/doinst.sh sha256 ", files[4]);
            hex_hash(files[4], e->hash);
            fputs(" review-required\n", files[4]);
        } else if (slack && aggregate && e->metadata &&
                   strcmp(e->original, "install") &&
                   strcmp(e->original, "install/slack-desc") &&
                   strcmp(e->original, "install/doinst.sh")) {
            char id[64];
            snprintf(id, sizeof id, "slackware-control-%zu", i);
            requirement(files[2], id, name, "foreign", e->original, "any", "any", "any", "-",
                        e->original, "slackware-control-file");
        }
        if (deb && aggregate && (!strcmp(e->original, "@control/preinst") ||
            !strcmp(e->original, "@control/postinst") || !strcmp(e->original, "@control/prerm") ||
            !strcmp(e->original, "@control/postrm"))) {
            fputs("foreign-script deb unknown ", files[4]); token(files[4], e->stream.path);
            fputs(" sha256 ", files[4]); hex_hash(files[4], e->hash);
            fputs(" review-required\n", files[4]);
        } else if (deb && aggregate && e->metadata &&
                   strcmp(e->original, "@control/control") && strcmp(e->original, "@control/md5sums")) {
            char id[64];
            snprintf(id, sizeof id, "deb-control-%zu", i);
            requirement(files[2], id, name, "foreign", e->original, "any", "any", "any", "-",
                        e->original, "deb-control-file");
        }
    }
    entries[count++] = (struct holy_stream_entry){"HOLY", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1};
    for (i = 0; i < 7; ++i) {
        int failed = ferror(files[i]);
        if (fclose(files[i])) failed = 1;
        files[i] = NULL;
        if (failed || !append_text(input, &entries[count++], names[i], text[i], sizes[i])) goto done;
    }
    entries[count++] = (struct holy_stream_entry){"HOLY/foreign", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1};
    entries[count++] = (struct holy_stream_entry){xbps ? "HOLY/foreign/xbps" :
                                                   apk ? "HOLY/foreign/apk" :
                                                   slack ? "HOLY/foreign/slackware" :
                                                   deb ? "HOLY/foreign/deb" : rpm ? "HOLY/foreign/rpm" : "HOLY/foreign/pacman",
                                                   NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1};
    for (i = 0; i < input->count; ++i)
        if (input->entries[i].metadata &&
            (!slack || strcmp(input->entries[i].original, "install")))
            entries[count++] = input->entries[i].stream;
    entries[count++] = (struct holy_stream_entry){"DATA", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1};
    for (i = 0; i < input->count; ++i)
        if (!input->entries[i].metadata && belongs(input, i, group)) entries[count++] = input->entries[i].stream;
    if (fflush(input->spool) || fsync(fileno(input->spool))) goto done;
    length = strlen(name) + strlen(arch) + strlen(libc) + 10;
    filename = malloc(length);
    if (!filename) goto done;
    snprintf(filename, length, "%s--%s--%s.holy", name, arch, libc);
    path = joined(output, filename);
    if (!path || !holy_pack_stream(fileno(input->spool), entries, count, output_fd, filename)) goto done;
    {
        char descriptor[64], digest[65];
        int fd = openat(output_fd, filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
        if (fd < 0) goto done;
        snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fd);
        ok = input_hash(descriptor, digest);
        close(fd);
        if (!ok) goto done;
        ok = 0;
        fputs("output ", receipt); token(receipt, filename);
        fprintf(receipt, " %s %s %s\n", digest, arch, libc);
        if (ferror(receipt)) goto done;
    }
    printf("imported "); token(stdout, path); printf(" original %s arch %s libc %s\n", hash, arch, libc);
    ok = 1;
done:
    for (i = 0; i < 7; ++i) { if (files[i]) fclose(files[i]); free(text[i]); }
    free(entries); free(filename); free(path);
    return ok;
}

static int preserve_original(const char *snapshot, int output)
{
    char buffer[65536];
    int input = open(snapshot, O_RDONLY | O_CLOEXEC);
    int fd = openat(output, "original", O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600), ok = 0;
    ssize_t got;
    if (input < 0 || fd < 0) goto done;
    while ((got = read(input, buffer, sizeof buffer)) != 0) {
        size_t offset = 0;
        if (got < 0) { if (errno == EINTR) continue; goto done; }
        while (offset < (size_t)got) {
            ssize_t n = write(fd, buffer + offset, (size_t)got - offset);
            if (n < 0 && errno == EINTR) continue;
            if (n <= 0) goto done;
            offset += (size_t)n;
        }
    }
    ok = !fsync(fd) && !fsync(output);
done:
    if (input >= 0) close(input);
    if (fd >= 0) close(fd);
    return ok;
}

int holy_import_pacman(const char *input_path, const char *source, const char *output)
{
    struct foreign_input input = {0};
    struct holy_pacman_metadata metadata = {0};
    struct holy_pacman_error error = {0};
    struct stat st;
    char *snapshot = NULL, hash[65], temporary[43] = {0};
    FILE *receipt = NULL;
    int input_fd = -1, output_fd = -1, result = 1, common;
    size_t i;
    if (!*source || !strcmp(source, "local")) return 2;
    for (i = 0; source[i]; ++i)
        if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2;
    input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; }
    snapshot = holy_stage_fd(input_fd, "holy-import");
    if (!snapshot || !input_hash(snapshot, hash) || mkdir(output, 0700)) goto done;
    output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() ||
        (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done;
    result = collect_archive(snapshot, &input, FOREIGN_PACMAN, 0);
    if (result) goto done;
    if (!validate_paths(&input) || !holy_pacman_parse(input.pkginfo, input.pkginfo_size, &metadata, &error)) {
        if (error.message) fprintf(stderr, "holypkg: PKGINFO:%zu: %s\n", error.line, error.message);
        result = 2; goto done;
    }
    result = 3;
    if (input.unknown) { fputs("holypkg: unknown payload ABI or executable format requires classification\n", stderr); goto done; }
    if (metadata.package_type && strcmp(metadata.package_type, "pkg") &&
        strcmp(metadata.package_type, "split") && strcmp(metadata.package_type, "debug")) {
        fputs("holypkg: source or unknown package type requires review\n", stderr); goto done;
    }
    for (i = 0; i < metadata.count; ++i) if (metadata.fields[i].kind == HOLY_PACMAN_BACKUP) {
        fputs("holypkg: pacman backup paths require config-manifest support\n", stderr); goto done;
    }
    if (!input.group_count) common = add_group(&input, "noarch", "nolibc");
    else if (input.group_count == 1) common = 0;
    else common = add_group(&input, "noarch", "nolibc");
    if (common < 0) { result = 6; goto done; }
    for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common;
    result = 1;
    {
        int fd = holy_temporary_at(output_fd, temporary);
        if (fd < 0) goto done;
        receipt = fdopen(fd, "w");
        if (!receipt) { close(fd); goto done; }
    }
    fprintf(receipt, "format holy-import-record-1\nfamily pacman\nconverter holy-pacman-1\noriginal-sha256 %s\nsource-name ", hash);
    token(receipt, source); fputs("\nverification unverified\n", receipt);
    for (i = 0; i < input.group_count; ++i)
        if (!write_output(&input, &metadata, NULL, NULL, NULL, NULL, NULL, source, hash, output, output_fd,
                          receipt, (int)i, NULL, NULL, NULL, NULL, NULL)) goto done;
    fputs("state complete\n", receipt);
    if (fflush(receipt) || fsync(fileno(receipt))) goto done;
    if (fclose(receipt)) { receipt = NULL; goto done; }
    receipt = NULL;
    if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done;
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: pacman import incomplete (status %d); no installed state changed\n", result);
    if (receipt) fclose(receipt);
    if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); }
    if (input_fd >= 0) close(input_fd);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    holy_pacman_free(&metadata); free_input(&input);
    return result;
}

static int lower_digest(const char *value);

int holy_import_deb_verified(const char *input_path, const char *source, const char *output,
                             const char *expected_hash, const char *verification,
                             const char *key_hash, const char *signature_hash,
                             const char *index_hash, const char *source_url)
{
    struct foreign_input input = {0};
    struct deb_metadata metadata = {0};
    struct stat st;
    char *snapshot = NULL, hash[65], temporary[43] = {0};
    FILE *receipt = NULL;
    int input_fd = -1, output_fd = -1, result = 1, common;
    size_t i;
    if (!input_path || !source || !output || !*source || !strcmp(source, "local") ||
        !verification ||
        (strcmp(verification, "unverified") && strcmp(verification, "pinned-unverified") &&
         strcmp(verification, "release-gpgv-user-key") &&
         strcmp(verification, "inrelease-gpgv-user-key")) ||
        (strcmp(verification, "unverified") &&
         (!lower_digest(expected_hash) || !lower_digest(index_hash) || !source_url)) ||
        (!strcmp(verification, "unverified") &&
         (expected_hash || index_hash || source_url || key_hash || signature_hash)) ||
        ((key_hash || signature_hash) &&
         (!lower_digest(key_hash) || !lower_digest(signature_hash))) ||
        (!strcmp(verification, "pinned-unverified") && (key_hash || signature_hash)) ||
        (strstr(verification, "gpgv") && (!key_hash || !signature_hash))) return 2;
    for (i = 0; source[i]; ++i)
        if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2;
    input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; }
    snapshot = holy_stage_fd(input_fd, "holy-import");
    if (!snapshot || !input_hash(snapshot, hash)) goto done;
    if (expected_hash && strcmp(expected_hash, hash)) { result = 4; goto done; }
    if (mkdir(output, 0700)) goto done;
    output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() ||
        (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done;
    result = collect_deb(snapshot, &input);
    if (result) goto done;
    if (!validate_paths(&input) || !verify_deb_md5sums(&input) ||
        !parse_deb(input.pkginfo, input.pkginfo_size, &metadata)) {
        fputs("holypkg: malformed deb control or payload paths\n", stderr);
        result = 2; goto done;
    }
    result = 3;
    if (input.unknown) { fputs("holypkg: unknown payload ABI or executable format requires classification\n", stderr); goto done; }
    if (strcmp(metadata.arch, "all") && strcmp(metadata.arch, "amd64") && strcmp(metadata.arch, "i386")) {
        fputs("holypkg: unsupported Debian architecture requires classification\n", stderr); goto done;
    }
    if (!mark_deb_conffiles(&input)) { result = 2; goto done; }
    for (i = 0; i < input.group_count; ++i) {
        const char *arch = input.groups[i].arch;
        if ((!strcmp(metadata.arch, "all") && strcmp(arch, "noarch")) ||
            (!strcmp(metadata.arch, "amd64") && strcmp(arch, "x86_64")) ||
            (!strcmp(metadata.arch, "i386") && strcmp(arch, "x86"))) {
            fputs("holypkg: Debian architecture differs from payload ELF\n", stderr); goto done;
        }
    }
    if (!input.group_count) common = add_group(&input, "noarch", "nolibc");
    else if (input.group_count == 1) common = 0;
    else common = add_group(&input, "noarch", "nolibc");
    if (common < 0) { result = 6; goto done; }
    for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common;
    result = 1;
    {
        int fd = holy_temporary_at(output_fd, temporary);
        if (fd < 0) goto done;
        receipt = fdopen(fd, "w");
        if (!receipt) { close(fd); goto done; }
    }
    fprintf(receipt, "format holy-import-record-1\nfamily deb\nconverter holy-deb-1\noriginal-sha256 %s\nsource-name ", hash);
    token(receipt, source); fprintf(receipt, "\nverification %s\n", verification);
    if (key_hash) fprintf(receipt, "keyring-sha256 %s\n", key_hash);
    if (signature_hash) fprintf(receipt, "signature-sha256 %s\n", signature_hash);
    if (index_hash) fprintf(receipt, "index-sha256 %s\n", index_hash);
    if (source_url) {
        fputs("source-url ", receipt); token(receipt, source_url);
        fputc('\n', receipt);
    }
    for (i = 0; i < input.group_count; ++i)
        if (!write_output(&input, NULL, &metadata, NULL, NULL, NULL, NULL, source, hash, output, output_fd,
                          receipt, (int)i, verification, key_hash, signature_hash,
                          index_hash, source_url)) goto done;
    fputs("state complete\n", receipt);
    if (fflush(receipt) || fsync(fileno(receipt))) goto done;
    if (fclose(receipt)) { receipt = NULL; goto done; }
    receipt = NULL;
    if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done;
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: deb import incomplete (status %d); no installed state changed\n", result);
    if (receipt) fclose(receipt);
    if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); }
    if (input_fd >= 0) close(input_fd);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    free_deb(&metadata); free_input(&input);
    return result;
}

int holy_import_deb(const char *input_path, const char *source, const char *output)
{
    return holy_import_deb_verified(input_path, source, output, NULL,
                                    "unverified", NULL, NULL, NULL, NULL);
}

int holy_import_slackware(const char *input_path, const char *source, const char *output)
{
    struct foreign_input input = {0};
    struct slack_metadata metadata = {0};
    struct stat st;
    char *snapshot = NULL, hash[65], temporary[43] = {0};
    FILE *receipt = NULL;
    int input_fd = -1, output_fd = -1, result = 1, common;
    size_t i;
    if (!*source || !strcmp(source, "local")) return 2;
    for (i = 0; source[i]; ++i)
        if ((unsigned char)source[i] <= 32 || source[i] == ':' ||
            source[i] == '/' || source[i] == '@') return 2;
    if (!parse_slack_name(input_path, &metadata)) { result = 2; goto done; }
    input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) ||
        st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) {
        result = 6; goto done;
    }
    snapshot = holy_stage_fd(input_fd, "holy-import");
    if (!snapshot || !input_hash(snapshot, hash) || mkdir(output, 0700)) goto done;
    output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() ||
        (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done;
    if (!slack_codec_matches(snapshot, input_path)) { result = 2; goto done; }
    result = collect_archive(snapshot, &input, FOREIGN_SLACKWARE, metadata.lzma);
    if (result) goto done;
    if (!validate_paths(&input)) { result = 2; goto done; }
    result = 3;
    if (input.unknown) {
        fputs("holypkg: unknown payload ABI or executable format requires classification\n", stderr);
        goto done;
    }
    if (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") &&
        strcmp(metadata.arch, "i386") && strcmp(metadata.arch, "i486") &&
        strcmp(metadata.arch, "i586") && strcmp(metadata.arch, "i686")) {
        fputs("holypkg: unsupported Slackware architecture requires classification\n", stderr);
        goto done;
    }
    for (i = 0; i < input.group_count; ++i) {
        const char *arch = input.groups[i].arch;
        if ((!strcmp(metadata.arch, "noarch") && strcmp(arch, "noarch")) ||
            (!strcmp(metadata.arch, "x86_64") && strcmp(arch, "x86_64") &&
             strcmp(arch, "x86")) ||
            (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") &&
             strcmp(arch, "x86"))) {
            fputs("holypkg: Slackware architecture differs from payload ELF\n", stderr);
            goto done;
        }
    }
    if (!input.group_count) common = add_group(&input, "noarch", "nolibc");
    else if (input.group_count == 1) common = 0;
    else common = add_group(&input, "noarch", "nolibc");
    if (common < 0) { result = 6; goto done; }
    for (i = 0; i < input.count; ++i)
        if (input.entries[i].group < 0) input.entries[i].group = common;
    result = 1;
    {
        int fd = holy_temporary_at(output_fd, temporary);
        if (fd < 0) goto done;
        receipt = fdopen(fd, "w");
        if (!receipt) { close(fd); goto done; }
    }
    fprintf(receipt, "format holy-import-record-1\nfamily slackware\nconverter holy-slackware-1\noriginal-sha256 %s\nsource-name ", hash);
    token(receipt, source); fputs("\nverification unverified\n", receipt);
    for (i = 0; i < input.group_count; ++i)
        if (!write_output(&input, NULL, NULL, &metadata, NULL, NULL, NULL, source, hash,
                          output, output_fd, receipt, (int)i, NULL, NULL, NULL, NULL, NULL)) goto done;
    fputs("state complete\n", receipt);
    if (fflush(receipt) || fsync(fileno(receipt))) goto done;
    if (fclose(receipt)) { receipt = NULL; goto done; }
    receipt = NULL;
    if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done;
    result = 0;
done:
    if (result)
        fprintf(stderr, "holypkg: Slackware import incomplete (status %d); no installed state changed\n", result);
    if (receipt) fclose(receipt);
    if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); }
    if (input_fd >= 0) close(input_fd);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    free_slack(&metadata); free_input(&input);
    return result;
}

int holy_import_apk_verified(const char *input_path, const char *source, const char *output,
                             const char *public_key, const char *expected_hash,
                             const char *expected_key_hash, const char *index_hash,
                             const char *source_url)
{
    struct foreign_input input = {0};
    struct apk_metadata metadata = {0};
    struct stat st;
    FILE *parts[3] = {0}, *receipt = NULL;
    char digests[3][65] = {{0}}, *snapshot = NULL, *key_snapshot = NULL;
    char hash[65], key_hash[65] = {0}, verification[16] = "unverified";
    char temporary[43] = {0};
    int input_fd = -1, output_fd = -1, count, control, result = 1, common;
    size_t i;
    if (!input_path || !source || !output || !*source || !strcmp(source, "local") ||
        (!!expected_hash != !!index_hash) || (!!expected_hash != !!source_url) ||
        (expected_hash && (!lower_digest(expected_hash) || !lower_digest(index_hash))) ||
        (expected_key_hash && (!public_key || !lower_digest(expected_key_hash)))) return 2;
    for (i = 0; source[i]; ++i)
        if ((unsigned char)source[i] <= 32 || source[i] == ':' ||
            source[i] == '/' || source[i] == '@') return 2;
    input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) ||
        st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) {
        result = 6; goto done;
    }
    snapshot = holy_stage_fd(input_fd, "holy-import");
    if (!snapshot || !input_hash(snapshot, hash)) goto done;
    if (expected_hash && strcmp(expected_hash, hash)) { result = 4; goto done; }
    if (mkdir(output, 0700)) goto done;
    output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() ||
        (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done;
    result = 2;
    count = holy_apk_gzip_parts(snapshot, parts, digests, 4ULL * 1024 * 1024 * 1024);
    if (count < 2) goto done;
    control = count - 2;
    for (i = 0; i < (size_t)count; ++i) {
        char descriptor[64];
        enum foreign_archive_kind kind = (int)i < control ? FOREIGN_APK_SIGNATURE :
                                         (int)i == control ? FOREIGN_APK_CONTROL : FOREIGN_APK_DATA;
        if (fstat(fileno(parts[i]), &st) ||
            (kind != FOREIGN_APK_DATA && st.st_size > 16 * 1024 * 1024)) goto done;
        snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fileno(parts[i]));
        result = collect_archive(descriptor, &input, kind, 0);
        if (result) goto done;
    }
    if (!validate_paths(&input) || !parse_apk(input.pkginfo, input.pkginfo_size, &metadata)) {
        result = 2; goto done;
    }
    if (metadata.datahash) {
        if (strlen(metadata.datahash) != 64 ||
            strspn(metadata.datahash, "0123456789abcdefABCDEF") != 64 ||
            strcasecmp(metadata.datahash, digests[count - 1])) {
            fputs("holypkg: APK datahash differs from compressed data member\n", stderr);
            result = 2; goto done;
        }
    }
    if (public_key) {
        const char *keyname = strrchr(public_key, '/');
        keyname = keyname ? keyname + 1 : public_key;
        if (count != 3 || !metadata.datahash ||
            !(key_snapshot = holy_stage_local(public_key, "holy-apk-key")) ||
            !holy_apk_key_fingerprint(key_snapshot, key_hash) ||
            !holy_apk_verify_signature(parts[0], parts[1], key_snapshot,
                                       keyname, verification)) {
            fputs("holypkg: APK package signature verification failed\n", stderr);
            result = 4; goto done;
        }
        if (expected_key_hash && strcmp(expected_key_hash, key_hash)) {
            result = 4; goto done;
        }
    }
    result = 3;
    if (input.unknown) {
        fputs("holypkg: unknown APK payload ABI or executable format requires classification\n", stderr);
        goto done;
    }
    if (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") &&
        strcmp(metadata.arch, "x86")) {
        fputs("holypkg: unsupported APK architecture requires classification\n", stderr);
        goto done;
    }
    for (i = 0; i < input.group_count; ++i) {
        const char *arch = input.groups[i].arch;
        if ((!strcmp(metadata.arch, "noarch") && strcmp(arch, "noarch")) ||
            (!strcmp(metadata.arch, "x86_64") && strcmp(arch, "x86_64") && strcmp(arch, "x86")) ||
            (!strcmp(metadata.arch, "x86") && strcmp(arch, "x86"))) {
            fputs("holypkg: APK architecture differs from payload ELF\n", stderr);
            goto done;
        }
    }
    if (!input.group_count) common = add_group(&input, "noarch", "nolibc");
    else if (input.group_count == 1) common = 0;
    else common = add_group(&input, "noarch", "nolibc");
    if (common < 0) { result = 6; goto done; }
    for (i = 0; i < input.count; ++i)
        if (input.entries[i].group < 0) input.entries[i].group = common;
    result = 1;
    {
        int fd = holy_temporary_at(output_fd, temporary);
        if (fd < 0) goto done;
        receipt = fdopen(fd, "w");
        if (!receipt) { close(fd); goto done; }
    }
    fprintf(receipt, "format holy-import-record-1\nfamily apk\nconverter holy-apk-1\noriginal-sha256 %s\nsource-name ", hash);
    token(receipt, source);
    fprintf(receipt, "\nverification %s\ndata-sha256 %s\n",
            verification, digests[count - 1]);
    if (key_hash[0]) fprintf(receipt, "public-key-sha256 %s\n", key_hash);
    if (index_hash) fprintf(receipt, "index-sha256 %s\n", index_hash);
    if (source_url) {
        fputs("source-url ", receipt); token(receipt, source_url);
        fputc('\n', receipt);
    }
    for (i = 0; i < input.group_count; ++i)
        if (!write_output(&input, NULL, NULL, NULL, &metadata, NULL, NULL, source, hash,
                          output, output_fd, receipt, (int)i, verification,
                          key_hash[0] ? key_hash : NULL, NULL,
                          index_hash, source_url)) goto done;
    fputs("state complete\n", receipt);
    if (fflush(receipt) || fsync(fileno(receipt))) goto done;
    if (fclose(receipt)) { receipt = NULL; goto done; }
    receipt = NULL;
    if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done;
    result = 0;
done:
    if (result)
        fprintf(stderr, "holypkg: APK import incomplete (status %d); no installed state changed\n", result);
    if (receipt) fclose(receipt);
    for (i = 0; i < 3; ++i) if (parts[i]) fclose(parts[i]);
    if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); }
    if (input_fd >= 0) close(input_fd);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    if (key_snapshot) { unlink(key_snapshot); free(key_snapshot); }
    free_apk(&metadata); free_input(&input);
    return result;
}

int holy_import_apk(const char *input_path, const char *source, const char *output,
                    const char *public_key)
{
    return holy_import_apk_verified(input_path, source, output, public_key,
                                    NULL, NULL, NULL, NULL);
}

static int lower_digest(const char *value)
{
    return value && strlen(value) == 64 && strspn(value, "0123456789abcdef") == 64;
}

int holy_import_xbps_verified(const char *input_path, const char *source, const char *output,
                              const char *expected_hash, const char *verification,
                              const char *key_hash, const char *signature_hash,
                              const char *index_hash, const char *source_url)
{
    struct foreign_input input = {0};
    struct xbps_metadata metadata = {0};
    struct stat st;
    char *snapshot = NULL, hash[65], temporary[43] = {0};
    FILE *receipt = NULL;
    int input_fd = -1, output_fd = -1, result = 1, common;
    size_t i;
    if (!input_path || !source || !output || !*source || !strcmp(source, "local") ||
        !verification ||
        (strcmp(verification, "unverified") && strcmp(verification, "hash-pinned") &&
         strcmp(verification, "rsa-sha256")) ||
        (strcmp(verification, "unverified") && !lower_digest(expected_hash)) ||
        (expected_hash && !lower_digest(expected_hash)) ||
        (index_hash && !lower_digest(index_hash)) ||
        (!!index_hash != !!source_url) ||
        (strcmp(verification, "unverified") && !index_hash) ||
        (!strcmp(verification, "unverified") && index_hash) ||
        (!strcmp(verification, "rsa-sha256") ?
         !lower_digest(key_hash) || !lower_digest(signature_hash) :
         key_hash || signature_hash)) return 2;
    for (i = 0; source[i]; ++i)
        if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2;
    input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; }
    snapshot = holy_stage_fd(input_fd, "holy-import");
    if (!snapshot || !input_hash(snapshot, hash)) goto done;
    if (expected_hash && strcmp(expected_hash, hash)) { result = 4; goto done; }
    if (mkdir(output, 0700)) goto done;
    output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() ||
        (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done;
    result = collect_archive(snapshot, &input, FOREIGN_XBPS, 0);
    if (result) goto done;
    if (!validate_paths(&input) || !xbps_parse(&input, &metadata) || !xbps_files(&input)) {
        fputs("holypkg: XBPS plist or payload manifest is invalid\n", stderr);
        result = 2; goto done;
    }
    result = 3;
    if (input.unknown) { fputs("holypkg: unknown XBPS payload ABI requires classification\n", stderr); goto done; }
    if (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") &&
        strcmp(metadata.arch, "i686") && strcmp(metadata.arch, "x86_64-musl") &&
        strcmp(metadata.arch, "i686-musl")) {
        fputs("holypkg: unsupported XBPS architecture requires classification\n", stderr); goto done;
    }
    for (i = 0; i < input.group_count; ++i) {
        const char *arch = input.groups[i].arch;
        if ((!strcmp(metadata.arch, "noarch") && strcmp(arch, "noarch")) ||
            (!strncmp(metadata.arch, "x86_64", 6) && strcmp(arch, "x86_64") && strcmp(arch, "x86")) ||
            (!strncmp(metadata.arch, "i686", 4) && strcmp(arch, "x86")) ||
            (strstr(metadata.arch, "-musl") && strcmp(input.groups[i].libc, "musl") &&
             strcmp(input.groups[i].libc, "nolibc")) ||
            (!strstr(metadata.arch, "-musl") && strcmp(input.groups[i].libc, "glibc") &&
             strcmp(input.groups[i].libc, "nolibc"))) {
            fputs("holypkg: XBPS architecture differs from payload ELF\n", stderr); goto done;
        }
    }
    if (!input.group_count) common = add_group(&input, "noarch", "nolibc");
    else if (input.group_count == 1) common = 0;
    else common = add_group(&input, "noarch", "nolibc");
    if (common < 0) { result = 6; goto done; }
    for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common;
    result = 1;
    {
        int fd = holy_temporary_at(output_fd, temporary);
        if (fd < 0) goto done;
        receipt = fdopen(fd, "w");
        if (!receipt) { close(fd); goto done; }
    }
    fprintf(receipt, "format holy-import-record-1\nfamily xbps\nconverter holy-xbps-1\noriginal-sha256 %s\nsource-name ", hash);
    token(receipt, source); fprintf(receipt, "\nverification %s\n", verification);
    if (key_hash) fprintf(receipt, "public-key-sha256 %s\n", key_hash);
    if (signature_hash) fprintf(receipt, "signature-sha256 %s\n", signature_hash);
    if (index_hash) fprintf(receipt, "index-sha256 %s\n", index_hash);
    if (source_url) {
        fputs("source-url ", receipt); token(receipt, source_url);
        fputc('\n', receipt);
    }
    for (i = 0; i < input.group_count; ++i)
        if (!write_output(&input, NULL, NULL, NULL, NULL, &metadata, NULL, source, hash,
                          output, output_fd, receipt, (int)i, verification,
                          key_hash, signature_hash, index_hash, source_url)) goto done;
    fputs("state complete\n", receipt);
    if (fflush(receipt) || fsync(fileno(receipt))) goto done;
    if (fclose(receipt)) { receipt = NULL; goto done; }
    receipt = NULL;
    if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done;
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: XBPS import incomplete (status %d); no installed state changed\n", result);
    if (receipt) fclose(receipt);
    if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); }
    if (input_fd >= 0) close(input_fd);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    plist_free(metadata.props); free(metadata.name); free(metadata.version);
    free(metadata.release); free(metadata.arch); free_input(&input);
    return result;
}

int holy_import_xbps(const char *input_path, const char *source, const char *output)
{
    return holy_import_xbps_verified(input_path, source, output, NULL,
                                     "unverified", NULL, NULL, NULL, NULL);
}

#ifdef HOLY_HAVE_RPM
static int rpm_header(const char *snapshot, struct rpm_metadata *meta)
{
    static const rpmTagVal scripts[] = {RPMTAG_PREIN, RPMTAG_POSTIN, RPMTAG_PREUN,
                                        RPMTAG_POSTUN, RPMTAG_PRETRANS, RPMTAG_POSTTRANS,
                                        RPMTAG_VERIFYSCRIPT, RPMTAG_TRIGGERSCRIPTS,
                                        RPMTAG_FILETRIGGERSCRIPTS, RPMTAG_TRANSFILETRIGGERSCRIPTS};
    rpmts ts = rpmtsCreate();
    FD_t fd = NULL;
    Header h = NULL;
    const char *value;
    size_t i;
    int result = 2;
    if (!ts) return 1;
    rpmtsSetVSFlags(ts, _RPMVSF_NOSIGNATURES);
    fd = Fopen(snapshot, "r.ufdio");
    if (!fd || rpmReadPackageFile(ts, fd, snapshot, &h) != RPMRC_OK || !h) goto done;
    if (headerGetNumber(h, RPMTAG_RPMFORMAT) &&
        headerGetNumber(h, RPMTAG_RPMFORMAT) != 4 &&
        headerGetNumber(h, RPMTAG_RPMFORMAT) != 6) {
        fputs("holypkg: unsupported RPM payload format\n", stderr);
        result = 6; goto done;
    }
    for (i = 0; i < sizeof scripts / sizeof *scripts; ++i)
        if (headerGetString(h, scripts[i])) {
            fputs("holypkg: RPM scriptlets require review support\n", stderr);
            result = 3; goto done;
        }
    value = headerGetString(h, RPMTAG_NAME); if (!value || !apk_simple_name(value)) goto done;
    meta->name = strdup(value);
    value = headerGetString(h, RPMTAG_VERSION); if (!value || !*value) goto done;
    meta->version = strdup(value);
    value = headerGetString(h, RPMTAG_RELEASE); if (!value || !*value) goto done;
    meta->release = strdup(value);
    value = headerGetString(h, RPMTAG_ARCH); if (!value || !*value) goto done;
    meta->arch = strdup(value);
    if (!meta->name || !meta->version || !meta->release || !meta->arch) { result = 1; goto done; }
    {
        uint64_t epoch = headerGetNumber(h, RPMTAG_EPOCH);
        if (epoch) {
            size_t length = strlen(meta->version) + 32;
            char *with_epoch = malloc(length);
            if (!with_epoch) { result = 1; goto done; }
            snprintf(with_epoch, length, "%llu:%s", (unsigned long long)epoch, meta->version);
            free(meta->version);
            meta->version = with_epoch;
        }
        if (!holy_rpm_version_valid(meta->version) ||
            !holy_rpm_version_valid(meta->release) ||
            strchr(meta->release, ':') || strchr(meta->release, '-')) {
            fputs("holypkg: invalid RPM version or release\n", stderr);
            result = 2; goto done;
        }
    }
    if (strcmp(meta->arch, "noarch") && strcmp(meta->arch, "i686") &&
        strcmp(meta->arch, "x86_64")) {
        fputs("holypkg: RPM source arch requires mapping\n", stderr);
        result = 3; goto done;
    }
    meta->header = h; h = NULL;
    result = 0;
done:
    if (h) headerFree(h);
    if (fd) Fclose(fd);
    rpmtsFree(ts);
    return result;
}

static int rpm_files(struct foreign_input *input, const struct rpm_metadata *meta)
{
    rpmfiles files = rpmfilesNew(NULL, meta->header, RPMTAG_BASENAMES, 0);
    unsigned char *seen = calloc(input->count ? input->count : 1, 1);
    struct foreign_entry **sorted = malloc((input->count ? input->count : 1) * sizeof *sorted);
    rpm_count_t i;
    int ok = 0;
    if (!files || !seen || !sorted) goto done;
    for (i = 0; i < input->count; ++i) sorted[i] = &input->entries[i];
    qsort(sorted, input->count, sizeof *sorted, path_order);
    for (i = 0; i < rpmfilesFC(files); ++i) {
        char *name = rpmfilesFN(files, i);
        const char *relative = name;
        struct foreign_entry *entry;
        size_t j;
        rpmfileAttrs flags = rpmfilesFFlags(files, i);
        if (!name) goto done;
        while (*relative == '/') ++relative;
        while (!strncmp(relative, "./", 2)) relative += 2;
        entry = find_path(sorted, input->count, relative);
        if (!entry) { free(name); goto done; }
        j = (size_t)(entry - input->entries);
        if (seen[j] || (flags & RPMFILE_GHOST) ||
            (rpmfilesFCaps(files, i) && *rpmfilesFCaps(files, i)) ||
            ((flags & RPMFILE_CONFIG) && entry->stream.directory)) { free(name); goto done; }
        seen[j] = 1;
        entry->config = !!(flags & RPMFILE_CONFIG);
        free(name);
    }
    for (i = 0; i < input->count; ++i) if (!seen[i]) goto done;
    ok = 1;
done:
    rpmfilesFree(files); free(seen); free(sorted);
    return ok;
}

static const EVP_MD *rpm_digest(int algorithm)
{
    switch (algorithm) {
    case PGPHASHALGO_MD5: return EVP_md5();
    case PGPHASHALGO_SHA1: return EVP_sha1();
    case PGPHASHALGO_SHA256: return EVP_sha256();
    case PGPHASHALGO_SHA512: return EVP_sha512();
    case PGPHASHALGO_SHA3_256: return EVP_sha3_256();
    default: return NULL;
    }
}

static int rpm_payload_to_tar(const char *snapshot, FILE *tar)
{
    rpmts ts = rpmtsCreate();
    FD_t fd = NULL;
    Header h = NULL;
    rpmfiles files = NULL;
    rpmfi fi = NULL;
    struct archive *writer = NULL;
    struct archive_entry *entry = NULL;
    char **targets = NULL;
    const char *compression;
    char mode[64], buffer[65536];
    rpm_count_t count = 0;
    int result = 2, next = RPMERR_ITER_END;
    size_t i;
    if (!ts) return 1;
    rpmtsSetVSFlags(ts, _RPMVSF_NOSIGNATURES);
    fd = Fopen(snapshot, "r.ufdio");
    if (!fd || rpmReadPackageFile(ts, fd, snapshot, &h) != RPMRC_OK || !h) goto done;
    compression = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
    if (!compression) compression = "gzip";
    if (strlen(compression) > sizeof mode - 3) goto done;
    snprintf(mode, sizeof mode, "r.%s", compression);
    {
        FD_t decoded = Fdopen(fd, mode);
        if (!decoded) { result = 6; goto done; }
        fd = decoded;
    }
    files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER);
    if (!files) goto done;
    count = rpmfilesFC(files);
    if (count > 100000) goto done;
    targets = calloc(count ? count : 1, sizeof *targets);
    writer = archive_write_new(); entry = archive_entry_new();
    if (!targets || !writer || !entry) { result = 1; goto done; }
    if (archive_write_set_format_pax_restricted(writer) != ARCHIVE_OK ||
        archive_write_open_FILE(writer, tar) != ARCHIVE_OK) goto done;
    fi = rpmfiNewArchiveReader(fd, files, RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST);
    if (!fi) goto done;
    while ((next = rpmfiNext(fi)) >= 0) {
        const char *name = rpmfiFN(fi), *relative;
        const int *links = NULL;
        struct stat st;
        int index = rpmfiFX(fi), algorithm = 0;
        size_t digest_size = 0;
        const unsigned char *expected;
        char *clean;
        uint32_t nlinks;
        if (!name || index < 0 || (rpm_count_t)index >= count || rpmfiStat(fi, 0, &st)) goto done;
        relative = name;
        while (*relative == '/') ++relative;
        clean = normalized(relative, S_ISDIR(st.st_mode));
        if (!clean) goto done;
        archive_entry_clear(entry);
        archive_entry_set_pathname(entry, clean);
        archive_entry_copy_stat(entry, &st);
        archive_entry_set_uname(entry, rpmfiFUser(fi));
        archive_entry_set_gname(entry, rpmfiFGroup(fi));
        if (S_ISLNK(st.st_mode)) archive_entry_set_symlink(entry, rpmfiFLink(fi));
        nlinks = rpmfiFLinks(fi, &links);
        if (S_ISREG(st.st_mode) && nlinks > 1) {
            if (rpmfiArchiveHasContent(fi)) {
                uint32_t j;
                if (!links) { free(clean); goto done; }
                archive_entry_set_size(entry, rpmfiFSize(fi));
                for (j = 0; j < nlinks; ++j) {
                    int linked = links[j];
                    if (linked < 0 || (rpm_count_t)linked >= count || targets[linked]) { free(clean); goto done; }
                    targets[linked] = strdup(clean);
                    if (!targets[linked]) { free(clean); result = 1; goto done; }
                }
            } else {
                if (!targets[index]) { free(clean); goto done; }
                archive_entry_set_hardlink(entry, targets[index]);
                archive_entry_set_size(entry, 0);
            }
        }
        free(clean);
        if (archive_write_header(writer, entry) != ARCHIVE_OK) goto done;
        if (S_ISREG(st.st_mode) && rpmfiArchiveHasContent(fi)) {
            rpm_loff_t left = rpmfiFSize(fi);
            EVP_MD_CTX *digest = EVP_MD_CTX_new();
            unsigned char actual[EVP_MAX_MD_SIZE];
            unsigned actual_size = 0;
            const EVP_MD *md;
            expected = rpmfiFDigest(fi, &algorithm, &digest_size);
            md = rpm_digest(algorithm);
            if (left > 1024LL * 1024 * 1024 || !expected || !md || !digest ||
                digest_size != (size_t)EVP_MD_size(md) || EVP_DigestInit_ex(digest, md, NULL) != 1) {
                EVP_MD_CTX_free(digest); result = 6; goto done;
            }
            while (left) {
                size_t want = left > (rpm_loff_t)sizeof buffer ? sizeof buffer : (size_t)left;
                ssize_t got = rpmfiArchiveRead(fi, buffer, want);
                size_t offset = 0;
                if (got <= 0 || EVP_DigestUpdate(digest, buffer, (size_t)got) != 1) {
                    EVP_MD_CTX_free(digest); goto done;
                }
                while (offset < (size_t)got) {
                    la_ssize_t written = archive_write_data(writer, buffer + offset, (size_t)got - offset);
                    if (written <= 0) { EVP_MD_CTX_free(digest); goto done; }
                    offset += (size_t)written;
                }
                left -= got;
            }
            if (EVP_DigestFinal_ex(digest, actual, &actual_size) != 1 ||
                actual_size != digest_size || memcmp(actual, expected, digest_size)) {
                EVP_MD_CTX_free(digest); goto done;
            }
            EVP_MD_CTX_free(digest);
        }
    }
    if (next != RPMERR_ITER_END || rpmfiArchiveClose(fi) ||
        archive_write_close(writer) != ARCHIVE_OK || fflush(tar) || fseeko(tar, 0, SEEK_SET)) goto done;
    result = 0;
done:
    if (fi) rpmfiFree(fi);
    if (writer) archive_write_free(writer);
    if (entry) archive_entry_free(entry);
    for (i = 0; i < count; ++i) free(targets ? targets[i] : NULL);
    free(targets);
    rpmfilesFree(files);
    if (h) headerFree(h);
    if (fd) Fclose(fd);
    rpmtsFree(ts);
    return result;
}
#endif

int holy_import_rpm(const char *input_path, const char *source, const char *output)
{
#ifndef HOLY_HAVE_RPM
    (void)input_path; (void)source; (void)output;
    fputs("holypkg: RPM importer requires librpm at build time\n", stderr);
    return 6;
#else
    struct foreign_input input = {0};
    struct rpm_metadata metadata = {0};
    struct stat st;
    char *snapshot = NULL, hash[65], temporary[43] = {0};
    FILE *receipt = NULL, *tar = NULL;
    char descriptor[64];
    int input_fd = -1, output_fd = -1, result = 1, common;
    size_t i;
    if (!*source || !strcmp(source, "local")) return 2;
    for (i = 0; source[i]; ++i)
        if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2;
    input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
        st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; }
    snapshot = holy_stage_fd(input_fd, "holy-import");
    if (!snapshot || !input_hash(snapshot, hash) || mkdir(output, 0700)) goto done;
    output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() ||
        (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done;
    result = rpm_header(snapshot, &metadata);
    if (result) goto done;
    tar = tmpfile();
    if (!tar) goto done;
    result = rpm_payload_to_tar(snapshot, tar);
    if (result) goto done;
    snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fileno(tar));
    result = collect_archive(descriptor, &input, FOREIGN_RPM, 0);
    if (result) goto done;
    if (!validate_paths(&input) || !rpm_files(&input, &metadata)) { result = 2; goto done; }
    result = 3;
    if (input.unknown) {
        fputs("holypkg: unknown RPM payload ABI requires classification\n", stderr);
        goto done;
    }
    if (!strcmp(metadata.arch, "noarch") && input.group_count) {
        fputs("holypkg: noarch RPM contains machine code\n", stderr);
        goto done;
    }
    if (!input.group_count) common = add_group(&input, "noarch", "nolibc");
    else if (input.group_count == 1) common = 0;
    else common = add_group(&input, "noarch", "nolibc");
    if (common < 0) { result = 6; goto done; }
    for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common;
    result = 1;
    {
        int fd = holy_temporary_at(output_fd, temporary);
        if (fd < 0) goto done;
        receipt = fdopen(fd, "w");
        if (!receipt) { close(fd); goto done; }
    }
    fprintf(receipt, "format holy-import-record-1\nfamily rpm\nconverter holy-rpm-1\noriginal-sha256 %s\nsource-name ", hash);
    token(receipt, source); fputs("\nverification unverified\n", receipt);
    for (i = 0; i < input.group_count; ++i)
        if (!write_output(&input, NULL, NULL, NULL, NULL, NULL, &metadata, source, hash,
                          output, output_fd, receipt, (int)i, NULL, NULL, NULL, NULL, NULL)) goto done;
    fputs("state complete\n", receipt);
    if (fflush(receipt) || fsync(fileno(receipt))) goto done;
    if (fclose(receipt)) { receipt = NULL; goto done; }
    receipt = NULL;
    if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done;
    result = 0;
done:
    if (result) fprintf(stderr, "holypkg: RPM import incomplete (status %d); no installed state changed\n", result);
    if (receipt) fclose(receipt);
    if (tar) fclose(tar);
    if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); }
    if (input_fd >= 0) close(input_fd);
    if (snapshot) { unlink(snapshot); free(snapshot); }
    if (metadata.header) headerFree(metadata.header);
    free(metadata.name); free(metadata.version); free(metadata.release); free(metadata.arch);
    free_input(&input);
    return result;
#endif
}