2
0
mirror of https://github.com/rehlds/metamod-r.git synced 2025-04-08 10:39:01 +03:00
metamod-r/metamod/src/mplugin.cpp
2017-01-07 03:20:55 +03:00

1603 lines
48 KiB
C++

#include "precompiled.h"
// Parse a line from plugins.ini into a plugin.
// meta_errno values:
// - ME_COMMENT ignored commented line
// - ME_FORMAT invalid line format
// - ME_OSNOTSUP plugin is not for this OS
bool MPlugin::ini_parseline(char *line)
{
char *token;
char *ptr_token;
char *cp;
// skip whitespace at start of line
while (*line == ' ' || *line == '\t') line++;
// remove whitespace at end of line
cp = line + Q_strlen(line) - 1;
while (*cp == ' ' || *cp == '\t') *cp-- = '\0';
// skip empty lines
if (line[0] == '\0')
{
META_DEBUG(7, ("ini: Ignoring empty line: %s", line));
RETURN_ERRNO(false, ME_BLANK);
}
if (line[0] == '#' || line[0] == ';' || Q_strstr(line, "//") == line)
{
META_DEBUG(7, ("ini: Ignoring commented line: %s", line));
RETURN_ERRNO(false, ME_COMMENT);
}
// grab platform ("win32" or "linux")
token = strtok_r(line, " \t", &ptr_token);
if (!token)
RETURN_ERRNO(false, ME_FORMAT);
if (Q_stricmp(token, PLATFORM) == 0)
{
pfspecific = 0;
}
else if (Q_stricmp(token, PLATFORM_SPC) == 0)
{
pfspecific = 1;
}
else
{
// plugin is not for this OS
META_DEBUG(7, ("ini: Ignoring entry for %s", token));
RETURN_ERRNO(false, ME_OSNOTSUP);
}
// grab filename
token = strtok_r(NULL, " \t\r\n", &ptr_token);
if (!token)
{
RETURN_ERRNO(false, ME_FORMAT);
}
Q_strncpy(filename, token, sizeof filename - 1);
filename[sizeof filename - 1] = '\0';
normalize_pathname(filename);
// Store name of just the actual _file_, without dir components.
cp = Q_strrchr(filename, '/');
if (cp)
file = cp + 1;
else
file = filename;
// Grab description.
// Just get the the rest of the line, minus line-termination.
token = strtok_r(NULL, "\n\r", &ptr_token);
if (token)
{
token = token + strspn(token, " \t"); // skip whitespace
Q_strncpy(desc, token, sizeof desc - 1);
desc[sizeof desc - 1] = '\0';
}
else
{
// If no description is specified, temporarily use plugin file,
// until plugin can be queried, and desc replaced with info->name.
Q_snprintf(desc, sizeof(desc), "<%s>", file);
}
// Make full pathname (from gamedir if relative, remove "..",
// backslashes, etc).
full_gamedir_path(filename, pathname);
source = PS_INI;
status = PL_VALID;
return true;
}
// Unload a plugin from plugin request
// meta_errno values:
// - errno's from unload()
bool MPlugin::plugin_unload(plid_t plid, PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
{
PLUG_ACTION old_action;
MPlugin *pl_unloader;
if (!(pl_unloader = g_plugins->find(plid)))
{
META_WARNING("dll: Not unloading plugin '%s'; plugin that requested unload is not found.", desc);
RETURN_ERRNO(false, ME_BADREQ);
}
else if (pl_unloader->index == index)
{
META_WARNING("dll: Not unloading plugin '%s'; Plugin tried to unload itself.", desc);
RETURN_ERRNO(false, ME_UNLOAD_SELF);
}
else if (is_unloader)
{
META_WARNING("dll: Not unloading plugin '%s'; Plugin is unloading plugin that tried to unload it.", desc);
RETURN_ERRNO(false, ME_UNLOAD_UNLOADER);
}
else
{
unloader_index = pl_unloader->index;
}
//block unloader from being unloaded by other plugin
pl_unloader->is_unloader = true;
//try unload
old_action = action;
action = PA_UNLOAD;
if (unload(now, reason, PNL_PLG_FORCED))
{
META_DEBUG(1,("Unloaded plugin '%s'", desc));
pl_unloader->is_unloader = false;
return true;
}
pl_unloader->is_unloader = false;
//can't unload plugin now, set delayed
if (meta_errno == ME_DELAYED)
{
action = old_action;
meta_errno = ME_NOTALLOWED;
META_DEBUG(2, ("dll: Failed unload plugin '%s'; can't detach now: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(PT_ANYTIME, SL_SIMPLE)));
}
return false;
}
// Parse a filename string from PEXT_LOAD_PLUGIN_BY_ *function into a plugin.
// meta_errno values:
bool MPlugin::plugin_parseline(const char *fname, int loader_index)
{
char *cp;
source_plugin_index = loader_index;
Q_strncpy(filename, fname, sizeof filename - 1);
filename[sizeof filename - 1] = '\0';
normalize_pathname(filename);
//store just name of the actual _file, without path
cp = Q_strrchr(filename, '/');
if (cp)
file = cp + 1;
else
file = filename;
//grab description
//temporarily use plugin file until plugin can be queried
Q_snprintf(desc, sizeof(desc), "<%s>", file);
//make full pathname
full_gamedir_path(filename, pathname);
source = PS_PLUGIN;
status = PL_VALID;
return true;
}
// Parse a line from console "load" command into a plugin.
// meta_errno values:
// - ME_FORMAT invalid line format
bool MPlugin::cmd_parseline(const char *line)
{
char buf[NAME_MAX + PATH_MAX + MAX_DESC_LEN];
char *token;
char *ptr_token;
char *cp;
Q_strncpy(buf, line, sizeof buf - 1);
buf[sizeof buf - 1] = '\0';
// remove "load"
token = strtok_r(buf, " \t", &ptr_token);
if (!token)
RETURN_ERRNO(false, ME_FORMAT);
// grab filename
token = strtok_r(NULL, " \t", &ptr_token);
if (!token)
RETURN_ERRNO(false, ME_FORMAT);
Q_strncpy(filename, token, sizeof filename - 1);
filename[sizeof filename - 1] = '\0';
normalize_pathname(filename);
// store name of just the actual _file_, without dir components
cp = Q_strrchr(filename, '/');
if (cp)
file = cp + 1;
else
file = filename;
// Grab description.
// Specify no delimiter chars, as we just want the rest of the line.
token = strtok_r(NULL, "", &ptr_token);
if (token)
{
token = token + strspn(token, " \t"); // skip whitespace
Q_strncpy(desc, token, sizeof desc - 1);
desc[sizeof desc - 1] = '\0';
}
else
{
// if no description is specified, temporarily use plugin file,
// until plugin can be queried, and desc replaced with info->name.
Q_snprintf(desc, sizeof(desc), "<%s>", file);
}
// Make full pathname (from gamedir if relative, remove "..",
// backslashes, etc).
full_gamedir_path(filename, pathname);
source = PS_CMD;
status = PL_VALID;
return true;
}
// Make sure this plugin has the necessary minimal information.
// meta_errno values:
// - ME_ARGUMENT missing necessary fields in plugin
bool MPlugin::check_input(void)
{
// doublecheck our input/state
if (status < PL_VALID)
{
META_ERROR("dll: Tried to operate on plugin[%d] with a non-valid status (%d)", index, str_status());
RETURN_ERRNO(false, ME_ARGUMENT);
}
if (!file || !file[0])
{
META_ERROR("dll: Tried to operate on plugin[%d] with an empty file", index);
RETURN_ERRNO(false, ME_ARGUMENT);
}
if (!filename[0])
{
META_ERROR("dll: Tried to operate on plugin[%d] with an empty filename", index);
RETURN_ERRNO(false, ME_ARGUMENT);
}
if (!pathname[0])
{
META_ERROR("dll: Tried to operate on plugin[%d] with an empty pathname", index);
RETURN_ERRNO(false, ME_ARGUMENT);
}
if (!desc[0])
{
// if no description is specified, temporarily use plugin file,
// until plugin can be queried, and desc replaced with info->name.
Q_snprintf(desc, sizeof(desc), "<%s>", file);
}
return true;
}
// Try to resolve a plugin's filename as a (possibly partial) path to an
// actual filename on disk, to facilitate easier console load-command
// arguments. Uses resolve_dirs, resolve_prefix, and resolve_suffix below.
// Example paths that it tries:
// filename
// Gamedir/filename.dll, Gamedir/filename.so
// Gamedir/filename_i386.so
// Gamedir/dlls/mm_filename_i386.so
// Gamedir/dlls/filename_mm_i386.so
// Gamedir/dlls/filename_MM_i386.so
// meta_errno values:
// - ME_NOTFOUND couldn't find a matching file for the partial name
// - errno's from check_input()
bool MPlugin::resolve(void)
{
char *found;
char *cp;
int len;
if (!check_input())
{
// details logged, meta_errno set in check_input()
return false;
}
if (is_absolute_path(filename))
found = resolve_prefix(filename);
else
found = resolve_dirs(filename);
if (!found)
{
META_DEBUG(2, ("Couldn't resolve '%s' to file", filename));
RETURN_ERRNO(false, ME_NOTFOUND);
}
META_DEBUG(2, ("Resolved '%s' to file '%s'", filename, found));
// store pathname: the resolved path (should be absolute)
Q_strncpy(pathname, found, sizeof pathname - 1);
pathname[sizeof pathname - 1] = '\0';
// store file: the name of the file (without dir)
cp = Q_strrchr(pathname, '/');
if (cp)
file = cp + 1;
else
file = pathname;
// store pathname: the gamedir relative path, or an absolute path
len = Q_strlen(GameDLL.gamedir);
if (Q_strnicmp(pathname, GameDLL.gamedir, len) == 0)
{
Q_strncpy(filename, pathname + len + 1, sizeof filename - 1);
filename[sizeof filename - 1] = '\0';
}
else
{
Q_strncpy(filename, pathname, sizeof filename - 1);
filename[sizeof filename - 1] = '\0';
}
return true;
}
// For the given path, tries to find file in several possible
// directories.
// Try:
// GAMEDIR/filename
// GAMEDIR/dlls/filename
// meta_errno values:
// - none
char *MPlugin::resolve_dirs(char *path)
{
struct stat st;
static char buf[PATH_MAX ];
char *found;
Q_snprintf(buf, sizeof(buf), "%s/%s", GameDLL.gamedir, path);
// try this path
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
// try other file prefixes in this path
if ((found = resolve_prefix(buf)))
return found;
Q_snprintf(buf, sizeof(buf), "%s/dlls/%s", GameDLL.gamedir, path);
// try this path
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
// try other file prefixes for this path
if ((found = resolve_prefix(buf)))
return found;
return NULL;
}
// For the given path, tries several possible filename prefixes.
// Try:
// dir/mm_file
// dir/file
// meta_errno values:
// - none
char *MPlugin::resolve_prefix(char *path) const
{
struct stat st;
char *cp, *fname;
char dname[PATH_MAX ];
static char buf[PATH_MAX ];
char *found;
// try "mm_" prefix FIRST.
// split into dirname and filename
Q_strncpy(dname, path, sizeof dname - 1);
dname[sizeof dname - 1] = '\0';
cp = Q_strrchr(dname, '/');
if (cp)
{
*cp = '\0';
fname = cp + 1;
Q_snprintf(buf, sizeof(buf), "%s/mm_%s", dname, fname);
}
else
{
// no directory in given path
Q_snprintf(buf, sizeof(buf), "mm_%s", path);
}
// try this path
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
// try other suffixes for this path
if ((found = resolve_suffix(buf)))
return found;
// try other suffixes for the original path
if ((found = resolve_suffix(path)))
return found;
return NULL;
}
// For the given path, tries several different filename suffixes.
// Try:
// path
// path_mm
// path_MM
// path.so (linux), path.dll (win32), path.dylib (osx)
// path_i386.so, path_i486.so, etc (if linux)
// meta_errno values:
// - none
char *MPlugin::resolve_suffix(char *path) const
{
struct stat st;
static char buf[PATH_MAX ];
char *found;
// Hmm, recursion.
if (!Q_strstr(path, "_mm"))
{
char *tmpbuf;
Q_snprintf(buf, sizeof(buf), "%s_mm", path);
tmpbuf = Q_strdup(buf);
found = resolve_suffix(tmpbuf);
Q_free(tmpbuf);
if (found) return found;
}
else if (!Q_strstr(path, "_MM"))
{
char *tmpbuf;
Q_snprintf(buf, sizeof(buf), "%s_MM", path);
tmpbuf = Q_strdup(buf);
found = resolve_suffix(tmpbuf);
Q_free(tmpbuf);
if (found) return found;
}
#ifdef _WIN32
Q_snprintf(buf, sizeof(buf), "%s.dll", path);
#else
Q_snprintf(buf, sizeof(buf), "%s.so", path);
#endif
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
#ifndef _WIN32
Q_snprintf(buf, sizeof(buf), "%s_i386.so", path);
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
Q_snprintf(buf, sizeof(buf), "%s_i486.so", path);
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
Q_snprintf(buf, sizeof(buf), "%s_i586.so", path);
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
Q_snprintf(buf, sizeof(buf), "%s_i686.so", path);
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
Q_snprintf(buf, sizeof(buf), "%s_amd64.so", path);
if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
return buf;
#endif
return NULL;
}
// Check if a passed string starts with a known platform postfix.
// It does not check beyond the period in order to work for both
// Linux and Win32.
static bool is_platform_postfix(char *pf)
{
if (NULL == pf) return false;
if (0 == Q_strncmp(pf, "_i386.", 6)) return true;
if (0 == Q_strncmp(pf, "_i486.", 6)) return true;
if (0 == Q_strncmp(pf, "_i586.", 6)) return true;
if (0 == Q_strncmp(pf, "_i686.", 6)) return true;
if (0 == Q_strncmp(pf, "_amd64.", 7)) return true;
return false;
}
// Check if a given plugin is the same but possibly for a
// different platform. A match is considered to be found if
// 1. the filename without the path is the same, or
// 2a. for an attached plugin the logtag is the same, or
// 2b. the description is the same, or
// 3. a significant part of the filename is the same.
// A significant part of a plugin name is currently defined to:
// the part up to a known platform postfix as determined by
// the is_platform_postfix() function (see above), or
// the part up to the last dot, if one exists.
// meta_errno values:
// - none
bool MPlugin::platform_match(MPlugin *other)
{
char *end, *other_end;
int prefixlen;
if (status == PL_EMPTY || other->status == PL_EMPTY)
return false;
if (Q_strcmp(file, other->file) == 0)
return true;
if (status >= PL_OPENED && other->status >= PL_OPENED && Q_strcmp(info->logtag, other->info->logtag) == 0)
return true;
if (*desc != '\0' && Q_stricmp(desc, other->desc) == 0)
return true;
end = Q_strrchr(file, '_');
if (end == NULL || !is_platform_postfix(end)) end = Q_strrchr(file, '.');
other_end = Q_strrchr(other->file, '_');
if (other_end == NULL || !is_platform_postfix(other_end))
other_end = Q_strrchr(other->file, '.');
if (end == NULL || other_end == NULL)
return false;
prefixlen = end - file;
if ((other_end - other->file) != prefixlen)
return false;
if (Q_strncmp(file, other->file, prefixlen) == 0)
return true;
return false;
}
// Load a plugin; query, check allowed time, attach.
// meta_errno values:
// - ME_ARGUMENT missing necessary fields in plugin
// - ME_ALREADY this plugin already loaded
// - ME_BADREQ plugin not marked for load
// - ME_DELAYED load request is delayed (till changelevel?)
// - ME_NOTALLOWED plugin not loadable after startup
// - errno's from query()
// - errno's from attach()
// - errno's from check_input()
bool MPlugin::load(PLUG_LOADTIME now)
{
if (!check_input())
{
// details logged, meta_errno set in check_input()
RETURN_ERRNO(false, ME_ARGUMENT);
}
if (status >= PL_RUNNING)
{
META_ERROR("dll: Not loading plugin '%s'; already loaded (status=%s)", desc, str_status());
RETURN_ERRNO(false, ME_ALREADY);
}
if (action != PA_LOAD && action != PA_ATTACH)
{
META_ERROR("dll: Not loading plugin '%s'; not marked for load (action=%s)", desc, str_action());
RETURN_ERRNO(false, ME_BADREQ);
}
if (status < PL_OPENED)
{
// query plugin; open file and get info about it
if (!query())
{
META_ERROR("dll: Skipping plugin '%s'; couldn't query", desc);
if (meta_errno != ME_DLOPEN)
{
if (!sys_module.unload())
{
META_ERROR("dll: Couldn't close plugin file '%s': %s", file, "invalid handle");
}
}
status = PL_BADFILE;
info = NULL; //prevent crash
// meta_errno should be already set in query()
return false;
}
status = PL_OPENED;
}
// are we allowed to attach this plugin at this time?
if (info->loadable < now)
{
if (info->loadable > PT_STARTUP)
{
// will try to attach again at next opportunity
META_DEBUG(2, ("dll: Delaying load plugin '%s'; can't attach now: allowed=%s; now=%s", desc, str_loadable(), str_loadtime(now, SL_SIMPLE)));
RETURN_ERRNO(false, ME_DELAYED);
}
else
{
META_DEBUG(2, ("dll: Failed load plugin '%s'; can't attach now: allowed=%s; now=%s", desc, str_loadable(), str_loadtime(now, SL_SIMPLE)));
// don't try to attach again later
action = PA_NONE;
RETURN_ERRNO(false, ME_NOTALLOWED);
}
}
// attach plugin; get function tables
if (attach(now) != true)
{
META_ERROR("dll: Failed to attach plugin '%s'", desc);
// Note we don't dlclose() here, since we're returning PL_FAILED,
// which implies that it's been dlopened and queried successfully.
// Doing so causes crashes, because things like "meta list" try to
// look at *info, which is in the DLL memory space and unaccessible
// (segfault) after dlclosed.
status = PL_FAILED;
// meta_errno should be already set in attach()
return false;
}
status = PL_RUNNING;
action = PA_NONE;
// If not loading at server startup, then need to call plugin's
// GameInit, since we've passed that.
if (now != PT_STARTUP)
{
FN_GAMEINIT pfn_gameinit = NULL;
if (dllapi_table && (pfn_gameinit = dllapi_table->pfnGameInit))
pfn_gameinit();
}
// If loading during map, then I'd like to call plugin's
// ServerActivate, since we've passed that. However, I'm not sure what
// arguments to give it... So, we'll just have to say for the
// moment that plugins that are loadable during a map can't need to
// hook ServerActivate.
META_LOG("dll: Loaded plugin '%s': %s v%s %s, %s", desc, info->name,
info->version, info->date, info->author);
return true;
}
// Query a plugin:
// - dlopen() the file, store the handle
// - dlsym() and call:
// Meta_Init (if present) - tell dll it'll be used as a metamod plugin
// GiveFnptrsToDll - give engine function ptrs
// Meta_Query - say "hi" and get info about plugin
// meta_errno values:
// - ME_DLOPEN dlopen/loadlibrary failed; see dlerror() for details
// - ME_DLMISSING couldn't find a query() or giveFuncs() in plugin
// - ME_DLERROR plugin query() returned error
// - ME_NULLDATA info struct from query() was null
bool MPlugin::query(void)
{
META_INIT_FN pfn_init;
GIVE_ENGINE_FUNCTIONS_FN pfn_give_engfuncs;
META_QUERY_FN pfn_query;
// open the plugin DLL
if (!sys_module.load(pathname))
{
META_ERROR("dll: Failed query plugin '%s'; Couldn't open file '%s': %s", desc, pathname, sys_module.getloaderror());
RETURN_ERRNO(false, ME_DLOPEN);
}
// First, we check to see if they have a Meta_Query. We would normally
// dlsym this just prior to calling it, after having called
// GiveFnptrsToDll, but things like standalone-DLL bots do all their
// startup in GiveFnptrsToDll, and trying to load them as metamod
// plugins creates all sorts of crashes. So, we do a trivial check
// first to see if the DLL looks like a metamod plugin before
// proceeding with the normal order. Note that we still have to call
// GiveFnptrsToDll before Meta_Query, because the latter typically uses
// engine functions like AlertMessage, which have to be passed along via
// GiveFnptrsToDll.
pfn_query = (META_QUERY_FN)sys_module.getsym("Meta_Query");
if (!pfn_query)
{
META_ERROR("dll: Failed query plugin '%s'; Couldn't find Meta_Query(): %s", desc, "function not found");
// caller will dlclose()
RETURN_ERRNO(false, ME_DLMISSING);
}
// Call Meta_Init, if present. This is an optional plugin routine to
// allow plugin to do any special initializing or other processing
// prior to the standard query/attach procedure.
//
// In particular, this should allow for DLL's that can operate as both
// a standalone dll AND a metamod plugin. This routine has to be
// called _before_ GiveFnptrsToDll, since the dll will usually do all
// its startup processing in GiveFn, and has to know at that point
// whether it needs to do just mm plugin startup, or all the extra
// startup needed for a standalone DLL.
//
// This passes nothing and returns nothing, and the routine in the
// plugin can NOT use any g_engine functions, as they haven't been
// provided yet (done next, in GiveFnptrsToDll).
pfn_init = (META_INIT_FN)sys_module.getsym("Meta_Init");
if (pfn_init)
{
pfn_init();
META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Init()", desc));
}
else
{
META_DEBUG(5, ("dll: no Meta_Init present in plugin '%s'", desc));
// don't return; not an error
}
// pass on engine function table and globals to plugin
if (!(pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN)sys_module.getsym("GiveFnptrsToDll")))
{
// META_ERROR("dll: Couldn't find GiveFnptrsToDll() in plugin '%s': %s", desc, DLERROR());
META_ERROR("dll: Failed query plugin '%s'; Couldn't find GiveFnptrsToDll(): %s", desc, "function not found");
// caller will dlclose()
RETURN_ERRNO(false, ME_DLMISSING);
}
pfn_give_engfuncs(g_engine.pl_funcs, g_engine.globals);
META_DEBUG(6, ("dll: Plugin '%s': Called GiveFnptrsToDll()", desc));
// Call plugin's Meta_Query(), to pass our meta interface version, and get
// plugin's info structure.
meta_errno = ME_NOERROR;
info = NULL;
// Make a copy of the meta_util function table for each plugin, for the
// same reason.
Q_memcpy(&mutil_funcs, &MetaUtilFunctions, sizeof(mutil_funcs));
if (pfn_query(META_INTERFACE_VERSION, &info, &mutil_funcs) != TRUE)
{
META_ERROR("dll: Failed query plugin '%s'; Meta_Query returned error",
desc);
meta_errno = ME_DLERROR;
}
else
{
META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Query() successfully",
desc));
}
// Check for interface differences... Generally, a difference in major
// version will be incompatible, and a plugin that expects a later
// minor version will be incompatible (it's expecting things that this
// Metamod won't be supplying). g_plugins that use an older minor
// version will still work, as backward-compability within major
// version is expected (forward-compatibility is not).
//
// Note, this check is done regardless of whether meta_query returns an
// error.
if (info && !FStrEq(info->ifvers, META_INTERFACE_VERSION))
{
int mmajor = 0, mminor = 0, pmajor = 0, pminor = 0;
META_DEBUG(3, ("dll: Note: Plugin '%s' interface version didn't match; expected %s, found %s", desc, META_INTERFACE_VERSION, info->ifvers));
sscanf(META_INTERFACE_VERSION, "%d:%d", &mmajor, &mminor);
sscanf(info->ifvers, "%d:%d", &pmajor, &pminor);
// If plugin has later interface version, it's incompatible
// (update metamod).
if (pmajor > mmajor || (pmajor == mmajor && pminor > mminor))
{
META_ERROR("dll: Plugin '%s' requires a newer version of Metamod (Metamod needs at least interface %s not the current %s)", desc, info->ifvers, META_INTERFACE_VERSION);
meta_errno = ME_IFVERSION;
}
// If plugin has older major interface version, it's incompatible
// (update plugin).
else if (pmajor < mmajor)
{
META_ERROR("dll: Plugin '%s' is out of date and incompatible with this version of Metamod; please find a newer version of the plugin (plugin needs at least interface %s not the current %s)", desc, META_INTERFACE_VERSION, info->ifvers);
meta_errno = ME_IFVERSION;
}
// Plugin has identical major, with older minor. This is supposed to be
// backwards compatible, so we warn, but we still accept it.
else if (pmajor == mmajor && pminor < mminor)
META_LOG("dll: Note: plugin '%s' is using an older interface version (%s), not the latest interface version (%s); there might be an updated version of the plugin", desc, info->ifvers, META_INTERFACE_VERSION);
else
META_LOG("dll: Plugin '%s': unexpected version comparision; metavers=%s, mmajor=%d, mminor=%d; plugvers=%s, pmajor=%d, pminor=%d", desc, META_INTERFACE_VERSION, mmajor, mminor, info->ifvers, pmajor, pminor);
}
if (meta_errno == ME_IFVERSION)
{
META_ERROR("dll: Rejected plugin '%s' due to interface version incompatibility (mm=%s, pl=%s)", desc, META_INTERFACE_VERSION, info->ifvers);
// meta_errno is set already above
// caller will dlclose()
return false;
}
else if (meta_errno != ME_NOERROR)
// some other error, already logged
return false;
if (!info)
{
META_ERROR("dll: Failed query plugin '%s'; Empty info structure", desc);
// caller will dlclose()
RETURN_ERRNO(false, ME_NULLRESULT);
}
// Replace temporary desc with plugin's internal name.
if (desc[0] == '<')
{
Q_strncpy(desc, info->name, sizeof desc - 1);
desc[sizeof desc - 1] = '\0';
}
META_DEBUG(6, ("dll: Plugin '%s': Query successful", desc));
return true;
}
// Attach a plugin:
// - dlsym() and call:
// Meta_Attach - get table of api tables, give meta_globals
// - if provided by plugin, call various "Get" function pointers,
// and store resulting function tables:
// GetEntityAPI (std)
// GetEntityAPI2 (std sdk2)
// GetNewDLLFunctions (std sdk2)
//
// GetEntityAPI_Post (meta)
// GetEntityAPI2_Post (meta)
// GetNewDLLFunctions_Post (meta)
//
// GetEngineFunctions (meta)
// GetEngineFunctions_Post (meta)
// meta_errno values:
// - ME_DLMISSING couldn't find meta_attach() in plugin
// - ME_DLERROR plugin attach() returned error
// - ME_NOMEM failed malloc
bool MPlugin::attach(PLUG_LOADTIME now)
{
int ret;
int iface_vers;
META_ATTACH_FN pfn_attach;
META_FUNCTIONS meta_table;
// Make copy of gameDLL's function tables for each plugin, so we don't
// risk the plugins screwing with the tables everyone uses.
if (GameDLL.funcs.dllapi_table && !gamedll_funcs.dllapi_table)
{
gamedll_funcs.dllapi_table = (DLL_FUNCTIONS *)Q_malloc(sizeof(DLL_FUNCTIONS));
if (!gamedll_funcs.dllapi_table)
{
META_ERROR("dll: Failed attach plugin '%s': Failed malloc() for dllapi_table");
RETURN_ERRNO(false, ME_NOMEM);
}
Q_memcpy(gamedll_funcs.dllapi_table, GameDLL.funcs.dllapi_table, sizeof(DLL_FUNCTIONS));
}
if (GameDLL.funcs.newapi_table && !gamedll_funcs.newapi_table)
{
gamedll_funcs.newapi_table = (NEW_DLL_FUNCTIONS *)Q_calloc(1, sizeof(meta_new_dll_functions_t));
if (!gamedll_funcs.newapi_table)
{
META_ERROR("dll: Failed attach plugin '%s': Failed malloc() for newapi_table");
RETURN_ERRNO(false, ME_NOMEM);
}
static_cast<meta_new_dll_functions_t *>(gamedll_funcs.newapi_table)->set_from(GameDLL.funcs.newapi_table);
}
if (!(pfn_attach = (META_ATTACH_FN)sys_module.getsym("Meta_Attach")))
{
META_ERROR("dll: Failed attach plugin '%s': Couldn't find Meta_Attach(): %s", desc, "function not found");
// caller will dlclose()
RETURN_ERRNO(false, ME_DLMISSING);
}
Q_memset(&meta_table, 0, sizeof(meta_table));
// get table of function tables,
// give public meta globals
ret = pfn_attach(now, &meta_table, &g_metaGlobals, &gamedll_funcs);
if (ret != TRUE)
{
META_ERROR("dll: Failed attach plugin '%s': Error from Meta_Attach(): %d", desc, ret);
// caller will dlclose()
RETURN_ERRNO(false, ME_DLERROR);
}
META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Attach() successfully", desc));
// Rather than duplicate code, we use another ugly macro. Again,
// a function isn't an option since we have varying types.
#define GET_FUNC_TABLE_FROM_PLUGIN(pfnGetFuncs, STR_GetFuncs, struct_field, API_TYPE, TABLE_TYPE, vers_pass, vers_int, vers_want) \
if (meta_table.pfnGetFuncs) { \
if (!struct_field) \
struct_field = (TABLE_TYPE *)Q_calloc(1, sizeof(TABLE_TYPE)); \
if (meta_table.pfnGetFuncs(struct_field, vers_pass)) { \
META_DEBUG(3, ("dll: Plugin '%s': Found %s", desc, STR_GetFuncs)); \
} \
else { \
META_ERROR("dll: Failure calling %s in plugin '%s'", STR_GetFuncs, desc); \
if (vers_int != vers_want) \
META_ERROR("dll: Interface version didn't match; expected %d, found %d", vers_want, vers_int); \
} \
} \
else { \
META_DEBUG(5, ("dll: Plugin '%s': No %s", desc, STR_GetFuncs)); \
if (struct_field) \
Q_free(struct_field); \
struct_field = NULL; \
}
// Look for API-NEW interface in plugin. We do this before API2/API, because
// that's what the engine appears to do..
// [But we only do this if the gamedll provides these, so that we don't
// give a plugin the idea that we'll call them, when in fact we won't
// (since we don't export them to the engine when the gamedll doesn't
// provide them).]
// We now do this even when the gamedll doesn't provide these because
// the plugins might want to use new functions like CvarValue() also
// with older gamedlls which do not use the API-NEW themselves.
// It is yet unknown if this causes any problems in the engine.
iface_vers = NEW_DLL_FUNCTIONS_VERSION;
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetNewDLLFunctions, "GetNewDLLFunctions", newapi_table, NEW_DLL_FUNCTIONS_FN, meta_new_dll_functions_t, &iface_vers, iface_vers, NEW_DLL_FUNCTIONS_VERSION);
iface_vers = NEW_DLL_FUNCTIONS_VERSION;
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetNewDLLFunctions_Post, "GetNewDLLFunctions_Post", newapi_post_table, NEW_DLL_FUNCTIONS_FN, meta_new_dll_functions_t, &iface_vers, iface_vers, NEW_DLL_FUNCTIONS_VERSION);
// Look for API2 interface in plugin; preferred over API-1.
iface_vers = INTERFACE_VERSION;
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI2, "GetEntityAPI2", dllapi_table, APIFUNCTION2, DLL_FUNCTIONS, &iface_vers, iface_vers, INTERFACE_VERSION);
iface_vers = INTERFACE_VERSION;
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI2_Post, "GetEntityAPI2_Post", dllapi_post_table, APIFUNCTION2, DLL_FUNCTIONS, &iface_vers, iface_vers, INTERFACE_VERSION);
// Look for old-style API in plugin, if API2 interface wasn't found.
if (!dllapi_table && !dllapi_post_table) {
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI, "GetEntityAPI", dllapi_table, APIFUNCTION, DLL_FUNCTIONS, INTERFACE_VERSION, INTERFACE_VERSION, INTERFACE_VERSION);
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI_Post, "GetEntityAPI_Post", dllapi_post_table, APIFUNCTION, DLL_FUNCTIONS, INTERFACE_VERSION, INTERFACE_VERSION, INTERFACE_VERSION);
}
// Look for g_engine interface.
iface_vers = ENGINE_INTERFACE_VERSION;
if (meta_table.pfnGetEngineFunctions) {
if (!engine_table) engine_table = (meta_enginefuncs_t *)calloc(1, sizeof(meta_enginefuncs_t));
if (meta_table.pfnGetEngineFunctions(engine_table, &iface_vers)) {
do {
if (meta_debug.value < 3) break; else (*g_engfuncs.pfnAlertMessage)(at_logged, "[META] (debug:%d) %s\n", 3, UTIL_VarArgs("dll: Plugin '%s': Found %s", desc, "GetEngineFunctions"));
}
while (0);
}
else {
META_ERROR("dll: Failure calling %s in plugin '%s'", "GetEngineFunctions", desc);
if (iface_vers != 138) META_ERROR("dll: Interface version didn't match; expected %d, found %d", 138, iface_vers);
}
}
else {
do {
if (meta_debug.value < 5) break; else (*g_engfuncs.pfnAlertMessage)(at_logged, "[META] (debug:%d) %s\n", 5, UTIL_VarArgs("dll: Plugin '%s': No %s", desc, "GetEngineFunctions"));
}
while (0);
if (engine_table) free(engine_table);
engine_table = 0;
};
iface_vers = ENGINE_INTERFACE_VERSION;
GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEngineFunctions_Post, "GetEngineFunctions_Post", engine_post_table, GET_ENGINE_FUNCTIONS_FN, meta_enginefuncs_t, &iface_vers, iface_vers, ENGINE_INTERFACE_VERSION);
if (!dllapi_table && !dllapi_post_table && !newapi_table && !newapi_post_table && !engine_table && !engine_post_table) {
META_LOG("dll: Plugin '%s' isn't catching _any_ functions ??", desc);
}
time_loaded = time(NULL);
return true;
}
// Unload a plugin. Check time, detach.
// meta_errno values:
// - ME_ARGUMENT missing necessary fields in plugin
// - ME_ALREADY this plugin already unloaded
// - ME_BADREQ plugin not marked for unload
// - ME_DELAYED unload request is delayed (till changelevel?)
// - ME_NOTALLOWED plugin not unloadable after startup
// - errno's from check_input()
bool MPlugin::unload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason, PL_UNLOAD_REASON real_reason)
{
if (!check_input())
{
// details logged, meta_errno set in check_input()
RETURN_ERRNO(false, ME_ARGUMENT);
}
if (status < PL_RUNNING)
{
if (reason != PNL_CMD_FORCED && reason != PNL_RELOAD)
{
META_ERROR("dll: Not unloading plugin '%s'; already unloaded (status=%s)", desc, str_status());
RETURN_ERRNO(false, ME_ALREADY);
}
}
if (action != PA_UNLOAD && action != PA_RELOAD)
{
META_WARNING("dll: Not unloading plugin '%s'; not marked for unload (action=%s)", desc, str_action());
RETURN_ERRNO(false, ME_BADREQ);
}
// Are we allowed to detach this plugin at this time?
// If forcing unload, we disregard when plugin wants to be unloaded.
if (info && info->unloadable < now)
{
if (reason == PNL_CMD_FORCED)
{
META_DEBUG(2, ("dll: Forced unload plugin '%s' overriding allowed times: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(now, SL_SIMPLE)));
}
else
{
if (info->unloadable > PT_STARTUP)
{
META_DEBUG(2, ("dll: Delaying unload plugin '%s'; can't detach now: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(now, SL_SIMPLE)));
// caller should give message to user
// try to unload again at next opportunity
RETURN_ERRNO(false, ME_DELAYED);
}
else
{
META_DEBUG(2, ("dll: Failed unload plugin '%s'; can't detach now: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(now, SL_SIMPLE)));
// don't try to unload again later
action = PA_NONE;
RETURN_ERRNO(false, ME_NOTALLOWED);
}
}
}
// If unloading during map, then I'd like to call plugin's
// ServerDeactivate. However, I don't want to do this until I start
// calling ServerActivate when loading during map, since the SDK
// indicates these two routines should match call for call.
// detach plugin
if (!detach(now, reason))
{
if (reason == PNL_RELOAD)
{
META_DEBUG(2, ("dll: Reload plugin '%s' overriding failed detach", desc));
}
else if (reason == PNL_CMD_FORCED)
{
META_DEBUG(2, ("dll: Forced unload plugin '%s' overriding failed detach"));
}
else
{
META_WARNING("dll: Failed to detach plugin '%s'; ", desc);
// meta_errno should be already set in detach()
return false;
}
}
g_plugins->clear_source_plugin_index(index);
// successful detach, or forced unload
// Unmark registered commands for this plugin (by index number).
g_regCmds->remove(index);
// Unmark registered cvars for this plugin (by index number).
g_regCvars->disable(index);
#ifdef UNFINISHED
// Remove all requested hooks from this plugin (by index number).
Hooks->remove_all(info);
#endif
// Close the file. Note: after this, attempts to reference any memory
// locations in the file will produce a segfault.
if (!sys_module.unload())
{
// If DLL cannot be closed, OS is badly broken or we are giving invalid handle.
// So we don't return here but instead remove plugin from our listings.
META_WARNING("dll: Couldn't close plugin file '%s': %s", file, "invalid handle");
}
if (action == PA_UNLOAD)
{
status = PL_EMPTY;
clear();
}
else if (action == PA_RELOAD)
{
status = PL_VALID;
action = PA_LOAD;
clear();
}
META_LOG("dll: Unloaded plugin '%s' for reason '%s'", desc, str_reason(reason, real_reason));
return true;
}
// Inform plugin we're going to unload it.
// meta_errno values:
// - ME_DLMISSING couldn't find meta_detach() in plugin
// - ME_DLERROR plugin detach() returned error
bool MPlugin::detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
{
int ret;
META_DETACH_FN pfn_detach;
// If we have no handle, i.e. no dll loaded, we return true because the
// dll is obviously detached. We shouldn't call DLSYM() with a NULL
// handle since this will DLSYM() ourself.
if (!sys_module.gethandle())
return true;
if (!(pfn_detach = (META_DETACH_FN)sys_module.getsym("Meta_Detach")))
{
META_ERROR("dll: Error detach plugin '%s': Couldn't find Meta_Detach(): %s", desc, "function not found");
// caller will dlclose()
RETURN_ERRNO(false, ME_DLMISSING);
}
ret = pfn_detach(now, reason);
if (ret != TRUE)
{
META_ERROR("dll: Failed detach plugin '%s': Error from Meta_Detach(): %d", desc, ret);
RETURN_ERRNO(false, ME_DLERROR);
}
META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Detach() successfully", desc));
return true;
}
// Reload a plugin; unload and load again.
// meta_errno values:
// - ME_NOTALLOWED plugin not loadable after startup
// - errno's from check_input()
// - errno's from unload()
// - errno's from load()
bool MPlugin::reload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
{
if (!check_input())
{
// details logged, meta_errno set in check_input()
RETURN_ERRNO(false, ME_ARGUMENT);
}
// Are we allowed to load this plugin at this time?
// If we cannot load the plugin after unloading it, we keep it.
if (info && info->loadable < now)
{
if (info->loadable > PT_STARTUP)
{
META_DEBUG(2, ("dll: Delaying reload plugin '%s'; would not be able to reattach now: allowed=%s; now=%s", desc, str_loadable(), str_loadtime(now, SL_SIMPLE)));
// caller should give message to user
// try to reload again at next opportunity
RETURN_ERRNO(false, ME_DELAYED);
}
else
{
META_DEBUG(2, ("dll: Failed reload plugin '%s'; would not be able to reattach now: allowed=%s; now=%s", desc, str_loadable(), str_loadtime(now, SL_SIMPLE)));
// don't try to reload again later
action = PA_NONE;
RETURN_ERRNO(false, ME_NOTALLOWED);
}
}
if (status < PL_RUNNING)
{
META_WARNING("dll: Plugin '%s' isn't running; Forcing unload plugin for reloading", desc);
reason = PNL_RELOAD;
}
if (!unload(now, reason, reason))
{
META_WARNING("dll: Failed to unload plugin '%s' for reloading", desc);
// meta_errno should be set already in unload()
return false;
}
if (!load(now))
{
META_WARNING("dll: Failed to reload plugin '%s' after unloading", desc);
// meta_errno should be set already in load()
return false;
}
return true;
}
// Pause a plugin; temporarily disabled for API routines.
// meta_errno values:
// - ME_ALREADY this plugin already paused
// - ME_BADREQ can't pause; not running
// - ME_NOTALLOWED plugin doesn't want to be paused
bool MPlugin::pause()
{
if (status == PL_PAUSED)
{
META_ERROR("Not pausing plugin '%s'; already paused", desc);
RETURN_ERRNO(false, ME_ALREADY);
}
if (status != PL_RUNNING)
{
META_ERROR("Cannot pause plugin '%s'; not currently running (status=%s)", desc, str_status());
RETURN_ERRNO(false, ME_BADREQ);
}
// are we allowed to pause this plugin?
if (info->unloadable < PT_ANYPAUSE)
{
META_ERROR("Cannot pause plugin '%s'; not allowed by plugin (allowed=%s)", desc, str_unloadable());
action = PA_NONE;
RETURN_ERRNO(false, ME_NOTALLOWED);
}
status = PL_PAUSED;
META_LOG("Paused plugin '%s'", desc);
return true;
}
// Unpause a plugin.
// meta_errno values:
// - ME_BADREQ can't unpause; not paused
bool MPlugin::unpause()
{
if (status != PL_PAUSED)
{
META_ERROR("Cannot unpause plugin '%s'; not currently paused (status=%s)", desc, str_status());
RETURN_ERRNO(false, ME_BADREQ);
}
status = PL_RUNNING;
META_LOG("Unpaused plugin '%s'", desc);
return true;
}
// Retry pending action, presumably from a previous failure.
// meta_errno values:
// - ME_BADREQ no pending action
// - errno's from load()
// - errno's from unload()
// - errno's from reload()
bool MPlugin::retry(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
{
if (action == PA_LOAD)
return load(now);
else if (action == PA_ATTACH)
return load(now);
else if (action == PA_UNLOAD)
return unload(now, reason, reason);
else if (action == PA_RELOAD)
return reload(now, reason);
else
{
META_ERROR("No pending action to retry for plugin '%s'; (status=%s, action=%s)", desc, str_status(), str_action());
RETURN_ERRNO(false, ME_BADREQ);
}
}
// Clear a plugin (it failed a previous action and should be
// removed from the list, or it's being unloaded).
// meta_errno values:
// - ME_BADREQ not marked for clearing
// - ME_DLERROR failed to dlclose
bool MPlugin::clear(void)
{
if (status != PL_FAILED && status != PL_BADFILE && status != PL_EMPTY && status != PL_OPENED)
{
META_ERROR("Cannot clear plugin '%s'; not marked as failed, empty, or open (status=%s)", desc, str_status());
RETURN_ERRNO(false, ME_BADREQ);
}
// If file is open, close the file. Note: after this, attempts to
// reference any memory locations in the file will produce a segfault.
if (!sys_module.unload())
{
META_ERROR("dll: Couldn't close plugin file '%s': %s", file, "invalid handle");
status = PL_FAILED;
RETURN_ERRNO(false, ME_DLERROR);
}
if (gamedll_funcs.dllapi_table) Q_free(gamedll_funcs.dllapi_table);
if (gamedll_funcs.newapi_table) Q_free(gamedll_funcs.newapi_table);
if (dllapi_table) Q_free(dllapi_table);
if (dllapi_post_table) Q_free(dllapi_post_table);
if (newapi_table) Q_free(newapi_table);
if (newapi_post_table) Q_free(newapi_post_table);
if (engine_table) Q_free(engine_table);
if (engine_post_table) Q_free(engine_post_table);
status = PL_EMPTY;
action = PA_NULL;
info = NULL;
time_loaded = 0;
dllapi_table = NULL;
dllapi_post_table = NULL;
newapi_table = NULL;
newapi_post_table = NULL;
engine_table = NULL;
engine_post_table = NULL;
gamedll_funcs.dllapi_table = NULL;
gamedll_funcs.newapi_table = NULL;
source_plugin_index = 0;
unloader_index = 0;
is_unloader = false;
return true;
}
// List information about plugin to console.
void MPlugin::show()
{
char *cp, *tstr;
const int width = 13;
int n;
META_CONS("%*s: %s", width, "name", info ? info->name : "(nil)");
META_CONS("%*s: %s", width, "desc", desc);
META_CONS("%*s: %s", width, "status", str_status());
META_CONS("%*s: %s", width, "action", str_action());
META_CONS("%*s: %s", width, "filename", filename);
META_CONS("%*s: %s", width, "file", file);
META_CONS("%*s: %s", width, "pathname", pathname);
META_CONS("%*s: %d", width, "index", index);
META_CONS("%*s: %s", width, "source", str_source());
META_CONS("%*s: %s", width, "loadable", str_loadable(SL_ALLOWED));
META_CONS("%*s: %s", width, "unloadable", str_unloadable(SL_ALLOWED));
META_CONS("%*s: %s", width, "version", info ? info->version : "(nil)");
META_CONS("%*s: %s", width, "date", info ? info->date : "(nil)");
META_CONS("%*s: %s", width, "author", info ? info->author : "(nil)");
META_CONS("%*s: %s", width, "url", info ? info->url : "(nil)");
META_CONS("%*s: %s", width, "logtag", info ? info->logtag : "(nil)");
META_CONS("%*s: %s", width, "ifvers", info ? info->ifvers : "(nil)");
// ctime() includes newline at EOL
tstr = ctime(&time_loaded);
if ((cp = Q_strchr(tstr, '\n')))
*cp = '\0';
META_CONS("%*s: %s", width, "last loaded", tstr);
// XXX show file time ?
if (dllapi_table)
{
META_CONS("DLLAPI functions:");
SHOW_DEF_DLLAPI(dllapi_table," ", "");
META_CONS("%d functions (dllapi)", n);
}
else
META_CONS("No DLLAPI functions.");
if (dllapi_post_table)
{
META_CONS("DLLAPI-Post functions:");
SHOW_DEF_DLLAPI(dllapi_post_table, " ", "_Post");
META_CONS("%d functions (dllapi post)", n);
}
else
META_CONS("No DLLAPI-Post functions.");
if (newapi_table)
{
META_CONS("NEWAPI functions:");
SHOW_DEF_NEWAPI(newapi_table, " ", "");
META_CONS("%d functions (newapi)", n);
}
else
META_CONS("No NEWAPI functions.");
if (newapi_post_table)
{
META_CONS("NEWAPI-Post functions:");
SHOW_DEF_NEWAPI(newapi_post_table, " ", "_Post");
META_CONS("%d functions (newapi post)", n);
}
else
META_CONS("No NEWAPI-Post functions.");
if (engine_table)
{
META_CONS("Engine functions:");
SHOW_DEF_ENGINE(engine_table, " ", "");
META_CONS("%d functions (engine)", n);
}
else
META_CONS("No Engine functions.");
if (engine_post_table)
{
META_CONS("Engine-Post functions:");
SHOW_DEF_ENGINE(engine_post_table, " ", "_Post");
META_CONS("%d functions (engine post)", n);
}
else
META_CONS("No Engine-Post functions.");
g_regCmds->show(index);
g_regCvars->show(index);
if (g_plugins->found_child_plugins(index))
g_plugins->show(index);
else
META_CONS("No child plugins.");
}
// Check whether the file on disk looks like it's been updated since we
// last loaded the plugin.
// meta_errno values:
// - ME_NOFILE couldn't find file
// - ME_NOERROR no error; false indicates file not newer
bool MPlugin::newer_file()
{
struct stat st;
time_t file_time;
if (stat(pathname, &st) != 0)
RETURN_ERRNO(false, ME_NOFILE);
file_time = st.st_ctime > st.st_mtime ? st.st_ctime : st.st_mtime;
META_DEBUG(5, ("newer_file? file=%s; load=%d, file=%d; ctime=%d, mtime=%d", file, time_loaded, file_time, st.st_ctime, st.st_mtime));
if (file_time > time_loaded)
return true;
else
RETURN_ERRNO(false, ME_NOERROR);
}
// Return a string describing status of plugin.
// SIMPLE is the default.
// SHOW is max 4 chars, for "show" output.
// meta_errno values:
// - none
const char *MPlugin::str_status(STR_STATUS fmt)
{
switch (status)
{
case PL_EMPTY:
if (fmt == ST_SHOW) return "empt";
else return "empty";
case PL_VALID:
if (fmt == ST_SHOW) return"info";
else return "valid";
case PL_BADFILE:
if (fmt == ST_SHOW) return "badf";
else return "badfile";
case PL_OPENED:
if (fmt == ST_SHOW) return "open";
else return "opened";
case PL_FAILED:
if (fmt == ST_SHOW) return "fail";
else return "failed";
case PL_RUNNING:
if (fmt == ST_SHOW) return "RUN";
else return "running";
case PL_PAUSED:
if (fmt == ST_SHOW) return "PAUS";
else return "paused";
default:
if (fmt == ST_SHOW) return UTIL_VarArgs("UNK%d", status);
return UTIL_VarArgs("unknown (%d)", status);
}
}
// Return a string (one word) describing requested action for plugin.
// SIMPLE is the default.
// SHOW is max 4 chars, for "show" output.
// meta_errno values:
// - none
const char *MPlugin::str_action(STR_ACTION fmt)
{
switch (action)
{
case PA_NULL:
if (fmt == SA_SHOW) return "NULL";
else return "null";
case PA_NONE:
if (fmt == SA_SHOW) return " - ";
else return "none";
case PA_KEEP:
if (fmt == SA_SHOW) return "keep";
else return "keep";
case PA_LOAD:
if (fmt == SA_SHOW) return "load";
else return "load";
case PA_ATTACH:
if (fmt == SA_SHOW) return "atch";
else return "attach";
case PA_UNLOAD:
if (fmt == SA_SHOW) return "unld";
else return "unload";
case PA_RELOAD:
if (fmt == SA_SHOW) return "relo";
else return "reload";
default:
if (fmt == SA_SHOW) return UTIL_VarArgs("UNK%d", action);
else return UTIL_VarArgs("unknown (%d)", action);
}
}
// Return a string describing given plugin loadtime.
// SIMPLE is the default.
// SHOW is max 3 chars, for "show" output.
// ALLOWED is in terms of when it's allowed to load/unload.
// NOW is to describe current situation of load/unload attempt.
// meta_errno values:
// - none
const char *MPlugin::str_loadtime(PLUG_LOADTIME ptime, STR_LOADTIME fmt)
{
static const char *rPrintLoadTime[][26] = {
// SL_SIMPLE // SL_SHOW // SL_ALLOWED // SL_NOW
{ "never", "Never", "never", "never" }, // PT_NEVER
{ "startup", "Start", "at server startup", "during server startup" }, // PT_STARTUP
{ "changelevel","Chlvl", "at changelevel", "during changelevel" }, // PT_CHANGELEVEL
{ "anytime", "ANY", "at any time", "during map" }, // PT_ANYTIME
{ "pausable", "Pause", "at any time, and pausable", "for requested pause" }, // PT_ANYPAUSE
};
if (ptime >= PT_NEVER || ptime <= PT_ANYPAUSE)
return rPrintLoadTime[ptime][fmt];
if (fmt == SL_SHOW)
return UTIL_VarArgs("UNK-%d", ptime);
return UTIL_VarArgs("unknown (%d)", ptime);
}
// Return a string describing why a plugin is to be unloaded.
// meta_errno values:
// - none
const char *MPlugin::str_reason(PL_UNLOAD_REASON preason, PL_UNLOAD_REASON preal_reason)
{
char buf[128];
if (preason == PNL_PLUGIN)
preason = PNL_NULL;
else if (preason == PNL_PLG_FORCED)
preason = PNL_NULL;
switch (preal_reason)
{
case PNL_NULL:
return "null";
case PNL_INI_DELETED:
return "deleted from ini file";
case PNL_FILE_NEWER:
return "file on disk is newer";
case PNL_COMMAND:
return "server command";
case PNL_CMD_FORCED:
return "forced by server command";
case PNL_PLUGIN:
{
Q_strncpy(buf, str_reason(PNL_NULL, preason), sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
return UTIL_VarArgs("%s (request from plugin[%d])", buf, unloader_index);
}
case PNL_PLG_FORCED:
{
Q_strncpy(buf, str_reason(PNL_NULL, preason), sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
return UTIL_VarArgs("%s (forced request from plugin[%d])", buf, unloader_index);
}
case PNL_RELOAD:
return "reloading";
default:
return UTIL_VarArgs("unknown (%d)", preal_reason);
}
}
// Return a string describing how the plugin was loaded.
// meta_errno values:
// - none
const char *MPlugin::str_source(STR_SOURCE fmt)
{
switch (source)
{
case PS_INI:
if (fmt == SO_SHOW) return "ini";
else return "ini file";
case PS_CMD:
if (fmt == SO_SHOW) return "cmd";
else return "console command";
case PS_PLUGIN:
if (source_plugin_index <= 0)
{
if (fmt == SO_SHOW) return "plUN";
else return "unloaded plugin";
}
else
{
if (fmt == SO_SHOW) return UTIL_VarArgs("pl%d", source_plugin_index);
else return UTIL_VarArgs("plugin [%d]", source_plugin_index);
}
default:
if (fmt == SO_SHOW) return UTIL_VarArgs("UNK%d", source);
else return UTIL_VarArgs("unknown (%d)", source);
}
}