diff options
| author | 2024-01-08 12:17:20 -0600 | |
|---|---|---|
| committer | 2024-01-08 12:17:20 -0600 | |
| commit | d38c13b2fe1293b499312bb4cfd66a56108c4b5e (patch) | |
| tree | 998438c400a6b05f13d145270e95a9236ed7fb87 | |
| parent | add to-do list (diff) | |
WIP: JRE stuff
| -rw-r--r-- | src/cmd-instance.c | 12 | ||||
| -rw-r--r-- | src/cmd-version.c | 15 | ||||
| -rw-r--r-- | src/digest/digest.h | 19 | ||||
| -rw-r--r-- | src/digest/sha1.c | 26 | ||||
| -rw-r--r-- | src/l2su.h | 19 | ||||
| -rw-r--r-- | src/launcherutil.c | 168 | ||||
| -rw-r--r-- | src/macros.h | 7 | ||||
| -rw-r--r-- | src/meson.build | 5 | ||||
| -rw-r--r-- | src/runtime.c | 775 | ||||
| -rw-r--r-- | src/runtime.h | 9 | ||||
| -rw-r--r-- | src/version.c | 4 |
11 files changed, 1035 insertions, 24 deletions
diff --git a/src/cmd-instance.c b/src/cmd-instance.c index 7a569e8..a09bffa 100644 --- a/src/cmd-instance.c +++ b/src/cmd-instance.c @@ -232,16 +232,8 @@ unsigned cmd_instance_remove(struct l2_context_node *ctx, char **args) /* delete the thing if it is necessary */ if (flag_remove) { - res = nftw(removepath, &cmd__delete_with_reckless_abandon, 128, FTW_DEPTH | FTW_PHYS); - switch (res) { - case -2: - CMD_FATAL0(CMD_MSG_INSTANCE_DELDIR_ERR("unreadable file/folder encountered")); - case -1: - CMD_FATAL0(CMD_MSG_INSTANCE_DELDIR_ERR("error walking file tree")); - case 0: - break; - default: - CMD_FATAL(CMD_MSG_INSTANCE_DELDIR_ERR("error removing a file: %s"), strerror(res)); + if (l2_launcher_rm_tree(removepath, 0) < 0) { + CMD_FATAL0("Could not delete instance directory."); } } diff --git a/src/cmd-version.c b/src/cmd-version.c index fbf87b1..fc9b7a8 100644 --- a/src/cmd-version.c +++ b/src/cmd-version.c @@ -1,6 +1,7 @@ #include "command.h" #include "commands.h" #include "digest/digest.h" +#include "runtime.h" #include "version.h" #include "l2su.h" #include "macros.h" @@ -36,6 +37,20 @@ unsigned cmd_version_list_remote(struct l2_context_node *ctx, char **args) unsigned cmd_version_list_local(struct l2_context_node *ctx, char **args) { + json_t *manifest; + if (l2_runtime_load_manifest(&manifest) < 0) { + CMD_FATAL0("Failed to load manifest"); + } + + json_dumpf(manifest, stdout, JSON_INDENT(4)); + putchar('\n'); + + if (l2_runtime_install_component(manifest, "jre-legacy") < 0) { + CMD_FATAL0("Failed to install component"); + } + + json_decref(manifest); + return CMD_RESULT_SUCCESS; } diff --git a/src/digest/digest.h b/src/digest/digest.h index d047b8e..2887b8a 100644 --- a/src/digest/digest.h +++ b/src/digest/digest.h @@ -4,20 +4,24 @@ #include <stdint.h> #include <stddef.h> -#define L2_SHA1_BLOCKLEN (64) /* 512 / 8 = 64 */ -#define L2_SHA1_DIGESTLEN (5) /* 160 / 32 = 5 */ +#include "macros.h" + +#define L2_SHA1__BLOCKLEN (64) /* 512 / 8 = 64 */ +#define L2_SHA1__DIGESTLEN (5) /* 160 / 32 = 5 */ + #define L2_SHA1_HEX_STRLEN (40) +#define L2_SHA1_DIGEST_BYTES (20) typedef struct tag_l2_sha1_state { /* struct members are internal do not use directly */ - uint32_t state[L2_SHA1_DIGESTLEN]; - uint8_t chunk[L2_SHA1_BLOCKLEN]; + uint32_t state[L2_SHA1__DIGESTLEN]; + uint8_t chunk[L2_SHA1__BLOCKLEN]; size_t nchunk; uint64_t totallen; } l2_sha1_state_t; typedef struct tag_l2_sha1_digest { - uint32_t state[L2_SHA1_DIGESTLEN]; + uint32_t state[L2_SHA1__DIGESTLEN]; } l2_sha1_digest_t; void l2_sha1_init(l2_sha1_state_t *st); @@ -25,8 +29,11 @@ void l2_sha1_update(l2_sha1_state_t *st, const void *data, size_t sz); void l2_sha1_finalize(l2_sha1_state_t *st, l2_sha1_digest_t *digest); int l2_sha1_digest_compare(const l2_sha1_digest_t *d1, const l2_sha1_digest_t *d2); -void l2_sha1_digest_copy(l2_sha1_digest_t *restrict dest, const l2_sha1_digest_t *restrict src); +void l2_sha1_digest_copy(l2_sha1_digest_t *L2_RESTRICT dest, const l2_sha1_digest_t *L2_RESTRICT src); void l2_sha1_digest_to_hex(const l2_sha1_digest_t *dg, char *out); int l2_sha1_digest_from_hex(l2_sha1_digest_t *dg, const char *in); +void l2_sha1_digest_to_buffer(const l2_sha1_digest_t *dg, void *odata); +void l2_sha1_digest_from_buffer(l2_sha1_digest_t *dg, const void *data); + #endif /* include guard */ diff --git a/src/digest/sha1.c b/src/digest/sha1.c index b8f697e..3998a3b 100644 --- a/src/digest/sha1.c +++ b/src/digest/sha1.c @@ -24,7 +24,7 @@ void l2_sha1_init(l2_sha1_state_t *st) st->state[3] = L2_SHA1_H3; st->state[4] = L2_SHA1_H4; - memset(st->chunk, 0, L2_SHA1_BLOCKLEN); + memset(st->chunk, 0, L2_SHA1__BLOCKLEN); st->nchunk = 0; st->totallen = 0; } @@ -90,7 +90,7 @@ void l2_sha1_update(l2_sha1_state_t *st, const void *data, size_t sz) size_t rem; st->totallen += sz; - while (sz >= (rem = (L2_SHA1_BLOCKLEN - st->nchunk))) { + while (sz >= (rem = (L2_SHA1__BLOCKLEN - st->nchunk))) { memcpy(st->chunk + st->nchunk, dbytes, rem); l2_sha1__update_int(st); @@ -109,14 +109,14 @@ void l2_sha1_finalize(l2_sha1_state_t *st, l2_sha1_digest_t *digest) { st->chunk[st->nchunk] = UINT8_C(0x80); ++st->nchunk; - if (st->nchunk > (L2_SHA1_BLOCKLEN - 8)) { + if (st->nchunk > (L2_SHA1__BLOCKLEN - 8)) { /* must pad the rest of the way */ - memset(st->chunk + st->nchunk, 0, L2_SHA1_BLOCKLEN - st->nchunk); + memset(st->chunk + st->nchunk, 0, L2_SHA1__BLOCKLEN - st->nchunk); l2_sha1__update_int(st); st->nchunk = 0; } - size_t rem = (L2_SHA1_BLOCKLEN - st->nchunk) - 8; + size_t rem = (L2_SHA1__BLOCKLEN - st->nchunk) - 8; uint64_t len = l2_htobe64(st->totallen * 8); memset(st->chunk + st->nchunk, 0, rem); st->nchunk += rem; @@ -209,6 +209,22 @@ int l2_sha1__hex_to_uint32(const char *in, uint32_t *out) return 0; } +void l2_sha1_digest_to_buffer(const l2_sha1_digest_t *dg, void *odata) +{ + uint32_t *data = odata; + for (int i = 0; i < L2_SHA1__DIGESTLEN; ++i) { + data[i] = l2_htobe32(dg->state[i]); + } +} + +void l2_sha1_digest_from_buffer(l2_sha1_digest_t *dg, const void *data) +{ + const uint32_t *pstate = data; + for (int i = 0; i < L2_SHA1__DIGESTLEN; ++i) { + dg->state[i] = l2_betoh32(pstate[i]); + } +} + #if L2_SHA1_STANDALONE_TEST int main(int argc, char **argv) { @@ -46,6 +46,25 @@ int l2_launcher_open_config(const char *path, int flags, mode_t mode); int l2_launcher_mkdir_parents(const char *path); int l2_launcher_mkdir_parents_ex(const char *path, unsigned ignore); +int l2_launcher_rm_tree(const char *path, int nfds); + +struct l2_ftw_data { + int depth; + int reloffset; + void *user; +}; + +enum { + L2_FTW_FILE = 0, + L2_FTW_DIR = 1, + L2_FTW_SYMLINK = 2, + L2_FTW_TYPEMASK = 3, + L2_FTW_NONEXIST = 4 +}; + +typedef int (l2_ftw_proc_t)(const char * /*fname*/, const struct stat * /*sb*/, int /*flags*/, struct l2_ftw_data * /*data*/); + +int l2_launcher_ftw(const char *path, int depth, l2_ftw_proc_t *proc, void *user); char *l2_launcher_parse_iso_time(const char *str, struct tm *ts); diff --git a/src/launcherutil.c b/src/launcherutil.c index e1e8c40..b292df9 100644 --- a/src/launcherutil.c +++ b/src/launcherutil.c @@ -13,6 +13,8 @@ #include <sys/stat.h> #include <stdarg.h> #include <alloca.h> +#include <ftw.h> +#include <dirent.h> /* handcoded string functions * @@ -138,6 +140,44 @@ int l2_launcher_open_config(const char *path, int flags, mode_t mode) return instfd; } +int l2_launcher__rmtree_ftw_delete_with_reckless_abandon(const char *fpath, const struct stat *st, int typeflag, struct FTW *ftwbuf) +{ + L2_UNUSED(ftwbuf); + L2_UNUSED(st); + + switch (typeflag) { + case FTW_F: + case FTW_SL: + if (unlink(fpath) < 0) { + CMD_WARN("l2_rm_tree: Failed to delete file or symlink %s: %s", fpath, strerror(errno)); + return 1; + } + break; + case FTW_DP: + if (rmdir(fpath) < 0) { + CMD_WARN("l2_rm_tree: Failed to delete directory %s: %s", fpath, strerror(errno)); + return 1; + } + break; + case FTW_DNR: + CMD_WARN("l2_rm_tree: Not traversing into directory I cannot read: %s", fpath); + break; + case FTW_NS: + CMD_WARN("l2_rm_tree: Not deleting/traversing file I cannot stat: %s", fpath); + break; + } + return 0; +} + +int l2_launcher_rm_tree(const char *path, int nfds) +{ + if (nfds <= 0) nfds = 64; + + int res = nftw(path, &l2_launcher__rmtree_ftw_delete_with_reckless_abandon, nfds, FTW_DEPTH | FTW_PHYS); + if (res != 0) return -1; + return 0; +} + int l2_launcher_mkdir_parents(const char *path) { return l2_launcher_mkdir_parents_ex(path, 0); @@ -465,3 +505,131 @@ cleanup: if (pc) curl_easy_cleanup(pc); return res; } + +struct l2_ftw__data_int { + struct l2_ftw_data user; + + size_t pathcap; + size_t pathlen; + int abort; +}; + +int l2_launcher__ftw_update_path(char **path, struct l2_ftw__data_int *ftw, const char *entname) +{ + size_t entlen = strlen(entname); + if (ftw->pathlen + entlen + 2 > ftw->pathcap) { + char *newpathbuf = realloc(*path, ftw->pathlen + entlen + 2); + if (!newpathbuf) return -1; + *path = newpathbuf; + ftw->pathcap = ftw->pathlen + entlen + 2; + } + + (*path)[ftw->pathlen] = '/'; + ++ftw->pathlen; + memcpy(*path + ftw->pathlen, entname, entlen + 1); + ftw->pathlen += entlen; + return 0; +} + +int l2_launcher__ftw_internal(char **path, int depth, l2_ftw_proc_t *proc, struct l2_ftw__data_int *ftw) +{ + if (depth <= 0) { + CMD_DEBUG0("myftw: interation depth exceeded"); + return -1; + } + + DIR *thisdir = opendir(*path); + int res; + if (!thisdir) { + if (errno != ENOENT || ftw->user.depth == 0) { + CMD_DEBUG("myftw: could not open %s: %s", *path, strerror(errno)); + return -1; + } + CMD_DEBUG("myftw: directory %s does not exist. (maybe it was deleted?)", *path); + return 0; + } + + ++ftw->user.depth; + + struct dirent *ent; + struct stat st; + size_t pathlen = ftw->pathlen; + + errno = 0; + while ((ent = readdir(thisdir))) { + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; + + int flags = 0; + if (fstatat(dirfd(thisdir), ent->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + CMD_DEBUG("myftw: stat failed %s: %s", *path, strerror(errno)); + res = -1; + goto cleanup; + } + + ftw->pathlen = pathlen; + (*path)[pathlen] = '\0'; + l2_launcher__ftw_update_path(path, ftw, ent->d_name); + + if (S_ISREG(st.st_mode)) { + flags |= L2_FTW_FILE; + } + + if (S_ISDIR(st.st_mode)) { + flags |= L2_FTW_DIR; + } + + if (S_ISLNK(st.st_mode)) { + flags |= L2_FTW_SYMLINK; + } + + if ((res = (*proc)(*path, &st, flags, (struct l2_ftw_data *)ftw))) { + CMD_DEBUG("myftw: user aborted %s: %d", *path, res); + ftw->abort = 1; + goto cleanup; + } + + if (S_ISDIR(st.st_mode)) { + if ((res = l2_launcher__ftw_internal(path, depth - 1, proc, ftw)) < 0 || ftw->abort) { + goto cleanup; + } + } + + errno = 0; + } + + if (errno) { + CMD_DEBUG("myftw: readdir failed on %s: %s", *path, strerror(errno)); + res = -1; + goto cleanup; + } + + --ftw->user.depth; + res = 0; + +cleanup: + closedir(thisdir); + return res; +} + +int l2_launcher_ftw(const char *path, int depth, l2_ftw_proc_t *proc, void *user) +{ + struct l2_ftw__data_int data; + size_t pathlen = strlen(path); + + data.user.depth = 0; + data.user.reloffset = pathlen + 1; + data.user.user = user; + data.abort = 0; + + char *pathdup = strdup(path); + data.pathcap = pathlen + 1; + data.pathlen = pathlen; + + if (!pathdup) return -1; + + int res = l2_launcher__ftw_internal(&pathdup, depth, proc, &data); + + free(pathdup); + + return res; +} diff --git a/src/macros.h b/src/macros.h index 668816c..684323e 100644 --- a/src/macros.h +++ b/src/macros.h @@ -43,6 +43,7 @@ #define L2_URL_META_BASE "https://piston-meta.mojang.com" #define L2_URL_META_VERSION_MANIFEST L2_URL_META_BASE "/mc/game/version_manifest_v2.json" +#define L2_URL_META_RUNTIME_MANIFEST L2_URL_META_BASE "/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json" #define L2_URL_RESOURCES_BASE "https://resources.download.minecraft.net" #ifdef __GNUC__ @@ -54,4 +55,10 @@ #define L2_FORMAT(_unused1, _unused2, _unused3) #endif +#ifdef __cplusplus +#define L2_RESTRICT +#else +#define L2_RESTRICT restrict +#endif + #endif /* include guard */ diff --git a/src/meson.build b/src/meson.build index 0dc22ca..c3b11f5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,7 @@ -launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c', 'launcherutil.c', 'instance.c', 'cmd-version.c', 'digest/sha1.c', 'version.c', 'subst.c', 'downloadpool.c', 'assets.c', 'args.c', 'launch.c', 'jniwrap.c') +launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c', + 'launcherutil.c', 'instance.c', 'cmd-version.c', 'digest/sha1.c', 'version.c', + 'subst.c', 'downloadpool.c', 'assets.c', 'args.c', 'launch.c', 'jniwrap.c', + 'runtime.c') configure_file(input : 'config.h.in', output : 'config.h', configuration : config_data) config_include_dir = include_directories('.') diff --git a/src/runtime.c b/src/runtime.c new file mode 100644 index 0000000..b20353c --- /dev/null +++ b/src/runtime.c @@ -0,0 +1,775 @@ +#include "runtime.h" +#include "config.h" +#include "digest/digest.h" +#include "macros.h" +#include "l2su.h" +#include "endian.h" + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <jansson.h> +#include <stdio.h> +#include <stdint.h> +#include <curl/curl.h> +#include <unistd.h> +#include <time.h> +#include <inttypes.h> +#include <ftw.h> + +#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(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(jfiles) < 0) { + goto cleanup; + } + + json_decref(jcompmanifest); + return 0; + +cleanup: + if (jcompmanifest) json_decref(jcompmanifest); + return -1; +} + +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 = (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; +} diff --git a/src/runtime.h b/src/runtime.h new file mode 100644 index 0000000..bf2e523 --- /dev/null +++ b/src/runtime.h @@ -0,0 +1,9 @@ +#ifndef L2SU_RUNTIME_H_INCLUDED +#define L2SU_RUNTIME_H_INCLUDED + +#include <jansson.h> + +int l2_runtime_load_manifest(json_t **manifest); +int l2_runtime_install_component(json_t *manifest, const char *component); + +#endif /* include guard */ diff --git a/src/version.c b/src/version.c index e7e064d..a80988c 100644 --- a/src/version.c +++ b/src/version.c @@ -189,8 +189,8 @@ bool l2_version__download_remote(struct l2_version_remote *remote, json_t **ojso l2_sha1_finalize(&st, &dig); if (l2_sha1_digest_compare(&dig, &remote->sha1)) { - char d1[L2_SHA1_DIGESTLEN + 1]; - char d2[L2_SHA1_DIGESTLEN + 1]; + char d1[L2_SHA1_HEX_STRLEN + 1]; + char d2[L2_SHA1_HEX_STRLEN + 1]; l2_sha1_digest_to_hex(&dig, d1); l2_sha1_digest_to_hex(&remote->sha1, d2); |
