_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file src/deps.c
#define _POSIX_C_SOURCE 200809L
#include "deps.h"
#include "config.h"
#include "stage.h"
#include "verify.h"
#include <archive.h>
#include <archive_entry.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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);
}