#include "runtime.h" #include "config.h" #include "digest/digest.h" #include "macros.h" #include "l2su.h" #include "endian.h" #include "downloadpool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define L2_RUNTIME__MANIFEST_EXP_TIME ((time_t)120) int l2_runtime__read_manifest_info(l2_sha1_digest_t *digest, size_t *sz); int l2_runtime__write_manifest_info(const l2_sha1_digest_t *digest, size_t sz, time_t now); int l2_runtime__download_manifest(json_t **manifest); int l2_runtime__load_local_manifest(const l2_sha1_digest_t *expect_digest, size_t expect_size, json_t **manifest); int l2_runtime_load_manifest(json_t **manifest) { int res; l2_sha1_digest_t exp_digest; size_t psize; if ((res = l2_runtime__read_manifest_info(&exp_digest, &psize)) < 0) { return res; } else if (res) { if ((res = l2_runtime__load_local_manifest(&exp_digest, psize, manifest)) < 0) { return res; } else if (res) { return 0; } } return l2_runtime__download_manifest(manifest); } int l2_runtime__read_manifest_info(l2_sha1_digest_t *digest, size_t *sz) { char *path; size_t pathlen; char dgbytes[L2_SHA1_DIGEST_BYTES]; uint64_t outsz = 0; int retval = -1; L2_ASPRINTF(path, pathlen, "%s/runtime/.manifestinfo", l2_state.paths.data); FILE *infofp = fopen(path, "rb"); if (!infofp) { if (errno == ENOENT) { CMD_DEBUG0("Downloading manifest: manifest info not found!"); return 0; } else { CMD_WARN("Failed to check if the runtime manifest should be downloaded: %s", strerror(errno)); return -1; } } errno = 0; /* fread may not set errno on partial reads... */ if (fread(dgbytes, L2_SHA1_DIGEST_BYTES, 1, infofp) < 1) { CMD_WARN("Failed to read digest from runtime manifest info: %s", strerror(errno)); goto cleanup; } if (fread(&outsz, sizeof(uint64_t), 1, infofp) < 1) { CMD_WARN("Failed to read size from runtime manifest info: %s", strerror(errno)); goto cleanup; } uint64_t rdtime; if (fread(&rdtime, sizeof(uint64_t), 1, infofp) < 1) { CMD_WARN("Failed to read time from runtime manifest info: %s", strerror(errno)); goto cleanup; } rdtime = l2_betoh64(rdtime); if (time(NULL) - rdtime > L2_RUNTIME__MANIFEST_EXP_TIME) { CMD_DEBUG0("Redownloading runtime manifest (it expired hehe)"); retval = 0; goto cleanup; } l2_sha1_digest_from_buffer(digest, dgbytes); *sz = (size_t)l2_betoh64(outsz); fclose(infofp); infofp = NULL; return 1; cleanup: if (infofp) fclose(infofp); return retval; } int l2_runtime__write_manifest_info(const l2_sha1_digest_t *digest, size_t sz, time_t tm) { char *path; size_t pathlen; uint64_t outsz = l2_htobe64((uint64_t)sz); L2_ASPRINTF(path, pathlen, "%s/runtime/.manifestinfo", l2_state.paths.data); FILE *infofp = fopen(path, "wb"); if (!infofp) { CMD_WARN("Failed to write runtime manifest information: %s", strerror(errno)); return -1; } char dgbytes[L2_SHA1_DIGEST_BYTES]; l2_sha1_digest_to_buffer(digest, dgbytes); errno = 0; if (fwrite(dgbytes, L2_SHA1_DIGEST_BYTES, 1, infofp) < 1) { CMD_WARN("Failed to write digest to runtime manifest info: %s", strerror(errno)); goto cleanup; } if (fwrite(&outsz, sizeof(uint64_t), 1, infofp) < 1) { CMD_WARN("Failed to write size to runtime manifest info: %s", strerror(errno)); goto cleanup; } uint64_t wrtime = l2_htobe64((uint64_t)tm); if (fwrite(&wrtime, sizeof(uint64_t), 1, infofp) < 1) { CMD_WARN("Failed to write time to runtime manifest info: %s", strerror(errno)); goto cleanup; } fclose(infofp); infofp = NULL; return 0; cleanup: if (infofp) fclose(infofp); return -1; } struct l2_runtime__dlinfo { l2_sha1_state_t st; char *data; size_t len; size_t cap; FILE *ofp; }; size_t l2_runtime__manifest_dlcb(char *buf, size_t size, size_t nmemb, void *user) { struct l2_runtime__dlinfo *info = user; size_t realsz = size * nmemb; l2_sha1_update(&info->st, buf, realsz); if (fwrite(buf, size, nmemb, info->ofp) < nmemb) { return CURL_WRITEFUNC_ERROR; } if (info->len + realsz > info->cap) { size_t newcap = 16; while (newcap && info->len + realsz > newcap) newcap <<= 1; if (!newcap) return CURL_WRITEFUNC_ERROR; char *newbuf = realloc(info->data, newcap); if (!newbuf) { CMD_DEBUG("Failed to download runtime manifest: could not grow buffer from %zu to %zu bytes!", info->cap, newcap); return CURL_WRITEFUNC_ERROR; } info->cap = newcap; info->data = newbuf; } memcpy(info->data + info->len, buf, realsz); info->len += realsz; return realsz; } int l2_runtime__download_manifest(json_t **manifest) { char errbuf[CURL_ERROR_SIZE]; CURL *pc = curl_easy_init(); if (!pc) return -1; struct l2_runtime__dlinfo dlinfo; char *path; size_t pathlen; L2_ASPRINTF(path, pathlen, "%s/runtime/manifest.json", l2_state.paths.data); errno = 0; if (l2_launcher_mkdir_parents_ex(path, 1) < 0) { CMD_WARN("Failed to create directories for runtime manifest: %s", strerror(errno)); return -1; } memset(errbuf, 0, sizeof(errbuf)); memset(&dlinfo, 0, sizeof(struct l2_runtime__dlinfo)); l2_sha1_init(&dlinfo.st); dlinfo.ofp = fopen(path, "w"); if (!dlinfo.ofp) { CMD_WARN("Failed to open %s for writing: %s", path, strerror(errno)); return -1; } curl_easy_setopt(pc, CURLOPT_USERAGENT, L2_USER_AGENT); curl_easy_setopt(pc, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(pc, CURLOPT_URL, L2_URL_META_RUNTIME_MANIFEST); curl_easy_setopt(pc, CURLOPT_WRITEFUNCTION, &l2_runtime__manifest_dlcb); curl_easy_setopt(pc, CURLOPT_WRITEDATA, &dlinfo); CURLcode code = curl_easy_perform(pc); if (code != CURLE_OK) { CMD_WARN("Failed to download runtime manifest: CURL error: %s: %s", curl_easy_strerror(code), errbuf); goto cleanup; } long httpres; curl_easy_getinfo(pc, CURLINFO_RESPONSE_CODE, &httpres); if (httpres / 100 != 2) { CMD_WARN("Failed to download runtime manifest: server responded with %ld", httpres); goto cleanup; } curl_easy_cleanup(pc); pc = NULL; /* we must ultimately trust what we download (sad) */ json_error_t err; json_t *myjson = json_loadb(dlinfo.data, dlinfo.len, JSON_REJECT_DUPLICATES, &err); if (!myjson) { CMD_WARN("Failed to parse runtime manifest JSON: %s", err.text); goto cleanup; } free(dlinfo.data); dlinfo.data = NULL; fclose(dlinfo.ofp); dlinfo.ofp = NULL; l2_sha1_digest_t rddg; l2_sha1_finalize(&dlinfo.st, &rddg); if (l2_runtime__write_manifest_info(&rddg, dlinfo.len, time(NULL)) < 0) { CMD_WARN("Failed to write runtime info: %s", strerror(errno)); } *manifest = myjson; return 0; cleanup: if (pc) curl_easy_cleanup(pc); if (dlinfo.ofp) { fclose(dlinfo.ofp); if (unlink(path) < 0) { CMD_WARN("Failed to delete runtime manifest %s: %s", path, strerror(errno)); } } free(dlinfo.data); return -1; } int l2_runtime__load_local_manifest(const l2_sha1_digest_t *expect_digest, size_t expect_size, json_t **manifest) { int res; char *manifestpath; size_t manifestsz; L2_ASPRINTF(manifestpath, manifestsz, "%s/runtime/manifest.json", l2_state.paths.data); FILE *fp = fopen(manifestpath, "r"); if (!fp) { if (errno == ENOENT) { CMD_DEBUG0("Could not find local manifest!"); return 0; } else { CMD_WARN("Failed to open runtime manifest for reading: %s", strerror(errno)); return -1; } } /* check integrity */ #define L2_RUNTIME__INFO_READBUF_SZ (4096) uint8_t buf[L2_RUNTIME__INFO_READBUF_SZ]; size_t nread; size_t nread_total = 0; l2_sha1_state_t st; l2_sha1_digest_t rddg; l2_sha1_init(&st); while ((nread = fread(buf, 1, L2_RUNTIME__INFO_READBUF_SZ, fp))) { l2_sha1_update(&st, buf, nread); nread_total += nread; } #undef L2_RUNTIME__INFO_READBUF_SZ l2_sha1_finalize(&st, &rddg); if (l2_sha1_digest_compare(expect_digest, &rddg)) { char expstr[L2_SHA1_HEX_STRLEN + 1], gotstr[L2_SHA1_HEX_STRLEN + 1]; l2_sha1_digest_to_hex(expect_digest, expstr); l2_sha1_digest_to_hex(&rddg, gotstr); CMD_DEBUG("Not using saved runtime manifest: SHA1 mismatch (expected: %s, got: %s)", expstr, gotstr); res = 0; goto unlink_cleanup; } if (expect_size != nread_total) { CMD_DEBUG("Not using saved runtime manifest: size mismatch (expected: %zu bytes, got %zu bytes)", expect_size, nread_total); res = 0; goto unlink_cleanup; } rewind(fp); json_error_t err; json_t *rmanifest = json_loadf(fp, JSON_REJECT_DUPLICATES, &err); if (!rmanifest) { CMD_WARN("JSON error loading runtime manifest: %s", err.text); res = -1; goto cleanup; } *manifest = rmanifest; res = 1; goto cleanup; unlink_cleanup: if (unlink(manifestpath) < 0) { CMD_WARN("Could not delete modified runtime manifest: %s", strerror(errno)); } cleanup: if (fp) fclose(fp); return res; } int l2_runtime__load_component_manifest(const char *compname, const l2_sha1_digest_t *expect_digest, size_t expect_size, json_t **compmanifest); int l2_runtime__download_component_manifest(const char *url, const char *compname, const l2_sha1_digest_t *expect_digest, size_t expect_size, json_t **compmanifest); int l2_runtime__read_component_manifest_info(const char *compname, char **version, time_t *reltime); int l2_runtime__save_component_manifest_info(const char *compname, const char *version, time_t reltime); int l2_runtime__check_runtime_ftw(const char *fpath, const struct stat *sb, int typeflag, struct l2_ftw_data *ftwbuf); int l2_runtime__ensure_runtime_files(const char *basepath, json_t *jfiles); int l2_runtime_install_component(json_t *manifest, const char *component) { json_t *jrtcomps; if (json_unpack(manifest, "{s:{s:o}}", L2SU_JRE_ARCH, component, &jrtcomps) < 0) { return -1; } if (!json_is_array(jrtcomps)) { return -1; } if (!json_array_size(jrtcomps)) { CMD_ERROR("The JRE %s is not supported on your architecture (%s)!", component, L2SU_JRE_ARCH); return 0; } json_t *comp = json_array_get(jrtcomps, 0); const char *digest, *url; json_int_t manifestsz; if (json_unpack(comp, "{s:{s:s, s:I, s:s}}", "manifest", "sha1", &digest, "size", &manifestsz, "url", &url) < 0) { CMD_MSG0("bug", "Failed to parse java runtime manifest!"); return -1; } l2_sha1_digest_t expect_digest; if (l2_sha1_digest_from_hex(&expect_digest, digest) < 0) { CMD_WARN("Runtime manifest has invalid SHA1 hash: %s", digest); return -1; } json_t *jcompmanifest = NULL; int res; if ((res = l2_runtime__load_component_manifest(component, &expect_digest, (size_t)manifestsz, &jcompmanifest)) < 0) { return -1; } else if (!res) { if ((res = l2_runtime__download_component_manifest(url, component, &expect_digest, (size_t)manifestsz, &jcompmanifest)) < 0) { return -1; } } char *jrepath; size_t jrepathlen; L2_ASPRINTF(jrepath, jrepathlen, "%s/runtime/%s/%s", l2_state.paths.data, L2SU_JRE_ARCH, component); errno = 0; if (l2_launcher_mkdir_parents(jrepath) < 0) { CMD_WARN("Failed to create directory %s: %s", jrepath, strerror(errno)); goto cleanup; } json_t *jfiles = json_object_get(jcompmanifest, "files"); if (!json_is_object(jfiles)) { CMD_WARN("Runtime manifest %s has an invalid format: no \"files\" object!", component); res = -1; goto cleanup; } /* clear the runtime directory of files/folders that should not exist */ errno = 0; if (l2_launcher_ftw(jrepath, 32, &l2_runtime__check_runtime_ftw, jfiles) < 0) { CMD_WARN("Failed to check JRE files in %s: %s", jrepath, strerror(errno)); goto cleanup; } if (l2_runtime__ensure_runtime_files(jrepath, jfiles) < 0) { goto cleanup; } json_decref(jcompmanifest); return 0; cleanup: if (jcompmanifest) json_decref(jcompmanifest); return -1; } struct l2_runtime__file_info { /* set at init */ char *lzmaurl, *rawurl; char *path; l2_sha1_digest_t expect_digest; size_t expect_size; l2_sha1_state_t digest_state; size_t nread_total; FILE *ofile; bool lzma_active; lzma_stream lz; struct l2_runtime__file_info *next; }; void l2_runtime__file_info_cleanup(struct l2_runtime__file_info *info) { free(info->lzmaurl); free(info->rawurl); if (info->ofile) { fclose(info->ofile); if (info->path && unlink(info->path) < 0 && errno != ENOENT) { CMD_WARN("Failed to delete %s: %s", info->path, strerror(errno)); } } free(info->path); if (info->lzma_active) { lzma_end(&info->lz); } } int l2_runtime__ensure_one_file(const char *filename, json_t *file, struct l2_runtime__file_info **ptail) { l2_sha1_digest_t expect_digest; const char *expect_digest_str; json_int_t expect_size; struct l2_runtime__file_info *info = NULL; const char *raw_url = NULL; json_t *lzma_download = NULL; if (json_unpack(file, "{s:{s:{s:s, s:I, s?:s}, s?:o}}", "downloads", "raw", "sha1", &expect_digest_str, "size", &expect_size, "url", &raw_url, "lzma", &lzma_download) < 0) { CMD_DEBUG0("invalid format for runtime manifest: couldn't get downloads"); return -1; } if (l2_sha1_digest_from_hex(&expect_digest, expect_digest_str) < 0) { CMD_DEBUG("invalid format for runtime manifest: sha1 hash is nonsensical %s", expect_digest_str); return -1; } FILE *fp = fopen(filename, "rb"); if (!fp) { if (errno == ENOENT) { goto ok_we_have_to_download_the_file; } else { CMD_WARN("Cannot open %s for reading: %s", filename, strerror(errno)); return -1; } } int integres = l2_launcher_check_integrity(fp, &expect_digest, (size_t)expect_size); fclose(fp); fp = NULL; if (integres < 0) { CMD_WARN("Failed to check file integrity for %s.", filename); return -1; } else if (integres) { CMD_DEBUG("Not downloading %s, integrity info matches :)", filename); return 0; } ok_we_have_to_download_the_file: info = calloc(1, sizeof(struct l2_runtime__file_info)); if (!info) { CMD_DEBUG("allocation failed! %s %s", filename, strerror(errno)); return -1; } const char *lzma_url = NULL; if (lzma_download && json_unpack(lzma_download, "{s:s}", "url", &lzma_url) < 0) { CMD_DEBUG0("invalid format for lzma download: no url"); goto cleanup; } if (lzma_url) { info->lzmaurl = strdup(lzma_url); if (!info->lzmaurl) goto cleanup; } else { if (!raw_url) { CMD_WARN("No download for %s!", filename); goto cleanup; } info->rawurl = strdup(raw_url); } info->path = strdup(filename); if (!info->path) { goto cleanup; } info->expect_size = (size_t)expect_size; l2_sha1_digest_copy(&info->expect_digest, &expect_digest); if (*ptail) { (*ptail)->next = info; } *ptail = info; return 0; cleanup: l2_runtime__file_info_cleanup(info); free(info); return -1; } int l2_runtime__ensure_one_link(const char *filename, json_t *file) { /* TODO */ const char *target; size_t targetlen; if (json_unpack(file, "{s:s%}", "target", &target, &targetlen) < 0) { CMD_DEBUG0("bad link"); return -1; } char *buf = alloca(targetlen + 1); ssize_t nread; if ((nread = readlink(filename, buf, targetlen + 1)) < 0) { if (errno == ENOENT) { goto set_link; } CMD_WARN("Could not read link at %s: %s", filename, strerror(errno)); return -1; } buf[targetlen] = '\0'; if (targetlen == (size_t)nread && !strcmp(buf, target)) { CMD_DEBUG("link %s is good", filename); return 0; } if (unlink(filename) < 0) { CMD_WARN("Failed to delete symlink %s: %s", filename, strerror(errno)); return -1; } set_link: if (symlink(target, filename) < 0) { CMD_WARN("Failed to create symlink %s to %s: %s", filename, target, strerror(errno)); return -1; } return 0; } int l2_runtime__component_update_file(const char *fname, json_t *file, const char *basepath, struct l2_runtime__file_info **ptail) { char *path; size_t pathsz; L2_ASPRINTF(path, pathsz, "%s/%s", basepath, fname); const char *typestr; if (json_unpack(file, "{s:s}", "type", &typestr) < 0) { CMD_DEBUG0("bad type info or something idk"); return -1; } mode_t exptype = 0; if (!strcmp(typestr, "file")) { exptype = S_IFREG; } else if (!strcmp(typestr, "directory")) { exptype = S_IFDIR; } else if (!strcmp(typestr, "link")) { exptype = S_IFLNK; } else { CMD_DEBUG("unknown file type %s, %s", path, typestr); return -1; } struct stat sp; bool exists = true; if (lstat(path, &sp) < 0) { if (errno == ENOENT) { exists = false; } else { CMD_WARN("could not stat %s: %s", path, strerror(errno)); return -1; } } if (exists && (sp.st_mode & S_IFMT) != exptype) { /* should never happen besides by a race condition */ CMD_WARN("File %s has wrong type! (should be %s (%o), got %o)", path, typestr, exptype, sp.st_mode & S_IFMT); return -1; } switch (exptype) { case S_IFDIR: if (!exists && mkdir(path, 0755) < 0) { CMD_WARN("Failed to make directory %s: %s", path, strerror(errno)); return -1; } return 0; case S_IFREG: return l2_runtime__ensure_one_file(path, file, ptail); case S_IFLNK: return l2_runtime__ensure_one_link(path, file); } CMD_MSG0("bug", "expected file type was not file, link, or directory!!!!"); abort(); } const char *l2_runtime__get_lzma_error(lzma_ret lzret) { switch (lzret) { case LZMA_OK: case LZMA_STREAM_END: return NULL; case LZMA_MEM_ERROR: return "out of memory"; case LZMA_MEMLIMIT_ERROR: return "memory limit reached"; case LZMA_FORMAT_ERROR: return "LZMA/XZ format error"; case LZMA_OPTIONS_ERROR: return "invalid usage"; case LZMA_BUF_ERROR: return "file is truncated or corrupt"; case LZMA_DATA_ERROR: return "file is corrupt"; default: return "unknown error"; } } size_t l2_runtime__dl_lzma_callback(char *buf, size_t size, size_t nmemb, void *user) { struct l2_runtime__file_info *info = user; size_t realsz = size * nmemb; info->lz.total_in = 0; uint8_t outbuf[4096]; info->lz.next_in = (const uint8_t *)buf; info->lz.avail_in = realsz; do { info->lz.next_out = outbuf; info->lz.avail_out = sizeof(outbuf); lzma_ret lzret = lzma_code(&info->lz, LZMA_RUN); const char *errstr = l2_runtime__get_lzma_error(lzret); if (errstr) { CMD_ERROR("Error downloading %s: %s (error %u)", info->path, errstr, lzret); return CURL_WRITEFUNC_ERROR; } size_t ndecoded = sizeof(outbuf) - info->lz.avail_out; if (!ndecoded) return realsz; info->nread_total += ndecoded; l2_sha1_update(&info->digest_state, outbuf, ndecoded); errno = 0; if (fwrite(outbuf, 1, ndecoded, info->ofile) < ndecoded) { CMD_WARN("Partial write %s: %s", info->path, strerror(errno)); return CURL_WRITEFUNC_ERROR; } } while (info->lz.avail_in); return realsz; } size_t l2_runtime__dl_raw_callback(char *buf, size_t size, size_t nmemb, void *user) { struct l2_runtime__file_info *info = user; size_t realsz = size * nmemb; info->nread_total += realsz; l2_sha1_update(&info->digest_state, buf, realsz); errno = 0; if (fwrite(buf, size, nmemb, info->ofile) < nmemb) { CMD_WARN("Partial write %s: %s", info->path, strerror(errno)); return CURL_WRITEFUNC_ERROR; } return realsz; } int l2_runtime__dl_init(CURL *curl, void *user) { struct l2_runtime__file_info *info = user; info->ofile = fopen(info->path, "wb"); if (!info->ofile) { CMD_WARN("Failed to open %s for writing: %s", info->path, strerror(errno)); return -1; } l2_sha1_init(&info->digest_state); if (info->lzmaurl) { lzma_stream strm = LZMA_STREAM_INIT; lzma_ret lzret; memcpy(&info->lz, &strm, sizeof(lzma_stream)); switch (lzret = lzma_auto_decoder(&info->lz, UINT64_MAX, 0)) { case LZMA_OK: break; case LZMA_MEM_ERROR: CMD_WARN("Failed to initialize LZMA stream for %s: out of memory", info->path); return -1; case LZMA_OPTIONS_ERROR: CMD_WARN("Failed to initialize LZMA stream for %s: invalid argument", info->path); return -1; case LZMA_PROG_ERROR: CMD_WARN("Failed to initialize LZMA stream for %s: bug", info->path); return -1; default: CMD_WARN("Failed to initialize LZMA stream for %s: unknown error %u", info->path, lzret); return -1; } curl_easy_setopt(curl, CURLOPT_URL, info->lzmaurl); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &l2_runtime__dl_lzma_callback); info->lzma_active = true; } else { curl_easy_setopt(curl, CURLOPT_URL, info->rawurl); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &l2_runtime__dl_raw_callback); info->lzma_active = false; } curl_easy_setopt(curl, CURLOPT_USERAGENT, L2_USER_AGENT); curl_easy_setopt(curl, CURLOPT_WRITEDATA, user); return 0; } int l2_runtime__dl_complete(CURL *curl, CURLcode code, void *user) { struct l2_runtime__file_info *info = user; if (code != CURLE_OK) { CMD_WARN("Download for %s failed: CURL error: %s", info->path, curl_easy_strerror(code)); return -1; } long httpres = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpres); if (httpres / 100 != 2) { CMD_WARN("Download for %s failed: server responded with %lu", info->path, httpres); return -1; } if (info->lzma_active) { uint8_t outbuf[4096]; info->lz.next_in = NULL; info->lz.avail_in = 0; lzma_ret lzret; while (true) { info->lz.next_out = outbuf; info->lz.avail_out = sizeof(outbuf); lzret = lzma_code(&info->lz, LZMA_FINISH); const char *errstr = l2_runtime__get_lzma_error(lzret); if (errstr) { CMD_WARN("Download for %s failed: LZMA error: %s (error %u)", info->path, errstr, lzret); return -1; } size_t ndecoded = sizeof(outbuf) - info->lz.avail_out; if (!ndecoded) break; l2_sha1_update(&info->digest_state, outbuf, ndecoded); info->nread_total += ndecoded; errno = 0; if (fwrite(outbuf, 1, ndecoded, info->ofile) < ndecoded) { CMD_WARN("Download for %s failed: partial write: %s", info->path, strerror(errno)); return -1; } if (lzret == LZMA_STREAM_END) break; } lzma_end(&info->lz); info->lzma_active = false; } fclose(info->ofile); info->ofile = NULL; l2_sha1_digest_t rddg; l2_sha1_finalize(&info->digest_state, &rddg); if (l2_sha1_digest_compare(&rddg, &info->expect_digest)) { char expstr[L2_SHA1_HEX_STRLEN + 1], gotstr[L2_SHA1_HEX_STRLEN + 1]; l2_sha1_digest_to_hex(&info->expect_digest, expstr); l2_sha1_digest_to_hex(&rddg, gotstr); CMD_WARN("Download for %s failed: SHA1 mismatch (expected %s, got %s)", info->path, expstr, gotstr); goto cleanup_unlink; } if (info->nread_total != info->expect_size) { CMD_WARN("Download for %s failed: size mismatch (expected %zu bytes, got %zu bytes)", info->path, info->expect_size, info->nread_total); goto cleanup_unlink; } return 0; cleanup_unlink: if (unlink(info->path) < 0) { CMD_WARN("Failed to delete %s: %s", info->path, strerror(errno)); } return -1; } int l2_runtime__ensure_runtime_files(const char *basepath, json_t *jfiles) { int ret = 0; struct l2_runtime__file_info *info_head, *info_tail; info_head = info_tail = NULL; l2_dlpool_t *dlpool = NULL; char *fullpath = NULL; size_t len = 0; json_t *file; const char *fname; json_object_foreach(jfiles, fname, file) { if (!l2_launcher_sprintf_resize(&fullpath, &len, "%s/%s", basepath, fname)) { ret = -1; goto cleanup; } if (l2_runtime__component_update_file(fname, file, basepath, &info_tail) < 0) { ret = -1; goto cleanup; } if (info_tail && !info_head) info_head = info_tail; } if (!info_head) { CMD_INFO("No files to download for %s.", basepath); ret = 0; goto cleanup; } dlpool = l2_dlpool_init(&l2_runtime__dl_init, &l2_runtime__dl_complete); if (!dlpool) { ret = -1; goto cleanup; } for (struct l2_runtime__file_info *cur = info_head; cur; cur = cur->next) { if (l2_dlpool_add(dlpool, cur) <= 0) { ret = -1; goto cleanup; } } if (l2_dlpool_perform(dlpool) < 0) { ret = -1; } else { ret = 0; } cleanup: free(fullpath); if (dlpool) l2_dlpool_cleanup(dlpool); for (struct l2_runtime__file_info *temp; info_head; info_head = temp) { temp = info_head->next; l2_runtime__file_info_cleanup(info_head); free(info_head); } return ret; } int l2_runtime__check_runtime_ftw(const char *fpath, const struct stat *sb, int typeflag, struct l2_ftw_data *ftw) { L2_UNUSED(sb); json_t *jfiles = ftw->user; json_t *myent = json_object_get(jfiles, fpath + ftw->reloffset); int expect_type; if (json_is_object(myent)) { const char *typestr; if (json_unpack(myent, "{s:s}", "type", &typestr) < 0) { CMD_DEBUG("couldn't unpack %s (no type)", fpath); return -1; } if (!strcmp(typestr, "file")) { expect_type = L2_FTW_FILE; } else if (!strcmp(typestr, "directory")) { expect_type = L2_FTW_DIR; } else if (!strcmp(typestr, "link")) { expect_type = L2_FTW_SYMLINK; } else { CMD_WARN("Encountered file %s which has unknown type \"%s\"!", fpath, typestr); return -1; } } if (!json_is_object(myent) || (expect_type ^ typeflag) & L2_FTW_TYPEMASK) { CMD_DEBUG("File %s should not exist.", fpath); if (S_ISDIR(sb->st_mode)) { errno = 0; if (l2_launcher_rm_tree(fpath, 50) < 0) { CMD_WARN("Failed to delete directory %s (which should not exist): %s", fpath, strerror(errno)); return -1; } } else { if (unlink(fpath) < 0) { CMD_WARN("Failed to delete symlink or regular file %s (which should not exist): %s", fpath, strerror(errno)); return -1; } } return 0; } /* here, we know the type is correct and myent is an object */ if ((typeflag & L2_FTW_TYPEMASK) == L2_FTW_FILE) { int executable; if (json_unpack(myent, "{s:b}", "executable", &executable) < 0) { CMD_DEBUG("couldn't unpack %s (no executable)", fpath); return -1; } unsigned desired_mode; if (executable) { desired_mode = (sb->st_mode & 07777) | 0111; } else { desired_mode = (sb->st_mode & 07777) & ~0111; } if ((sb->st_mode & 07777) != desired_mode && chmod(fpath, desired_mode) < 0) { CMD_WARN("Failed chmod(%s, %#o): %s", fpath, desired_mode, strerror(errno)); return -1; } } return 0; } int l2_runtime__load_component_manifest(const char *compname, const l2_sha1_digest_t *expect_digest, size_t expect_size, json_t **compmanifest) { int retval = -1; char *comppath; size_t complen; L2_ASPRINTF(comppath, complen, "%s/runtime/%s/%s.json", l2_state.paths.data, L2SU_JRE_ARCH, compname); FILE *fp = fopen(comppath, "r"); if (!fp) { if (errno == ENOENT) { return 0; } else { CMD_WARN("Failed to load local %s: %s", compname, strerror(errno)); return -1; } } #define L2_RUNTIME__COMP_READBUF_SZ (4096) char readbuf[L2_RUNTIME__COMP_READBUF_SZ]; size_t nread; size_t nread_total = 0; l2_sha1_state_t st; l2_sha1_digest_t rddg; l2_sha1_init(&st); while ((nread = fread(readbuf, 1, L2_RUNTIME__COMP_READBUF_SZ, fp)) < L2_RUNTIME__COMP_READBUF_SZ) { l2_sha1_update(&st, readbuf, nread); nread_total += nread; } #undef L2_RUNTIME__COMP_READBUF_SZ l2_sha1_finalize(&st, &rddg); if (l2_sha1_digest_compare(expect_digest, &rddg)) { retval = 0; goto cleanup; } if (nread_total != expect_size) { retval = 0; goto cleanup; } rewind(fp); json_error_t err; json_t *js = json_loadf(fp, JSON_REJECT_DUPLICATES, &err); if (!js) { CMD_WARN("Failed to parse local %s: %s", comppath, err.text); goto cleanup; } *compmanifest = js; retval = 1; cleanup: if (fp) fclose(fp); return retval; } /* TODO: refactor this I feel like I have written this function 100 times */ int l2_runtime__download_component_manifest(const char *url, const char *compname, const l2_sha1_digest_t *expect_digest, size_t expect_size, json_t **compmanifest) { CURL *pc = NULL; char errbuf[CURL_ERROR_SIZE]; char *comppath; size_t complen; bool cleanup_delete = false; L2_ASPRINTF(comppath, complen, "%s/runtime/%s/%s.json", l2_state.paths.data, L2SU_JRE_ARCH, compname); if (l2_launcher_mkdir_parents_ex(comppath, 1) < 0) { CMD_WARN("Failed to create directories to download %s manifest", compname); return -1; } struct l2_runtime__dlinfo dlinfo; memset(&dlinfo, 0, sizeof(struct l2_runtime__dlinfo)); memset(errbuf, 0, sizeof(errbuf)); l2_sha1_init(&dlinfo.st); dlinfo.ofp = fopen(comppath, "w"); if (!dlinfo.ofp) { CMD_WARN("Failed to open %s for writing: %s", comppath, strerror(errno)); goto cleanup; } pc = curl_easy_init(); if (!pc) { CMD_WARN("Failed to initialize CURL for downloading %s!", compname); goto cleanup; } curl_easy_setopt(pc, CURLOPT_USERAGENT, L2_USER_AGENT); curl_easy_setopt(pc, CURLOPT_URL, url); curl_easy_setopt(pc, CURLOPT_WRITEFUNCTION, &l2_runtime__manifest_dlcb); curl_easy_setopt(pc, CURLOPT_WRITEDATA, &dlinfo); curl_easy_setopt(pc, CURLOPT_ERRORBUFFER, errbuf); CURLcode code; if ((code = curl_easy_perform(pc)) != CURLE_OK) { CMD_WARN("Failed to download %s manifest: %s: %s", compname, curl_easy_strerror(code), errbuf); goto cleanup; } long httpres = 0; curl_easy_getinfo(pc, CURLINFO_RESPONSE_CODE, &httpres); if (httpres / 100 != 2) { CMD_WARN("Failed to download %s manifest: server responded with %ld", compname, httpres); goto cleanup; } fclose(dlinfo.ofp); dlinfo.ofp = NULL; curl_easy_cleanup(pc); pc = NULL; l2_sha1_digest_t rddg; l2_sha1_finalize(&dlinfo.st, &rddg); if (l2_sha1_digest_compare(&rddg, expect_digest)) { char expstr[L2_SHA1_HEX_STRLEN + 1], gotstr[L2_SHA1_HEX_STRLEN + 1]; l2_sha1_digest_to_hex(expect_digest, expstr); l2_sha1_digest_to_hex(&rddg, gotstr); CMD_WARN("Failed to download %s manifest: SHA1 mismatch (expected %s, got %s)", compname, expstr, gotstr); cleanup_delete = true; goto cleanup; } if (expect_size != dlinfo.len) { CMD_WARN("Failed to download %s manifest: size mismatch (expected %zu bytes, got %zu bytes)", compname, expect_size, dlinfo.len); cleanup_delete = true; goto cleanup; } json_error_t err; json_t *jcomp = json_loadb(dlinfo.data, dlinfo.len, JSON_REJECT_DUPLICATES, &err); if (!jcomp) { CMD_WARN("Failed to download %s manifest: JSON parse error: %s", compname, err.text); goto cleanup; } free(dlinfo.data); dlinfo.data = NULL; *compmanifest = jcomp; return 0; cleanup: free(dlinfo.data); if (dlinfo.ofp) { fclose(dlinfo.ofp); cleanup_delete = true; } if (cleanup_delete && unlink(comppath) < 0) { CMD_WARN("Failed to delete %s: %s", comppath, strerror(errno)); } if (pc) curl_easy_cleanup(pc); return -1; } int l2_runtime__read_component_manifest_info(const char *compname, char **version, time_t *reltime) { char *infopath; size_t pathlen; uint64_t releasetime; uint64_t verlen; char *vername = NULL; L2_ASPRINTF(infopath, pathlen, "%s/runtime/%s/.%s.info", l2_state.paths.data, L2SU_JRE_ARCH, compname); FILE *fp = fopen(infopath, "rb"); if (!fp) { if (errno == ENOENT) { return 0; } else { CMD_WARN("Failed to read component manifest info %s: %s", infopath, strerror(errno)); return -1; } } errno = 0; if (fread(&verlen, sizeof(uint64_t), 1, fp) < 1) { CMD_WARN("Failed to read version length in %s: %s", infopath, strerror(errno)); goto cleanup; } verlen = l2_betoh64(verlen); vername = calloc(verlen + 1, 1); if (!vername) { CMD_WARN("Failed to allocate %" PRIx64 "-byte buffer: %s", verlen, strerror(errno)); goto cleanup; } if (fread(vername, 1, verlen, fp) < verlen) { CMD_WARN("Failed to read version in %s: %s", infopath, strerror(errno)); goto cleanup; } vername[verlen] = '\0'; if (fread(&releasetime, sizeof(uint64_t), 1, fp) < 1) { CMD_WARN("Failed to read release time in %s: %s", infopath, strerror(errno)); goto cleanup; } *version = vername; *reltime = (time_t)l2_betoh64(releasetime); return 0; cleanup: if (fp) fclose(fp); if (vername) free(vername); return -1; } int l2_runtime__save_component_manifest_info(const char *compname, const char *version, time_t reltime) { char *infopath; size_t pathlen; size_t verstrlen = strlen(version); uint64_t wverlen = l2_htobe64((uint64_t)verstrlen); uint64_t wreltime = l2_htobe64((uint64_t)reltime); L2_ASPRINTF(infopath, pathlen, "%s/runtime/%s/.%s.info", l2_state.paths.data, L2SU_JRE_ARCH, compname); FILE *fp = fopen(infopath, "wb"); if (!fp) { CMD_WARN("Failed to write component manifest info %s: %s", infopath, strerror(errno)); return -1; } errno = 0; if (fwrite(&wverlen, sizeof(uint64_t), 1, fp) < 1) { CMD_WARN("Failed to write version length in %s: %s", infopath, strerror(errno)); goto cleanup; } if (fwrite(version, 1, verstrlen, fp) < verstrlen) { CMD_WARN("Failed to write version name in %s: %s", infopath, strerror(errno)); goto cleanup; } if (fwrite(&wreltime, sizeof(uint64_t), 1, fp) < 1) { CMD_WARN("Failed to write release time in %s: %s", infopath, strerror(errno)); goto cleanup; } fclose(fp); return 0; cleanup: if (fp) { fclose(fp); if (unlink(infopath) < 0) { CMD_WARN("Failed to remove %s: %s", infopath, strerror(errno)); } } return -1; }