Merge pull request #290 from rcgordon/autoupdater

Initial shot at writing an ioquake3 autoupdater. Thank you icculus, and everyone who contributed to his patreon! https://www.patreon.com/icculus
This commit is contained in:
Zachary J. Slater 2017-06-02 22:46:22 -10:00 committed by GitHub
commit 87eecd7bc1
7 changed files with 1468 additions and 22 deletions

View file

@ -35,6 +35,9 @@ endif
ifndef BUILD_RENDERER_OPENGL2
BUILD_RENDERER_OPENGL2=
endif
ifndef BUILD_AUTOUPDATER # DON'T build unless you mean to!
BUILD_AUTOUPDATER=0
endif
#############################################################################
#
@ -228,6 +231,10 @@ ifndef USE_YACC
USE_YACC=0
endif
ifndef USE_AUTOUPDATER # DON'T include unless you mean to!
USE_AUTOUPDATER=0
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,8 +277,9 @@ 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),)
@ -286,6 +295,7 @@ ifneq ($(BUILD_CLIENT),0)
CURL_LIBS ?= -lcurl
OPENAL_LIBS ?= -lopenal
endif
# Use sdl2-config if all else fails
ifeq ($(SDL_CFLAGS),)
ifneq ($(call bin_path, sdl2-config),)
@ -293,7 +303,6 @@ ifneq ($(BUILD_CLIENT),0)
SDL_LIBS ?= $(shell sdl2-config --libs)
endif
endif
endif
# Add git version info
USE_GIT=
@ -367,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
@ -594,6 +604,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
@ -834,6 +846,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 +902,7 @@ ifeq ($(PLATFORM),sunos)
THREAD_LIBS=-lpthread
LIBS=-lsocket -lnsl -ldl -lm
AUTOUPDATER_LIBS += -ldl
BOTCFLAGS=-O0
@ -986,6 +1001,16 @@ ifneq ($(BUILD_GAME_QVM),0)
endif
endif
ifneq ($(BUILD_AUTOUPDATER),0)
# 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
ifeq ($(USE_OPENAL),1)
CLIENT_CFLAGS += -DUSE_OPENAL
ifeq ($(USE_OPENAL_DLOPEN),1)
@ -1086,6 +1111,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
@ -1317,6 +1347,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 ""
@ -1342,6 +1375,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
@ -1561,6 +1595,27 @@ $(Q3ASM): $(Q3ASMOBJ)
$(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS)
#############################################################################
# AUTOUPDATER
#############################################################################
define DO_AUTOUPDATER_CC
$(echo_cmd) "AUTOUPDATER_CC $<"
$(Q)$(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) -o $@ $(Q3AUTOUPDATEROBJ) $(AUTOUPDATER_LIBS)
#############################################################################
# CLIENT/SERVER
#############################################################################
@ -1664,6 +1719,7 @@ Q3OBJ = \
$(B)/client/sdl_snd.o \
\
$(B)/client/con_log.o \
$(B)/client/sys_autoupdater.o \
$(B)/client/sys_main.o
ifdef MINGW
@ -2200,6 +2256,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)

125
autoupdater-readme.txt Normal file
View file

@ -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.

View file

@ -0,0 +1,977 @@
/*
The code in this file is in the public domain. The rest of ioquake3
is licensed under the GPLv2. Do not mingle code, please!
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#if defined(_MSC_VER) && (_MSC_VER < 1600)
typedef __int64 int64_t;
#else
#include <stdint.h>
#endif
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <wininet.h>
#include <tlhelp32.h>
#define PIDFMT "%u"
#define PIDFMTCAST unsigned int
typedef DWORD PID;
#else
#include <signal.h>
#include <curl/curl.h>
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
#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"
#elif defined(_WIN32)
#define AUTOUPDATE_PLATFORM "windows"
#else
#error Please define your platform.
#endif
#ifdef __i386__
#define AUTOUPDATE_ARCH "x86"
#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 = NULL;
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 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;
#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;
#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) {
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) {
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 <dlfcn.h>
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 prepHttpLib(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
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
die("curl_global_init() failed!");
}
}
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);
restoreRollbacks();
freeManifest();
infof("Updater ending (in failure), %s", timestamp());
exit(1);
}
static void outOfMemory(void) NEVER_RETURNS;
static void outOfMemory(void)
{
die("Out of memory");
}
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 " 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);
}
}
}
static void downloadURL(const char *from, const char *to)
{
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);
infof("Downloading from '%s' to '%s'", fullurl, to);
buildParentDirs(to);
io = fopen(to, "wb");
if (!io) {
die("Failed to open output file");
}
if (!runHttpDownload(fullurl, io)) {
fclose(io);
remove(to);
die("Download failed");
}
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, uint8 *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 *) calloc(1, 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;
/* 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");
}
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), PIDFMT, (PIDFMTCAST) options.waitforprocess);
launchProcess(options.updateself, options.updateself, "--waitpid", pidstr, NULL);
} else {
launchProcess(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;
uint8 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), PIDFMT, (PIDFMTCAST) options.waitforprocess);
launchProcess(to, to, "--updateself", argv0, "--waitpid", pidstr, NULL);
} else {
launchProcess(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");
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);
remove(rollbackPath);
if (rename(item->fname, rollbackPath) == -1) {
die("failed to move to rollback dir");
}
}
installUpdatedFile(item);
}
}
static void waitToApplyUpdates(void)
{
if (options.waitforprocess) {
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");
}
}
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 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)
{
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); /* don't trigger signal when fd3 closes */
#endif
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]);
}
prepHttpLib();
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();
shutdownHttpLib();
infof("Updater ending, %s", timestamp());
return 0;
}

156
code/autoupdater/sha256.c Normal file
View file

@ -0,0 +1,156 @@
/*********************************************************************
* 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 <stdlib.h>
#include <memory.h>
#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 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,
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 uint8 data[])
{
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]);
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 uint8 data[], size_t len)
{
size_t 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, uint8 hash[])
{
uint32 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;
}
}

42
code/autoupdater/sha256.h Normal file
View file

@ -0,0 +1,42 @@
/*********************************************************************
* 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 <stddef.h>
/****************************** MACROS ******************************/
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
/**************************** DATA TYPES ****************************/
#ifdef _MSC_VER
typedef unsigned __int8 uint8;
typedef unsigned __int32 uint32;
typedef unsigned __int64 uint64;
#else
#include <stdint.h>
typedef uint8_t uint8;
typedef uint32_t uint32;
typedef uint64_t uint64;
#endif
typedef struct {
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 uint8 data[], size_t len);
void sha256_final(SHA256_CTX *ctx, uint8 hash[]);
#endif // SHA256_H

View file

@ -0,0 +1,86 @@
/*
The code in this file is in the public domain. The rest of ioquake3
is licensed under 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 <windows.h>
# else
# include <unistd.h>
# endif
# include <stdio.h>
# include <string.h>
#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;
STARTUPINFO startinfo;
char cmdline[128];
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);
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';
if (chdir(argv[0]) == -1) {
_exit(1); /* oh well. */
}
#ifdef __APPLE__
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);
}
_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. */
}

View file

@ -683,6 +683,9 @@ int main( int argc, char **argv )
int i;
char commandLine[ MAX_STRING_CHARS ] = { 0 };
extern void Sys_LaunchAutoupdater(int argc, char **argv);
Sys_LaunchAutoupdater(argc, argv);
#ifndef DEDICATED
// SDL version check