summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/include/log.h7
-rw-r--r--lib/include/net.h29
-rw-r--r--lib/include/util.h8
-rw-r--r--lib/include/vector.h27
-rw-r--r--lib/log.c6
-rw-r--r--lib/meson.build2
-rw-r--r--lib/net.c327
-rw-r--r--lib/util.c30
-rw-r--r--lib/vector.c122
-rw-r--r--main.c98
10 files changed, 649 insertions, 7 deletions
diff --git a/lib/include/log.h b/lib/include/log.h
index a8549e0..1483559 100644
--- a/lib/include/log.h
+++ b/lib/include/log.h
@@ -1,3 +1,6 @@
+#ifndef VL_LOG_H_INCLUDED
+#define VL_LOG_H_INCLUDED
+
#include <stdio.h>
#include <stdarg.h>
@@ -7,6 +10,8 @@
#define LOG_WARN 3u
#define LOG_ERROR 4u
+/* Log functions MUST leave errno untouched */
+
void vl_log_setlevel(unsigned level);
void vl_logv(unsigned level, const char *fmt, va_list args);
@@ -50,3 +55,5 @@ void vl_log(unsigned level, const char *fmt, ...) __attribute__((format(printf,
#define LOG_ERROR_ENABLED
#define vl_error(...) vl_log(LOG_ERROR, __VA_ARGS__)
#define vl_errorv(...) vl_logv(LOG_ERROR, __VA_ARGS__)
+
+#endif
diff --git a/lib/include/net.h b/lib/include/net.h
index 5ee7fc6..610ed77 100644
--- a/lib/include/net.h
+++ b/lib/include/net.h
@@ -2,6 +2,7 @@
#define VL_NET_H_INCLUDED
#include "arena.h"
+#include "sha1.h"
enum {
/* The operation completed successfully */
@@ -61,4 +62,32 @@ int vl_net_ensure_verified(const char *url, const char *target_path, unsigned fl
* Returns NET_EIO if the downloaded file is not readable or could not be verified due to an I/O issue. */
int vl_net_verify(const char *target_path, unsigned flags, ...);
+enum {
+ /* The job hasn't been started yet. */
+ STATUS_WAITING = 0u,
+
+ /* The job is in flight. */
+ STATUS_RUNNING = 1u,
+
+ /* The job is finished without errors. */
+ STATUS_COMPLETE = 2u,
+
+ /* The job is finished with errors. */
+ STATUS_ERROR = 3u,
+
+ /* The job finished successfully, but the downloaded file failed integrity checks. */
+ STATUS_INTEGRITY = 4u
+};
+
+struct vl_download_job {
+ const char *url;
+ const char *opath;
+ size_t expect_len;
+ vl_sha1 expect_hash;
+ unsigned verify_flags;
+ unsigned status;
+};
+
+int vl_net_download_all(struct vl_download_job *jobs, size_t njobs, size_t simult);
+
#endif
diff --git a/lib/include/util.h b/lib/include/util.h
new file mode 100644
index 0000000..5ef3c8b
--- /dev/null
+++ b/lib/include/util.h
@@ -0,0 +1,8 @@
+#ifndef VL_UTIL_H_INCLUDED
+#define VL_UTIL_H_INCLUDED
+
+#include <sys/stat.h>
+
+int vl_mkdir_parents(int fd, char *path, mode_t mode);
+
+#endif
diff --git a/lib/include/vector.h b/lib/include/vector.h
new file mode 100644
index 0000000..02ed84e
--- /dev/null
+++ b/lib/include/vector.h
@@ -0,0 +1,27 @@
+#ifndef VL_VECTOR_H_INCLUDED
+#define VL_VECTOR_H_INCLUDED
+
+#include <stddef.h>
+
+/* implements a vector type (array of same-sized values) */
+
+typedef struct vl__vector_tag vl_vector;
+
+vl_vector *vl_vector_new(size_t sz);
+vl_vector *vl_vector_new_ex(size_t sz, size_t init_cap);
+void vl_vector_free(vl_vector *vec);
+
+/* will abort if realloc fails to shrink the allocation (should never happen) */
+void vl_vector_shrink_to_size(vl_vector *vec);
+
+#define vl_newvec(_t) vl_vector_new(sizeof(_t))
+#define vl_newvec_ex(_t, _ic) vl_vector_new_ex(sizeof(_t), _ic)
+
+int vl_vector_push(vl_vector *vec, void *data);
+int vl_vector_push_vec(vl_vector *vec, void *data, size_t n);
+int vl_vector_push_2d(vl_vector *vec, void **data, size_t n);
+
+size_t vl_vector_size(const vl_vector *vec);
+void *vl_vector_values(const vl_vector *vec, size_t *psz);
+
+#endif
diff --git a/lib/log.c b/lib/log.c
index 4924782..6f0f40a 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -1,5 +1,6 @@
#include <string.h>
#include <time.h>
+#include <errno.h>
#include "log.h"
/* TODO: replace this with something thread-safe later. */
@@ -41,6 +42,7 @@ void vl_logv(unsigned level, const char *fmt, va_list args)
char datebuf[MAX_LOGTIMELEN];
time_t now;
struct tm now_tm;
+ int en = errno;
if (log_level > level) return;
if (level > LOG_ERROR) return;
@@ -55,7 +57,9 @@ void vl_logv(unsigned level, const char *fmt, va_list args)
strcpy(datebuf, "???");
}
- fprintf(log_stream(level), "[%s] %s: %s\n", datebuf, log_names[level], buf);
+ fprintf(log_stream(level), "[%s] %5s: %s\n", datebuf, log_names[level], buf);
+
+ errno = en;
}
void vl_log(unsigned level, const char *fmt, ...)
diff --git a/lib/meson.build b/lib/meson.build
index d2fca24..04c2cd9 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -1,5 +1,5 @@
vaclaunch_lib_inc = include_directories('include')
vaclaunch_libs = both_libraries('vaclaunch',
- files('manifest.c', 'log.c', 'net.c', 'sha1.c', 'arena.c'),
+ files('manifest.c', 'log.c', 'net.c', 'sha1.c', 'arena.c', 'util.c', 'vector.c'),
include_directories : vaclaunch_lib_inc,
dependencies : [ curl_dep, jansson_dep ])
diff --git a/lib/net.c b/lib/net.c
index 99c7deb..f400974 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -492,3 +492,330 @@ int vl_net_ensure_verified(const char *url, const char *target_path, unsigned fl
return ret;
}
+struct dl_all_ctx {
+ struct write_ctx wrctx;
+ struct vl_download_job *cur_job;
+ char errbuf[CURL_ERROR_SIZE];
+};
+
+static int setup_easy(CURLM *multi, CURL *easy, struct vl_download_job *job)
+{
+ int ret = NET_EUNSPEC;
+ CURLcode ecode;
+ CURLMcode mcode;
+
+ struct dl_all_ctx *dlctx;
+
+ if ((ecode = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &dlctx)) != CURLE_OK || !dlctx) {
+ vl_error("Bad curl easy handle! curl_easy_getinfo(CURLINFO_PRIVATE): %s", curl_easy_strerror(ecode));
+ abort(); /* weird should never happen */
+ }
+
+#define C(_ex) CHECK(_ex, #_ex, ecode, dlctx->errbuf, cleanup, ret)
+
+ C(curl_easy_setopt(easy, CURLOPT_URL, job->url));
+
+#undef C
+
+ if (!(dlctx->wrctx.ofile = fopen(job->opath, "wb"))) {
+ vl_warn("Failed to open output file %s: %s", job->opath, strerror(errno));
+ ret = NET_EIO;
+ goto cleanup;
+ }
+
+ dlctx->wrctx.opath = NULL;
+ dlctx->wrctx.total_read = 0;
+ vl_sha1_init(&dlctx->wrctx.sha1_state);
+
+ dlctx->cur_job = job;
+ job->status = STATUS_RUNNING;
+
+ if ((mcode = curl_multi_add_handle(multi, easy)) != CURLM_OK) {
+ vl_warn("Failed to add easy handle to multi (for %s): %s", job->url, curl_multi_strerror(mcode));
+ goto cleanup;
+ }
+
+ ret = NET_OK;
+
+cleanup:
+ if (ret != NET_OK && dlctx->wrctx.ofile) {
+ fclose(dlctx->wrctx.ofile);
+ dlctx->wrctx.ofile = NULL;
+
+ if (remove(job->opath) < 0) {
+ vl_debug("...also failed to delete touched file %s: %s", job->opath, strerror(errno));
+ } else {
+ vl_trace("removed touched file %s", job->opath);
+ }
+ }
+
+ return ret;
+}
+
+static int xfer_complete(CURLM *multi, CURL *easy, CURLcode ecode)
+{
+ struct dl_all_ctx *ctx = NULL;
+ int ret = NET_EUNSPEC;
+ long statuscode;
+ CURLcode myecode;
+ CURLMcode mcode;
+
+ if ((myecode = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &ctx)) != CURLE_OK || !ctx) {
+ vl_error("Failed to get context information: curl_easy_getinfo(CURLINFO_PRIVATE): %s", curl_easy_strerror(myecode));
+ abort(); /* weird! should never happen */
+ }
+
+ vl_trace("Handling completed transfer %s", ctx->cur_job->url);
+
+ if ((mcode = curl_multi_remove_handle(multi, easy)) != CURLM_OK) {
+ vl_warn("Failed to remove easy handle from multi (for %s): %s", ctx->cur_job->url, curl_multi_strerror(mcode));
+ goto cleanup;
+ }
+
+ if (ecode != CURLE_OK) {
+ ctx->cur_job->status = STATUS_ERROR;
+ ret = translate_curlcode(ecode);
+ goto cleanup;
+ }
+
+ /* transfer completed successfully, make sure everything else is right also. */
+
+#define C(_ex) CHECK(_ex, #_ex, myecode, ctx->errbuf, cleanup, ret)
+
+ C(curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &statuscode));
+
+ if (statuscode / 100 != 2) {
+ /* should never happen */
+ vl_warn("Bad HTTP response code from %s: %ld", ctx->cur_job->url, statuscode);
+ ret = NET_ESTATUS;
+ goto cleanup;
+ }
+
+ vl_trace("Got %ld OK from %s", statuscode, ctx->cur_job->url);
+
+ /* compare integrity info */
+
+ if (ctx->cur_job->verify_flags & VERIFY_SIZE) {
+ if (ctx->cur_job->expect_len != ctx->wrctx.total_read) {
+ vl_warn("Bad integrity on downloaded file %s: size mismatch: expected %zu bytes, got %zu bytes", ctx->cur_job->url, ctx->cur_job->expect_len, ctx->wrctx.total_read);
+ ret = NET_EINTEGRITY;
+ ctx->cur_job->status = STATUS_INTEGRITY;
+ goto cleanup;
+ }
+
+ vl_trace("size matches %s: %zu", ctx->cur_job->url, ctx->cur_job->expect_len);
+ }
+
+ if (ctx->cur_job->verify_flags & VERIFY_SHA1) {
+ vl_sha1 got_hash;
+ vl_sha1_finalize(&ctx->wrctx.sha1_state, got_hash);
+
+ char exp_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1];
+ char got_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1];
+
+ exp_hex[VL_SHA1_DIGEST_HEX_STRLEN] = '\0';
+ got_hex[VL_SHA1_DIGEST_HEX_STRLEN] = '\0';
+
+ vl_sha1_encode(ctx->cur_job->expect_hash, exp_hex);
+ vl_sha1_encode(got_hash, got_hex);
+
+ if (memcmp(ctx->cur_job->expect_hash, got_hash, sizeof(vl_sha1))) {
+ vl_warn("Bad integrity on downloaded file %s: sha1 mismatch: expected %s, got %s", ctx->cur_job->url, exp_hex, got_hex);
+ ret = NET_EINTEGRITY;
+ ctx->cur_job->status = STATUS_INTEGRITY;
+ goto cleanup;
+ }
+
+ vl_trace("sha1 matches %s: %s", ctx->cur_job->url, exp_hex);
+ }
+
+#undef C
+
+ ctx->cur_job->status = STATUS_COMPLETE;
+ ret = NET_OK;
+
+cleanup:
+ if (ctx->wrctx.ofile) {
+ fclose(ctx->wrctx.ofile);
+ ctx->wrctx.ofile = NULL;
+ }
+
+ if (ret != NET_OK) {
+ if (remove(ctx->cur_job->opath) < 0) {
+ vl_debug("...and failed to clean up after failed job: remove(%s): %s", ctx->cur_job->opath, strerror(errno));
+ } else {
+ vl_trace("removed touched file (failed job): %s", ctx->cur_job->opath);
+ }
+ }
+
+ ctx->cur_job = NULL;
+
+ return ret;
+}
+
+int vl_net_download_all(struct vl_download_job *jobs, size_t njobs, size_t simult)
+{
+ vl_arena *arena = NULL;
+ int ret = NET_EUNSPEC;
+ int tempret;
+ CURLM *multi = NULL;
+ CURL **easy_pool = NULL;
+ struct dl_all_ctx *dlcontexts = NULL;
+ size_t next_job = 0;
+ size_t completed_jobs = 0;
+ CURLcode ecode;
+
+ CURLMcode mcode;
+ int nrunning = 0;
+ CURLMsg *mmsg;
+ int nmsg = 0;
+
+ /* sanity check for simultaneous transfer count */
+ if (simult < 8) simult = 8;
+ if (simult > njobs) simult = njobs;
+
+ arena = vl_arena_new((size_t)1 << 14);
+ if (!arena) {
+ vl_warn("Error allocating arena for download job!");
+ goto cleanup;
+ }
+
+ /* array of easy handles */
+ easy_pool = vl_arena_push(arena, simult * sizeof(CURL *));
+ memset(easy_pool, 0, simult * sizeof(CURL *)); /* erm but NULL isn't necessarily represented as all 0s.. */
+
+ /* context info for each easy handle */
+ dlcontexts = vl_arena_push(arena, simult * sizeof(struct dl_all_ctx));
+ memset(dlcontexts, 0, simult * sizeof(struct dl_all_ctx));
+
+ multi = curl_multi_init();
+ if (!multi) {
+ vl_warn("Error creating multi handle for download job");
+ goto cleanup;
+ }
+
+ /* initialize each easy handle with default info */
+ for (size_t i = 0; i < simult; ++i) {
+ easy_pool[i] = curl_easy_init();
+ if (!easy_pool[i]) {
+ vl_warn("Failed to create easy handle %zu", i);
+ goto cleanup;
+ }
+
+ if ((ecode = curl_easy_setopt(easy_pool[i], CURLOPT_ERRORBUFFER, dlcontexts[i].errbuf)) != CURLE_OK) {
+ vl_warn("Failed to set error buffer on easy handle %zu: %s", i, curl_easy_strerror(ecode));
+ goto cleanup;
+ }
+
+#define C(_ex) CHECK(_ex, #_ex, ecode, dlcontexts[i].errbuf, cleanup, ret)
+
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_USERAGENT, VL_USER_AGENT));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_HTTPGET, 1L));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_FOLLOWLOCATION, CURLFOLLOW_ALL));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_TIMEOUT, 60L));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_FAILONERROR, 1L));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_WRITEFUNCTION, &handle_write));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_WRITEDATA, &dlcontexts[i].wrctx));
+ C(curl_easy_setopt(easy_pool[i], CURLOPT_PRIVATE, dlcontexts + i));
+
+#undef C
+ }
+
+ vl_trace("Setting off %zu initial jobs.", simult);
+ for (size_t i = 0; i < simult; ++i) {
+ if ((tempret = setup_easy(multi, easy_pool[i], jobs + i)) != NET_OK) {
+ vl_trace("Failed to set up initial job %zu", i);
+ ret = tempret;
+ goto cleanup;
+ }
+ }
+
+ next_job = simult;
+
+ do {
+ vl_trace("Starting download loop.");
+ mcode = curl_multi_perform(multi, &nrunning);
+ if (mcode != CURLM_OK) {
+ vl_warn("Error in CURL transfer: curl_multi_perform: %s", curl_multi_strerror(mcode));
+ goto cleanup;
+ }
+
+ /* handle complete transfers */
+ do {
+ mmsg = curl_multi_info_read(multi, &nmsg);
+ if (!mmsg) break;
+
+ if (mmsg->msg == CURLMSG_DONE) {
+ /* a transfer has completed */
+ ecode = mmsg->data.result;
+ if ((tempret = xfer_complete(multi, mmsg->easy_handle, ecode)) != NET_OK) {
+ ret = tempret;
+ goto cleanup;
+ }
+
+ ++completed_jobs;
+
+ /* set up the next transfer */
+ if (next_job < njobs) {
+ if ((tempret = setup_easy(multi, mmsg->easy_handle, jobs + next_job)) != NET_OK) {
+ vl_trace("failed to set up job %zu", next_job);
+ ret = tempret;
+ goto cleanup;
+ }
+
+ vl_trace("Set up job %zu", next_job);
+ ++next_job;
+ }
+ }
+ } while (mmsg);
+
+ vl_trace("Download loop complete: %d running, next job: %zu, njobs: %zu, completed: %zu", nrunning, next_job, njobs, completed_jobs);
+
+ if (completed_jobs >= njobs) {
+ vl_trace("Breaking out early %zu >= %zu.", completed_jobs, njobs);
+ break;
+ }
+
+ mcode = curl_multi_poll(multi, NULL, 0, 1000, NULL);
+ if (mcode != CURLM_OK) {
+ vl_warn("Error in CURL transfer: curl_multi_poll: %s", curl_multi_strerror(mcode));
+ goto cleanup;
+ }
+ } while (completed_jobs < njobs);
+
+ ret = NET_OK;
+
+cleanup:
+ if (easy_pool) {
+ for (size_t i = 0; i < simult; ++i) {
+ if (easy_pool[i]) {
+ if (multi) {
+ /* if this call returns an error then idc I did my best */
+ curl_multi_remove_handle(multi, easy_pool[i]);
+ }
+ curl_easy_cleanup(easy_pool[i]);
+ }
+ }
+ }
+
+ if (multi) {
+ curl_multi_cleanup(multi);
+ }
+
+ for (size_t i = 0; i < simult; ++i) {
+ if (dlcontexts[i].cur_job && dlcontexts[i].wrctx.ofile) {
+ fclose(dlcontexts[i].wrctx.ofile);
+ if (remove(dlcontexts[i].cur_job->opath) < 0) {
+ vl_debug("...also failed to remove touched file %s: %s", dlcontexts[i].cur_job->opath, strerror(errno));
+ } else {
+ vl_trace("Removed touched file %s", dlcontexts[i].cur_job->opath);
+ }
+ }
+ }
+
+ if (arena) {
+ vl_arena_free(arena);
+ }
+
+ return ret;
+}
diff --git a/lib/util.c b/lib/util.c
new file mode 100644
index 0000000..6cfcf1f
--- /dev/null
+++ b/lib/util.c
@@ -0,0 +1,30 @@
+#include "log.h"
+#include "util.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
+/* will clobber path */
+int vl_mkdir_parents(int fd, char *path, mode_t mode)
+{
+ int last = 0;
+
+ char *ch = path;
+ do {
+ if (*ch == '/' || (*ch == '\0' && (last = 1))) {
+ /* last is set to 1 iff *ch == '\0' */
+ *ch = '\0';
+
+ if (mkdirat(fd, path, mode) < 0 && errno != EEXIST) {
+ vl_debug("Failed to create directory %s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ if (!last) *ch = '/';
+ }
+ } while (*(ch++));
+
+ return 0;
+}
diff --git a/lib/vector.c b/lib/vector.c
new file mode 100644
index 0000000..eb7233c
--- /dev/null
+++ b/lib/vector.c
@@ -0,0 +1,122 @@
+#include "vector.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+struct vl__vector_tag {
+ size_t cap, sz, len;
+ void *data; /* cap * len */
+};
+
+vl_vector *vl_vector_new(size_t sz)
+{
+ return vl_vector_new_ex(sz, 16);
+}
+
+vl_vector *vl_vector_new_ex(size_t sz, size_t init_cap)
+{
+ vl_vector *vec = calloc(1, sizeof(vl_vector));
+ if (!vec) return NULL;
+
+ if (init_cap < 16) init_cap = 16;
+
+ vec->cap = init_cap;
+ vec->len = 0;
+ vec->sz = sz;
+
+ void *data = calloc(init_cap, sz);
+ if (!data) {
+ free(vec);
+ return NULL;
+ }
+
+ vec->data = data;
+
+ return vec;
+}
+
+void vl_vector_free(vl_vector *vec)
+{
+ if (!vec) return;
+
+ free(vec->data);
+ free(vec);
+}
+
+static int vec_grow(vl_vector *vec, size_t newlen)
+{
+ void *temp = reallocarray(vec->data, newlen, vec->sz);
+ if (!temp) return -1;
+ vec->data = temp;
+
+ return 0;
+}
+
+static size_t next_po2(size_t sz)
+{
+ assert(sz > 0);
+ return (size_t)(1 << (64 - __builtin_clzll((unsigned long long)sz)));
+}
+
+static int ensure_fits(vl_vector *vec, size_t toadd)
+{
+ if (vec->len + toadd <= vec->cap) {
+ return 0;
+ }
+
+ size_t newcap = next_po2(vec->len + toadd);
+ return vec_grow(vec, newcap);
+}
+
+/* will abort if realloc fails to shrink the allocation (should never happen) */
+void vl_vector_shrink_to_size(vl_vector *vec)
+{
+ if (vec_grow(vec, vec->len) < 0) abort();
+}
+
+#define vl_newvec(_t) vl_vector_new(sizeof(_t))
+#define vl_newvec_ex(_t, _ic) vl_vector_new_ex(sizeof(_t), _ic)
+
+int vl_vector_push(vl_vector *vec, void *data)
+{
+ return vl_vector_push_vec(vec, data, 1);
+}
+
+int vl_vector_push_vec(vl_vector *vec, void *data, size_t n)
+{
+ if (ensure_fits(vec, n) < 0) {
+ return -1;
+ }
+
+ unsigned char *d = vec->data;
+ memcpy(d + (vec->len * vec->sz), data, n * vec->sz);
+ vec->len += n;
+ return 0;
+}
+
+int vl_vector_push_2d(vl_vector *vec, void **data, size_t n)
+{
+ if (ensure_fits(vec, n) < 0) {
+ return -1;
+ }
+
+ unsigned char *d = vec->data;
+ for (size_t i = 0; i < n; ++i) {
+ memcpy(d + ((vec->len + i) * vec->sz), data[i], vec->sz);
+ }
+
+ vec->len += n;
+
+ return 0;
+}
+
+size_t vl_vector_size(const vl_vector *vec)
+{
+ return vec->len;
+}
+
+void *vl_vector_values(const vl_vector *vec, size_t *psz)
+{
+ if (psz) *psz = vec->len;
+ return vec->data;
+}
diff --git a/main.c b/main.c
index 93fe7e2..1f1709f 100644
--- a/main.c
+++ b/main.c
@@ -1,8 +1,15 @@
-#include <locale.h> /* for setlocale */
-#include <stdio.h>
#include "log.h"
#include "arena.h"
#include "net.h"
+#include "sha1.h"
+#include "vector.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h> /* for setlocale */
+#include <jansson.h>
+#include <libgen.h>
int main(void)
{
@@ -10,12 +17,93 @@ int main(void)
vl_log_setlevel(LOG_TRACE);
vl_arena *arena = vl_arena_new(8192);
- int ret = vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", "manifest.json");
+ vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", "manifest.json");
+ vl_arena_reset(arena);
+
+ vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", "runtime-manifest.json");
vl_arena_reset(arena);
- ret = vl_net_ensure_cached(arena, "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", "runtime-manifest.json");
- printf("%d\n", ret);
+ json_error_t err;
+ json_t *manifest = json_load_file("manifest.json", 0, &err), *ver;
+ size_t idx;
+ if (!manifest) {
+ vl_error("bad: %s", err.text);
+ exit(EXIT_FAILURE);
+ }
+
+ json_t *versions = json_object_get(manifest, "versions");
+
+ json_array_foreach(versions, idx, ver) {
+ const char *verid;
+ const char *url;
+ const char *sha1_hex;
+ json_unpack(ver, "{s:s,s:s,s:s}", "id", &verid, "url", &url, "sha1", &sha1_hex);
+ if (!strcmp(verid, "1.8.9") && url) {
+ vl_sha1 hash;
+ vl_sha1_decode(hash, sha1_hex);
+ vl_net_ensure_verified(url, "1.8.9.json", VERIFY_SHA1, hash);
+ }
+ }
+
+ json_decref(manifest);
+
+ json_t *oneeightnine = json_load_file("1.8.9.json", 0, &err);
+ if (!oneeightnine) {
+ vl_error("bad ver: %s", err.text);
+ exit(EXIT_FAILURE);
+ }
+
+ json_t *libs = json_object_get(oneeightnine, "libraries"), *lib;
+ vl_vector *vec = vl_newvec(struct vl_download_job);
+
+ json_array_foreach(libs, idx, lib) {
+ const char *url;
+ const char *path;
+ json_int_t sz;
+ const char *sha1hex;
+ size_t sha1hexlen;
+
+ struct vl_download_job tjob;
+
+ if (json_unpack(lib, "{s:{s:{s:s,s:s,s:I,s:s%}}}", "downloads", "artifact", "url", &url, "path", &path, "size", &sz, "sha1", &sha1hex, &sha1hexlen) < 0) {
+ continue;
+ }
+
+ path = basename(vl_arena_strdup(arena, path));
+
+ tjob.url = url;
+ tjob.opath = path;
+ tjob.expect_len = (size_t)sz;
+
+ if (sha1hexlen != VL_SHA1_DIGEST_HEX_STRLEN || vl_sha1_decode(tjob.expect_hash, sha1hex) < 0) {
+ vl_error("bad expect hash %s", sha1hex);
+ continue;
+ }
+
+ tjob.verify_flags = VERIFY_SIZE | VERIFY_SHA1;
+
+ if (vl_net_verify(tjob.opath, VERIFY_SIZE | VERIFY_SHA1, tjob.expect_len, tjob.expect_hash) == NET_OK) {
+ vl_trace("fine %s", tjob.opath);
+ continue;
+ }
+
+ if (vl_vector_push(vec, &tjob) < 0) {
+ abort();
+ }
+ }
+
+ size_t njobs;
+ struct vl_download_job *jobs = vl_vector_values(vec, &njobs);
+ printf("%d\n", vl_net_download_all(jobs, njobs, 8));
+
+ for (size_t n = 0; n < njobs; ++n) {
+ printf("job %s status: %u\n", jobs[n].url, jobs[n].status);
+ }
+
+ json_decref(oneeightnine);
+ vl_vector_free(vec);
vl_arena_free(arena);
+
return 0;
}