dvander@2562: /** dvander@2562: * vim: set ts=4 : dvander@2562: * ============================================================================= dvander@2562: * SourceMod Updater Extension dvander@2562: * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved. dvander@2562: * ============================================================================= dvander@2562: * dvander@2562: * This program is free software; you can redistribute it and/or modify it under dvander@2562: * the terms of the GNU General Public License, version 3.0, as published by the dvander@2562: * Free Software Foundation. dvander@2562: * dvander@2562: * This program is distributed in the hope that it will be useful, but WITHOUT dvander@2562: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS dvander@2562: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more dvander@2562: * details. dvander@2562: * dvander@2562: * You should have received a copy of the GNU General Public License along with dvander@2562: * this program. If not, see . dvander@2562: * dvander@2562: * As a special exception, AlliedModders LLC gives you permission to link the dvander@2562: * code of this program (as well as its derivative works) to "Half-Life 2," the dvander@2562: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software dvander@2562: * by the Valve Corporation. You must obey the GNU General Public License in dvander@2562: * all respects for all other code used. Additionally, AlliedModders LLC grants dvander@2562: * this exception to all derivative works. AlliedModders LLC defines further dvander@2562: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), dvander@2562: * or . dvander@2562: * dvander@2562: * Version: $Id$ dvander@2562: */ dvander@2562: dvander@2783: #include dvander@2562: #include dvander@2562: #include dvander@2562: #include dvander@2562: #include dvander@2562: #include "extension.h" dvander@2562: #include "Updater.h" dvander@2562: #include dvander@2562: #include dvander@2562: dvander@2575: #define DEFAULT_UPDATE_URL "http://www.sourcemod.net/update/" dvander@2575: dvander@2562: using namespace SourceHook; dvander@2562: dvander@2562: SmUpdater g_Updater; /**< Global singleton for extension's main interface */ dvander@2562: SMEXT_LINK(&g_Updater); dvander@2562: dvander@2562: IWebternet *webternet; dvander@2562: static List update_errors; dvander@2562: static IThreadHandle *update_thread; dvander@2575: static String update_url; dvander@2562: dvander@2562: bool SmUpdater::SDK_OnLoad(char *error, size_t maxlength, bool late) dvander@2562: { dvander@2562: sharesys->AddDependency(myself, "webternet.ext", true, true); dvander@2562: dvander@2562: SM_GET_IFACE(WEBTERNET, webternet); dvander@2682: dvander@2682: const char *url = smutils->GetCoreConfigValue("AutoUpdateURL"); dvander@2682: if (url == NULL) dvander@2682: { dvander@2682: url = DEFAULT_UPDATE_URL; dvander@2682: } dvander@2682: update_url.assign(url); dvander@2562: dvander@2562: ThreadParams params; dvander@2562: params.flags = Thread_Default; dvander@2562: params.prio = ThreadPrio_Low; dvander@2562: update_thread = threader->MakeThread(this, ¶ms); dvander@2562: dvander@2562: if (update_thread == NULL) dvander@2562: { dvander@2562: smutils->Format(error, maxlength, "Could not create thread"); dvander@2562: return false; dvander@2562: } dvander@2562: dvander@2562: return true; dvander@2562: } dvander@2562: dvander@2562: void SmUpdater::SDK_OnUnload() dvander@2562: { dvander@2562: /* An interface drop might have killed this thread. dvander@2562: * But if the handle is still there, we have to wait. dvander@2562: */ dvander@2562: if (update_thread != NULL) dvander@2562: { dvander@2562: update_thread->WaitForThread(); dvander@2562: update_thread->DestroyThis(); dvander@2562: } dvander@2562: dvander@2562: /* Clear message tables */ dvander@2634: List::iterator iter = update_errors.begin(); dvander@2562: while (iter != update_errors.end()) dvander@2562: { dvander@2562: iter = update_errors.erase(iter); dvander@2562: } dvander@2562: } dvander@2562: dvander@2562: bool SmUpdater::QueryInterfaceDrop(SourceMod::SMInterface *pInterface) dvander@2562: { dvander@2562: if (pInterface == webternet) dvander@2562: { dvander@2562: return false; dvander@2562: } dvander@2562: dvander@2562: return true; dvander@2562: } dvander@2562: dvander@2562: void SmUpdater::NotifyInterfaceDrop(SMInterface *pInterface) dvander@2562: { dvander@2562: if (pInterface == webternet) dvander@2562: { dvander@2562: /* Can't be in the thread if we're losing this extension. */ dvander@2562: update_thread->WaitForThread(); dvander@2562: update_thread->DestroyThis(); dvander@2562: update_thread = NULL; dvander@2562: } dvander@2562: } dvander@2562: dvander@2573: static void PumpUpdate(void *data) dvander@2562: { dvander@2562: String *str; dvander@2628: bool new_files = false; dvander@2562: List::iterator iter; dvander@2562: dvander@2573: char path[PLATFORM_MAX_PATH]; dvander@2573: UpdatePart *temp; dvander@2573: UpdatePart *part = (UpdatePart*)data; dvander@2573: while (part != NULL) dvander@2573: { dvander@2576: if (strstr(part->file, "..") != NULL) dvander@2576: { dvander@2576: /* Naughty naughty */ dvander@2576: AddUpdateError("Detected invalid path escape (..): %s", part->file); dvander@2576: goto skip_create; dvander@2576: } dvander@2573: if (part->data == NULL) dvander@2573: { dvander@2573: smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file); dvander@2573: if (libsys->IsPathDirectory(path)) dvander@2573: { dvander@2679: goto skip_create; dvander@2573: } dvander@2573: if (!libsys->CreateFolder(path)) dvander@2573: { dvander@2573: AddUpdateError("Could not create folder: %s", path); dvander@2573: } dvander@2573: else dvander@2573: { dvander@2573: smutils->LogMessage(myself, "Created folder \"%s\" from updater", path); dvander@2573: } dvander@2573: } dvander@2573: else dvander@2573: { dvander@2573: smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file); dvander@2630: FILE *fp = fopen(path, "wb"); dvander@2573: if (fp == NULL) dvander@2573: { dvander@2573: AddUpdateError("Could not open %s for writing", path); dvander@2679: goto skip_create; dvander@2573: } dvander@2633: if (fwrite(part->data, 1, part->length, fp) != part->length) dvander@2633: { dvander@2633: AddUpdateError("Could not write file %s", path); dvander@2633: } dvander@2633: else dvander@2633: { dvander@2633: smutils->LogMessage(myself, dvander@2633: "Successfully updated gamedata file \"%s\"", dvander@2633: part->file); dvander@2633: new_files = true; dvander@2633: } dvander@2573: fclose(fp); dvander@2573: } dvander@2576: skip_create: dvander@2573: temp = part->next; dvander@2573: free(part->data); dvander@2573: free(part->file); dvander@2573: delete part; dvander@2573: part = temp; dvander@2573: } dvander@2573: dvander@2562: if (update_errors.size()) dvander@2562: { dvander@2562: smutils->LogError(myself, "--- BEGIN ERRORS FROM AUTOMATIC UPDATER ---"); dvander@2562: dvander@2562: for (iter = update_errors.begin(); dvander@2562: iter != update_errors.end(); dvander@2562: iter++) dvander@2562: { dvander@2562: str = (*iter); dvander@2562: smutils->LogError(myself, "%s", str->c_str()); dvander@2562: } dvander@2562: dvander@2562: smutils->LogError(myself, "--- END ERRORS FROM AUTOMATIC UPDATER ---"); dvander@2562: } dvander@2628: dvander@2628: if (new_files) dvander@2628: { dvander@2628: const char *force_restart = smutils->GetCoreConfigValue("ForceRestartAfterUpdate"); dvander@2628: if (force_restart == NULL || strcasecmp(force_restart, "yes") != 0) dvander@2628: { dvander@2628: smutils->LogMessage(myself, dvander@2628: "SourceMod has been updated, please reload it or restart your server."); dvander@2628: } dvander@2628: else dvander@2628: { dvander@2628: char buffer[255]; dvander@2628: smutils->Format(buffer, dvander@2628: sizeof(buffer), dvander@2628: "meta unload %d\n", dvander@2628: smutils->GetPluginId()); dvander@2628: gamehelpers->ServerCommand(buffer); dvander@2628: smutils->Format(buffer, dvander@2628: sizeof(buffer), dvander@2628: "changelevel \"%s\"\n", dvander@2628: gamehelpers->GetCurrentMap()); dvander@2628: gamehelpers->ServerCommand(buffer); dvander@2628: gamehelpers->ServerCommand("echo SourceMod has been restarted from an automatic update.\n"); dvander@2628: } dvander@2628: } dvander@2562: } dvander@2562: dvander@2562: void SmUpdater::RunThread(IThreadHandle *pHandle) dvander@2562: { dvander@2562: UpdateReader ur; dvander@2562: dvander@2575: ur.PerformUpdate(update_url.c_str()); dvander@2562: dvander@2573: smutils->AddFrameAction(PumpUpdate, ur.DetachParts()); dvander@2562: } dvander@2562: dvander@2562: void SmUpdater::OnTerminate(IThreadHandle *pHandle, bool cancel) dvander@2562: { dvander@2562: } dvander@2562: dvander@2562: void AddUpdateError(const char *fmt, ...) dvander@2562: { dvander@2562: va_list ap; dvander@2562: char buffer[2048]; dvander@2562: dvander@2562: va_start(ap, fmt); dvander@2562: smutils->FormatArgs(buffer, sizeof(buffer), fmt, ap); dvander@2562: va_end(ap); dvander@2562: dvander@2562: update_errors.push_back(new String(buffer)); dvander@2562: } dvander@2783: dvander@2790: const char *SmUpdater::GetExtensionVerString() dvander@2783: { asherkin@3630: return SM_VERSION_STRING; dvander@2783: } dvander@2783: dvander@2790: const char *SmUpdater::GetExtensionDateString() dvander@2783: { dvander@2783: return SM_BUILD_TIMESTAMP; dvander@2783: } dvander@2783: