diff options
Diffstat (limited to 'src/version.c')
| -rw-r--r-- | src/version.c | 394 |
1 files changed, 375 insertions, 19 deletions
diff --git a/src/version.c b/src/version.c index 9eb7d0e..a1a5e5c 100644 --- a/src/version.c +++ b/src/version.c @@ -5,6 +5,7 @@ #include "macros.h" #include "endian.h" #include "command.h" +#include "downloadpool.h" #include <curl/curl.h> #include <jansson.h> @@ -13,16 +14,17 @@ #include <string.h> #include <stdint.h> #include <alloca.h> +#include <sys/stat.h> #include <unistd.h> #include <pcre2.h> const char *const l2_version__messages[] = { "Success", - "Malformed version manifest", - "Version manifest JSON error", + "Malformed version", + "Version JSON error", "Allocation failed", "OS error", - "Error downloading version manifest", + "Error downloading version", "Version not found", "Max recursion depth exceeded", NULL @@ -571,6 +573,8 @@ int l2_version_check_integrity(FILE *fp, l2_sha1_digest_t *digest, size_t sz) l2_sha1_digest_t rdigest; l2_sha1_state_t st; + if (!digest && sz == 0) return 1; + l2_sha1_init(&st); while ((nread = fread(buf, 1, VER_READBUF_SZ, fp)) > 0) { @@ -595,30 +599,376 @@ unsigned l2_version__collect_libraries(json_t *jlibs, l2_subst_t *subst, struct void l2_version__free_libraries(struct l2_version_library *libs); int l2_version__get_library_artifact_path(json_t *lib, l2_subst_t *subst, const char *classifier, char **path); -unsigned l2_version_download_libraries(json_t *jlibraries, l2_version_feature_match_proc_t *feature_matcher) +int l2_version__library_should_download(struct l2_version_library *lib); + +struct l2_version__library_dl_data { + l2_sha1_state_t st; + size_t sz; + + FILE *ofile; + struct l2_version_library *lib; + struct l2_version__library_dl_data *next; +}; + +size_t l2_version__lib_writecb(char *ptr, size_t size, size_t nmemb, void *user) { - struct l2_version_library *plib = NULL; - l2_subst_t *sp; + struct l2_version__library_dl_data *data = user; + size_t realsz = size * nmemb; + + if (fwrite(ptr, size, nmemb, data->ofile) < nmemb) { + CMD_DEBUG("Error writing to file %s", data->lib->fullpath); + return CURL_WRITEFUNC_ERROR; + } + + data->sz += realsz; + l2_sha1_update(&data->st, ptr, realsz); + return realsz; +} + +int l2_version__dlpool_libs_init(CURL *curl, void *user) +{ + struct l2_version__library_dl_data *data = user; + + l2_sha1_init(&data->st); + + if (l2_launcher_mkdir_parents_ex(data->lib->fullpath, 1) < 0) { + CMD_WARN("Failed to make directory for libraries: %s", strerror(errno)); + return -1; + } + + data->ofile = fopen(data->lib->fullpath, "wb"); + if (!data->ofile) { + CMD_WARN("Failed to open %s for writing: %s", data->lib->fullpath, strerror(errno)); + return -1; + } - l2_subst_init(&sp); - l2_subst_add(sp, "arch", L2SU_LAUNCHER_ARCH_BITS); + /* curl */ + curl_easy_setopt(curl, CURLOPT_URL, data->lib->url); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, data); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &l2_version__lib_writecb); - unsigned u = l2_version__collect_libraries(jlibraries, sp, &plib, feature_matcher); - char hexdig[L2_SHA1_HEX_STRLEN + 1]; - for (struct l2_version_library *lib = plib; lib; lib = lib->next) { - if (lib->has_digest) { - l2_sha1_digest_to_hex(&lib->digest, hexdig); + return 0; +} + +int l2_version__dlpool_libs_complete(CURL *curl, CURLcode code, void *user) +{ + long rescode; + struct l2_version__library_dl_data *data = user; + + fclose(data->ofile); + data->ofile = NULL; + + if (code != CURLE_OK) { + CMD_WARN("Failed to download %s: %s", data->lib->url, curl_easy_strerror(code)); + return -1; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rescode); + if (rescode / 100 != 2) { + CMD_WARN("Failed to download %s: server responded with %ld", data->lib->url, rescode); + return -1; + } + + if (data->lib->has_digest) { + l2_sha1_digest_t dig; + l2_sha1_finalize(&data->st, &dig); + if (l2_sha1_digest_compare(&dig, &data->lib->digest)) { + char expected[L2_SHA1_HEX_STRLEN + 1], got[L2_SHA1_HEX_STRLEN + 1]; + l2_sha1_digest_to_hex(&dig, got); + l2_sha1_digest_to_hex(&data->lib->digest, expected); + + CMD_WARN("Failed to download %s: SHA1 mismatch (expect: %s, got: %s)", data->lib->url, expected, got); + + if (unlink(data->lib->fullpath) < 0) { + CMD_WARN("Failed to delete %s: %s", data->lib->fullpath, strerror(errno)); + } + + return -1; } + } + + if (data->lib->size > 0 && data->sz != data->lib->size) { + CMD_WARN("Failed to download %s: size mismatch (expect: %zu bytes, got: %zu bytes)", data->lib->fullpath, data->lib->size, data->sz); - CMD_DEBUG("Library: %s url: %s dig: %s", lib->artifactpath, lib->url, lib->has_digest ? hexdig : "(none)"); - if (lib->extract.extracttype != EXTRACT_NONE) { - CMD_DEBUG("Extract: %s", lib->extract.extracttype == EXTRACT_NEW ? "new" : "old"); + if (unlink(data->lib->fullpath) < 0) { + CMD_WARN("Failed to delete %s: %s", data->lib->fullpath, strerror(errno)); } + return -1; } - l2_version__free_libraries(plib); - l2_subst_free(sp); - return u; + CMD_DEBUG("Downloaded library %s.", data->lib->url); + return 0; +} + +unsigned l2_version__actually_download_libraries(struct l2_version_library *libs) +{ + unsigned res = VERSION_SUCCESS; + struct l2_version__library_dl_data *head = NULL, *tail = NULL; + l2_dlpool_t *pool = NULL; + int dlres; + + pool = l2_dlpool_init(&l2_version__dlpool_libs_init, &l2_version__dlpool_libs_complete); + if (!pool) { + res = VERSION_EALLOC; + goto cleanup; + } + + for (struct l2_version_library *cur = libs; cur; cur = cur->next) { + dlres = l2_version__library_should_download(cur); + if (dlres < 0) { + res = VERSION_EDOWNLOAD; + goto cleanup; + } + + if (!dlres) continue; + + struct l2_version__library_dl_data *curdata = calloc(1, sizeof(struct l2_version__library_dl_data)); + if (!curdata) { + res = VERSION_EALLOC; + goto cleanup; + } + + curdata->lib = cur; + + if ((dlres = l2_dlpool_add(pool, curdata)) < 0) { + free(curdata); + res = VERSION_EALLOC; + goto cleanup; + } else if (dlres == 0) { + free(curdata); + res = VERSION_EDOWNLOAD; + goto cleanup; + } + + if (tail) { + tail->next = curdata; + tail = curdata; + } else { + head = tail = curdata; + } + } + + if (head != NULL) { + if (l2_dlpool_perform(pool) < 0) { + CMD_WARN0("Error downloading libraries."); + res = VERSION_EDOWNLOAD; + } else { + CMD_INFO0("Successfully downloaded libraries."); + res = VERSION_SUCCESS; + } + } else { + CMD_INFO0("No libraries to download."); + res = VERSION_SUCCESS; + } + +cleanup: + for (struct l2_version__library_dl_data *cur = head, *temp; cur; cur = temp) { + temp = cur->next; + + if (cur->ofile) { + fclose(cur->ofile); + if (unlink(cur->lib->fullpath) < 0) { + CMD_WARN("Error deleting partially downloaded file %s: %s", cur->lib->fullpath, strerror(errno)); + } + } + free(cur); + } + + if (pool) l2_dlpool_cleanup(pool); + return res; +} + +int l2_version__library_should_download(struct l2_version_library *lib) +{ + FILE *lfile = fopen(lib->fullpath, "rb"); + + if (!lfile) { + if (errno == ENOENT) { + return 1; + } else { + CMD_DEBUG("Failed to open %s for reading: %s", lib->fullpath, strerror(errno)); + return -1; + } + } + + int res = l2_version_check_integrity(lfile, lib->has_digest ? &lib->digest : NULL, lib->size); + fclose(lfile); + + switch (res) { + case 0: + CMD_DEBUG("Downloading library %s, the SHA1 or size doesn't match.", lib->url); + return 1; + case 1: + CMD_DEBUG("Not downloading library %s.", lib->url); + return 0; + default: + return res; + } +} + +struct l2_version__integrity_data { + char *baseurl; + struct l2_version_library *lib; /* here for convenience */ + char digest_hex[L2_SHA1_HEX_STRLEN + 1]; + size_t len; +}; + +size_t l2_version__integ_writecb(char *ptr, size_t size, size_t nmemb, void *user) +{ + /* NOTE: this function does not check what it's reading (or anything after the 40th character). + * It could be argued that this is a problem (the file may have stuff after the hash), but idc LOL. */ + + struct l2_version__integrity_data *integ = user; + size_t realsz = size * nmemb; + size_t rem = L2_SHA1_HEX_STRLEN - integ->len; + if (rem <= 0) return realsz; + + size_t copylen = L2_BADMIN(rem, realsz); + + memcpy(integ->digest_hex, ptr, copylen); + integ->len += copylen; + return realsz; +} + +int l2_version__integ_dl_init(CURL *curl, void *user) +{ + struct l2_version_library *lib = user; + char *fullurl; + size_t sz; + + L2_ASPRINTF(fullurl, sz, "%s.sha1", lib->url); + + curl_easy_setopt(curl, CURLOPT_URL, fullurl); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, user); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &l2_version__integ_writecb); + + CMD_DEBUG("Downloading signature for library: %s", fullurl); + + return 0; +} + +int l2_version__integ_dl_finish(CURL *curl, CURLcode code, void *user) +{ + struct l2_version__integrity_data *integ = user; + long res; + + if (code != CURLE_OK) { + CMD_WARN("Error downloading integrity (%s): %s", integ->baseurl, curl_easy_strerror(code)); + return -1; /* vanilla considers this non-fatal */ + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res); + if ((res / 100) != 2) { + CMD_WARN("Error downloading integrity (%s): server returned HTTP %ld", integ->baseurl, res); + CMD_WARN0("Proceeding under the assumption that this resource is not checksummed. Integrity will not be checked."); + return 0; + } + + if (l2_sha1_digest_from_hex(&integ->lib->digest, integ->digest_hex) < 0) { + CMD_WARN("Error downloading integrity (%s): %s", integ->baseurl, "invalid sha1 digest returned"); + return -1; /* vanilla considers this non-fatal (though the comparison will always* fail) */ + } + + CMD_DEBUG("Retrieved SHA1 information for %s", integ->baseurl); + integ->lib->has_digest = true; + return 0; +} + +unsigned l2_version__retrieve_library_integ(struct l2_version_library *libs) +{ + unsigned res = VERSION_SUCCESS; + struct l2_version__integrity_data *integ = NULL; + l2_dlpool_t *pool = NULL; + size_t nlibs = 0; + + for (struct l2_version_library *cur = libs; cur; cur = cur->next) { + if (!cur->has_digest) ++nlibs; + } + + if (!nlibs) return VERSION_SUCCESS; + + integ = calloc(nlibs, sizeof(struct l2_version__integrity_data)); + if (!integ) { + res = VERSION_EALLOC; + goto cleanup; + } + + pool = l2_dlpool_init(&l2_version__integ_dl_init, &l2_version__integ_dl_finish); + if (!pool) { + res = VERSION_EALLOC; + goto cleanup; + } + + nlibs = 0; + int addres; + for (struct l2_version_library *cur = libs; cur; cur = cur->next) { + if (cur->has_digest) continue; + + integ[nlibs].lib = cur; + integ[nlibs].baseurl = cur->url; + if ((addres = l2_dlpool_add(pool, integ + nlibs)) < 0) { + res = VERSION_EALLOC; + goto cleanup; + } else if (addres == 0) { + integ[nlibs].lib = NULL; /* signal that this one is invalid */ + continue; + } + + ++nlibs; + } + + if (l2_dlpool_perform(pool) < 0) { + res = VERSION_EDOWNLOAD; + goto cleanup; + } + + free(integ); + l2_dlpool_cleanup(pool); + return VERSION_SUCCESS; + +cleanup: + free(integ); + if (pool) l2_dlpool_cleanup(pool); + return res; +} + +unsigned l2_version_download_libraries(json_t *jlibraries, l2_version_feature_match_proc_t *feature_matcher) +{ + struct l2_version_library *plib = NULL; + unsigned ret = VERSION_EDOWNLOAD; + l2_subst_t *sp = NULL; + + if (l2_subst_init(&sp) < 0) { + ret = VERSION_EALLOC; + goto cleanup; + } + + if (l2_subst_add(sp, "arch", L2SU_LAUNCHER_ARCH_BITS) < 0) { + ret = VERSION_EALLOC; + goto cleanup; + } + + ret = l2_version__collect_libraries(jlibraries, sp, &plib, feature_matcher); + if (ret != VERSION_SUCCESS) goto cleanup; + + ret = l2_version__retrieve_library_integ(plib); + if (ret != VERSION_SUCCESS) { + CMD_WARN0("Failed to retrieve integrity information on libraries for which it was missing!"); + goto cleanup; + } + + ret = l2_version__actually_download_libraries(plib); + if (ret != VERSION_SUCCESS) { + CMD_WARN0("Library downloads failed."); + goto cleanup; + } + + /* TODO: return libraries */ + +cleanup: + if (sp) l2_subst_free(sp); + if (plib) l2_version__free_libraries(plib); + return ret; } unsigned l2_version__library_get_classifier(json_t *lib, const char **class) @@ -661,6 +1011,11 @@ unsigned l2_version__gen_one_library(json_t *lib, l2_subst_t *subst, struct l2_v goto cleanup; } + if (!(retlib->fullpath = l2_launcher_sprintf_alloc("%s/libraries/%s", l2_state.paths.data, retlib->artifactpath))) { + res = VERSION_EALLOC; + goto cleanup; + } + if (classifier) { retlib->extract.extracttype = EXTRACT_OLD; } else if (has_class) { @@ -848,6 +1203,7 @@ void l2_version__free_libraries(struct l2_version_library *libs) next = libs->next; free(libs->artifactpath); + free(libs->fullpath); free(libs->url); excludecur = libs->extract.exclude; |
