sourcemod/core/logic/PluginSys.h
Peace-Maker df59fd5b0e Fix scheduled plugin unloading
When unloading a plugin no plugin is allowed to execute code. There's
been some flawed logic in the scheduling, leading it to bail out if the
plugin was marked for scheduled unloading..

Also change the "error" message of `sm plugins unload` to something
nice.
2016-02-29 16:40:24 +01:00

505 lines
14 KiB
C++

/**
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
#define _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <IPluginSys.h>
#include <IHandleSys.h>
#include <IForwardSys.h>
#include <sh_list.h>
#include <sh_stack.h>
#include <sh_vector.h>
#include <sh_string.h>
#include "common_logic.h"
#include <IRootConsoleMenu.h>
#include <sm_stringhashmap.h>
#include <sm_namehashset.h>
#include "ITranslator.h"
#include "IGameConfigs.h"
#include "NativeOwner.h"
#include "ShareSys.h"
#include "PhraseCollection.h"
#include <am-string.h>
#include <bridge/include/IScriptManager.h>
#include <am-function.h>
#include <ReentrantList.h>
class CPlayer;
using namespace SourceHook;
enum LoadRes
{
LoadRes_Successful,
LoadRes_AlreadyLoaded,
LoadRes_Failure,
LoadRes_NeverLoad
};
enum APLRes
{
APLRes_Success,
APLRes_Failure,
APLRes_SilentFailure
};
// Plugin membership state.
enum class PluginState
{
// The plugin has not yet been added to the global plugin list.
Unregistered,
// The plugin is a member of the global plugin list.
Registered,
// The plugin has been evicted.
Evicted,
// The plugin is waiting to be unloaded.
WaitingToUnload
};
class CPlugin :
public SMPlugin,
public CNativeOwner
{
public:
CPlugin(const char *file);
~CPlugin();
public:
PluginType GetType();
SourcePawn::IPluginContext *GetBaseContext();
sp_context_t *GetContext();
void *GetPluginStructure();
const char *GetFilename();
bool IsDebugging();
PluginStatus GetStatus();
PluginStatus Status() const;
bool IsSilentlyFailed();
const sm_plugininfo_t *GetPublicInfo();
bool SetPauseState(bool paused);
unsigned int GetSerial();
IdentityToken_t *GetIdentity();
unsigned int CalcMemUsage();
bool SetProperty(const char *prop, void *ptr);
bool GetProperty(const char *prop, void **ptr, bool remove=false);
void DropEverything();
SourcePawn::IPluginRuntime *GetRuntime();
CNativeOwner *ToNativeOwner() {
return this;
}
struct ExtVar {
char *name;
char *file;
bool autoload;
bool required;
};
typedef ke::Lambda<bool(const sp_pubvar_t *, const ExtVar& ext)> ExtVarCallback;
bool ForEachExtVar(const ExtVarCallback& callback);
void ForEachLibrary(ke::Lambda<void(const char *)> callback);
public:
/**
* Creates a plugin object with default values.
* If an error buffer is specified, and an error occurs, the error will be copied to the buffer
* and NULL will be returned.
* If an error buffer is not specified, the error will be copied to an internal buffer and
* a valid (but error-stated) CPlugin will be returned.
*/
static CPlugin *Create(const char *file);
static inline bool matches(const char *file, const CPlugin *plugin)
{
return strcmp(plugin->m_filename, file) == 0;
}
public:
// Evicts the plugin from memory and sets an error state.
void EvictWithError(PluginStatus status, const char *error_fmt, ...);
void FinishEviction();
// Initializes the plugin's identity information
void InitIdentity();
// Calls the OnPluginLoad function, and sets any failed states if necessary.
// After invoking AskPluginLoad, its state is either Running or Failed.
APLRes AskPluginLoad();
// Transition to the fully running state, if possible.
bool OnPluginStart();
void Call_OnPluginEnd();
void Call_OnAllPluginsLoaded();
void Call_OnLibraryAdded(const char *lib);
// Returns true if a plugin is usable.
bool IsRunnable();
// Get languages info.
IPhraseCollection *GetPhrases();
PluginState State() const {
return m_state;
}
void SetRegistered();
void SetWaitingToUnload();
PluginStatus GetDisplayStatus() const {
return m_status;
}
bool IsEvictionCandidate() const;
public:
// Returns true if the plugin was running, but is now invalid.
bool WasRunning();
Handle_t GetMyHandle();
bool AddFakeNative(IPluginFunction *pFunc, const char *name, SPVM_FAKENATIVE_FUNC func);
void AddConfig(bool autoCreate, const char *cfg, const char *folder);
size_t GetConfigCount();
AutoConfig *GetConfig(size_t i);
inline void AddLibrary(const char *name) {
m_Libraries.push_back(name);
}
inline bool HasLibrary(const char *name) {
return m_Libraries.find(name) != m_Libraries.end();
}
void LibraryActions(LibraryAction action);
void SyncMaxClients(int max_clients);
// Returns true if the plugin's underlying file structure has a newer
// modification time than either when it was first instantiated or
// since the last call to HasUpdatedFile().
bool HasUpdatedFile();
const char *GetDateTime() const {
return m_DateTime;
}
int GetFileVersion() const {
return m_FileVersion;
}
const char *GetErrorMsg() const {
assert(m_status != Plugin_Running && m_status != Plugin_Loaded);
return m_errormsg;
}
void AddRequiredLib(const char *name);
bool ForEachRequiredLib(ke::Lambda<bool(const char *)> callback);
bool HasMissingFakeNatives() const {
return m_FakeNativesMissing;
}
bool HasMissingLibrary() const {
return m_LibraryMissing;
}
bool HasFakeNatives() const {
return m_fakes.length() > 0;
}
// True if we got far enough into the second pass to call OnPluginLoaded
// in the listener list.
bool EnteredSecondPass() const {
return m_EnteredSecondPass;
}
bool IsInErrorState() const {
if (m_status == Plugin_Running || m_status == Plugin_Loaded)
return false;
return true;
}
bool TryCompile();
void BindFakeNativesTo(CPlugin *other);
protected:
void DependencyDropped(CPlugin *pOwner);
private:
time_t GetFileTimeStamp();
bool ReadInfo();
void DestroyIdentity();
private:
// This information is static for the lifetime of the plugin.
char m_filename[PLATFORM_MAX_PATH];
unsigned int m_serial;
PluginStatus m_status;
PluginState m_state;
bool m_AddedLibraries;
bool m_EnteredSecondPass;
// Statuses that are set during failure.
bool m_SilentFailure;
bool m_FakeNativesMissing;
bool m_LibraryMissing;
char m_errormsg[256];
// Internal properties that must by reset if the runtime is evicted.
ke::AutoPtr<IPluginRuntime> m_pRuntime;
ke::AutoPtr<CPhraseCollection> m_pPhrases;
IPluginContext *m_pContext;
sp_pubvar_t *m_MaxClientsVar;
StringHashMap<void *> m_Props;
CVector<AutoConfig *> m_configs;
List<String> m_Libraries;
bool m_bGotAllLoaded;
int m_FileVersion;
// Information that survives past eviction.
List<String> m_RequiredLibs;
IdentityToken_t *m_ident;
time_t m_LastFileModTime;
Handle_t m_handle;
char m_DateTime[256];
// Cached.
sm_plugininfo_t m_info;
ke::AString info_name_;
ke::AString info_author_;
ke::AString info_description_;
ke::AString info_version_;
ke::AString info_url_;
};
class CPluginManager :
public IScriptManager,
public SMGlobalClass,
public IHandleTypeDispatch,
public IRootConsoleCommand
{
public:
CPluginManager();
~CPluginManager();
public:
/* Implements iterator class */
class CPluginIterator
: public IPluginIterator,
public IPluginsListener
{
public:
CPluginIterator(ReentrantList<CPlugin *>& in);
virtual ~CPluginIterator();
virtual bool MorePlugins();
virtual IPlugin *GetPlugin();
virtual void NextPlugin();
void Release();
void OnPluginDestroyed(IPlugin *plugin) override;
private:
ke::LinkedList<CPlugin *> mylist;
ke::LinkedList<CPlugin *>::iterator current;
};
friend class CPluginManager::CPluginIterator;
public: //IScriptManager
IPlugin *LoadPlugin(const char *path,
bool debug,
PluginType type,
char error[],
size_t maxlength,
bool *wasloaded);
bool UnloadPlugin(IPlugin *plugin);
IPlugin *FindPluginByContext(const sp_context_t *ctx);
unsigned int GetPluginCount();
IPluginIterator *GetPluginIterator();
void AddPluginsListener(IPluginsListener *listener);
void RemovePluginsListener(IPluginsListener *listener);
void LoadAll(const char *config_path, const char *plugins_path);
void RefreshAll();
SMPlugin *FindPluginByOrder(unsigned num) {
return GetPluginByOrder(num);
}
SMPlugin *FindPluginByIdentity(IdentityToken_t *ident) {
return GetPluginFromIdentity(ident);
}
SMPlugin *FindPluginByContext(IPluginContext *ctx) {
return GetPluginByCtx(ctx->GetContext());
}
SMPlugin *FindPluginByContext(sp_context_t *ctx) {
return GetPluginByCtx(ctx);
}
SMPlugin *FindPluginByConsoleArg(const char *text);
SMPlugin *FindPluginByHandle(Handle_t hndl, HandleError *errp) {
return static_cast<SMPlugin *>(PluginFromHandle(hndl, errp));
}
const CVector<SMPlugin *> *ListPlugins();
void FreePluginList(const CVector<SMPlugin *> *plugins);
public: //SMGlobalClass
void OnSourceModAllInitialized();
void OnSourceModShutdown();
ConfigResult OnSourceModConfigChanged(const char *key, const char *value, ConfigSource source, char *error, size_t maxlength);
void OnSourceModMaxPlayersChanged(int newvalue);
public: //IHandleTypeDispatch
void OnHandleDestroy(HandleType_t type, void *object);
bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize);
public: //IRootConsoleCommand
void OnRootConsoleCommand(const char *cmdname, const ICommandArgs *command) override;
public:
/**
* Loads all plugins not yet loaded
*/
void LoadAll_FirstPass(const char *config, const char *basedir);
/**
* Runs the second loading pass for all plugins
*/
void LoadAll_SecondPass();
/**
* Returns whether anything loaded will be a late load.
*/
bool IsLateLoadTime() const;
/**
* Converts a Handle to an IPlugin if possible.
*/
IPlugin *PluginFromHandle(Handle_t handle, HandleError *err);
/**
* Finds a plugin based on its index. (starts on index 1)
*/
CPlugin *GetPluginByOrder(int num);
int GetOrderOfPlugin(IPlugin *pl);
/**
* Internal version of FindPluginByContext()
*/
CPlugin *GetPluginByCtx(const sp_context_t *ctx);
/**
* Gets status text for a status code
*/
const char *GetStatusText(PluginStatus status);
/**
* Add public functions from all running or paused
* plugins to the specified forward if the names match.
*/
void AddFunctionsToForward(const char *name, IChangeableForward *pForward);
/**
* Iterates through plugins to call OnAllPluginsLoaded.
*/
void AllPluginsLoaded();
CPlugin *GetPluginFromIdentity(IdentityToken_t *pToken);
void Shutdown();
void OnLibraryAction(const char *lib, LibraryAction action);
bool LibraryExists(const char *lib);
bool ReloadPlugin(CPlugin *pl);
void UnloadAll();
void SyncMaxClients(int max_clients);
void ListPluginsToClient(CPlayer *player, const CCommand &args);
void _SetPauseState(CPlugin *pPlugin, bool pause);
void ForEachPlugin(ke::Lambda<void(CPlugin *)> callback);
private:
LoadRes LoadPlugin(CPlugin **pPlugin, const char *path, bool debug, PluginType type);
void LoadAutoPlugin(const char *plugin);
/**
* Recursively loads all plugins in the given directory.
*/
void LoadPluginsFromDir(const char *basedir, const char *localdir);
/**
* Adds a plugin object. This is wrapped by LoadPlugin functions.
*/
void AddPlugin(CPlugin *pPlugin);
// First pass for loading a plugin, and its helpers.
CPlugin *CompileAndPrep(const char *path);
bool MalwareCheckPass(CPlugin *pPlugin);
// Runs the second loading pass on a plugin.
bool RunSecondPass(CPlugin *pPlugin);
void LoadExtensions(CPlugin *pPlugin);
bool RequireExtensions(CPlugin *pPlugin);
bool FindOrRequirePluginDeps(CPlugin *pPlugin);
void UnloadPluginImpl(CPlugin *plugin);
void Purge(CPlugin *plugin);
public:
inline IdentityToken_t *GetIdentity()
{
return m_MyIdent;
}
IPluginManager *GetOldAPI();
private:
void TryRefreshDependencies(CPlugin *pOther);
private:
ReentrantList<IPluginsListener *> m_listeners;
ReentrantList<CPlugin *> m_plugins;
ke::LinkedList<CPluginIterator *> m_iterators;
typedef decltype(m_listeners)::iterator ListenerIter;
typedef decltype(m_plugins)::iterator PluginIter;
NameHashSet<CPlugin *> m_LoadLookup;
bool m_AllPluginsLoaded;
IdentityToken_t *m_MyIdent;
/* Dynamic native stuff */
List<FakeNative *> m_Natives;
bool m_LoadingLocked;
// Config
bool m_bBlockBadPlugins;
// Forwards
IForward *m_pOnLibraryAdded;
IForward *m_pOnLibraryRemoved;
};
extern CPluginManager g_PluginSys;
#endif //_INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_