Working Windows port of the autoupdater!

This commit is contained in:
Ryan C. Gordon 2017-06-02 00:49:42 -04:00
parent 8cf088ae27
commit 82977da9c8
3 changed files with 304 additions and 97 deletions

View file

@ -592,6 +592,8 @@ ifdef MINGW
endif endif
LIBS= -lws2_32 -lwinmm -lpsapi LIBS= -lws2_32 -lwinmm -lpsapi
AUTOUPDATER_LIBS += -lwininet
# clang 3.4 doesn't support this # clang 3.4 doesn't support this
ifneq ("$(CC)", $(findstring "$(CC)", "clang" "clang++")) ifneq ("$(CC)", $(findstring "$(CC)", "clang" "clang++"))
CLIENT_LDFLAGS += -mwindows CLIENT_LDFLAGS += -mwindows
@ -985,7 +987,12 @@ ifneq ($(BUILD_GAME_QVM),0)
endif endif
ifneq ($(BUILD_AUTOUPDATER),0) 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) TARGETS += $(B)/$(AUTOUPDATER_BIN)
endif endif
@ -1325,6 +1332,9 @@ endif
@echo " CLIENT_LIBS:" @echo " CLIENT_LIBS:"
$(call print_wrapped, $(CLIENT_LIBS)) $(call print_wrapped, $(CLIENT_LIBS))
@echo "" @echo ""
@echo " AUTOUPDATER_LIBS:"
$(call print_wrapped, $(AUTOUPDATER_LIBS))
@echo ""
@echo " Output:" @echo " Output:"
$(call print_list, $(NAKED_TARGETS)) $(call print_list, $(NAKED_TARGETS))
@echo "" @echo ""
@ -1588,7 +1598,7 @@ $(B)/autoupdater/%.o: $(AUTOUPDATERSRCDIR)/%.c
$(B)/$(AUTOUPDATER_BIN): $(Q3AUTOUPDATEROBJ) $(B)/$(AUTOUPDATER_BIN): $(Q3AUTOUPDATEROBJ)
$(echo_cmd) "AUTOUPDATER_LD $@" $(echo_cmd) "AUTOUPDATER_LD $@"
$(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(LIBS) $(Q)$(CC) $(LDFLAGS) -o $@ $(Q3AUTOUPDATEROBJ) $(AUTOUPDATER_LIBS)
############################################################################# #############################################################################

View file

@ -6,16 +6,37 @@ is licensed until the GPLv2. Do not mingle code, please!
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
#ifdef _MSC_VER
typedef __int64 int64_t;
#else
#include <stdint.h>
#endif
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <signal.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> #include <curl/curl.h>
typedef pid_t PID;
#define PIDFMT "%llu"
#define PIDFMTCAST unsigned long long
#endif
#include "sha256.h" #include "sha256.h"
#ifndef AUTOUPDATE_USER_AGENT #ifndef AUTOUPDATE_USER_AGENT
#define AUTOUPDATE_USER_AGENT "ioq3autoupdater/0.1" #define AUTOUPDATE_USER_AGENT "ioq3autoupdater/0.1"
#endif #endif
@ -35,6 +56,8 @@ is licensed until the GPLv2. Do not mingle code, please!
#define AUTOUPDATE_PLATFORM "mac" #define AUTOUPDATE_PLATFORM "mac"
#elif defined(__linux__) #elif defined(__linux__)
#define AUTOUPDATE_PLATFORM "linux" #define AUTOUPDATE_PLATFORM "linux"
#elif defined(_WIN32)
#define AUTOUPDATE_PLATFORM "windows"
#else #else
#error Please define your platform. #error Please define your platform.
#endif #endif
@ -154,7 +177,146 @@ static void restoreRollbacks(void)
static void die(const char *why) NEVER_RETURNS; 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 #undef curl_easy_setopt
#include <dlfcn.h> #include <dlfcn.h>
typedef void (*CURLFN_curl_easy_cleanup)(CURL *curl); 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_init CURL_curl_global_init;
static CURLFN_curl_global_cleanup CURL_curl_global_cleanup; static CURLFN_curl_global_cleanup CURL_curl_global_cleanup;
static void load_libcurl(void) static void prepHttpLib(void)
{ {
#ifdef __APPLE__ #ifdef __APPLE__
const char *libname = "libcurl.4.dylib"; const char *libname = "libcurl.4.dylib";
@ -195,21 +357,69 @@ static void load_libcurl(void)
LOADCURLSYM(curl_easy_perform); LOADCURLSYM(curl_easy_perform);
LOADCURLSYM(curl_global_init); LOADCURLSYM(curl_global_init);
LOADCURLSYM(curl_global_cleanup); 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 static void shutdownHttpLib(void)
#define curl_easy_init CURL_curl_easy_init {
#define curl_easy_setopt CURL_curl_easy_setopt if (curl_global_cleanup) {
#define curl_easy_perform CURL_curl_easy_perform curl_global_cleanup();
#define curl_global_init CURL_curl_global_init }
#define curl_global_cleanup CURL_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 #endif
static void die(const char *why) static void die(const char *why)
{ {
infof("FAILURE: %s", why); infof("FAILURE: %s", why);
curl_global_cleanup();
restoreRollbacks(); restoreRollbacks();
freeManifest(); freeManifest();
infof("Updater ending (in failure), %s", timestamp()); infof("Updater ending (in failure), %s", timestamp());
@ -222,12 +432,6 @@ static void outOfMemory(void)
die("Out of memory"); 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) static void buildParentDirs(const char *_path)
{ {
char *ptr; char *ptr;
@ -267,7 +471,7 @@ static void parseArgv(int argc, char **argv)
for (i = 1; i < argc; i += 2) { for (i = 1; i < argc; i += 2) {
if (strcmp(argv[i], "--waitpid") == 0) { if (strcmp(argv[i], "--waitpid") == 0) {
options.waitforprocess = atoll(argv[i + 1]); 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) { } else if (strcmp(argv[i], "--updateself") == 0) {
options.updateself = argv[i + 1]; options.updateself = argv[i + 1];
infof("We are updating ourself ('%s')", options.updateself); 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; FILE *io = NULL;
const size_t len = strlen(AUTOUPDATE_URL) + strlen(url) + 1; const size_t len = strlen(AUTOUPDATE_URL) + strlen(from) + 1;
CURL *curl = curl_easy_init(); char *fullurl = (char *) alloca(len);
if (!curl) {
die("curl_easy_init() failed");
}
fullurl = (char *) alloca(len);
if (!fullurl) { if (!fullurl) {
outOfMemory(); outOfMemory();
} }
snprintf(fullurl, len, "%s%s", AUTOUPDATE_URL, from);
snprintf(fullurl, len, "%s%s", AUTOUPDATE_URL, url); infof("Downloading from '%s' to '%s'", fullurl, to);
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); buildParentDirs(to);
io = fopen(to, "wb"); io = fopen(to, "wb");
@ -333,12 +497,11 @@ static void downloadURL(const char *from, const char *to)
die("Failed to open output file"); die("Failed to open output file");
} }
curl = prepCurl(from, io); if (!runHttpDownload(fullurl, io)) {
if (curl_easy_perform(curl) != CURLE_OK) { fclose(io);
remove(to); remove(to);
die("Download failed"); die("Download failed");
} }
curl_easy_cleanup(curl);
if (fclose(io) == EOF) { if (fclose(io) == EOF) {
die("Can't flush file on close. i/o error? Disk full?"); die("Can't flush file on close. i/o error? Disk full?");
@ -361,7 +524,7 @@ static int hexcvt(const int ch)
return 0; return 0;
} }
static void convertSha256(char *str, BYTE *sha256) static void convertSha256(char *str, uint8 *sha256)
{ {
int i; int i;
for (i = 0; i < 32; i++) { for (i = 0; i < 32; i++) {
@ -446,6 +609,32 @@ static void upgradeSelfAndRestart(const char *argv0)
FILE *in = NULL; FILE *in = NULL;
FILE *out = 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"); in = fopen(argv0, "rb");
if (!in) { if (!in) {
die("Can't open self for input while upgrading updater"); die("Can't open self for input while upgrading updater");
@ -491,10 +680,10 @@ static void upgradeSelfAndRestart(const char *argv0)
if (options.waitforprocess) { if (options.waitforprocess) {
char pidstr[64]; char pidstr[64];
snprintf(pidstr, sizeof (pidstr), "%lld", (long long) options.waitforprocess); snprintf(pidstr, sizeof (pidstr), PIDFMT, (PIDFMTCAST) options.waitforprocess);
execl(options.updateself, options.updateself, "--waitpid", pidstr, NULL); launchProcess(options.updateself, options.updateself, "--waitpid", pidstr, NULL);
} else { } else {
execl(options.updateself, options.updateself, NULL); launchProcess(options.updateself, options.updateself, NULL);
} }
die("Failed to relaunch upgraded updater"); 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) static void hashFile(const char *fname, unsigned char *sha256)
{ {
SHA256_CTX sha256ctx; SHA256_CTX sha256ctx;
BYTE buf[512]; uint8 buf[512];
FILE *io; FILE *io;
io = fopen(fname, "rb"); io = fopen(fname, "rb");
@ -611,10 +800,10 @@ static void maybeUpdateSelf(const char *argv0)
if (options.waitforprocess) { if (options.waitforprocess) {
char pidstr[64]; char pidstr[64];
snprintf(pidstr, sizeof (pidstr), "%lld", (long long) options.waitforprocess); snprintf(pidstr, sizeof (pidstr), PIDFMT, (PIDFMTCAST) options.waitforprocess);
execl(to, to, "--updateself", argv0, "--waitpid", pidstr, NULL); launchProcess(to, to, "--updateself", argv0, "--waitpid", pidstr, NULL);
} else { } else {
execl(to, to, "--updateself", argv0, NULL); launchProcess(to, to, "--updateself", argv0, NULL);
} }
die("Failed to initially launch upgraded updater"); die("Failed to initially launch upgraded updater");
} }
@ -668,20 +857,27 @@ static void applyUpdates(void)
} }
} }
static void waitToApplyUpdates(void) static void waitToApplyUpdates(void)
{ {
if (options.waitforprocess) { if (options.waitforprocess) {
/* ioquake3 opens a pipe on fd 3, and then forgets about it. We block 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 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 OS forcibly closes the pipe), we will unblock. Then we can loop on
kill() until the process is truly gone. */ kill() until the process is truly gone. */
int x = 0; int x = 0;
infof("Waiting for pid %lld to die...", (long long) options.waitforprocess);
read(3, &x, sizeof (x)); read(3, &x, sizeof (x));
info("Pipe has closed, waiting for process to fully go away now."); info("Pipe has closed, waiting for process to fully go away now.");
while (kill(options.waitforprocess, 0) == 0) { while (kill(options.waitforprocess, 0) == 0) {
usleep(100000); usleep(100000);
} }
#endif
}
info("pid is gone, continuing"); info("pid is gone, continuing");
} }
} }
@ -725,7 +921,9 @@ static void chdirToBasePath(const char *argv0)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); /* don't trigger signal when fd3 closes */ signal(SIGPIPE, SIG_IGN); /* don't trigger signal when fd3 closes */
#endif
logfile = stdout; logfile = stdout;
chdirToBasePath(argv[0]); chdirToBasePath(argv[0]);
@ -749,13 +947,7 @@ int main(int argc, char **argv)
upgradeSelfAndRestart(argv[0]); upgradeSelfAndRestart(argv[0]);
} }
#ifndef _WIN32 prepHttpLib();
load_libcurl();
#endif
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
die("curl_global_init() failed!");
}
downloadManifest(); /* see if we need an update at all. */ downloadManifest(); /* see if we need an update at all. */
@ -771,7 +963,7 @@ int main(int argc, char **argv)
} }
freeManifest(); freeManifest();
curl_global_cleanup(); shutdownHttpLib();
infof("Updater ending, %s", timestamp()); infof("Updater ending, %s", timestamp());

View file

@ -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. */ /* We don't need the Unix pipe() tapdance here because Windows lets children wait on parent processes. */
PROCESS_INFORMATION procinfo; PROCESS_INFORMATION procinfo;
STARTUPINFO startinfo;
char cmdline[128]; char cmdline[128];
sprintf(cmdline, AUTOUPDATER_BIN " --waitpid %u", (unsigned int) GetCurrentProcessId()); memset(&procinfo, '\0', sizeof (procinfo));
if (CreateProcessA(AUTOUPDATER_BIN, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &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 */ /* close handles now so child cleans up immediately if nothing to do */
CloseHandle(procinfo.hProcess); CloseHandle(procinfo.hProcess);