#define _POSIX_C_SOURCE 200809L #include "deps.h" #include "config.h" #include "stage.h" #include "verify.h" #include #include #include #include #include #include #define DEPS_LIMIT (1024u * 1024u) struct requirement { char **fields; }; static int one_of(const char *value, const char *const *values, size_t count) { size_t i; for (i = 0; i < count; ++i) if (!strcmp(value, values[i])) return 1; return 0; } int holy_package_or_each(const char *expression, holy_package_or_visit visit, void *opaque) { char *copy = NULL, *cursor, *part; size_t count = 0; int ok = 0; if (!expression || !*expression || strlen(expression) > 65536 || !(copy = strdup(expression))) return 0; cursor = copy; while (cursor) { char *next = strchr(cursor, '|'); char *name, *relation, *version; size_t i; part = cursor; if (next) { *next = 0; cursor = next + 1; } else cursor = NULL; name = part; relation = strchr(part, '@'); if (++count > 64 || !relation) goto done; *relation++ = 0; version = strchr(relation, '@'); if (!version) goto done; *version++ = 0; if (!*name || !*version || strchr(version, '@') || (!strcmp(relation, "any") ? strcmp(version, "-") : strcmp(relation, "eq") && strcmp(relation, "ge") && strcmp(relation, "gt") && strcmp(relation, "le") && strcmp(relation, "lt"))) goto done; for (i = 0; name[i]; ++i) if (!((name[i] >= 'a' && name[i] <= 'z') || (name[i] >= '0' && name[i] <= '9') || (i && (name[i] == '+' || name[i] == '-' || name[i] == '.')))) goto done; if (strcmp(relation, "any")) { if (!strcmp(version, "-")) goto done; for (i = 0; version[i]; ++i) if ((unsigned char)version[i] <= 32 || (unsigned char)version[i] >= 127 || version[i] == '|' || version[i] == '@') goto done; } if (visit && !visit(opaque, name, relation, version)) goto done; } ok = count >= 2; done: free(copy); return ok; } static int valid_requirement(char **v, size_t n) { static const char *const kinds[] = { "package", "package-or", "file", "command", "soname", "symbol-version", "build", "foreign" }; static const char *const arches[] = {"any", "x86", "x86_64", "noarch"}; static const char *const libcs[] = {"any", "glibc", "musl", "nolibc"}; static const char *const relations[] = {"any", "eq", "ge", "le", "gt", "lt"}; size_t i; if (n != 11 || strcmp(v[0], "require") || !v[1][0] || !v[2][0] || !v[4][0] || !v[9][0] || !v[10][0] || !one_of(v[3], kinds, sizeof kinds / sizeof *kinds) || !one_of(v[5], arches, sizeof arches / sizeof *arches) || !one_of(v[6], libcs, sizeof libcs / sizeof *libcs) || !one_of(v[7], relations, sizeof relations / sizeof *relations) || (!strcmp(v[7], "any") ? strcmp(v[8], "-") : !v[8][0] || !strcmp(v[8], "-")) || (!strcmp(v[3], "package-or") && (strcmp(v[5], "any") || strcmp(v[6], "any") || strcmp(v[7], "any") || !holy_package_or_each(v[4], NULL, NULL)))) return 0; for (i = 0; v[1][i]; ++i) if (!((v[1][i] >= 'a' && v[1][i] <= 'z') || (v[1][i] >= 'A' && v[1][i] <= 'Z') || (v[1][i] >= '0' && v[1][i] <= '9') || v[1][i] == '-' || v[1][i] == '_' || v[1][i] == '.')) return 0; return 1; } int holy_deps_record_valid(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) { char *v[] = {"require", (char *)id, (char *)consumer, (char *)kind, (char *)name, (char *)arch, (char *)libc, (char *)relation, (char *)version, (char *)original, (char *)evidence}; return valid_requirement(v, 11); } static int parse(char *data, size_t length, struct requirement **items, size_t *count) { size_t start = 0, pos, line = 0; char *error = NULL; for (pos = 0; pos <= length; ++pos) { char **v = NULL; size_t n = 0, i; struct requirement *next; if (pos < length && data[pos] != '\n') continue; ++line; if (memchr(data + start, '\0', pos - start) || pos - start > 65536 || !holy_lex(data + start, pos - start, &v, &n, "HOLY/deps", line, &error)) { fprintf(stderr, "holypkg: HOLY/deps:%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_requirement(v, n) || *count >= DEPS_LIMIT / 11) { fprintf(stderr, "holypkg: HOLY/deps:%zu: unsupported requirement\n", line); holy_tokens_free(v, n); return 0; } for (i = 0; i < *count; ++i) if (!strcmp((*items)[i].fields[1], v[1])) { fprintf(stderr, "holypkg: HOLY/deps:%zu: duplicate requirement id\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 *value) { const unsigned char *p = (const unsigned char *)value; 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 *value) { const unsigned char *p = (const unsigned char *)value; 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_local(const char *package, int emit, size_t *result_count, holy_requirement_visit visitor, void *opaque) { char *snapshot = holy_stage_local(package, "holy-deps"); char *data = NULL; struct archive *archive = NULL; struct archive_entry *entry; struct requirement *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/deps")) { la_int64_t size = archive_entry_size(entry); size_t used = 0; if (seen++ || size < 0 || size > DEPS_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 (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], v[7], v[8], v[9], v[10])) goto done; } if (result_count) *result_count = count; if (emit == 1) { for (i = 0; i < count; ++i) { fputs("require", stdout); for (j = 1; j < 11; ++j) { putchar(' '); print_token(items[i].fields[j]); } putchar('\n'); } printf("requirements %zu\n", count); } else if (emit == 2) { static const char *const keys[] = { "id", "consumer", "kind", "name", "arch", "libc", "relation", "version", "original", "evidence" }; for (i = 0; i < count; ++i) { fputs("{\"schema\":\"holy-requirements-1\",\"type\":\"requirement\"", stdout); for (j = 1; j < 11; ++j) { printf(",\"%s\":", keys[j - 1]); json_string(items[i].fields[j]); } puts("}"); } printf("{\"schema\":\"holy-requirements-1\",\"type\":\"summary\",\"count\":%zu}\n", count); } ok = 1; done: if (!ok) { fprintf(stderr, "holypkg: requirements inspection failed\n"); if (emit == 2) puts("{\"schema\":\"holy-requirements-1\",\"type\":\"error\",\"code\":\"invalid-requirements\"}"); } for (i = 0; i < count; ++i) holy_tokens_free(items[i].fields, 11); free(items); free(data); if (archive) archive_read_free(archive); if (snapshot) { unlink(snapshot); free(snapshot); } return ok; } int holy_deps_local(const char *package) { return holy_deps_local_with_output(package, 1); } int holy_deps_local_with_output(const char *package, int emit) { return inspect_local(package, emit, NULL, NULL, NULL); } int holy_deps_count(const char *package, size_t *count) { *count = 0; return inspect_local(package, 0, count, NULL, NULL); } int holy_deps_visit(const char *package, holy_requirement_visit visitor, void *opaque) { return visitor && inspect_local(package, 0, NULL, visitor, opaque); }