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