muon/src/wrap.c
2022-04-18 10:27:46 -05:00

408 lines
8.8 KiB
C

#include "posix.h"
#include <string.h>
#include <stdlib.h>
#include "error.h"
#include "external/libarchive.h"
#include "external/libcurl.h"
#include "formats/ini.h"
#include "lang/eval.h"
#include "lang/interpreter.h"
#include "lang/workspace.h"
#include "log.h"
#include "platform/filesystem.h"
#include "platform/mem.h"
#include "platform/path.h"
#include "platform/run_cmd.h"
#include "sha_256.h"
#include "wrap.h"
static const char *wrap_field_names[wrap_fields_count] = {
[wf_directory] = "directory",
[wf_patch_url] = "patch_url",
[wf_patch_fallback_url] = "patch_fallback_url",
[wf_patch_filename] = "patch_filename",
[wf_patch_hash] = "patch_hash",
[wf_patch_directory] = "patch_directory",
[wf_source_url] = "source_url",
[wf_source_fallback_url] = "source_fallback_url",
[wf_source_filename] = "source_filename",
[wf_source_hash] = "source_hash",
[wf_lead_directory_missing] = "lead_directory_missing",
[wf_url] = "url",
[wf_revision] = "revision",
[wf_depth] = "depth",
[wf_push_url] = "push_url",
[wf_clone_recursive] = "clone_recursive",
};
static const char *wrap_type_section_header[wrap_type_count] = {
[wrap_type_file] = "wrap-file",
[wrap_type_git] = "wrap-git",
[wrap_provide] = "provide",
};
struct wrap_parse_ctx {
struct wrap wrap;
uint32_t field_lines[wrap_fields_count];
enum wrap_type section;
bool have_type;
};
static bool
lookup_wrap_str(const char *s, const char *strs[], uint32_t len, uint32_t *res)
{
uint32_t i;
for (i = 0; i < len; ++i) {
if (strcmp(s, strs[i]) == 0) {
*res = i;
return true;
}
}
return false;
}
static bool
wrap_parse_cb(void *_ctx, struct source *src, const char *sect,
const char *k, const char *v, uint32_t line)
{
uint32_t res;
struct wrap_parse_ctx *ctx = _ctx;
if (!sect) {
error_messagef(src, line, 1, log_error, "key not under wrap section");
return false;
} else if (!k) {
if (!lookup_wrap_str(sect, wrap_type_section_header, wrap_type_count, &res)) {
error_messagef(src, line, 1, log_error, "invalid section '%s'", sect);
return false;
}
ctx->section = res;
if (res == wrap_provide) {
return true;
}
if (ctx->have_type) {
error_messagef(src, line, 1, log_error, "conflicting wrap types");
return false;
}
ctx->wrap.type = res;
ctx->have_type = true;
return true;
} else if (ctx->section == wrap_provide) {
L("TODO: handle provide %s = '%s'", k, v);
return true;
}
assert(k && v);
if (!lookup_wrap_str(k, wrap_field_names, wrap_fields_count, &res)) {
error_messagef(src, line, 1, log_error, "invalid key \"%s\"", k);
return false;
} else if (ctx->wrap.fields[res]) {
error_messagef(src, line, 1, log_error, "duplicate key \"%s\"", k);
return false;
}
ctx->wrap.fields[res] = v;
ctx->field_lines[res] = line;
return true;
}
static bool
checksum(const uint8_t *file_buf, uint64_t len, const char *sha256)
{
char buf[3] = { 0 };
uint32_t i;
uint8_t b, hash[32];
if (strlen(sha256) != 64) {
LOG_E("checksum '%s' is not 64 characters long", sha256);
return false;
}
calc_sha_256(hash, file_buf, len);
for (i = 0; i < 64; i += 2) {
memcpy(buf, &sha256[i], 2);
b = strtol(buf, NULL, 16);
if (b != hash[i / 2]) {
LOG_E("checksum mismatch");
return false;
}
}
return true;
}
static bool
fetch_checksum_extract(const char *src, const char *dest, const char *sha256,
const char *dest_dir)
{
bool res = false;
uint8_t *dlbuf = NULL;
uint64_t dlbuf_len;
muon_curl_init();
if (!muon_curl_fetch(src, &dlbuf, &dlbuf_len)) {
goto ret;
} else if (!checksum(dlbuf, dlbuf_len, sha256)) {
goto ret;
} else if (!muon_archive_extract((const char *)dlbuf, dlbuf_len, dest_dir)) {
goto ret;
}
res = true;
ret:
if (dlbuf) {
z_free(dlbuf);
}
muon_curl_deinit();
return res;
}
static bool
validate_wrap(struct wrap_parse_ctx *ctx, const char *file)
{
uint32_t i;
enum req { invalid, required, optional };
enum req field_req[wrap_fields_count] = {
[wf_directory] = optional,
[wf_patch_directory] = optional
};
if (ctx->wrap.fields[wf_patch_url]
|| ctx->wrap.fields[wf_patch_filename]
|| ctx->wrap.fields[wf_patch_hash]) {
field_req[wf_patch_url] = required;
field_req[wf_patch_filename] = required;
field_req[wf_patch_hash] = required;
field_req[wf_patch_fallback_url] = optional;
field_req[wf_patch_directory] = invalid;
}
switch (ctx->wrap.type) {
case wrap_type_file:
field_req[wf_source_filename] = required;
field_req[wf_source_url] = required;
field_req[wf_source_hash] = required;
field_req[wf_source_fallback_url] = optional;
field_req[wf_lead_directory_missing] = optional;
break;
case wrap_type_git:
field_req[wf_url] = required;
field_req[wf_revision] = required;
field_req[wf_depth] = optional;
field_req[wf_clone_recursive] = optional;
break;
default:
assert(false && "unreachable");
return false;
}
bool valid = true;
for (i = 0; i < wrap_fields_count; ++i) {
switch (field_req[i]) {
case optional:
break;
case required:
if (!ctx->wrap.fields[i]) {
error_messagef(&ctx->wrap.src, 1, 1, log_error, "missing field '%s'", wrap_field_names[i]);
valid = false;
}
break;
case invalid:
if (ctx->wrap.fields[i]) {
error_messagef(&ctx->wrap.src, ctx->field_lines[i], 1, log_error, "invalid field");
valid = false;
}
break;
}
}
return valid;
}
void
wrap_destroy(struct wrap *wrap)
{
fs_source_destroy(&wrap->src);
if (wrap->buf) {
z_free(wrap->buf);
wrap->buf = NULL;
}
}
bool
wrap_parse(const char *wrap_file, struct wrap *wrap)
{
struct wrap_parse_ctx ctx = { 0 };
if (!ini_parse(wrap_file, &ctx.wrap.src, &ctx.wrap.buf, wrap_parse_cb, &ctx)) {
wrap_destroy(&ctx.wrap);
return false;
}
if (!validate_wrap(&ctx, wrap_file)) {
wrap_destroy(&ctx.wrap);
return false;
}
*wrap = ctx.wrap;
char name[PATH_MAX];
if (!path_basename(name, PATH_MAX, wrap_file)) {
return false;
}
uint32_t len = strlen(name);
assert(len > 5 && "wrap file doesn't end in .wrap??");
assert(len - 5 < PATH_MAX);
name[len - 5] = 0;
const char *dir;
if (wrap->fields[wf_directory]) {
dir = wrap->fields[wf_directory];
} else {
dir = name;
}
char subprojects[PATH_MAX];
if (!path_dirname(subprojects, PATH_MAX, wrap_file)) {
return false;
} else if (!path_join(wrap->dest_dir, PATH_MAX, subprojects, dir)) {
return false;
}
return true;
}
static bool
wrap_apply_patch(struct wrap *wrap, const char *subprojects)
{
if (wrap->fields[wf_patch_url] && !fetch_checksum_extract(wrap->fields[wf_patch_url],
wrap->fields[wf_patch_filename], wrap->fields[wf_patch_hash], subprojects)) {
return false;
} else if (wrap->fields[wf_patch_directory]) {
char patch_dir[PATH_MAX], buf[PATH_MAX];
if (!path_join(buf, PATH_MAX, subprojects, "packagefiles")) {
return false;
} else if (!path_join(patch_dir, PATH_MAX, buf, wrap->fields[wf_patch_directory])) {
return false;
}
if (!fs_copy_dir(patch_dir, wrap->dest_dir)) {
return false;
}
}
return true;
}
static bool
run_git(const char *const argv[])
{
struct run_cmd_ctx cmd_ctx = { .flags = run_cmd_ctx_flag_dont_capture };
if (!run_cmd_argv(&cmd_ctx, "git", (char *const *)argv, NULL)) {
return false;
} else if (cmd_ctx.status != 0) {
run_cmd_ctx_destroy(&cmd_ctx);
return false;
} else {
run_cmd_ctx_destroy(&cmd_ctx);
return true;
}
}
static bool
wrap_handle_git(struct wrap *wrap, const char *subprojects)
{
if (!run_git((const char *const[]) {
"git", "clone", wrap->fields[wf_url], wrap->dest_dir,
NULL
})) {
return false;
}
if (!run_git((const char *const[]) {
"git", "-C", wrap->dest_dir, "-c", "advice.detachedHead=false", "checkout", wrap->fields[wf_revision], "--",
NULL
})) {
return false;
}
return true;
}
static bool
wrap_handle_file(struct wrap *wrap, const char *subprojects)
{
const char *dest;
if (wrap->fields[wf_lead_directory_missing]) {
dest = wrap->dest_dir;
} else {
dest = subprojects;
}
if (!fs_dir_exists(dest)) {
if (!fs_mkdir(dest)) {
return false;
}
}
return fetch_checksum_extract(wrap->fields[wf_source_url],
wrap->fields[wf_source_filename], wrap->fields[wf_source_hash], dest);
}
bool
wrap_handle(const char *wrap_file, const char *subprojects, struct wrap *wrap, bool download)
{
if (!wrap_parse(wrap_file, wrap)) {
return false;
}
char meson_build[PATH_MAX];
if (!path_join(meson_build, PATH_MAX, wrap->dest_dir, "meson.build")) {
return false;
} else if (fs_file_exists(meson_build)) {
LOG_I("wrap already downloaded");
return true;
} else if (!download) {
LOG_E("wrap downloading disabled");
return false;
}
switch (wrap->type) {
case wrap_type_file:
if (!wrap_handle_file(wrap, subprojects)) {
return false;
}
break;
case wrap_type_git:
if (!wrap_handle_git(wrap, subprojects)) {
return false;
}
break;
default:
assert(false && "unreachable");
return false;
}
if (!wrap_apply_patch(wrap, subprojects)) {
return false;
}
return true;
}