aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cmd-instance.c12
-rw-r--r--src/cmd-version.c15
-rw-r--r--src/digest/digest.h19
-rw-r--r--src/digest/sha1.c26
-rw-r--r--src/l2su.h19
-rw-r--r--src/launcherutil.c168
-rw-r--r--src/macros.h7
-rw-r--r--src/meson.build5
-rw-r--r--src/runtime.c775
-rw-r--r--src/runtime.h9
-rw-r--r--src/version.c4
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)
{
diff --git a/src/l2su.h b/src/l2su.h
index e46aa12..477875c 100644
--- a/src/l2su.h
+++ b/src/l2su.h
@@ -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);