#define _POSIX_C_SOURCE 200809L #include "provides.h" #include "config.h" #include "stage.h" #include "verify.h" #include #include #include #include #include #include #include #include #define PROVIDES_LIMIT (1024u * 1024u) struct capability { char **fields; }; static const char *const capability_kinds[] = { "package", "file", "command", "soname", "symbol-version", "build" }; static int one_of(const char *value, const char *const *options, size_t count) { size_t i; for (i = 0; i < count; ++i) if (!strcmp(value, options[i])) return 1; return 0; } int holy_provides_kind(const char *kind) { return kind && one_of(kind, capability_kinds, sizeof capability_kinds / sizeof *capability_kinds); } static int valid(char **v, size_t n) { static const char *const arches[] = {"any", "x86", "x86_64", "noarch"}; static const char *const libcs[] = {"any", "glibc", "musl", "nolibc"}; if (n != 7 || strcmp(v[0], "provide")) return 0; if (!holy_provides_kind(v[1]) || !one_of(v[3], arches, sizeof arches / sizeof *arches) || !one_of(v[4], libcs, sizeof libcs / sizeof *libcs) || !v[2][0] || !v[5][0] || !v[6][0]) return 0; if (!strcmp(v[1], "file") && v[2][0] != '/') return 0; if (!strcmp(v[1], "soname") && strchr(v[2], '/')) return 0; return 1; } int holy_provides_claim_valid(const char *kind, const char *name, const char *arch, const char *libc, const char *version, const char *evidence) { char *v[7]; if (!kind || !name || !arch || !libc || !version || !evidence) return 0; v[0] = "provide"; v[1] = (char *)kind; v[2] = (char *)name; v[3] = (char *)arch; v[4] = (char *)libc; v[5] = (char *)version; v[6] = (char *)evidence; return valid(v, 7); } static int parse(char *data, size_t size, struct capability **items, size_t *count) { size_t start = 0, pos, line = 0; char *error = NULL; for (pos = 0; pos <= size; ++pos) { char **v = NULL; size_t n = 0, i; struct capability *next; if (pos < size && data[pos] != '\n') continue; ++line; if (memchr(data + start, '\0', pos - start) || pos - start > 65536 || !holy_lex(data + start, pos - start, &v, &n, "HOLY/provides", line, &error)) { fprintf(stderr, "holypkg: HOLY/provides:%zu: %s\n", line, error ? error : "invalid record"); free(error); holy_tokens_free(v, n); return 0; } start = pos + 1; if (!n) { holy_tokens_free(v, n); continue; } if (!valid(v, n) || *count >= PROVIDES_LIMIT / 7) { fprintf(stderr, "holypkg: HOLY/provides:%zu: unsupported capability\n", line); holy_tokens_free(v, n); return 0; } for (i = 0; i < *count; ++i) { char **existing = (*items)[i].fields; if (!strcmp(existing[1], v[1]) && !strcmp(existing[2], v[2]) && !strcmp(existing[3], v[3]) && !strcmp(existing[4], v[4]) && !strcmp(existing[5], v[5])) { fprintf(stderr, "holypkg: HOLY/provides:%zu: duplicate capability\n", line); holy_tokens_free(v, n); return 0; } } next = realloc(*items, (*count + 1) * sizeof **items); if (!next) { holy_tokens_free(v, n); return 0; } *items = next; (*items)[*count].fields = v; ++*count; } return 1; } static void print_token(const char *text) { const unsigned char *p = (const unsigned char *)text; putchar('"'); for (; *p; ++p) if (*p == '"' || *p == '\\') { putchar('\\'); putchar(*p); } else if (*p <= 32 || *p >= 127) printf("\\x%02x", (unsigned int)*p); else putchar(*p); putchar('"'); } static void json_string(const char *text) { const unsigned char *p = (const unsigned char *)text; putchar('"'); for (; *p; ++p) { if (*p == '"' || *p == '\\') { putchar('\\'); putchar(*p); } else if (*p >= 32 && *p < 127) putchar(*p); else printf("\\u%04x", (unsigned int)*p); } putchar('"'); } static int inspect(const char *package, int emit, const char *kind, const char *name, int *matched, holy_capability_visit visitor, void *opaque) { char *snapshot = holy_stage_local(package, "holy-provides"); char *data = NULL; struct archive *archive = NULL; struct archive_entry *entry; struct capability *items = NULL; size_t count = 0, i, j; int status, ok = 0, seen = 0; if (!snapshot || !holy_verify_with_output(snapshot, 0)) goto done; archive = archive_read_new(); if (!archive || archive_read_support_filter_lz4(archive) != ARCHIVE_OK || archive_read_support_format_tar(archive) != ARCHIVE_OK || archive_read_open_filename(archive, snapshot, 8192) != ARCHIVE_OK) goto done; while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) { if (!strcmp(archive_entry_pathname(entry), "HOLY/provides")) { la_int64_t size = archive_entry_size(entry); size_t used = 0; if (seen++ || size < 0 || size > PROVIDES_LIMIT) goto done; data = malloc((size_t)size + 1); if (!data) goto done; while (used < (size_t)size) { la_ssize_t got = archive_read_data(archive, data + used, (size_t)size - used); if (got <= 0) goto done; used += (size_t)got; } data[used] = '\0'; if (!parse(data, used, &items, &count)) goto done; } else if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done; } if (status != ARCHIVE_EOF || !seen) goto done; if (matched) { for (i = 0; i < count; ++i) if (!strcmp(items[i].fields[1], kind) && !strcmp(items[i].fields[2], name)) *matched = 1; } if (visitor) for (i = 0; i < count; ++i) { char **v = items[i].fields; if (!visitor(opaque, v[1], v[2], v[3], v[4], v[5], v[6])) goto done; } if (emit == 1) { for (i = 0; i < count; ++i) { fputs("provide", stdout); for (j = 1; j < 7; ++j) { putchar(' '); print_token(items[i].fields[j]); } putchar('\n'); } printf("capabilities %zu\n", count); } else if (emit == 2) { static const char *const keys[] = { "kind", "name", "arch", "libc", "version", "evidence" }; for (i = 0; i < count; ++i) { fputs("{\"schema\":\"holy-provides-1\",\"type\":\"capability\"", stdout); for (j = 1; j < 7; ++j) { printf(",\"%s\":", keys[j - 1]); json_string(items[i].fields[j]); } puts("}"); } printf("{\"schema\":\"holy-provides-1\",\"type\":\"summary\",\"count\":%zu}\n", count); } ok = 1; done: if (!ok) { fprintf(stderr, "holypkg: capabilities inspection failed\n"); if (emit == 2) puts("{\"schema\":\"holy-provides-1\",\"type\":\"error\",\"code\":\"invalid-provides\"}"); } for (i = 0; i < count; ++i) holy_tokens_free(items[i].fields, 7); free(items); free(data); if (archive) archive_read_free(archive); if (snapshot) { unlink(snapshot); free(snapshot); } return ok; } int holy_provides_local(const char *package, int emit) { return inspect(package, emit, NULL, NULL, NULL, NULL, NULL); } int holy_provides_match(const char *package, const char *kind, const char *name, int *matched) { if (!matched || !holy_provides_kind(kind) || !name || !*name) return 0; *matched = 0; return inspect(package, 0, kind, name, matched, NULL, NULL); } int holy_provides_visit(const char *package, holy_capability_visit visitor, void *opaque) { if (!visitor) return 0; return inspect(package, 0, NULL, NULL, NULL, visitor, opaque); } int holy_provides_visit_fd(int fd, holy_capability_visit visitor, void *opaque) { struct stat st; struct capability *items = NULL; char *data = NULL; size_t count = 0, used = 0, i; int ok = 0; if (!visitor || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > PROVIDES_LIMIT) return 0; data = malloc((size_t)st.st_size + 1); if (!data) return 0; while (used < (size_t)st.st_size) { ssize_t got = pread(fd, data + used, (size_t)st.st_size - used, (off_t)used); if (got < 0 && errno == EINTR) continue; if (got <= 0) goto done; used += (size_t)got; } data[used] = 0; if (!parse(data, used, &items, &count)) goto done; for (i = 0; i < count; ++i) { char **v = items[i].fields; if (!visitor(opaque, v[1], v[2], v[3], v[4], v[5], v[6])) goto done; } ok = 1; done: for (i = 0; i < count; ++i) holy_tokens_free(items[i].fields, 7); free(items); free(data); return ok; }