core/systems/PluginSys.h
author David Anderson <dvander@alliedmods.net>
Mon Oct 22 08:53:15 2007 +0000 (2007-10-22)
changeset 1638 2980312c1c94
parent 1630 2f2fe925f377
child 1689 451c11ef473a
permissions -rw-r--r--
- sourcemod now intelligently unloads plugins which are leaking insane amounts of handles. these unloads get logged to sourcemod_fatal.log
- unloading is now delayed if a plugin is in the middle of a callstack
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2007 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 #ifndef _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
    33 #define _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
    34 
    35 #include <time.h>
    36 #include <errno.h>
    37 #include <sys/stat.h>
    38 #include <IPluginSys.h>
    39 #include <IHandleSys.h>
    40 #include <IForwardSys.h>
    41 #include <sh_list.h>
    42 #include <sh_stack.h>
    43 #include <sh_vector.h>
    44 #include <sh_string.h>
    45 #include "sm_globals.h"
    46 #include "vm/sp_vm_basecontext.h"
    47 #include "PluginInfoDatabase.h"
    48 #include "sm_trie.h"
    49 #include "sourcemod.h"
    50 #include <IRootConsoleMenu.h>
    51 #if defined ORANGEBOX_BUILD
    52 #include "convar_sm_ob.h"
    53 #else
    54 #include "convar_sm.h"
    55 #endif
    56 
    57 using namespace SourceHook;
    58 
    59 /**
    60  * NOTES:
    61  *
    62  *  Currently this system needs a lot of work but it's good skeletally.  Plugin creation 
    63  * is done without actually compiling anything.  This is done by Load functions in the 
    64  * manager.  This will need a rewrite when we add context switching.
    65  *
    66  *  The plugin object itself has a few things to note.  The most important is that it stores
    67  * a table of function objects.  The manager marshals allocation and freeing of these objects.
    68  * The plugin object can be in erroneous states, they are:
    69  *   Plugin_Error   --> Some error occurred any time during or after compilation.
    70  *						This error can be cleared since the plugin itself is valid.
    71  *						However, the state itself being set prevents any runtime action.
    72  *   Plugin_BadLoad	--> The plugin failed to load entirely and nothing can be done to save it.
    73  *
    74  *  If a plugin fails to load externally, it is never added to the internal tracker.  However, 
    75  * plugins that failed to load from the internal loading mechanism are always tracked.  This 
    76  * allows users to see which automatically loaded plugins failed, and makes the interface a bit
    77  * more flexible.
    78  *
    79  *  Once a plugin is compiled, it sets its own state to Plugin_Created.  This state is still invalid
    80  * for execution.  SourceMod is a two pass system, and even though the second pass is not implemented
    81  * yet, it is structured so Plugin_Created must be switched to Plugin_Running in the second pass.  When
    82  * implemented, a Created plugin will be switched to Error in the second pass if it not loadable.
    83  *
    84  *  The two pass loading mechanism is described below.  Modules/natives are not implemented yet.
    85  * PASS ONE: All loadable plugins are found and have the following steps performed:
    86  *			 1.  Loading and compilation is attempted.
    87  *			 2.  If successful, all natives from Core are added.
    88  *			 3.  OnPluginLoad() is called.
    89  *			 4.  If failed, any user natives are scrapped and the process halts here.
    90  *			 5.  If successful, the plugin is ready for Pass 2.
    91  * INTERMEDIATE:
    92  *			 1.  All forced modules are loaded.
    93  * PASS TWO: All loaded plugins are found and have these steps performed:
    94  *			 1. Any modules referenced in the plugin that are not already loaded, are loaded.
    95  *			 2. If any module fails to load and the plugin requires it, load fails and jump to step 6.
    96  *			 3. If any natives are unresolved, check if they are found in the user-natives pool.
    97  *			 4. If yes, load succeeds.  If not, natives are passed through a native acceptance filter.
    98  *			 5. If the filter fails, the plugin is marked as failed.
    99  *			 6. If the plugin has failed to load at this point, any dynamic natives it has added are scrapped.
   100  *			    Furthermore, any plugin that referenced these natives must now have pass 2 re-ran.
   101  * PASS THREE (not a real pass):
   102  *			 7. Once all plugins are deemed to be loaded, OnPluginStart() is called
   103  */
   104 
   105 #define SM_CONTEXTVAR_MYSELF	0
   106 
   107 struct ContextPair
   108 {
   109 	ContextPair() : base(NULL), ctx(NULL), co(NULL)
   110 	{
   111 	};
   112 	BaseContext *base;
   113 	sp_context_t *ctx;
   114 	ICompilation *co;
   115 	IVirtualMachine *vm;
   116 };
   117 
   118 struct FakeNative
   119 {
   120 	IPluginContext *ctx;
   121 	IPluginFunction *call;
   122 	String name;
   123 	SPVM_NATIVE_FUNC func;
   124 };
   125 
   126 enum LoadRes
   127 {
   128 	LoadRes_Successful,
   129 	LoadRes_AlreadyLoaded,
   130 	LoadRes_Failure,
   131 	LoadRes_NeverLoad
   132 };
   133 
   134 struct AutoConfig
   135 {
   136 	String autocfg;
   137 	String folder;
   138 	bool create;
   139 };
   140 
   141 class CPlugin;
   142 struct WeakNative
   143 {
   144 	WeakNative(CPlugin *plugin, uint32_t index)
   145 	{
   146 		pl = plugin;
   147 		idx = index;
   148 	}
   149 	CPlugin *pl;
   150 	uint32_t idx;
   151 };
   152 
   153 class CPlugin : public IPlugin
   154 {
   155 	friend class CPluginManager;
   156 	friend class CFunction;
   157 public:
   158 	CPlugin(const char *file);
   159 	~CPlugin();
   160 public:
   161 	PluginType GetType();
   162 	SourcePawn::IPluginContext *GetBaseContext();
   163 	sp_context_t *GetContext();
   164 	const sm_plugininfo_t *GetPublicInfo();
   165 	const char *GetFilename();
   166 	bool IsDebugging();
   167 	PluginStatus GetStatus();
   168 	bool SetPauseState(bool paused);
   169 	unsigned int GetSerial();
   170 	const sp_plugin_t *GetPluginStructure();
   171 	IdentityToken_t *GetIdentity();
   172 	bool SetProperty(const char *prop, void *ptr);
   173 	bool GetProperty(const char *prop, void **ptr, bool remove=false);
   174 public:
   175 	/**
   176 	 * Creates a plugin object with default values.
   177 	 *   If an error buffer is specified, and an error occurs, the error will be copied to the buffer
   178 	 * and NULL will be returned.
   179 	 *   If an error buffer is not specified, the error will be copied to an internal buffer and 
   180 	 * a valid (but error-stated) CPlugin will be returned.
   181 	 */
   182 	static CPlugin *CreatePlugin(const char *file, char *error, size_t maxlength);
   183 public:
   184 	/**
   185 	 * Starts the initial compilation of a plugin.
   186 	 * Returns false if another compilation exists or there is a current context set.
   187 	 */
   188 	ICompilation *StartMyCompile(IVirtualMachine *vm);
   189 	/** 
   190 	 * Finalizes a compilation.  If error buffer is NULL, the error is saved locally.
   191 	 */
   192 	bool FinishMyCompile(char *error, size_t maxlength);
   193 	void CancelMyCompile();
   194 
   195 	/**
   196 	 * Sets an error state on the plugin
   197 	 */
   198 	void SetErrorState(PluginStatus status, const char *error_fmt, ...);
   199 
   200 	/**
   201 	 * Initializes the plugin's identity information
   202 	 */
   203 	void InitIdentity();
   204 
   205 	/**
   206 	 * Calls the OnPluginLoad function, and sets any failed states if necessary.
   207 	 * NOTE: Valid pre-states are: Plugin_Created
   208 	 * NOTE: If validated, plugin state is changed to Plugin_Loaded
   209 	 *
   210 	 * If the error buffer is NULL, the error message is cached locally.
   211 	 */
   212 	bool Call_AskPluginLoad(char *error, size_t maxlength);
   213 
   214 	/**
   215 	 * Calls the OnPluginStart function.
   216 	 * NOTE: Valid pre-states are: Plugin_Created
   217 	 * NOTE: Post-state will be Plugin_Running
   218 	 */
   219 	void Call_OnPluginStart();
   220 
   221 	/**
   222 	 * Calls the OnPluginEnd function.
   223 	 */
   224 	void Call_OnPluginEnd();
   225 
   226 	/**
   227 	 * Calls the OnAllPluginsLoaded function.
   228 	 */
   229 	void Call_OnAllPluginsLoaded();
   230 
   231 	/**
   232 	 * Toggles debug mode in the plugin
   233 	 */
   234 	bool ToggleDebugMode(bool debug, char *error, size_t maxlength);
   235 
   236 	/**
   237 	 * Returns true if a plugin is usable.
   238 	 */
   239 	bool IsRunnable();
   240 
   241 	/**
   242 	 * Adds a language file index to the plugin's list.
   243 	 */
   244 	void AddLangFile(unsigned int index);
   245 
   246 	/**
   247 	 * Get language file count for this plugin.
   248 	 */
   249 	size_t GetLangFileCount();
   250 
   251 	/**
   252 	 * Get language file index based on the vector index.
   253 	 */
   254 	unsigned int GetLangFileByIndex(unsigned int index);
   255 public:
   256 	/**
   257 	 * Returns the modification time during last plugin load.
   258 	 */
   259 	time_t GetTimeStamp();
   260 
   261 	/**
   262 	 * Returns the current modification time of the plugin file.
   263 	 */
   264 	time_t GetFileTimeStamp();
   265 
   266 	/** 
   267 	 * Returns true if the plugin was running, but is now invalid.
   268 	 */
   269 	bool WasRunning();
   270 
   271 	Handle_t GetMyHandle();
   272 
   273 	void AddConfig(bool autoCreate, const char *cfg, const char *folder);
   274 	unsigned int GetConfigCount();
   275 	AutoConfig *GetConfig(unsigned int i);
   276 	inline void AddLibrary(const char *name)
   277 	{
   278 		m_Libraries.push_back(name);
   279 	}
   280 	void LibraryActions(bool dropping);
   281 protected:
   282 	void UpdateInfo();
   283 	void SetTimeStamp(time_t t);
   284 	void DependencyDropped(CPlugin *pOwner);
   285 private:
   286 	ContextPair m_ctx;
   287 	PluginType m_type;
   288 	char m_filename[PLATFORM_MAX_PATH];
   289 	PluginStatus m_status;
   290 	unsigned int m_serial;
   291 	sm_plugininfo_t m_info;
   292 	sp_plugin_t *m_plugin;
   293 	char m_errormsg[256];
   294 	time_t m_LastAccess;
   295 	IdentityToken_t *m_ident;
   296 	Handle_t m_handle;
   297 	bool m_WasRunning;
   298 	CVector<unsigned int> m_PhraseFiles;
   299 	List<CPlugin *> m_dependents;
   300 	List<CPlugin *> m_dependsOn;
   301 	List<FakeNative *> m_fakeNatives;
   302 	List<WeakNative> m_WeakNatives;
   303 	List<String> m_RequiredLibs;
   304 	List<String> m_Libraries;
   305 	Trie *m_pProps;
   306 	bool m_FakeNativesMissing;
   307 	bool m_LibraryMissing;
   308 	CVector<AutoConfig *> m_configs;
   309 	bool m_bGotAllLoaded;
   310 };
   311 
   312 class CPluginManager : 
   313 	public IPluginManager,
   314 	public SMGlobalClass,
   315 	public IHandleTypeDispatch,
   316 	public IRootConsoleCommand
   317 {
   318 	friend class CPlugin;
   319 public:
   320 	CPluginManager();
   321 	~CPluginManager();
   322 public:
   323 	/* Implements iterator class */
   324 	class CPluginIterator : public IPluginIterator
   325 	{
   326 	public:
   327 		CPluginIterator(List<CPlugin *> *mylist);
   328 		virtual ~CPluginIterator();
   329 		virtual bool MorePlugins();
   330 		virtual IPlugin *GetPlugin();
   331 		virtual void NextPlugin();
   332 		void Release();
   333 	public:
   334 		void Reset();
   335 	private:
   336 		List<CPlugin *> *mylist;
   337 		List<CPlugin *>::iterator current;
   338 	};
   339 	friend class CPluginManager::CPluginIterator;
   340 public: //IPluginManager
   341 	IPlugin *LoadPlugin(const char *path, 
   342 								bool debug,
   343 								PluginType type,
   344 								char error[],
   345 								size_t maxlength,
   346 								bool *wasloaded);
   347 	bool UnloadPlugin(IPlugin *plugin);
   348 	IPlugin *FindPluginByContext(const sp_context_t *ctx);
   349 	unsigned int GetPluginCount();
   350 	IPluginIterator *GetPluginIterator();
   351 	void AddPluginsListener(IPluginsListener *listener);
   352 	void RemovePluginsListener(IPluginsListener *listener);
   353 public: //SMGlobalClass
   354 	void OnSourceModAllInitialized();
   355 	void OnSourceModShutdown();
   356 public: //IHandleTypeDispatch
   357 	void OnHandleDestroy(HandleType_t type, void *object);
   358 public: //IRootConsoleCommand
   359 	void OnRootConsoleCommand(const char *cmdname, const CCommand &command);
   360 public:
   361 	/**
   362 	 * Loads all plugins not yet loaded
   363 	 */
   364 	void LoadAll_FirstPass(const char *config, const char *basedir);
   365 
   366 	/**
   367 	 * Runs the second loading pass for all plugins
   368 	 */
   369 	void LoadAll_SecondPass();
   370 
   371 	/**
   372 	 * Tests a plugin file mask against a local folder.
   373 	 * The alias is searched backwards from localdir - i.e., given this input:
   374 	 *   csdm/ban        csdm/ban
   375 	 *   ban             csdm/ban
   376 	 *   csdm/ban        optional/csdm/ban
   377 	 * All of these will return true for an alias match.  
   378 	 * Wildcards are allowed in the filename.
   379 	 */
   380 	bool TestAliasMatch(const char *alias, const char *localdir);
   381 
   382 	/** 
   383 	 * Returns whether anything loaded will be a late load.
   384 	 */
   385 	bool IsLateLoadTime() const;
   386 
   387 	/**
   388 	 * Adds natives from core into the native pool.
   389 	 */
   390 	void RegisterNativesFromCore(sp_nativeinfo_t *natives);
   391 
   392 	/**
   393 	 * Converts a Handle to an IPlugin if possible.
   394 	 */
   395 	IPlugin *PluginFromHandle(Handle_t handle, HandleError *err);
   396 
   397 	/**
   398 	 * Finds a plugin based on its index. (starts on index 1)
   399 	 */
   400 	CPlugin *GetPluginByOrder(int num);
   401 
   402 	int GetOrderOfPlugin(IPlugin *pl);
   403 
   404 	/** 
   405 	 * Internal version of FindPluginByContext()
   406 	 */
   407 	inline CPlugin *GetPluginByCtx(const sp_context_t *ctx)
   408 	{
   409 		return reinterpret_cast<CPlugin *>(ctx->user[SM_CONTEXTVAR_MYSELF]);
   410 	}
   411 
   412 	/**
   413 	 * Gets status text for a status code 
   414 	 */
   415 	const char *GetStatusText(PluginStatus status);
   416 
   417 	/**
   418 	* Reload or update plugins on level shutdown.
   419 	*/
   420 	void ReloadOrUnloadPlugins();
   421 
   422 	/**
   423 	 * Add public functions from all running or paused
   424 	 * plugins to the specified forward if the names match.
   425 	 */
   426 	void AddFunctionsToForward(const char *name, IChangeableForward *pForward);
   427 
   428 	/**
   429 	 * Iterates through plugins to call OnAllPluginsLoaded.
   430 	 */
   431 	void AllPluginsLoaded();
   432 
   433 	CPlugin *GetPluginFromIdentity(IdentityToken_t *pToken);
   434 
   435 	void Shutdown();
   436 
   437 	void OnLibraryAction(const char *lib, bool is_a_plugin, bool drop);
   438 
   439 	bool LibraryExists(const char *lib);
   440 
   441 	bool ReloadPlugin(CPlugin *pl);
   442 
   443 	void UnloadAll();
   444 private:
   445 	LoadRes _LoadPlugin(CPlugin **pPlugin, const char *path, bool debug, PluginType type, char error[], size_t maxlength);
   446 
   447 	void LoadAutoPlugin(const char *plugin);
   448 
   449 	/**
   450 	 * Recursively loads all plugins in the given directory.
   451 	 */
   452 	void LoadPluginsFromDir(const char *basedir, const char *localdir);
   453 
   454 	/**
   455 	 * Adds a plugin object.  This is wrapped by LoadPlugin functions.
   456 	 */
   457 	void AddPlugin(CPlugin *pPlugin);
   458 
   459 	/**
   460 	 * Runs the second loading pass on a plugin.
   461 	 */
   462 	bool RunSecondPass(CPlugin *pPlugin, char *error, size_t maxlength);
   463 
   464 	/**
   465 	 * Adds any globally registered natives to a plugin
   466 	 */
   467 	void AddCoreNativesToPlugin(CPlugin *pPlugin);
   468 
   469 	/**
   470 	 * Runs an extension pass on a plugin.
   471 	 */
   472 	bool LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass, char *error, size_t maxlength);
   473 
   474 	/**
   475 	* Manages required natives.
   476 	*/
   477 	bool FindOrRequirePluginDeps(CPlugin *pPlugin, char *error, size_t maxlength);
   478 
   479 	void _SetPauseState(CPlugin *pPlugin, bool pause);
   480 protected:
   481 	/**
   482 	 * Caching internal objects
   483 	 */
   484 	void ReleaseIterator(CPluginIterator *iter);
   485 public:
   486 	inline IdentityToken_t *GetIdentity()
   487 	{
   488 		return m_MyIdent;
   489 	}
   490 public:
   491 	bool AddFakeNative(IPluginFunction *pFunction, const char *name, SPVM_FAKENATIVE_FUNC func);
   492 private:
   493 	void AddFakeNativesToPlugin(CPlugin *pPlugin);
   494 	void TryRefreshDependencies(CPlugin *pOther);
   495 private:
   496 	List<IPluginsListener *> m_listeners;
   497 	List<CPlugin *> m_plugins;
   498 	CStack<CPluginManager::CPluginIterator *> m_iters;
   499 	CPluginInfoDatabase m_PluginInfo;
   500 	Trie *m_LoadLookup;
   501 	bool m_AllPluginsLoaded;
   502 	IdentityToken_t *m_MyIdent;
   503 	Trie *m_pCoreNatives;
   504 
   505 	/* Dynamic native stuff */
   506 	List<FakeNative *> m_Natives;
   507 	Trie *m_pNativeLookup;
   508 
   509 	bool m_LoadingLocked;
   510 };
   511 
   512 extern CPluginManager g_PluginSys;
   513 
   514 #endif //_INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_