extensions/updater/extension.cpp
author David Anderson <dvander@alliedmods.net>
Sun Mar 01 01:18:09 2009 -0500 (2009-03-01)
changeset 2630 b0dcc1e3397c
parent 2628 541e48653064
child 2633 dff17e9ffd0e
permissions -rw-r--r--
Do binary writes instead.
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Updater Extension
     5  * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <stdio.h>
    33 #include <stdarg.h>
    34 #include <string.h>
    35 #include <stdlib.h>
    36 #include "extension.h"
    37 #include "Updater.h"
    38 #include <sh_list.h>
    39 #include <sh_string.h>
    40 
    41 #define DEFAULT_UPDATE_URL			"http://www.sourcemod.net/update/"
    42 
    43 using namespace SourceHook;
    44 
    45 SmUpdater g_Updater;		/**< Global singleton for extension's main interface */
    46 SMEXT_LINK(&g_Updater);
    47 
    48 IWebternet *webternet;
    49 static List<String *> update_errors;
    50 static IThreadHandle *update_thread;
    51 static String update_url;
    52 
    53 bool SmUpdater::SDK_OnLoad(char *error, size_t maxlength, bool late)
    54 {
    55 	sharesys->AddDependency(myself, "webternet.ext", true, true);
    56 
    57 	SM_GET_IFACE(WEBTERNET, webternet);
    58 	
    59 	ThreadParams params;
    60 	params.flags = Thread_Default;
    61 	params.prio = ThreadPrio_Low;
    62 	update_thread = threader->MakeThread(this, &params);
    63 
    64 	if (update_thread == NULL)
    65 	{
    66 		smutils->Format(error, maxlength, "Could not create thread");
    67 		return false;
    68 	}
    69 
    70 	const char *url = smutils->GetCoreConfigValue("AutoUpdateURL");
    71 	if (url == NULL)
    72 	{
    73 		url = DEFAULT_UPDATE_URL;
    74 	}
    75 	update_url.assign(url);
    76 
    77 	return true;
    78 }
    79 
    80 void SmUpdater::SDK_OnUnload()
    81 {
    82 	/* An interface drop might have killed this thread.  
    83 	 * But if the handle is still there, we have to wait.
    84 	 */
    85 	if (update_thread != NULL)
    86 	{
    87 		update_thread->WaitForThread();
    88 		update_thread->DestroyThis();
    89 	}
    90 
    91 	/* Clear message tables */
    92 	List<String *>::iterator iter;
    93 	while (iter != update_errors.end())
    94 	{
    95 		iter = update_errors.erase(iter);
    96 	}
    97 }
    98 
    99 bool SmUpdater::QueryInterfaceDrop(SourceMod::SMInterface *pInterface)
   100 {
   101 	if (pInterface == webternet)
   102 	{
   103 		return false;
   104 	}
   105 
   106 	return true;
   107 }
   108 
   109 void SmUpdater::NotifyInterfaceDrop(SMInterface *pInterface)
   110 {
   111 	if (pInterface == webternet)
   112 	{
   113 		/* Can't be in the thread if we're losing this extension. */
   114 		update_thread->WaitForThread();
   115 		update_thread->DestroyThis();
   116 		update_thread = NULL;
   117 	}
   118 }
   119 
   120 static void PumpUpdate(void *data)
   121 {
   122 	String *str;
   123 	bool new_files = false;
   124 	List<String *>::iterator iter;
   125 
   126 	char path[PLATFORM_MAX_PATH];
   127 	UpdatePart *temp;
   128 	UpdatePart *part = (UpdatePart*)data;
   129 	while (part != NULL)
   130 	{
   131 		if (strstr(part->file, "..") != NULL)
   132 		{
   133 			/* Naughty naughty */
   134 			AddUpdateError("Detected invalid path escape (..): %s", part->file);
   135 			goto skip_create;
   136 		}
   137 		if (part->data == NULL)
   138 		{
   139 			smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file);
   140 			if (libsys->IsPathDirectory(path))
   141 			{
   142 				continue;
   143 			}
   144 			if (!libsys->CreateFolder(path))
   145 			{
   146 				AddUpdateError("Could not create folder: %s", path);
   147 			}
   148 			else
   149 			{
   150 				smutils->LogMessage(myself, "Created folder \"%s\" from updater", path);
   151 			}
   152 		}
   153 		else
   154 		{
   155 			smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file);
   156 			FILE *fp = fopen(path, "wb");
   157 			if (fp == NULL)
   158 			{
   159 				AddUpdateError("Could not open %s for writing", path);
   160 				return;
   161 			}
   162 			fwrite(part->data, 1, part->length, fp);
   163 			fclose(fp);
   164 			smutils->LogMessage(myself,
   165 				"Successfully updated gamedata file \"%s\"",
   166 				part->file);
   167 			new_files = true;
   168 		}
   169 skip_create:
   170 		temp = part->next;
   171 		free(part->data);
   172 		free(part->file);
   173 		delete part;
   174 		part = temp;
   175 	}
   176 
   177 	if (update_errors.size())
   178 	{
   179 		smutils->LogError(myself, "--- BEGIN ERRORS FROM AUTOMATIC UPDATER ---");
   180 
   181 		for (iter = update_errors.begin();
   182 			 iter != update_errors.end();
   183 			 iter++)
   184 		{
   185 			str = (*iter);
   186 			smutils->LogError(myself, "%s", str->c_str());
   187 		}
   188 
   189 		smutils->LogError(myself, "--- END ERRORS FROM AUTOMATIC UPDATER ---");
   190 	}
   191 
   192 	if (new_files)
   193 	{
   194 		const char *force_restart = smutils->GetCoreConfigValue("ForceRestartAfterUpdate");
   195 		if (force_restart == NULL || strcasecmp(force_restart, "yes") != 0)
   196 		{
   197 			smutils->LogMessage(myself,
   198 				"SourceMod has been updated, please reload it or restart your server.");
   199 		}
   200 		else
   201 		{
   202 			char buffer[255];
   203 			smutils->Format(buffer,
   204 				sizeof(buffer),
   205 				"meta unload %d\n",
   206 				smutils->GetPluginId());
   207 			gamehelpers->ServerCommand(buffer);
   208 			smutils->Format(buffer,
   209 				sizeof(buffer),
   210 				"changelevel \"%s\"\n",
   211 				gamehelpers->GetCurrentMap());
   212 			gamehelpers->ServerCommand(buffer);
   213 			gamehelpers->ServerCommand("echo SourceMod has been restarted from an automatic update.\n");
   214 		}
   215 	}
   216 }
   217 
   218 void SmUpdater::RunThread(IThreadHandle *pHandle)
   219 {
   220 	UpdateReader ur;
   221 
   222 	ur.PerformUpdate(update_url.c_str());
   223 
   224 	smutils->AddFrameAction(PumpUpdate, ur.DetachParts());
   225 }
   226 
   227 void SmUpdater::OnTerminate(IThreadHandle *pHandle, bool cancel)
   228 {
   229 }
   230 
   231 void AddUpdateError(const char *fmt, ...)
   232 {
   233 	va_list ap;
   234 	char buffer[2048];
   235 
   236 	va_start(ap, fmt);
   237 	smutils->FormatArgs(buffer, sizeof(buffer), fmt, ap);
   238 	va_end(ap);
   239 
   240 	update_errors.push_back(new String(buffer));
   241 }