From 4729c683fd0cca6223955da36eff8b7285e6ad31 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 25 May 2017 14:13:18 -0400 Subject: [PATCH 01/20] Initial shot at writing an ioquake3 autoupdater. --- Makefile | 85 +++- autoupdater-readme.txt | 125 ++++++ code/autoupdater/autoupdater.c | 721 +++++++++++++++++++++++++++++++++ code/autoupdater/sha256.c | 158 ++++++++ code/autoupdater/sha256.h | 34 ++ code/sys/sys_main.c | 46 +++ 6 files changed, 1147 insertions(+), 22 deletions(-) create mode 100644 autoupdater-readme.txt create mode 100644 code/autoupdater/autoupdater.c create mode 100644 code/autoupdater/sha256.c create mode 100644 code/autoupdater/sha256.h diff --git a/Makefile b/Makefile index cd98122b..06507596 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,9 @@ endif ifndef BUILD_RENDERER_OPENGL2 BUILD_RENDERER_OPENGL2= endif +ifndef BUILD_AUTOUPDATER + BUILD_AUTOUPDATER= +endif ############################################################################# # @@ -228,6 +231,10 @@ ifndef USE_YACC USE_YACC=0 endif +ifndef USE_AUTOUPDATER +USE_AUTOUPDATER=1 +endif + ifndef DEBUG_CFLAGS DEBUG_CFLAGS=-ggdb -O0 endif @@ -262,6 +269,7 @@ LBURGDIR=$(MOUNT_DIR)/tools/lcc/lburg Q3CPPDIR=$(MOUNT_DIR)/tools/lcc/cpp Q3LCCETCDIR=$(MOUNT_DIR)/tools/lcc/etc Q3LCCSRCDIR=$(MOUNT_DIR)/tools/lcc/src +AUTOUPDATERSRCDIR=$(MOUNT_DIR)/autoupdater LOKISETUPDIR=misc/setup NSISDIR=misc/nsis SDLHDIR=$(MOUNT_DIR)/SDL2 @@ -269,29 +277,30 @@ LIBSDIR=$(MOUNT_DIR)/libs bin_path=$(shell which $(1) 2> /dev/null) +# The autoupdater uses curl, so figure out its flags no matter what. # We won't need this if we only build the server -ifneq ($(BUILD_CLIENT),0) - # set PKG_CONFIG_PATH to influence this, e.g. - # PKG_CONFIG_PATH=/opt/cross/i386-mingw32msvc/lib/pkgconfig - ifneq ($(call bin_path, pkg-config),) - CURL_CFLAGS ?= $(shell pkg-config --silence-errors --cflags libcurl) - CURL_LIBS ?= $(shell pkg-config --silence-errors --libs libcurl) - OPENAL_CFLAGS ?= $(shell pkg-config --silence-errors --cflags openal) - OPENAL_LIBS ?= $(shell pkg-config --silence-errors --libs openal) - SDL_CFLAGS ?= $(shell pkg-config --silence-errors --cflags sdl2|sed 's/-Dmain=SDL_main//') - SDL_LIBS ?= $(shell pkg-config --silence-errors --libs sdl2) - FREETYPE_CFLAGS ?= $(shell pkg-config --silence-errors --cflags freetype2) - else - # assume they're in the system default paths (no -I or -L needed) - CURL_LIBS ?= -lcurl - OPENAL_LIBS ?= -lopenal - endif - # Use sdl2-config if all else fails - ifeq ($(SDL_CFLAGS),) - ifneq ($(call bin_path, sdl2-config),) - SDL_CFLAGS ?= $(shell sdl2-config --cflags) - SDL_LIBS ?= $(shell sdl2-config --libs) - endif + +# set PKG_CONFIG_PATH to influence this, e.g. +# PKG_CONFIG_PATH=/opt/cross/i386-mingw32msvc/lib/pkgconfig +ifneq ($(call bin_path, pkg-config),) + CURL_CFLAGS ?= $(shell pkg-config --silence-errors --cflags libcurl) + CURL_LIBS ?= $(shell pkg-config --silence-errors --libs libcurl) + OPENAL_CFLAGS ?= $(shell pkg-config --silence-errors --cflags openal) + OPENAL_LIBS ?= $(shell pkg-config --silence-errors --libs openal) + SDL_CFLAGS ?= $(shell pkg-config --silence-errors --cflags sdl2|sed 's/-Dmain=SDL_main//') + SDL_LIBS ?= $(shell pkg-config --silence-errors --libs sdl2) + FREETYPE_CFLAGS ?= $(shell pkg-config --silence-errors --cflags freetype2) +else + # assume they're in the system default paths (no -I or -L needed) + CURL_LIBS ?= -lcurl + OPENAL_LIBS ?= -lopenal +endif + +# Use sdl2-config if all else fails +ifeq ($(SDL_CFLAGS),) + ifneq ($(call bin_path, sdl2-config),) + SDL_CFLAGS ?= $(shell sdl2-config --cflags) + SDL_LIBS ?= $(shell sdl2-config --libs) endif endif @@ -975,6 +984,11 @@ ifneq ($(BUILD_GAME_QVM),0) endif endif +ifneq ($(BUILD_AUTOUPDATER),0) + AUTOUPDATER_BIN := autoupdater$(FULLBINEXT) + TARGETS += $(B)/$(AUTOUPDATER_BIN) +endif + ifeq ($(USE_OPENAL),1) CLIENT_CFLAGS += -DUSE_OPENAL ifeq ($(USE_OPENAL_DLOPEN),1) @@ -1075,6 +1089,11 @@ ifeq ($(USE_FREETYPE),1) RENDERER_LIBS += $(FREETYPE_LIBS) endif +ifeq ($(USE_AUTOUPDATER),1) + CLIENT_CFLAGS += -DUSE_AUTOUPDATER -DAUTOUPDATER_BIN=\\\"$(AUTOUPDATER_BIN)\\\" + SERVER_CFLAGS += -DUSE_AUTOUPDATER -DAUTOUPDATER_BIN=\\\"$(AUTOUPDATER_BIN)\\\" +endif + ifeq ("$(CC)", $(findstring "$(CC)", "clang" "clang++")) BASE_CFLAGS += -Qunused-arguments endif @@ -1331,6 +1350,7 @@ endif makedirs: @if [ ! -d $(BUILD_DIR) ];then $(MKDIR) $(BUILD_DIR);fi @if [ ! -d $(B) ];then $(MKDIR) $(B);fi + @if [ ! -d $(B)/autoupdater ];then $(MKDIR) $(B)/autoupdater;fi @if [ ! -d $(B)/client ];then $(MKDIR) $(B)/client;fi @if [ ! -d $(B)/client/opus ];then $(MKDIR) $(B)/client/opus;fi @if [ ! -d $(B)/client/vorbis ];then $(MKDIR) $(B)/client/vorbis;fi @@ -1550,6 +1570,27 @@ $(Q3ASM): $(Q3ASMOBJ) $(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS) +############################################################################# +# AUTOUPDATER +############################################################################# + +define DO_AUTOUPDATER_CC +$(echo_cmd) "AUTOUPDATER_CC $<" +$(Q)$(TOOLS_CC) $(CFLAGS) $(CURL_CFLAGS) -o $@ -c $< +endef + +Q3AUTOUPDATEROBJ = \ + $(B)/autoupdater/autoupdater.o \ + $(B)/autoupdater/sha256.o \ + +$(B)/autoupdater/%.o: $(AUTOUPDATERSRCDIR)/%.c + $(DO_AUTOUPDATER_CC) + +$(B)/$(AUTOUPDATER_BIN): $(Q3AUTOUPDATEROBJ) + $(echo_cmd) "AUTOUPDATER_LD $@" + $(Q)$(CC) $(LDFLAGS) $(CURL_LIBS) -o $@ $(Q3AUTOUPDATEROBJ) + + ############################################################################# # CLIENT/SERVER ############################################################################# diff --git a/autoupdater-readme.txt b/autoupdater-readme.txt new file mode 100644 index 00000000..1353f4f0 --- /dev/null +++ b/autoupdater-readme.txt @@ -0,0 +1,125 @@ +The updater program's code is public domain. The rest of ioquake3 is not. + +The source code to the autoupdater is in the code/autoupdater directory. +There is a small piece of code in ioquake3 itself at startup, too. + +(This is all Unix terminology, but similar approaches on Windows apply.) + +The updater is a separate program, written in C, with no dependencies on +the game. It (statically) links to libcurl and uses the C runtime, but +otherwise has no external dependencies. It has to be a single binary file +with no shared libraries. + +The basic flow looks like this: + +- The game launches as usual. +- Right after main() starts, the game creates a pipe, forks off a new process, + and execs the updater in that process. The game won't ever touch the pipe + again. It's just there to block the child app until the game terminates. +- The updater has no UI. It writes a log file. +- The updater downloads a manifest from a known URL over https://, using + libCurl. The base URL is platform-specific (it might be + https://example.com/mac/, or https://example.com/linux-x86/, whatever). + The manifest is at $BASEURL/manifest.txt +- The manifest looks like this: three lines per file... + +Contents/MacOS/baseq3/uix86_64.dylib +332428 +a49bbe77f8eb6c195265ea136f881f7830db58e4d8a883b27f59e1e23e396a20 + +- That's the file's path, its size in bytes, and an sha256 hash of the data. +- The file will be at this path under the base url on the webserver. +- The manifest only lists files that ever needed updating; it's not necessary + to list every file in the game's installation (unless you want to allow the + entire game to download). +- The updater will check each item in the manifest: + - Does the file not exist in the install? Needs downloading. + - Does the file have a different size? Needs downloading. + - Does the file have a different sha256sum? Needs downloading. + - Otherwise, file is up to date, leave it alone. +- If an item needs downloading, do these same checks against the file in the + download directory (if it's already there and matches, don't download again.) +- Download necessary files with libcurl, put it in a download directory. +- The downloaded file is also checked for size and sha256 vs the manifest, to + make sure there was no corruption or confusion. If a downloaded file doesn't + match what was expected, the updater aborts and will try again next time. + This could fail checksum due to i/o errors and compromised security, but + it might just be that a new version was being published and bad luck + happened, and a retry later could correct everything. +- If the updater itself needs upgrading, we deal with that first. It's + downloaded, then the updater relaunches from the downloaded binary with + a special command line. That relaunched process copies itself to the proper + location, and then relaunches _again_ to restart the normal updating + process with the new updater in its correct position. +- Once the downloads are complete and the updater itself doesn't need + upgrading, we are ready to start the normal upgrade. Since we can't replace + executables on some platforms while they are running, and swapping out a + game's data files at runtime isn't wise in general, the updater will now + block until the game terminates. It does this by reading on the pipe that + the game created when forking the updater; since the game never writes + anything to this pipe, it causes the updater to block until the pipe closes. + Since the game never deliberately closes the pipe either, it remains open + until the OS forcibly closes it as the game process terminates. Being an + unnamed pipe, it just vaporizes at this point, leaving no state that might + accidentally hang us up later, like a global semaphore or whatnot. This + technique also lets us localize the game's code changes to one small block + of C code, with no need to manage these resources elsewhere. +- As a sanity check, the updater will also kill(game_process_id, 0) until it + fails, sleeping for 100 milliseconds between each attempt, in case the + process is still being cleaned up by the OS after closing the pipe. +- Once the updater is confident the game process is gone, it will start + upgrading the appropriate files. It does this in two steps: it moves + the old file to a "rollback" directory so it's out of the way but still + available, then it moves the newly-downloaded file into place. Since these + are all simple renames and not copies, this can move fast. Any missing + parent directories are created, in case the update is adding a new file + in a directory that didn't previously exist. +- If something goes wrong at this point (file i/o error, etc), the updater + will roll back the changes by deleting the updated files, and moving the + files in the "rollback" directory back to their original locations. Then + the updater aborts. +- If nothing went wrong, the rollback files are deleted. And we are officially + up to date! The updater terminates. + + +The updater is designed to fail at any point. If a download fails, it'll +pick up and try again next time, etc. Completed downloads will remain, so it +will just need to download any missing/incomplete files. + +The server side just needs to be able to serve static files over HTTPS from +any standard Apache/nginx/whatever process. + +Failure points: +- If the updater fails when still downloading data, it just picks up on next + restart. +- If the updater fails when replacing files, it rolls back any changes it has + made. +- If the updater fails when rolling back, then running the updater again after + fixing the specific problem (disk error, etc?) will redownload and replace + any files that were left in an uncertain state. The only true point of + risk is crashing during a rollback and then having the updater bricked for + some reason, but that's an extremely small surface area, knock on wood. +- If the updater crashes or totally bricks, ioquake3 should just keep being + ioquake3. It will still launch and play, even if the updater is quietly + segfaulting in the background on startup. +- If an update bricks ioquake3 to the point where it can't run the updater, + running the updater directly should let it recover (assuming a future update + fixes the problem). + + +Items to consider for future revisions: +- GPG sign the manifest; if we can be confident that the manifest isn't + compromised, then the sha256 hashes of each file it contains should protect + the rest of the process. As it currently stands, we trust the download + server isn't compromised. +- Maybe put a limit on the number manifest downloads, so we only check once + every hour? Every day? +- Channels? Stable (what everyone gets by default), Nightly (once a day), + Experimental (some other work-in-progress branch), Bloody (literally the + latest commit). +- Let mods update, separate from the main game? + +Questions? Ask Ryan: icculus@icculus.org + +--ryan. + diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c new file mode 100644 index 00000000..e8b8d40c --- /dev/null +++ b/code/autoupdater/autoupdater.c @@ -0,0 +1,721 @@ +/* +The code in this file is in the public domain. The rest of ioquake3 +is licensed until the GPLv2. Do not mingle code, please! +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "sha256.h" + +#ifndef AUTOUPDATE_USER_AGENT +#define AUTOUPDATE_USER_AGENT "ioq3autoupdater/0.1" +#endif + + +#ifndef AUTOUPDATE_URL + +#ifndef AUTOUPDATE_BASEURL +#define AUTOUPDATE_BASEURL "https://upd.ioquake3.org/updates/v1" +#endif + +#ifndef AUTOUPDATE_PACKAGE +#define AUTOUPDATE_PACKAGE "ioquake3" +#endif + +#ifdef __APPLE__ +#define AUTOUPDATE_PLATFORM "mac" +#elif defined(__linux__) +#define AUTOUPDATE_PLATFORM "linux" +#else +#error Please define your platform. +#endif + +#ifdef __i386__ +#define AUTOUPDATE_ARCH "i386" +#elif defined(__x86_64__) +#define AUTOUPDATE_ARCH "x86-64" +#else +#error Please define your platform. +#endif + +#define AUTOUPDATE_URL AUTOUPDATE_BASEURL "/" AUTOUPDATE_PACKAGE "/" AUTOUPDATE_PLATFORM "/" AUTOUPDATE_ARCH "/" +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define NEVER_RETURNS __attribute__((noreturn)) +#else +#define NEVER_RETURNS +#endif + + +typedef struct +{ + pid_t waitforprocess; + const char *updateself; +} Options; + +static Options options; + + +typedef struct ManifestItem +{ + char *fname; + unsigned char sha256[32]; + int64_t len; + int update; + int rollback; + struct ManifestItem *next; +} ManifestItem; + +static ManifestItem *manifest; + +static void freeManifest(void) +{ + ManifestItem *item = manifest; + manifest = NULL; + + while (item != NULL) { + ManifestItem *next = item->next; + free(item->fname); + free(item); + item = next; + } + manifest = NULL; +} + + +static FILE *logfile = NULL; + +#define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __printf__, fmtargnumber, fmtargnumber+1 ))) + +static void info(const char *str) +{ + fputs(str, logfile); + fputs("\n", logfile); + fflush(logfile); +} + +static void infof(const char *fmt, ...) +#if defined(__GNUC__) || defined(__clang__) +__attribute__ (( format( __printf__, 1, 2 ))) +#endif +; + +static void infof(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(logfile, fmt, ap); + va_end(ap); + fputs("\n", logfile); + fflush(logfile); +} + +static void restoreRollbacks(void) +{ + /* you can't call die() in this function! If this fails, you're doomed. */ + ManifestItem *item; + for (item = manifest; item != NULL; item = item->next) { + if (item->rollback) { + char rollbackPath[64]; + snprintf(rollbackPath, sizeof (rollbackPath), "updates/rollbacks/%d", item->rollback); + infof("restore rollback: '%s' -> '%s'", rollbackPath, item->fname); + remove(item->fname); + rename(rollbackPath, item->fname); + } + } +} + + +static void die(const char *why) NEVER_RETURNS; +static void die(const char *why) +{ + infof("FAILURE: %s", why); + restoreRollbacks(); + freeManifest(); + exit(1); +} + +static void outOfMemory() NEVER_RETURNS; +static void outOfMemory() +{ + die("Out of memory"); +} + +static void makeDir(const char *dirname) +{ + /* !!! FIXME: we don't care if this fails right now. */ + mkdir(dirname, 0777); +} + +static void buildParentDirs(const char *_path) +{ + char *ptr; + char *path = (char *) alloca(strlen(_path) + 1); + if (!path) { + outOfMemory(); + } + strcpy(path, _path); + + for (ptr = path; *ptr; ptr++) { + if (*ptr == '/') { + *ptr = '\0'; + makeDir(path); + *ptr = '/'; + } + } +} + +static int64_t fileLength(const char *fname) +{ + struct stat statbuf; + if (stat(fname, &statbuf) == -1) { + return -1; + } + return (int64_t) statbuf.st_size; +} + +static void parseArgv(int argc, char **argv) +{ + int i; + + infof("command line (argc=%d)...", argc); + for (i = 0; i < argc; i++) { + infof(" argv[%d]: %s",i, argv[i]); + } + + for (i = 1; i < argc; i += 2) { + if (strcmp(argv[i], "--waitpid") == 0) { + options.waitforprocess = atoll(argv[i + 1]); + infof("We will wait for process %lld if necessary", (long long) options.waitforprocess); + } else if (strcmp(argv[i], "--updateself") == 0) { + options.updateself = argv[i + 1]; + infof("We are updating ourself ('%s')", options.updateself); + } + } +} + +static CURL *prepCurl(const char *url, FILE *outfile) +{ + char *fullurl; + const size_t len = strlen(AUTOUPDATE_URL) + strlen(url) + 1; + CURL *curl = curl_easy_init(); + if (!curl) { + die("curl_easy_init() failed"); + } + + fullurl = (char *) alloca(len); + if (!fullurl) { + outOfMemory(); + } + + snprintf(fullurl, len, "%s%s", AUTOUPDATE_URL, url); + + infof("Downloading from '%s'", fullurl); + + #if 0 + /* !!! FIXME: enable compression? */ + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); /* enable compression */ + + /* !!! FIXME; hook up proxy support to libcurl */ + curl_easy_setopt(curl, CURLOPT_PROXY, proxyURL); + #endif + + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_STDERR, logfile); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); + + curl_easy_setopt(curl, CURLOPT_URL, fullurl); + + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* allow redirects. */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, AUTOUPDATE_USER_AGENT); + + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); /* require valid SSL cert. */ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); /* require SSL cert with same hostname as we connected to. */ + + return curl; +} + +static void downloadURL(const char *from, const char *to) +{ + FILE *io; + CURL *curl; + + infof("Preparing to download to '%s'", to); + + buildParentDirs(to); + io = fopen(to, "wb"); + if (!io) { + die("Failed to open output file"); + } + + curl = prepCurl(from, io); + if (curl_easy_perform(curl) != CURLE_OK) { + remove(to); + die("Download failed"); + } + curl_easy_cleanup(curl); + + if (fclose(io) == EOF) { + die("Can't flush file on close. i/o error? Disk full?"); + } + + chmod(to, 0777); /* !!! FIXME */ +} + +static int hexcvt(const int ch) +{ + if ((ch >= 'a') && (ch <= 'f')) { + return (ch - 'a') + 10; + } else if ((ch >= 'A') && (ch <= 'F')) { + return (ch - 'A') + 10; + } else if ((ch >= '0') && (ch <= '9')) { + return ch - '0'; + } else { + die("Invalid hex character"); + } + return 0; +} + +static void convertSha256(char *str, BYTE *sha256) +{ + int i; + for (i = 0; i < 32; i++) { + const int a = hexcvt(*(str++)); + const int b = hexcvt(*(str++)); + *sha256 = (a << 4) | b; + sha256++; + } +} + +static void parseManifest(const char *fname) +{ + ManifestItem *item = NULL; + FILE *io = fopen(fname, "r"); + char buf[512]; + if (!io) { + die("Failed to open manifest for reading"); + } + + /* !!! FIXME: this code sucks. */ + while (fgets(buf, sizeof (buf), io)) { + char *ptr = (buf + strlen(buf)) - 1; + while (ptr >= buf) { + if ((*ptr != '\n') && (*ptr != '\r')) { + break; + } + *ptr = '\0'; + ptr--; + } + + if (!item && !buf[0]) { + continue; /* blank line between items or blank at EOF */ + } + + if (!item) { + infof("Next manifest item: %s", buf); + + item = (ManifestItem *) malloc(sizeof (ManifestItem)); + if (!item) { + outOfMemory(); + } + item->fname = strdup(buf); + if (!item->fname) { + outOfMemory(); + } + item->len = -1; + item->next = NULL; + } else if (item->len == -1) { + infof("Item size: %s", buf); + item->len = atoll(buf); + } else { + infof("Item sha256: %s", buf); + convertSha256(buf, item->sha256); + item->next = manifest; + manifest = item; + item = NULL; + } + } + + if (ferror(io)) { + die("Error reading manifest"); + } else if (item) { + die("Incomplete manifest"); + } + + fclose(io); +} + +static void downloadManifest(void) +{ + const char *manifestfname = "updates/manifest.txt"; + downloadURL("manifest.txt", manifestfname); + /* !!! FIXME: verify manifest download is complete... */ + parseManifest(manifestfname); +} + +static void upgradeSelfAndRestart(const char *argv0) NEVER_RETURNS; +static void upgradeSelfAndRestart(const char *argv0) +{ + const char *tempfname = "origUpdater"; + const char *why = NULL; + FILE *in = NULL; + FILE *out = NULL; + + in = fopen(argv0, "rb"); + if (!in) { + die("Can't open self for input while upgrading updater"); + } + + remove(tempfname); + if (rename(options.updateself, tempfname) == -1) { + die("Can't rename original while upgrading updater"); + } + + out = fopen(options.updateself, "wb"); + if (!out) { + die("Can't open file for output while upgrading updater"); + } + + while (!feof(in) && !why) { + char buf[512]; + const size_t br = fread(buf, 1, sizeof (buf), in); + if (br > 0) { + if (fwrite(buf, br, 1, out) != 1) { + why = "write failure while upgrading updater"; + } + } else if (ferror(in)) { + why = "read failure while upgrading updater"; + } + } + + fclose(in); + + if ((fclose(out) == EOF) && (!why)) { + why = "close failure while upgrading updater"; + } + + if (why) { + remove(options.updateself); + rename(tempfname, options.updateself); + die(why); + } + + remove(tempfname); + + chmod(options.updateself, 0777); + + if (options.waitforprocess) { + char pidstr[64]; + snprintf(pidstr, sizeof (pidstr), "%lld", (long long) options.waitforprocess); + execl(options.updateself, options.updateself, "--waitpid", pidstr, NULL); + } else { + execl(options.updateself, options.updateself, NULL); + } + die("Failed to relaunch upgraded updater"); +} + +static const char *justFilename(const char *path) +{ + const char *fname = strrchr(path, '/'); + return fname ? fname + 1 : path; +} + +static void hashFile(const char *fname, unsigned char *sha256) +{ + SHA256_CTX sha256ctx; + BYTE buf[512]; + FILE *io; + + io = fopen(fname, "rb"); + if (!io) { + die("Failed to open file for hashing"); + } + + sha256_init(&sha256ctx); + do { + size_t br = fread(buf, 1, sizeof (buf), io); + if (br > 0) { + sha256_update(&sha256ctx, buf, br); + } + if (ferror(io)) { + die("Error reading file for hashing"); + } + } while (!feof(io)); + + fclose(io); + + sha256_final(&sha256ctx, sha256); +} + +static int fileHashMatches(const char *fname, const unsigned char *wanted) +{ + unsigned char sha256[32]; + hashFile(fname, sha256); + return (memcmp(sha256, wanted, 32) == 0); +} + +static int fileNeedsUpdate(const ManifestItem *item) +{ + if (item->len != fileLength(item->fname)) { + infof("Update '%s', file size is different", item->fname); + return 1; /* obviously different. */ + } else if (!fileHashMatches(item->fname, item->sha256)) { + infof("Update '%s', file sha256 is different", item->fname); + return 1; + } + + infof("Don't update '%s', the file is already up to date", item->fname); + return 0; +} + +static void downloadFile(const ManifestItem *item) +{ + const char *outpath = "updates/downloads/"; + const size_t len = strlen(outpath) + strlen(item->fname) + 1; + char *to = (char *) alloca(len); + if (!to) { + outOfMemory(); + } + + snprintf(to, len, "%s%s", outpath, item->fname); + + if ((item->len == fileLength(to)) && fileHashMatches(to, item->sha256)) { + infof("Already downloaded '%s', not getting again", item->fname); + } else { + downloadURL(item->fname, to); + if ((item->len != fileLength(to)) || !fileHashMatches(to, item->sha256)) { + die("Download is incorrect or corrupted"); + } + } +} + +static int downloadUpdates(void) +{ + int updatesAvailable = 0; + ManifestItem *item; + for (item = manifest; item != NULL; item = item->next) { + item->update = fileNeedsUpdate(item); + if (item->update) { + updatesAvailable = 1; + downloadFile(item); + } + } + return updatesAvailable; +} + +static void maybeUpdateSelf(const char *argv0) +{ + ManifestItem *item; + + /* !!! FIXME: this needs to be a different string on macOS. */ + const char *fname = justFilename(argv0); + + for (item = manifest; item != NULL; item = item->next) { + if (strcasecmp(item->fname, fname) == 0) { + if (fileNeedsUpdate(item)) { + const char *outpath = "updates/downloads/"; + const size_t len = strlen(outpath) + strlen(item->fname) + 1; + char *to = (char *) alloca(len); + if (!to) { + outOfMemory(); + } + snprintf(to, len, "%s%s", outpath, item->fname); + info("Have to upgrade the updater"); + downloadFile(item); + chmod(to, 0777); + + if (options.waitforprocess) { + char pidstr[64]; + snprintf(pidstr, sizeof (pidstr), "%lld", (long long) options.waitforprocess); + execl(to, to, "--updateself", argv0, "--waitpid", pidstr, NULL); + } else { + execl(to, to, "--updateself", argv0, NULL); + } + die("Failed to initially launch upgraded updater"); + } + break; /* done in any case. */ + } + } +} + +static void installUpdatedFile(const ManifestItem *item) +{ + const char *basepath = "updates/downloads/"; + const size_t len = strlen(basepath) + strlen(item->fname) + 1; + char *downloadPath = (char *) alloca(len); + if (!downloadPath) { + outOfMemory(); + } + + snprintf(downloadPath, len, "%s%s", basepath, item->fname); + + infof("Moving file for update: '%s' -> '%s'", downloadPath, item->fname); + buildParentDirs(item->fname); + if (rename(downloadPath, item->fname) == -1) { + die("Failed to move updated file to final position"); + } +} + +static void applyUpdates(void) +{ + FILE *io; + ManifestItem *item; + for (item = manifest; item != NULL; item = item->next) { + if (!item->update) { + continue; + } + + io = fopen(item->fname, "rb"); + fclose(io); + if (io != NULL) { + static int rollbackIndex = 0; + char rollbackPath[64]; + item->rollback = ++rollbackIndex; + snprintf(rollbackPath, sizeof (rollbackPath), "updates/rollbacks/%d", rollbackIndex); + infof("Moving file for rollback: '%s' -> '%s'", item->fname, rollbackPath); + remove(rollbackPath); + if (rename(item->fname, rollbackPath) == -1) { + die("failed to move to rollback dir"); + } + } + + installUpdatedFile(item); + } +} + +static void waitToApplyUpdates(void) +{ + if (options.waitforprocess) { + /* ioquake3 opens a pipe on fd 3, and then forgets about it. We block + on a read to that pipe here. When the game process quits (and the + OS forcibly closes the pipe), we will unblock. Then we can loop on + kill() until the process is truly gone. */ + int x = 0; + infof("Waiting for pid %lld to die...", (long long) options.waitforprocess); + read(3, &x, sizeof (x)); + info("Pipe has closed, waiting for process to fully go away now."); + while (kill(options.waitforprocess, 0) == 0) { + usleep(100000); + } + info("pid is gone, continuing"); + } +} + +static void deleteRollbacks(void) +{ + ManifestItem *item; + for (item = manifest; item != NULL; item = item->next) { + if (item->rollback) { + char rollbackPath[64]; + snprintf(rollbackPath, sizeof (rollbackPath), "updates/rollbacks/%d", item->rollback); + infof("delete rollback: %s", rollbackPath); + remove(rollbackPath); + } + } +} + +static const char *timestamp(void) +{ + time_t t = time(NULL); + char *retval = asctime(localtime(&t)); + if (retval) { + char *ptr; + for (ptr = retval; *ptr; ptr++) { + if ((*ptr == '\r') || (*ptr == '\n')) { + *ptr = '\0'; + break; + } + } + } + return retval ? retval : "[date unknown]"; +} + +static void chdirToBasePath(const char *argv0) +{ + const char *fname = justFilename(argv0); + size_t len; + char *buf; + + if (fname == argv0) { /* no path? Assume we're already there. */ + return; + } + + len = ((size_t) (fname - argv0)) - 1; + buf = (char *) alloca(len); + if (!buf) { + outOfMemory(); + } + + memcpy(buf, argv0, len); + buf[len] = '\0'; + if (chdir(buf) == -1) { + infof("base path is '%s'", buf); + die("chdir to base path failed"); + } +} + +int main(int argc, char **argv) +{ + signal(SIGPIPE, SIG_IGN); /* don't trigger signal when fd3 closes */ + + logfile = stdout; + chdirToBasePath(argv[0]); + + makeDir("updates"); + makeDir("updates/downloads"); + makeDir("updates/rollbacks"); + + logfile = fopen("updates/updater-log.txt", "a"); + if (!logfile) { + logfile = stdout; + } + + infof("Updater starting, %s", timestamp()); + + parseArgv(argc, argv); + + /* if we have downloaded a new updater and restarted with that binary, + replace the original updater and restart again in the right place. */ + if (options.updateself) { + upgradeSelfAndRestart(argv[0]); + } + + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { + die("curl_global_init() failed!"); + } + + downloadManifest(); /* see if we need an update at all. */ + + maybeUpdateSelf(argv[0]); /* might relaunch if there's an updater upgrade. */ + + if (!downloadUpdates()) { + info("Nothing needs updating, so we're done here!"); + } else { + waitToApplyUpdates(); + applyUpdates(); + deleteRollbacks(); + info("You are now up to date!"); + } + + freeManifest(); + curl_global_cleanup(); + + infof("Updater ending, %s", timestamp()); + + return 0; +} + diff --git a/code/autoupdater/sha256.c b/code/autoupdater/sha256.c new file mode 100644 index 00000000..eb9c5c07 --- /dev/null +++ b/code/autoupdater/sha256.c @@ -0,0 +1,158 @@ +/********************************************************************* +* Filename: sha256.c +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Implementation of the SHA-256 hashing algorithm. + SHA-256 is one of the three algorithms in the SHA2 + specification. The others, SHA-384 and SHA-512, are not + offered in this implementation. + Algorithm specification can be found here: + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf + This implementation uses little endian byte order. +*********************************************************************/ + +/*************************** HEADER FILES ***************************/ +#include +#include +#include "sha256.h" + +/****************************** MACROS ******************************/ +#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) +#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) + +#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) +#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) +#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) +#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) + +/**************************** VARIABLES *****************************/ +static const WORD k[64] = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 +}; + +/*********************** FUNCTION DEFINITIONS ***********************/ +void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) +{ + WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); + for ( ; i < 64; ++i) + m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; ++i) { + t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; + t2 = EP0(a) + MAJ(a,b,c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +void sha256_init(SHA256_CTX *ctx) +{ + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} + +void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) +{ + WORD i; + + for (i = 0; i < len; ++i) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +void sha256_final(SHA256_CTX *ctx, BYTE hash[]) +{ + WORD i; + + i = ctx->datalen; + + // Pad whatever data is left in the buffer. + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + while (i < 56) + ctx->data[i++] = 0x00; + } + else { + ctx->data[i++] = 0x80; + while (i < 64) + ctx->data[i++] = 0x00; + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + // Append to the padding the total message's length in bits and transform. + ctx->bitlen += ctx->datalen * 8; + ctx->data[63] = ctx->bitlen; + ctx->data[62] = ctx->bitlen >> 8; + ctx->data[61] = ctx->bitlen >> 16; + ctx->data[60] = ctx->bitlen >> 24; + ctx->data[59] = ctx->bitlen >> 32; + ctx->data[58] = ctx->bitlen >> 40; + ctx->data[57] = ctx->bitlen >> 48; + ctx->data[56] = ctx->bitlen >> 56; + sha256_transform(ctx, ctx->data); + + // Since this implementation uses little endian byte ordering and SHA uses big endian, + // reverse all the bytes when copying the final state to the output hash. + for (i = 0; i < 4; ++i) { + hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; + hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; + hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; + hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; + hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; + hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; + hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; + hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; + } +} diff --git a/code/autoupdater/sha256.h b/code/autoupdater/sha256.h new file mode 100644 index 00000000..7123a30d --- /dev/null +++ b/code/autoupdater/sha256.h @@ -0,0 +1,34 @@ +/********************************************************************* +* Filename: sha256.h +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Defines the API for the corresponding SHA1 implementation. +*********************************************************************/ + +#ifndef SHA256_H +#define SHA256_H + +/*************************** HEADER FILES ***************************/ +#include + +/****************************** MACROS ******************************/ +#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest + +/**************************** DATA TYPES ****************************/ +typedef unsigned char BYTE; // 8-bit byte +typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines + +typedef struct { + BYTE data[64]; + WORD datalen; + unsigned long long bitlen; + WORD state[8]; +} SHA256_CTX; + +/*********************** FUNCTION DECLARATIONS **********************/ +void sha256_init(SHA256_CTX *ctx); +void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); +void sha256_final(SHA256_CTX *ctx, BYTE hash[]); + +#endif // SHA256_H diff --git a/code/sys/sys_main.c b/code/sys/sys_main.c index 6d7fe7bf..e7543044 100644 --- a/code/sys/sys_main.c +++ b/code/sys/sys_main.c @@ -31,6 +31,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include +#ifndef _WIN32 +#include +#endif + #ifndef DEDICATED #ifdef USE_LOCAL_HEADERS # include "SDL.h" @@ -659,6 +663,48 @@ int main( int argc, char **argv ) int i; char commandLine[ MAX_STRING_CHARS ] = { 0 }; +#ifdef USE_AUTOUPDATER +{ + #ifndef AUTOUPDATER_BIN + #error The build system should have defined AUTOUPDATER_BIN + #endif + + int updater_pipes[2]; + if (pipe(updater_pipes) == 0) + { + pid_t pid = fork(); + if (pid == -1) /* failure, oh well. */ + { + close(updater_pipes[0]); + close(updater_pipes[1]); + } + else if (pid == 0) /* child process */ + { + close(updater_pipes[1]); /* don't need write end. */ + if (dup2(updater_pipes[0], 3) != -1) + { + char pidstr[64]; + char *ptr = strrchr(argv[0], '/'); + if (ptr) + *ptr = '\0'; + chdir(argv[0]); + #ifdef __APPLE__ + chdir("../.."); /* put this at base of app bundle so paths make sense later. */ + #endif + snprintf(pidstr, sizeof (pidstr), "%lld", (long long) getppid()); + execl(AUTOUPDATER_BIN, AUTOUPDATER_BIN, "--waitpid", pidstr, NULL); + } + _exit(0); /* oh well. */ + } + else /* parent process */ + { + /* leave the write end open until we terminate so updater can block on it. */ + close(updater_pipes[0]); + } + } +} +#endif + #ifndef DEDICATED // SDL version check From 8e001e6b99e64163d9a32896592839f6aeb38e2c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 25 May 2017 22:01:47 -0400 Subject: [PATCH 02/20] autoupdater should build with $(CC), not $(TOOLS_CC), in Makefile. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 06507596..5315fbfc 100644 --- a/Makefile +++ b/Makefile @@ -1576,7 +1576,7 @@ $(Q3ASM): $(Q3ASMOBJ) define DO_AUTOUPDATER_CC $(echo_cmd) "AUTOUPDATER_CC $<" -$(Q)$(TOOLS_CC) $(CFLAGS) $(CURL_CFLAGS) -o $@ -c $< +$(Q)$(CC) $(CFLAGS) $(CURL_CFLAGS) -o $@ -c $< endef Q3AUTOUPDATEROBJ = \ From 240965ddbce2844afaabd0e6d7aba22b8940fa6b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 25 May 2017 22:02:33 -0400 Subject: [PATCH 03/20] Removed extraneous '\' in Makefile in autoupdater source file list. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5315fbfc..28fec01a 100644 --- a/Makefile +++ b/Makefile @@ -1581,7 +1581,7 @@ endef Q3AUTOUPDATEROBJ = \ $(B)/autoupdater/autoupdater.o \ - $(B)/autoupdater/sha256.o \ + $(B)/autoupdater/sha256.o $(B)/autoupdater/%.o: $(AUTOUPDATERSRCDIR)/%.c $(DO_AUTOUPDATER_CC) From 69829916b51c80c77297d96aedbd8b5be72e7d4a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2017 17:37:53 -0400 Subject: [PATCH 04/20] Fixed -Wstrict-prototypes warning. --- code/autoupdater/autoupdater.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index e8b8d40c..7e20d331 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -145,8 +145,8 @@ static void die(const char *why) exit(1); } -static void outOfMemory() NEVER_RETURNS; -static void outOfMemory() +static void outOfMemory(void) NEVER_RETURNS; +static void outOfMemory(void) { die("Out of memory"); } From 86e71b11eb87a83a0ea1c55c53ae9df470cad27d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2017 17:39:13 -0400 Subject: [PATCH 05/20] Fixed failure to link libcurl on some platforms. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 28fec01a..99f2ffe4 100644 --- a/Makefile +++ b/Makefile @@ -1588,7 +1588,7 @@ $(B)/autoupdater/%.o: $(AUTOUPDATERSRCDIR)/%.c $(B)/$(AUTOUPDATER_BIN): $(Q3AUTOUPDATEROBJ) $(echo_cmd) "AUTOUPDATER_LD $@" - $(Q)$(CC) $(LDFLAGS) $(CURL_LIBS) -o $@ $(Q3AUTOUPDATEROBJ) + $(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(CURL_LIBS) ############################################################################# From b892bcfdbc02c9089e60ebce854cf3b384b62139 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2017 18:02:48 -0400 Subject: [PATCH 06/20] Cleanup in failures a little better. --- code/autoupdater/autoupdater.c | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index 7e20d331..f2e05357 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -76,7 +76,7 @@ typedef struct ManifestItem struct ManifestItem *next; } ManifestItem; -static ManifestItem *manifest; +static ManifestItem *manifest = NULL; static void freeManifest(void) { @@ -92,6 +92,22 @@ static void freeManifest(void) manifest = NULL; } +static const char *timestamp(void) +{ + time_t t = time(NULL); + char *retval = asctime(localtime(&t)); + if (retval) { + char *ptr; + for (ptr = retval; *ptr; ptr++) { + if ((*ptr == '\r') || (*ptr == '\n')) { + *ptr = '\0'; + break; + } + } + } + return retval ? retval : "[date unknown]"; +} + static FILE *logfile = NULL; @@ -140,8 +156,10 @@ static void die(const char *why) NEVER_RETURNS; static void die(const char *why) { infof("FAILURE: %s", why); + curl_global_cleanup(); restoreRollbacks(); freeManifest(); + infof("Updater ending (in failure), %s", timestamp()); exit(1); } @@ -628,22 +646,6 @@ static void deleteRollbacks(void) } } -static const char *timestamp(void) -{ - time_t t = time(NULL); - char *retval = asctime(localtime(&t)); - if (retval) { - char *ptr; - for (ptr = retval; *ptr; ptr++) { - if ((*ptr == '\r') || (*ptr == '\n')) { - *ptr = '\0'; - break; - } - } - } - return retval ? retval : "[date unknown]"; -} - static void chdirToBasePath(const char *argv0) { const char *fname = justFilename(argv0); From f518f75149198ed45c1124f646e1351910279d21 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2017 20:15:59 -0400 Subject: [PATCH 07/20] Don't link directly to libcurl. Lots of Linux distros have different names (libcurl-gnutls.so vs etc), and version the symbols (curl_global_init@@CURL_LIBSSL_3), so it's more compatible to just dlsym the basic entry points we need and just demand that libcurl is installed at all. Alternately: we'll use our own libcurl build, but we'll probably have to dump SSL support to make this sane to do. --- Makefile | 2 +- code/autoupdater/autoupdater.c | 59 +++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 99f2ffe4..73db3f73 100644 --- a/Makefile +++ b/Makefile @@ -1588,7 +1588,7 @@ $(B)/autoupdater/%.o: $(AUTOUPDATERSRCDIR)/%.c $(B)/$(AUTOUPDATER_BIN): $(Q3AUTOUPDATEROBJ) $(echo_cmd) "AUTOUPDATER_LD $@" - $(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(CURL_LIBS) + $(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(LIBS) ############################################################################# diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index f2e05357..cffbfb52 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -151,8 +151,61 @@ static void restoreRollbacks(void) } } - static void die(const char *why) NEVER_RETURNS; + + +#ifndef _WIN32 /* hooray for Unix linker hostility! */ +#undef curl_easy_setopt +#include +typedef void (*CURLFN_curl_easy_cleanup)(CURL *curl); +typedef CURL *(*CURLFN_curl_easy_init)(void); +typedef CURLcode (*CURLFN_curl_easy_setopt)(CURL *curl, CURLoption option, ...); +typedef CURLcode (*CURLFN_curl_easy_perform)(CURL *curl); +typedef CURLcode (*CURLFN_curl_global_init)(long flags); +typedef void (*CURLFN_curl_global_cleanup)(void); + +static CURLFN_curl_easy_cleanup CURL_curl_easy_cleanup; +static CURLFN_curl_easy_init CURL_curl_easy_init; +static CURLFN_curl_easy_setopt CURL_curl_easy_setopt; +static CURLFN_curl_easy_perform CURL_curl_easy_perform; +static CURLFN_curl_global_init CURL_curl_global_init; +static CURLFN_curl_global_cleanup CURL_curl_global_cleanup; + +static void load_libcurl(void) +{ + #ifdef __APPLE__ + const char *libname = "libcurl.4.dylib"; + #else + const char *libname = "libcurl.so.4"; + #endif + + void *handle = dlopen(libname, RTLD_NOW | RTLD_GLOBAL); + if (!handle) { + infof("dlopen(\"%s\") failed: %s", libname, dlerror()); + die("Failed to load libcurl library"); + } + #define LOADCURLSYM(fn) \ + if ((CURL_##fn = (CURLFN_##fn) dlsym(handle, #fn)) == NULL) { \ + die("Failed to load libcurl symbol '" #fn "'"); \ + } + + LOADCURLSYM(curl_easy_cleanup); + LOADCURLSYM(curl_easy_init); + LOADCURLSYM(curl_easy_setopt); + LOADCURLSYM(curl_easy_perform); + LOADCURLSYM(curl_global_init); + LOADCURLSYM(curl_global_cleanup); +} + +#define curl_easy_cleanup CURL_curl_easy_cleanup +#define curl_easy_init CURL_curl_easy_init +#define curl_easy_setopt CURL_curl_easy_setopt +#define curl_easy_perform CURL_curl_easy_perform +#define curl_global_init CURL_curl_global_init +#define curl_global_cleanup CURL_curl_global_cleanup +#endif + + static void die(const char *why) { infof("FAILURE: %s", why); @@ -696,6 +749,10 @@ int main(int argc, char **argv) upgradeSelfAndRestart(argv[0]); } + #ifndef _WIN32 + load_libcurl(); + #endif + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { die("curl_global_init() failed!"); } From 67b0cccc75a6eb920f3db4977ec9fb5779f297c3 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2017 20:20:18 -0400 Subject: [PATCH 08/20] Don't fclose(NULL) if a file doesn't exist. --- code/autoupdater/autoupdater.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index cffbfb52..5cd053bf 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -651,10 +651,10 @@ static void applyUpdates(void) } io = fopen(item->fname, "rb"); - fclose(io); if (io != NULL) { static int rollbackIndex = 0; char rollbackPath[64]; + fclose(io); item->rollback = ++rollbackIndex; snprintf(rollbackPath, sizeof (rollbackPath), "updates/rollbacks/%d", rollbackIndex); infof("Moving file for rollback: '%s' -> '%s'", item->fname, rollbackPath); From 02b116aae049349ee343a6fcc4a0b0d1a0911575 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 31 May 2017 01:02:26 -0400 Subject: [PATCH 09/20] Initial Windows autoupdater support: the ioq3 internal bits. This is just the piece that will launch the autoupdater; the autoupdater itself will be a separate commit. --- code/sys/sys_main.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/code/sys/sys_main.c b/code/sys/sys_main.c index e7543044..81d5c977 100644 --- a/code/sys/sys_main.c +++ b/code/sys/sys_main.c @@ -31,9 +31,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include -#ifndef _WIN32 +#ifdef USE_AUTOUPDATER +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#include +#else #include #endif +#endif #ifndef DEDICATED #ifdef USE_LOCAL_HEADERS @@ -669,6 +674,20 @@ int main( int argc, char **argv ) #error The build system should have defined AUTOUPDATER_BIN #endif + #ifdef _WIN32 + { + /* We don't need the Unix pipe() tapdance here because Windows lets children wait on parent processes. */ + PROCESS_INFORMATION procinfo; + char cmdline[128]; + Com_sprintf(cmdline, sizeof (cmdline), AUTOUPDATER_BIN " --waitpid %u", (unsigned int) GetCurrentProcessId()); + if (CreateProcessA(AUTOUPDATER_BIN, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &procinfo)) + { + /* close handles now so child cleans up immediately if nothing to do */ + CloseHandle(procinfo.hProcess); + CloseHandle(procinfo.hThread); + } + } + #else int updater_pipes[2]; if (pipe(updater_pipes) == 0) { @@ -702,6 +721,7 @@ int main( int argc, char **argv ) close(updater_pipes[0]); } } + #endif } #endif From cf5dd87f57a70936cdb92537939a9013a9015ba1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 31 May 2017 01:04:17 -0400 Subject: [PATCH 10/20] Fix tabs vs spaces. --- code/sys/sys_main.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/code/sys/sys_main.c b/code/sys/sys_main.c index 81d5c977..5f1d4fc4 100644 --- a/code/sys/sys_main.c +++ b/code/sys/sys_main.c @@ -670,9 +670,9 @@ int main( int argc, char **argv ) #ifdef USE_AUTOUPDATER { - #ifndef AUTOUPDATER_BIN - #error The build system should have defined AUTOUPDATER_BIN - #endif + #ifndef AUTOUPDATER_BIN + #error The build system should have defined AUTOUPDATER_BIN + #endif #ifdef _WIN32 { @@ -707,9 +707,9 @@ int main( int argc, char **argv ) if (ptr) *ptr = '\0'; chdir(argv[0]); - #ifdef __APPLE__ - chdir("../.."); /* put this at base of app bundle so paths make sense later. */ - #endif + #ifdef __APPLE__ + chdir("../.."); /* put this at base of app bundle so paths make sense later. */ + #endif snprintf(pidstr, sizeof (pidstr), "%lld", (long long) getppid()); execl(AUTOUPDATER_BIN, AUTOUPDATER_BIN, "--waitpid", pidstr, NULL); } @@ -721,7 +721,7 @@ int main( int argc, char **argv ) close(updater_pipes[0]); } } - #endif + #endif } #endif From d0da0724e7cc048b8fd6b82e14bb48c76c86ebc2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 31 May 2017 01:21:21 -0400 Subject: [PATCH 11/20] Move the autoupdater launcher into its own public domain source file. So other games can steal this piece if they want. --- Makefile | 2 + code/sys/sys_autoupdater.c | 77 ++++++++++++++++++++++++++++++++++++++ code/sys/sys_main.c | 67 +-------------------------------- 3 files changed, 81 insertions(+), 65 deletions(-) create mode 100644 code/sys/sys_autoupdater.c diff --git a/Makefile b/Makefile index 73db3f73..964d3a76 100644 --- a/Makefile +++ b/Makefile @@ -1694,6 +1694,7 @@ Q3OBJ = \ $(B)/client/sdl_snd.o \ \ $(B)/client/con_log.o \ + $(B)/client/sys_autoupdater.o \ $(B)/client/sys_main.o ifdef MINGW @@ -2230,6 +2231,7 @@ Q3DOBJ = \ $(B)/ded/null_snddma.o \ \ $(B)/ded/con_log.o \ + $(B)/ded/sys_autoupdater.o \ $(B)/ded/sys_main.o ifeq ($(ARCH),x86) diff --git a/code/sys/sys_autoupdater.c b/code/sys/sys_autoupdater.c new file mode 100644 index 00000000..56873d4e --- /dev/null +++ b/code/sys/sys_autoupdater.c @@ -0,0 +1,77 @@ +/* +The code in this file is in the public domain. The rest of ioquake3 +is licensed until the GPLv2. Do not mingle code, please! +*/ + +#ifdef USE_AUTOUPDATER +# ifndef AUTOUPDATER_BIN +# error The build system should have defined AUTOUPDATER_BIN +# endif + +# ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 +# include +# else +# include +# endif + +# include +# include +#endif + +void Sys_LaunchAutoupdater(int argc, char **argv) +{ +#ifdef USE_AUTOUPDATER + #ifdef _WIN32 + { + /* We don't need the Unix pipe() tapdance here because Windows lets children wait on parent processes. */ + PROCESS_INFORMATION procinfo; + char cmdline[128]; + sprintf(cmdline, AUTOUPDATER_BIN " --waitpid %u", (unsigned int) GetCurrentProcessId()); + if (CreateProcessA(AUTOUPDATER_BIN, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &procinfo)) + { + /* close handles now so child cleans up immediately if nothing to do */ + CloseHandle(procinfo.hProcess); + CloseHandle(procinfo.hThread); + } + } + #else + int updater_pipes[2]; + if (pipe(updater_pipes) == 0) + { + pid_t pid = fork(); + if (pid == -1) /* failure, oh well. */ + { + close(updater_pipes[0]); + close(updater_pipes[1]); + } + else if (pid == 0) /* child process */ + { + close(updater_pipes[1]); /* don't need write end. */ + if (dup2(updater_pipes[0], 3) != -1) + { + char pidstr[64]; + char *ptr = strrchr(argv[0], '/'); + if (ptr) + *ptr = '\0'; + chdir(argv[0]); + #ifdef __APPLE__ + chdir("../.."); /* put this at base of app bundle so paths make sense later. */ + #endif + snprintf(pidstr, sizeof (pidstr), "%lld", (long long) getppid()); + execl(AUTOUPDATER_BIN, AUTOUPDATER_BIN, "--waitpid", pidstr, NULL); + } + _exit(0); /* oh well. */ + } + else /* parent process */ + { + /* leave the write end open until we terminate so updater can block on it. */ + close(updater_pipes[0]); + } + } + #endif +#endif + + (void) argc; (void) argv; /* possibly unused. Pacify compilers. */ +} + diff --git a/code/sys/sys_main.c b/code/sys/sys_main.c index 5f1d4fc4..44d74f92 100644 --- a/code/sys/sys_main.c +++ b/code/sys/sys_main.c @@ -31,15 +31,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include -#ifdef USE_AUTOUPDATER -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN 1 -#include -#else -#include -#endif -#endif - #ifndef DEDICATED #ifdef USE_LOCAL_HEADERS # include "SDL.h" @@ -668,62 +659,8 @@ int main( int argc, char **argv ) int i; char commandLine[ MAX_STRING_CHARS ] = { 0 }; -#ifdef USE_AUTOUPDATER -{ - #ifndef AUTOUPDATER_BIN - #error The build system should have defined AUTOUPDATER_BIN - #endif - - #ifdef _WIN32 - { - /* We don't need the Unix pipe() tapdance here because Windows lets children wait on parent processes. */ - PROCESS_INFORMATION procinfo; - char cmdline[128]; - Com_sprintf(cmdline, sizeof (cmdline), AUTOUPDATER_BIN " --waitpid %u", (unsigned int) GetCurrentProcessId()); - if (CreateProcessA(AUTOUPDATER_BIN, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &procinfo)) - { - /* close handles now so child cleans up immediately if nothing to do */ - CloseHandle(procinfo.hProcess); - CloseHandle(procinfo.hThread); - } - } - #else - int updater_pipes[2]; - if (pipe(updater_pipes) == 0) - { - pid_t pid = fork(); - if (pid == -1) /* failure, oh well. */ - { - close(updater_pipes[0]); - close(updater_pipes[1]); - } - else if (pid == 0) /* child process */ - { - close(updater_pipes[1]); /* don't need write end. */ - if (dup2(updater_pipes[0], 3) != -1) - { - char pidstr[64]; - char *ptr = strrchr(argv[0], '/'); - if (ptr) - *ptr = '\0'; - chdir(argv[0]); - #ifdef __APPLE__ - chdir("../.."); /* put this at base of app bundle so paths make sense later. */ - #endif - snprintf(pidstr, sizeof (pidstr), "%lld", (long long) getppid()); - execl(AUTOUPDATER_BIN, AUTOUPDATER_BIN, "--waitpid", pidstr, NULL); - } - _exit(0); /* oh well. */ - } - else /* parent process */ - { - /* leave the write end open until we terminate so updater can block on it. */ - close(updater_pipes[0]); - } - } - #endif -} -#endif + extern void Sys_LaunchAutoupdater(int argc, char **argv); + Sys_LaunchAutoupdater(argc, argv); #ifndef DEDICATED // SDL version check From a69020b21724fdd460120ab8d1e5764ce203dfd3 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 31 May 2017 03:39:45 -0400 Subject: [PATCH 12/20] Fixed up some types in sha256.* --- code/autoupdater/sha256.c | 16 +++++++--------- code/autoupdater/sha256.h | 24 ++++++++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/code/autoupdater/sha256.c b/code/autoupdater/sha256.c index eb9c5c07..0861860f 100644 --- a/code/autoupdater/sha256.c +++ b/code/autoupdater/sha256.c @@ -29,7 +29,7 @@ #define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) /**************************** VARIABLES *****************************/ -static const WORD k[64] = { +static const uint32 k[64] = { 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, @@ -41,9 +41,9 @@ static const WORD k[64] = { }; /*********************** FUNCTION DEFINITIONS ***********************/ -void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) +void sha256_transform(SHA256_CTX *ctx, const uint8 data[]) { - WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + uint32 a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; for (i = 0, j = 0; i < 16; ++i, j += 4) m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); @@ -96,9 +96,9 @@ void sha256_init(SHA256_CTX *ctx) ctx->state[7] = 0x5be0cd19; } -void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) +void sha256_update(SHA256_CTX *ctx, const uint8 data[], size_t len) { - WORD i; + size_t i; for (i = 0; i < len; ++i) { ctx->data[ctx->datalen] = data[i]; @@ -111,11 +111,9 @@ void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) } } -void sha256_final(SHA256_CTX *ctx, BYTE hash[]) +void sha256_final(SHA256_CTX *ctx, uint8 hash[]) { - WORD i; - - i = ctx->datalen; + uint32 i = ctx->datalen; // Pad whatever data is left in the buffer. if (ctx->datalen < 56) { diff --git a/code/autoupdater/sha256.h b/code/autoupdater/sha256.h index 7123a30d..fc9a09a9 100644 --- a/code/autoupdater/sha256.h +++ b/code/autoupdater/sha256.h @@ -16,19 +16,27 @@ #define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest /**************************** DATA TYPES ****************************/ -typedef unsigned char BYTE; // 8-bit byte -typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines +#ifdef _MSC_VER +typedef unsigned __int8 uint8; +typedef unsigned __int32 uint32; +typedef unsigned __int64 uint64; +#else +#include +typedef uint8_t uint8; +typedef uint32_t uint32; +typedef uint64_t uint64; +#endif typedef struct { - BYTE data[64]; - WORD datalen; - unsigned long long bitlen; - WORD state[8]; + uint8 data[64]; + uint32 datalen; + uint64 bitlen; + uint32 state[8]; } SHA256_CTX; /*********************** FUNCTION DECLARATIONS **********************/ void sha256_init(SHA256_CTX *ctx); -void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); -void sha256_final(SHA256_CTX *ctx, BYTE hash[]); +void sha256_update(SHA256_CTX *ctx, const uint8 data[], size_t len); +void sha256_final(SHA256_CTX *ctx, uint8 hash[]); #endif // SHA256_H From b6a83a1494031b090897657f8a3349cb35590f71 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 1 Jun 2017 13:00:37 -0400 Subject: [PATCH 13/20] ioquake3 calls this arch "x86" and not "i386". --- code/autoupdater/autoupdater.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index 5cd053bf..cf1dee55 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -40,7 +40,7 @@ is licensed until the GPLv2. Do not mingle code, please! #endif #ifdef __i386__ -#define AUTOUPDATE_ARCH "i386" +#define AUTOUPDATE_ARCH "x86" #elif defined(__x86_64__) #define AUTOUPDATE_ARCH "x86-64" #else From 8cf088ae276a7baa858e58eff78d6f72e368b8b0 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 1 Jun 2017 17:17:25 -0400 Subject: [PATCH 14/20] Fully initialize ManifestItems (rollback, etc, was uninitialized before!). --- code/autoupdater/autoupdater.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index cf1dee55..2576a719 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -399,7 +399,7 @@ static void parseManifest(const char *fname) if (!item) { infof("Next manifest item: %s", buf); - item = (ManifestItem *) malloc(sizeof (ManifestItem)); + item = (ManifestItem *) calloc(1, sizeof (ManifestItem)); if (!item) { outOfMemory(); } From 82977da9c86555ea7bab06330e91b74108d88edf Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 2 Jun 2017 00:49:42 -0400 Subject: [PATCH 15/20] Working Windows port of the autoupdater! --- Makefile | 14 +- code/autoupdater/autoupdater.c | 378 +++++++++++++++++++++++++-------- code/sys/sys_autoupdater.c | 9 +- 3 files changed, 304 insertions(+), 97 deletions(-) diff --git a/Makefile b/Makefile index 964d3a76..7b16040b 100644 --- a/Makefile +++ b/Makefile @@ -592,6 +592,8 @@ ifdef MINGW endif LIBS= -lws2_32 -lwinmm -lpsapi + AUTOUPDATER_LIBS += -lwininet + # clang 3.4 doesn't support this ifneq ("$(CC)", $(findstring "$(CC)", "clang" "clang++")) CLIENT_LDFLAGS += -mwindows @@ -985,7 +987,12 @@ ifneq ($(BUILD_GAME_QVM),0) endif ifneq ($(BUILD_AUTOUPDATER),0) - AUTOUPDATER_BIN := autoupdater$(FULLBINEXT) + # PLEASE NOTE that if you run an exe on Windows Vista or later + # with "setup", "install", "update" or other related terms, it + # will unconditionally trigger a UAC prompt, and in the case of + # ioq3 calling CreateProcess() on it, it'll just fail immediately. + # So don't call this thing "autoupdater" here! + AUTOUPDATER_BIN := autosyncerator$(FULLBINEXT) TARGETS += $(B)/$(AUTOUPDATER_BIN) endif @@ -1325,6 +1332,9 @@ endif @echo " CLIENT_LIBS:" $(call print_wrapped, $(CLIENT_LIBS)) @echo "" + @echo " AUTOUPDATER_LIBS:" + $(call print_wrapped, $(AUTOUPDATER_LIBS)) + @echo "" @echo " Output:" $(call print_list, $(NAKED_TARGETS)) @echo "" @@ -1588,7 +1598,7 @@ $(B)/autoupdater/%.o: $(AUTOUPDATERSRCDIR)/%.c $(B)/$(AUTOUPDATER_BIN): $(Q3AUTOUPDATEROBJ) $(echo_cmd) "AUTOUPDATER_LD $@" - $(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(LIBS) + $(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(AUTOUPDATER_LIBS) ############################################################################# diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index 2576a719..ed631e33 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -6,16 +6,37 @@ is licensed until the GPLv2. Do not mingle code, please! #include #include #include -#include #include +#ifdef _MSC_VER +typedef __int64 int64_t; +#else +#include +#endif + +#include #include #include -#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#include +#include +#include +#define PIDFMT "%u" +#define PIDFMTCAST unsigned int +typedef DWORD PID; +#else +#include #include +typedef pid_t PID; +#define PIDFMT "%llu" +#define PIDFMTCAST unsigned long long +#endif + #include "sha256.h" + #ifndef AUTOUPDATE_USER_AGENT #define AUTOUPDATE_USER_AGENT "ioq3autoupdater/0.1" #endif @@ -35,6 +56,8 @@ is licensed until the GPLv2. Do not mingle code, please! #define AUTOUPDATE_PLATFORM "mac" #elif defined(__linux__) #define AUTOUPDATE_PLATFORM "linux" +#elif defined(_WIN32) +#define AUTOUPDATE_PLATFORM "windows" #else #error Please define your platform. #endif @@ -154,7 +177,146 @@ static void restoreRollbacks(void) static void die(const char *why) NEVER_RETURNS; -#ifndef _WIN32 /* hooray for Unix linker hostility! */ + +#ifdef _WIN32 + +#define chmod(a,b) do {} while (0) +#define makeDir(path) mkdir(path) + +static void windowsWaitForProcessToDie(const DWORD pid) +{ + HANDLE h; + infof("Waiting on process ID #%u", (unsigned int) pid); + h = OpenProcess(SYNCHRONIZE, FALSE, pid); + if (!h) { +// !!! FIXME: what does this return if process is already dead? + die("OpenProcess failed"); + } + if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) { + die("WaitForSingleObject failed"); + } + CloseHandle(h); +} + +static void launchProcess(const char *exe, ...) +{ + PROCESS_INFORMATION procinfo; + STARTUPINFO startinfo; + va_list ap; + char cmdline[1024]; + char *ptr = cmdline; + size_t totallen = 0; + const char *arg = NULL; + + #define APPENDCMDLINE(str) { \ + const size_t len = strlen(str); \ + totallen += len; \ + if ((totallen + 1) < sizeof (cmdline)) { \ + strcpy(ptr, str); \ + ptr += len; \ + } \ + } + + va_start(ap, exe); + APPENDCMDLINE(exe); + while ((arg = va_arg(ap, const char *)) != NULL) { + APPENDCMDLINE(arg); + } + va_end(ap); + + if (totallen >= sizeof (cmdline)) { + die("command line too long to launch."); + } + + cmdline[totallen] = 0; + + infof("launching process '%s' with cmdline '%s'", exe, cmdline); + + memset(&procinfo, '\0', sizeof (procinfo)); + memset(&startinfo, '\0', sizeof (startinfo)); + startinfo.cb = sizeof (startinfo); + if (CreateProcessA(exe, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startinfo, &procinfo)) + { + CloseHandle(procinfo.hProcess); + CloseHandle(procinfo.hThread); + exit(0); /* we're done, it's launched. */ + } + + infof("CreateProcess failed: err=%d", (int) GetLastError()); +} + +static HINTERNET hInternet; +static void prepHttpLib(void) +{ + hInternet = InternetOpenA(AUTOUPDATE_USER_AGENT, + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, NULL, 0); + if (!hInternet) { + die("InternetOpen failed"); + } +} + +static void shutdownHttpLib(void) +{ + if (hInternet) { + InternetCloseHandle(hInternet); + hInternet = NULL; + } +} + +static int runHttpDownload(const char *from, FILE *to) +{ + /* !!! FIXME: some of this could benefit from GetLastError+FormatMessage. */ + int retval = 0; + DWORD httpcode = 0; + DWORD dwordlen = sizeof (DWORD); + DWORD zero = 0; + HINTERNET hUrl = InternetOpenUrlA(hInternet, from, NULL, 0, + INTERNET_FLAG_HYPERLINK | + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | + INTERNET_FLAG_NO_CACHE_WRITE | + INTERNET_FLAG_NO_COOKIES | + INTERNET_FLAG_NO_UI | + INTERNET_FLAG_RESYNCHRONIZE | + INTERNET_FLAG_RELOAD | + INTERNET_FLAG_SECURE, 0); + + if (!hUrl) { + infof("InternetOpenUrl failed. err=%d", (int) GetLastError()); + } else if (!HttpQueryInfo(hUrl, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &httpcode, &dwordlen, &zero)) { + infof("HttpQueryInfo failed. err=%d", (int) GetLastError()); + } else if (httpcode != 200) { + infof("HTTP request failed with response code %d", (int) httpcode); + } else { + while (1) { + DWORD br = 0; + BYTE buf[1024 * 64]; + if (!InternetReadFile(hUrl, buf, sizeof (buf), &br)) { + infof("InternetReadFile failed. err=%d", (int) GetLastError()); + break; + } else if (br == 0) { + retval = 1; + break; /* done! */ + } else { + if (fwrite(buf, br, 1, to) != 1) { + info("fwrite failed"); + break; + } + } + } + } + + InternetCloseHandle(hUrl); + return retval; +} + +#else /* Everything that isn't Windows. */ + +#define launchProcess execl +#define makeDir(path) mkdir(path, 0777) + +/* hooray for Unix linker hostility! */ #undef curl_easy_setopt #include typedef void (*CURLFN_curl_easy_cleanup)(CURL *curl); @@ -171,7 +333,7 @@ static CURLFN_curl_easy_perform CURL_curl_easy_perform; static CURLFN_curl_global_init CURL_curl_global_init; static CURLFN_curl_global_cleanup CURL_curl_global_cleanup; -static void load_libcurl(void) +static void prepHttpLib(void) { #ifdef __APPLE__ const char *libname = "libcurl.4.dylib"; @@ -195,21 +357,69 @@ static void load_libcurl(void) LOADCURLSYM(curl_easy_perform); LOADCURLSYM(curl_global_init); LOADCURLSYM(curl_global_cleanup); + + #define curl_easy_cleanup CURL_curl_easy_cleanup + #define curl_easy_init CURL_curl_easy_init + #define curl_easy_setopt CURL_curl_easy_setopt + #define curl_easy_perform CURL_curl_easy_perform + #define curl_global_init CURL_curl_global_init + #define curl_global_cleanup CURL_curl_global_cleanup + + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { + die("curl_global_init() failed!"); + } } -#define curl_easy_cleanup CURL_curl_easy_cleanup -#define curl_easy_init CURL_curl_easy_init -#define curl_easy_setopt CURL_curl_easy_setopt -#define curl_easy_perform CURL_curl_easy_perform -#define curl_global_init CURL_curl_global_init -#define curl_global_cleanup CURL_curl_global_cleanup +static void shutdownHttpLib(void) +{ + if (curl_global_cleanup) { + curl_global_cleanup(); + } +} + +static int runHttpDownload(const char *from, FILE *to) +{ + int retval; + CURL *curl = curl_easy_init(); + if (!curl) { + info("curl_easy_init() failed"); + return 0; + } + + #if 0 + /* !!! FIXME: enable compression? */ + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); /* enable compression */ + + /* !!! FIXME; hook up proxy support to libcurl */ + curl_easy_setopt(curl, CURLOPT_PROXY, proxyURL); + #endif + + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_STDERR, logfile); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, to); + + curl_easy_setopt(curl, CURLOPT_URL, from); + + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* allow redirects. */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, AUTOUPDATE_USER_AGENT); + + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); /* require valid SSL cert. */ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); /* require SSL cert with same hostname as we connected to. */ + + retval = (curl_easy_perform(curl) == CURLE_OK); + curl_easy_cleanup(curl); + return retval; +} #endif static void die(const char *why) { infof("FAILURE: %s", why); - curl_global_cleanup(); restoreRollbacks(); freeManifest(); infof("Updater ending (in failure), %s", timestamp()); @@ -222,12 +432,6 @@ static void outOfMemory(void) die("Out of memory"); } -static void makeDir(const char *dirname) -{ - /* !!! FIXME: we don't care if this fails right now. */ - mkdir(dirname, 0777); -} - static void buildParentDirs(const char *_path) { char *ptr; @@ -267,7 +471,7 @@ static void parseArgv(int argc, char **argv) for (i = 1; i < argc; i += 2) { if (strcmp(argv[i], "--waitpid") == 0) { options.waitforprocess = atoll(argv[i + 1]); - infof("We will wait for process %lld if necessary", (long long) options.waitforprocess); + infof("We will wait for process " PIDFMT " if necessary", (PIDFMTCAST) options.waitforprocess); } else if (strcmp(argv[i], "--updateself") == 0) { options.updateself = argv[i + 1]; infof("We are updating ourself ('%s')", options.updateself); @@ -275,57 +479,17 @@ static void parseArgv(int argc, char **argv) } } -static CURL *prepCurl(const char *url, FILE *outfile) +static void downloadURL(const char *from, const char *to) { - char *fullurl; - const size_t len = strlen(AUTOUPDATE_URL) + strlen(url) + 1; - CURL *curl = curl_easy_init(); - if (!curl) { - die("curl_easy_init() failed"); - } - - fullurl = (char *) alloca(len); + FILE *io = NULL; + const size_t len = strlen(AUTOUPDATE_URL) + strlen(from) + 1; + char *fullurl = (char *) alloca(len); if (!fullurl) { outOfMemory(); } + snprintf(fullurl, len, "%s%s", AUTOUPDATE_URL, from); - snprintf(fullurl, len, "%s%s", AUTOUPDATE_URL, url); - - infof("Downloading from '%s'", fullurl); - - #if 0 - /* !!! FIXME: enable compression? */ - curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); /* enable compression */ - - /* !!! FIXME; hook up proxy support to libcurl */ - curl_easy_setopt(curl, CURLOPT_PROXY, proxyURL); - #endif - - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); - curl_easy_setopt(curl, CURLOPT_STDERR, logfile); - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); - - curl_easy_setopt(curl, CURLOPT_URL, fullurl); - - curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* allow redirects. */ - curl_easy_setopt(curl, CURLOPT_USERAGENT, AUTOUPDATE_USER_AGENT); - - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); /* require valid SSL cert. */ - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); /* require SSL cert with same hostname as we connected to. */ - - return curl; -} - -static void downloadURL(const char *from, const char *to) -{ - FILE *io; - CURL *curl; - - infof("Preparing to download to '%s'", to); + infof("Downloading from '%s' to '%s'", fullurl, to); buildParentDirs(to); io = fopen(to, "wb"); @@ -333,12 +497,11 @@ static void downloadURL(const char *from, const char *to) die("Failed to open output file"); } - curl = prepCurl(from, io); - if (curl_easy_perform(curl) != CURLE_OK) { + if (!runHttpDownload(fullurl, io)) { + fclose(io); remove(to); die("Download failed"); } - curl_easy_cleanup(curl); if (fclose(io) == EOF) { die("Can't flush file on close. i/o error? Disk full?"); @@ -361,7 +524,7 @@ static int hexcvt(const int ch) return 0; } -static void convertSha256(char *str, BYTE *sha256) +static void convertSha256(char *str, uint8 *sha256) { int i; for (i = 0; i < 32; i++) { @@ -446,6 +609,32 @@ static void upgradeSelfAndRestart(const char *argv0) FILE *in = NULL; FILE *out = NULL; + /* unix replaces the process with execl(), but Windows needs to wait for the parent to terminate. */ + #ifdef _WIN32 + DWORD ppid = 0; + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h) { + const DWORD myPid = GetCurrentProcessId(); + PROCESSENTRY32 pe; + memset(&pe, '\0', sizeof (pe)); + pe.dwSize = sizeof(PROCESSENTRY32); + + if (Process32First(h, &pe)) { + do { + if (pe.th32ProcessID == myPid) { + ppid = pe.th32ParentProcessID; + break; + } + } while (Process32Next(h, &pe)); + } + CloseHandle(h); + } + if (!ppid) { + die("Can't determine parent process id"); + } + windowsWaitForProcessToDie(ppid); + #endif + in = fopen(argv0, "rb"); if (!in) { die("Can't open self for input while upgrading updater"); @@ -491,10 +680,10 @@ static void upgradeSelfAndRestart(const char *argv0) if (options.waitforprocess) { char pidstr[64]; - snprintf(pidstr, sizeof (pidstr), "%lld", (long long) options.waitforprocess); - execl(options.updateself, options.updateself, "--waitpid", pidstr, NULL); + snprintf(pidstr, sizeof (pidstr), PIDFMT, (PIDFMTCAST) options.waitforprocess); + launchProcess(options.updateself, options.updateself, "--waitpid", pidstr, NULL); } else { - execl(options.updateself, options.updateself, NULL); + launchProcess(options.updateself, options.updateself, NULL); } die("Failed to relaunch upgraded updater"); } @@ -508,7 +697,7 @@ static const char *justFilename(const char *path) static void hashFile(const char *fname, unsigned char *sha256) { SHA256_CTX sha256ctx; - BYTE buf[512]; + uint8 buf[512]; FILE *io; io = fopen(fname, "rb"); @@ -611,10 +800,10 @@ static void maybeUpdateSelf(const char *argv0) if (options.waitforprocess) { char pidstr[64]; - snprintf(pidstr, sizeof (pidstr), "%lld", (long long) options.waitforprocess); - execl(to, to, "--updateself", argv0, "--waitpid", pidstr, NULL); + snprintf(pidstr, sizeof (pidstr), PIDFMT, (PIDFMTCAST) options.waitforprocess); + launchProcess(to, to, "--updateself", argv0, "--waitpid", pidstr, NULL); } else { - execl(to, to, "--updateself", argv0, NULL); + launchProcess(to, to, "--updateself", argv0, NULL); } die("Failed to initially launch upgraded updater"); } @@ -668,19 +857,26 @@ static void applyUpdates(void) } } + static void waitToApplyUpdates(void) { if (options.waitforprocess) { - /* ioquake3 opens a pipe on fd 3, and then forgets about it. We block - on a read to that pipe here. When the game process quits (and the - OS forcibly closes the pipe), we will unblock. Then we can loop on - kill() until the process is truly gone. */ - int x = 0; - infof("Waiting for pid %lld to die...", (long long) options.waitforprocess); - read(3, &x, sizeof (x)); - info("Pipe has closed, waiting for process to fully go away now."); - while (kill(options.waitforprocess, 0) == 0) { - usleep(100000); + infof("Waiting for pid " PIDFMT " to die...", (PIDFMTCAST) options.waitforprocess); + { + #ifdef _WIN32 + windowsWaitForProcessToDie(options.waitforprocess); + #else + /* The parent opens a pipe on fd 3, and then forgets about it. We block + on a read to that pipe here. When the game process quits (and the + OS forcibly closes the pipe), we will unblock. Then we can loop on + kill() until the process is truly gone. */ + int x = 0; + read(3, &x, sizeof (x)); + info("Pipe has closed, waiting for process to fully go away now."); + while (kill(options.waitforprocess, 0) == 0) { + usleep(100000); + } + #endif } info("pid is gone, continuing"); } @@ -725,7 +921,9 @@ static void chdirToBasePath(const char *argv0) int main(int argc, char **argv) { + #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); /* don't trigger signal when fd3 closes */ + #endif logfile = stdout; chdirToBasePath(argv[0]); @@ -749,13 +947,7 @@ int main(int argc, char **argv) upgradeSelfAndRestart(argv[0]); } - #ifndef _WIN32 - load_libcurl(); - #endif - - if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { - die("curl_global_init() failed!"); - } + prepHttpLib(); downloadManifest(); /* see if we need an update at all. */ @@ -771,7 +963,7 @@ int main(int argc, char **argv) } freeManifest(); - curl_global_cleanup(); + shutdownHttpLib(); infof("Updater ending, %s", timestamp()); diff --git a/code/sys/sys_autoupdater.c b/code/sys/sys_autoupdater.c index 56873d4e..2982f855 100644 --- a/code/sys/sys_autoupdater.c +++ b/code/sys/sys_autoupdater.c @@ -26,9 +26,14 @@ void Sys_LaunchAutoupdater(int argc, char **argv) { /* We don't need the Unix pipe() tapdance here because Windows lets children wait on parent processes. */ PROCESS_INFORMATION procinfo; + STARTUPINFO startinfo; char cmdline[128]; - sprintf(cmdline, AUTOUPDATER_BIN " --waitpid %u", (unsigned int) GetCurrentProcessId()); - if (CreateProcessA(AUTOUPDATER_BIN, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &procinfo)) + memset(&procinfo, '\0', sizeof (procinfo)); + memset(&startinfo, '\0', sizeof (startinfo)); + startinfo.cb = sizeof (startinfo); + sprintf(cmdline, "" AUTOUPDATER_BIN " --waitpid %u", (unsigned int) GetCurrentProcessId()); + + if (CreateProcessA(AUTOUPDATER_BIN, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startinfo, &procinfo)) { /* close handles now so child cleans up immediately if nothing to do */ CloseHandle(procinfo.hProcess); From b5c54ec0190aaf47c6667286d3efeecfdbe13333 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 2 Jun 2017 01:31:45 -0400 Subject: [PATCH 16/20] Use stdint.h on Visual C if >= Visual Studio 2010. --- code/autoupdater/autoupdater.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index ed631e33..3a2d17b0 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -8,7 +8,7 @@ is licensed until the GPLv2. Do not mingle code, please! #include #include -#ifdef _MSC_VER +#if defined(_MSC_VER) && (_MSC_VER < 1600) typedef __int64 int64_t; #else #include From cd4aa2d9a9cffc5e600bbccc152397c8d5f744cc Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 2 Jun 2017 01:32:33 -0400 Subject: [PATCH 17/20] Don't fail if the game process went away before we were ready to wait for it. --- code/autoupdater/autoupdater.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index 3a2d17b0..7c0cebaa 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -189,7 +189,12 @@ static void windowsWaitForProcessToDie(const DWORD pid) infof("Waiting on process ID #%u", (unsigned int) pid); h = OpenProcess(SYNCHRONIZE, FALSE, pid); if (!h) { -// !!! FIXME: what does this return if process is already dead? + const DWORD err = GetLastError(); + if (err == ERROR_INVALID_PARAMETER) { + info("No such process; probably already dead. Carry on."); + return; /* process is (probably) already gone. */ + } + infof("OpenProcess failed. err=%d", (unsigned int) err); die("OpenProcess failed"); } if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) { From 063875e89a3d1ab4f98e0f0e1d47a7f8173b7f07 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 2 Jun 2017 01:39:03 -0400 Subject: [PATCH 18/20] Fixed linking on things that need -ldl, and compiler warnings. --- Makefile | 4 ++++ code/sys/sys_autoupdater.c | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7b16040b..322fc15d 100644 --- a/Makefile +++ b/Makefile @@ -376,6 +376,7 @@ ifneq (,$(findstring "$(PLATFORM)", "linux" "gnu_kfreebsd" "kfreebsd-gnu" "gnu") THREAD_LIBS=-lpthread LIBS=-ldl -lm + AUTOUPDATER_LIBS += -ldl CLIENT_LIBS=$(SDL_LIBS) RENDERER_LIBS = $(SDL_LIBS) -lGL @@ -834,6 +835,8 @@ ifeq ($(PLATFORM),irix64) SHLIBLDFLAGS=-shared LIBS=-ldl -lm -lgen + AUTOUPDATER_LIBS += -ldl + # FIXME: The X libraries probably aren't necessary? CLIENT_LIBS=-L/usr/X11/$(LIB) $(SDL_LIBS) \ -lX11 -lXext -lm @@ -888,6 +891,7 @@ ifeq ($(PLATFORM),sunos) THREAD_LIBS=-lpthread LIBS=-lsocket -lnsl -ldl -lm + AUTOUPDATER_LIBS += -ldl BOTCFLAGS=-O0 diff --git a/code/sys/sys_autoupdater.c b/code/sys/sys_autoupdater.c index 2982f855..1ed908d8 100644 --- a/code/sys/sys_autoupdater.c +++ b/code/sys/sys_autoupdater.c @@ -59,9 +59,13 @@ void Sys_LaunchAutoupdater(int argc, char **argv) char *ptr = strrchr(argv[0], '/'); if (ptr) *ptr = '\0'; - chdir(argv[0]); + if (chdir(argv[0]) == -1) { + _exit(1); /* oh well. */ + } #ifdef __APPLE__ - chdir("../.."); /* put this at base of app bundle so paths make sense later. */ + if (chdir("../..") == -1) { /* put this at base of app bundle so paths make sense later. */ + _exit(1); /* oh well. */ + } #endif snprintf(pidstr, sizeof (pidstr), "%lld", (long long) getppid()); execl(AUTOUPDATER_BIN, AUTOUPDATER_BIN, "--waitpid", pidstr, NULL); From 0eb497b01bb7cdd503bffab352f95495c74a470c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 2 Jun 2017 02:29:09 -0400 Subject: [PATCH 19/20] Disable the autoupdater; enable only if intentional, like for official builds. --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 322fc15d..1d472de2 100644 --- a/Makefile +++ b/Makefile @@ -35,8 +35,8 @@ endif ifndef BUILD_RENDERER_OPENGL2 BUILD_RENDERER_OPENGL2= endif -ifndef BUILD_AUTOUPDATER - BUILD_AUTOUPDATER= +ifndef BUILD_AUTOUPDATER # DON'T build unless you mean to! + BUILD_AUTOUPDATER=0 endif ############################################################################# @@ -231,8 +231,8 @@ ifndef USE_YACC USE_YACC=0 endif -ifndef USE_AUTOUPDATER -USE_AUTOUPDATER=1 +ifndef USE_AUTOUPDATER # DON'T include unless you mean to! +USE_AUTOUPDATER=0 endif ifndef DEBUG_CFLAGS From b33551dfa25794d7ffb61af89257c000cbdd6602 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 2 Jun 2017 11:28:33 -0400 Subject: [PATCH 20/20] Fixed comment typo: s/until/under --- code/autoupdater/autoupdater.c | 2 +- code/sys/sys_autoupdater.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c index 7c0cebaa..eb7937ee 100644 --- a/code/autoupdater/autoupdater.c +++ b/code/autoupdater/autoupdater.c @@ -1,6 +1,6 @@ /* The code in this file is in the public domain. The rest of ioquake3 -is licensed until the GPLv2. Do not mingle code, please! +is licensed under the GPLv2. Do not mingle code, please! */ #include diff --git a/code/sys/sys_autoupdater.c b/code/sys/sys_autoupdater.c index 1ed908d8..2f59e071 100644 --- a/code/sys/sys_autoupdater.c +++ b/code/sys/sys_autoupdater.c @@ -1,6 +1,6 @@ /* The code in this file is in the public domain. The rest of ioquake3 -is licensed until the GPLv2. Do not mingle code, please! +is licensed under the GPLv2. Do not mingle code, please! */ #ifdef USE_AUTOUPDATER