2
0
mirror of https://github.com/rehlds/metamod-r.git synced 2024-12-27 07:05:34 +03:00

Rewritten most of osdep code

This commit is contained in:
asmodai 2017-01-07 01:24:40 +03:00
parent 2ecf18165b
commit d206ad4a13
20 changed files with 356 additions and 409 deletions

View File

@ -77,7 +77,7 @@
<ImportLibrary>$(OutDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>psapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<Midl>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -140,7 +140,7 @@
<ImportLibrary>$(OutDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>psapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<Midl>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>

View File

@ -216,7 +216,7 @@ void compile_dllfunc_callbacks()
{
jitdata_t jitdata;
jitdata.plugins = g_plugins ? g_plugins->plist : nullptr;
jitdata.plugins_count = g_plugins ? g_plugins->endlist : 0;
jitdata.plugins_count = g_plugins ? g_plugins->max_loaded_count : 0;
jitdata.table_offset = offsetof(MPlugin, dllapi_table);
jitdata.post_table_offset = offsetof(MPlugin, dllapi_post_table);
@ -237,7 +237,7 @@ void compile_newdllfunc_callbacks()
{
jitdata_t jitdata;
jitdata.plugins = g_plugins ? g_plugins->plist : nullptr;
jitdata.plugins_count = g_plugins ? g_plugins->endlist : 0;
jitdata.plugins_count = g_plugins ? g_plugins->max_loaded_count : 0;
jitdata.table_offset = offsetof(MPlugin, newapi_table);
jitdata.post_table_offset = offsetof(MPlugin, newapi_post_table);

View File

@ -240,7 +240,7 @@ void compile_engfuncs_callbacks()
{
jitdata_t jitdata;
jitdata.plugins = g_plugins ? g_plugins->plist : nullptr;
jitdata.plugins_count = g_plugins ? g_plugins->endlist : 0;
jitdata.plugins_count = g_plugins ? g_plugins->max_loaded_count : 0;
jitdata.table_offset = offsetof(MPlugin, engine_table);
jitdata.post_table_offset = offsetof(MPlugin, engine_post_table);

View File

@ -14,12 +14,12 @@ void do_link_ent(ENTITY_FN *pfnEntity, int *missing, const char *entStr, entvars
if (!*pfnEntity)
{
META_DEBUG(9, ("Looking up game entity '%s'", entStr));
*pfnEntity = (ENTITY_FN)DLSYM(GameDLL.handle, entStr);
*pfnEntity = (ENTITY_FN)GameDLL.sys_module.getsym(entStr);
}
if (!*pfnEntity)
{
META_ERROR("Couldn't find game entity '%s' in game DLL '%s': %s", entStr, GameDLL.name, DLERROR());
META_ERROR("Couldn't find game entity '%s' in game DLL '%s': %s", entStr, GameDLL.name, CSysModule::getloaderror());
*missing = 1;
return;
}

View File

@ -292,9 +292,9 @@ bool meta_load_gamedll(void)
}
// open the game DLL
if (!(GameDLL.handle = DLOPEN(GameDLL.pathname)))
if (!GameDLL.sys_module.load(GameDLL.pathname))
{
META_ERROR("dll: Couldn't load game DLL %s: %s", GameDLL.pathname, DLERROR());
META_ERROR("dll: Couldn't load game DLL %s: %s", GameDLL.pathname, CSysModule::getloaderror());
RETURN_ERRNO(false, ME_DLOPEN);
}
@ -305,20 +305,21 @@ bool meta_load_gamedll(void)
// wanted to catch one of the functions, but now that plugins are
// dynamically loadable at any time, we have to always pass our table,
// so that any plugin loaded later can catch what they need to.
if ((pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN) DLSYM(GameDLL.handle, "GiveFnptrsToDll")))
if ((pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN)GameDLL.sys_module.getsym("GiveFnptrsToDll")))
{
pfn_give_engfuncs(&meta_engfuncs, gpGlobals);
META_DEBUG(3, ("dll: Game '%s': Called GiveFnptrsToDll", GameDLL.name));
}
else
{
META_ERROR("dll: Couldn't find GiveFnptrsToDll() in game DLL '%s': %s", GameDLL.name, DLERROR());
META_ERROR("dll: Couldn't find GiveFnptrsToDll() in game DLL '%s'", GameDLL.name);
RETURN_ERRNO(false, ME_DLMISSING);
}
// TODO
// Yes...another macro.
#define GET_FUNC_TABLE_FROM_GAME(gamedll, pfnGetFuncs, STR_GetFuncs, struct_field, API_TYPE, TABLE_TYPE, vers_pass, vers_int, vers_want, gotit) \
if ((pfnGetFuncs = (API_TYPE) DLSYM(gamedll.handle, STR_GetFuncs))) { \
if ((pfnGetFuncs = (API_TYPE)gamedll.sys_module.getsym(STR_GetFuncs))) { \
gamedll.funcs.struct_field = (TABLE_TYPE *)Q_calloc(1, sizeof(TABLE_TYPE)); \
if (!gamedll.funcs.struct_field) {\
META_ERROR("malloc failed for gamedll struct_field: %s", STR_GetFuncs); \

View File

@ -36,7 +36,7 @@ struct gamedll_t
char pathname[PATH_MAX]; // ie "/home/willday/half-life/cstrike/dlls/cs_i386.so"
char const *file; // ie "cs_i386.so"
char real_pathname[PATH_MAX]; // in case pathname overridden by bot, etc
DLHANDLE handle;
CSysModule sys_module;
gamedll_funcs_t funcs; // dllapi_table, newapi_table
};

View File

@ -1,7 +1,7 @@
#include "precompiled.h"
// Constructor
MPluginList::MPluginList(const char* ifile) : size(MAX_PLUGINS), endlist(0)
MPluginList::MPluginList(const char* ifile) : max_loaded_count(0)
{
// store filename of ini file
Q_strncpy(inifile, ifile, sizeof inifile - 1);
@ -9,28 +9,28 @@ MPluginList::MPluginList(const char* ifile) : size(MAX_PLUGINS), endlist(0)
// initialize array
Q_memset(plist, 0, sizeof(plist));
for (int i = 0; i < size; i++)
for (int i = 0; i < MAX_PLUGINS; i++)
{
plist[i].index = i + 1; // 1-based
}
endlist = 0;
max_loaded_count = 0;
}
// Find a plugin based on the plugin handle.
// meta_errno values:
// - ME_ARGUMENT invalid pindex
// - ME_NOTFOUND couldn't find a matching plugin
MPlugin *MPluginList::find(DLHANDLE handle)
MPlugin *MPluginList::find(module_handle_t handle)
{
if (!handle)
RETURN_ERRNO(NULL, ME_ARGUMENT);
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
if (plist[i].status < PL_VALID)
continue;
if (plist[i].handle == handle)
if (handle == plist[i].sys_module.gethandle())
return &plist[i];
}
@ -62,7 +62,7 @@ MPlugin *MPluginList::find(plid_t id)
if (!id)
RETURN_ERRNO(NULL, ME_ARGUMENT);
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
if (plist[i].status < PL_VALID)
continue;
@ -85,7 +85,7 @@ MPlugin *MPluginList::find(const char* findpath)
META_DEBUG(8, ("Looking for loaded plugin with dlfnamepath: %s", findpath));
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
META_DEBUG(9, ("Looking at: plugin %s loadedpath: %s", plist[i].file, plist[i].pathname));
@ -104,25 +104,16 @@ MPlugin *MPluginList::find(const char* findpath)
}
// Find a plugin that uses the given memory location.
// meta_errno values:
// - ME_ARGUMENT null memptr
// - ME_NOTFOUND couldn't find a matching plugin
// - errno's from DLFNAME()
MPlugin *MPluginList::find_memloc(void *memptr)
{
const char* dlfile;
for (int i = 0; i < max_loaded_count; i++) {
auto iplug = &plist[i];
if (!memptr)
RETURN_ERRNO(NULL, ME_ARGUMENT);
if (!(dlfile = DLFNAME(memptr)))
{
META_DEBUG(8, ("DLFNAME failed to find memloc %d", memptr));
// meta_errno should be already set in DLFNAME
return NULL;
if (iplug->sys_module.contain(memptr))
return iplug;
}
return find(dlfile);
return nullptr;
}
// Find a plugin with non-ambiguous prefix string matching desc, file,
@ -145,7 +136,7 @@ MPlugin *MPluginList::find_match(const char *prefix)
pfound = NULL;
len = Q_strlen(prefix);
Q_snprintf(buf, sizeof(buf), "mm_%s", prefix);
for (i = 0; i < endlist; i++)
for (i = 0; i < max_loaded_count; i++)
{
iplug = &plist[i];
if (iplug->status < PL_VALID)
@ -214,7 +205,7 @@ MPlugin *MPluginList::find_match(MPlugin* pmatch)
}
pfound = NULL;
for (i = 0; i < endlist; i++)
for (i = 0; i < max_loaded_count; i++)
{
iplug = &plist[i];
if (pmatch->platform_match(iplug))
@ -292,33 +283,34 @@ MPlugin* MPluginList::plugin_addload(plid_t plid, const char* fname, PLUG_LOADTI
return pl_added;
}
MPlugin* MPluginList::find_empty_slot()
{
for (int i = 0; i < MAX_PLUGINS; i++) {
if (plist[i].status == PL_EMPTY) {
if (i > max_loaded_count)
max_loaded_count = i + 1;
return &plist[i];
}
}
return nullptr;
}
// Add a plugin to the list.
// meta_errno values:
// - ME_MAXREACHED reached max plugins
MPlugin* MPluginList::add(MPlugin* padd)
{
int i;
MPlugin* iplug;
// Find either:
// - a slot in the list that's not being used
// - the end of the list
for (i = 0; i < endlist && plist[i].status != PL_EMPTY; i++)
;
auto iplug = find_empty_slot();
// couldn't find a slot to use
if (i == size)
if (!iplug)
{
META_ERROR("Couldn't add plugin '%s' to list; reached max plugins (%d)", padd->file, i);
META_ERROR("Couldn't add plugin '%s' to list; reached max plugins (%d)", padd->file, MAX_PLUGINS);
RETURN_ERRNO(NULL, ME_MAXREACHED);
}
// if we found the end of the list, advance end marker
if (i == endlist)
endlist++;
iplug = &plist[i];
// copy filename into this free slot
Q_strncpy(iplug->filename, padd->filename, sizeof iplug->filename - 1);
iplug->filename[sizeof iplug->filename - 1] = '\0';
@ -337,13 +329,8 @@ MPlugin* MPluginList::add(MPlugin* padd)
iplug->pathname[sizeof iplug->pathname - 1] = '\0';
normalize_pathname(iplug->pathname);
// copy source
iplug->source = padd->source;
// copy status
iplug->status = padd->status;
//copy other things
iplug->source_plugin_index = padd->source_plugin_index;
return iplug;
@ -375,7 +362,7 @@ bool MPluginList::ini_startup()
}
META_LOG("ini: Begin reading plugins list: %s", inifile);
for (n = 0 , ln = 1; !feof(fp) && fgets(line, sizeof(line), fp) && n < size; ln++)
for (n = 0 , ln = 1; !feof(fp) && fgets(line, sizeof(line), fp) && n < MAX_PLUGINS; ln++)
{
// Remove line terminations.
char* cp;
@ -420,7 +407,7 @@ bool MPluginList::ini_startup()
plist[n].action = PA_LOAD;
META_LOG("ini: Read plugin config for: %s", plist[n].desc);
n++;
endlist = n; // mark end of list
max_loaded_count = n; // mark end of list
}
META_LOG("ini: Finished reading plugins list: %s; Found %d plugins to load", inifile, n);
@ -452,7 +439,7 @@ bool MPluginList::ini_refresh()
}
META_LOG("ini: Begin re-reading plugins list: %s", inifile);
for (n = 0 , ln = 1; !feof(fp) && fgets(line, sizeof(line), fp) && n < size; ln++)
for (n = 0 , ln = 1; !feof(fp) && fgets(line, sizeof(line), fp) && n < MAX_PLUGINS; ln++)
{
// Remove line terminations.
char *cp;
@ -655,7 +642,7 @@ bool MPluginList::load()
}
META_LOG("dll: Loading plugins...");
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
if (plist[i].status < PL_VALID)
continue;
@ -690,7 +677,7 @@ bool MPluginList::refresh(PLUG_LOADTIME now)
}
META_LOG("dll: Updating plugins...");
for (i = 0; i < endlist; i++)
for (i = 0; i < max_loaded_count; i++)
{
iplug = &plist[i];
if (iplug->status < PL_VALID)
@ -767,7 +754,7 @@ bool MPluginList::refresh(PLUG_LOADTIME now)
// - none
void MPluginList::unpause_all(void)
{
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
auto iplug = &plist[i];
if (iplug->status == PL_PAUSED)
@ -781,7 +768,7 @@ void MPluginList::unpause_all(void)
// - none
void MPluginList::retry_all(PLUG_LOADTIME now)
{
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
auto iplug = &plist[i];
if (iplug->action != PA_NONE)
@ -806,7 +793,7 @@ void MPluginList::show(int source_index)
META_CONS(" %*s %-*s %-4s %-4s %-*s v%-*s %-*s %-5s %-5s", WIDTH_MAX_PLUGINS, "", sizeof(desc) - 1, "description", "stat", "pend",
sizeof(file) - 1, "file", sizeof(vers) - 1, "ers", 2 + WIDTH_MAX_PLUGINS, "src", "load ", "unlod");
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
pl = &plist[i];
if (pl->status < PL_VALID)
@ -857,7 +844,7 @@ void MPluginList::show_client(edict_t *pEntity)
MPlugin *pl;
META_CLIENT(pEntity, "Currently running plugins:");
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
pl = &plist[i];
if (pl->status != PL_RUNNING || !pl->info)
@ -880,7 +867,7 @@ bool MPluginList::found_child_plugins(int source_index) const
if (source_index <= 0)
return false;
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
if (plist[i].status < PL_VALID)
continue;
@ -897,7 +884,7 @@ void MPluginList::clear_source_plugin_index(int source_index)
if (source_index <= 0)
return;
for (int i = 0; i < endlist; i++)
for (int i = 0; i < max_loaded_count; i++)
{
if (plist[i].status < PL_VALID)
continue;

View File

@ -23,7 +23,8 @@ public:
MPlugin *find_memloc(void *memptr); // find by memory location
MPlugin *find_match(const char *prefix); // find by partial prefix match
MPlugin *find_match(MPlugin *pmatch); // find by platform_match()
MPlugin *find(DLHANDLE handle); // find by handle
MPlugin *find(module_handle_t handle); // find by handle
MPlugin *find_empty_slot();
MPlugin *add(MPlugin *padd);
bool found_child_plugins(int source_index) const;
@ -42,8 +43,7 @@ public:
void clear_source_plugin_index(int source_index);
public:
int loaded_count; // index of last used entry
int size; // size of list, ie MAX_PLUGINS
int max_loaded_count; // index of last used entry
char inifile[PATH_MAX]; // full pathname
MPlugin plist[MAX_PLUGINS]; // array of plugins
};

View File

@ -382,7 +382,7 @@ char *MPlugin::resolve_dirs(char *path)
// dir/file
// meta_errno values:
// - none
char *MPlugin::resolve_prefix(char *path)
char *MPlugin::resolve_prefix(char *path) const
{
struct stat st;
char *cp, *fname;
@ -432,7 +432,7 @@ char *MPlugin::resolve_prefix(char *path)
// path_i386.so, path_i486.so, etc (if linux)
// meta_errno values:
// - none
char *MPlugin::resolve_suffix(char *path)
char *MPlugin::resolve_suffix(char *path) const
{
struct stat st;
static char buf[PATH_MAX ];
@ -595,12 +595,10 @@ bool MPlugin::load(PLUG_LOADTIME now)
META_ERROR("dll: Skipping plugin '%s'; couldn't query", desc);
if (meta_errno != ME_DLOPEN)
{
if (DLCLOSE(handle) != 0)
if (!sys_module.unload())
{
META_ERROR("dll: Couldn't close plugin file '%s': %s", file, DLERROR());
META_ERROR("dll: Couldn't close plugin file '%s': %s", file, "invalid handle");
}
else
handle = NULL;
}
status = PL_BADFILE;
info = NULL; //prevent crash
@ -682,10 +680,9 @@ bool MPlugin::query(void)
META_QUERY_FN pfn_query;
// open the plugin DLL
if (!(handle = DLOPEN(pathname)))
if (!sys_module.load(pathname))
{
META_ERROR("dll: Failed query plugin '%s'; Couldn't open file '%s': %s",
desc, pathname, DLERROR());
META_ERROR("dll: Failed query plugin '%s'; Couldn't open file '%s': %s", desc, pathname, sys_module.getloaderror());
RETURN_ERRNO(false, ME_DLOPEN);
}
@ -699,10 +696,10 @@ bool MPlugin::query(void)
// 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) DLSYM(handle, "Meta_Query");
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, DLERROR());
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);
}
@ -721,7 +718,7 @@ bool MPlugin::query(void)
// 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) DLSYM(handle, "Meta_Init");
pfn_init = (META_INIT_FN)sys_module.getsym("Meta_Init");
if (pfn_init)
{
pfn_init();
@ -734,10 +731,10 @@ bool MPlugin::query(void)
}
// pass on engine function table and globals to plugin
if (!(pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN) DLSYM(handle, "GiveFnptrsToDll")))
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, 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);
}
@ -880,9 +877,9 @@ bool MPlugin::attach(PLUG_LOADTIME now)
}
static_cast<meta_new_dll_functions_t *>(gamedll_funcs.newapi_table)->set_from(GameDLL.funcs.newapi_table);
}
if (!(pfn_attach = (META_ATTACH_FN) DLSYM(handle, "Meta_Attach")))
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, DLERROR());
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);
}
@ -1082,13 +1079,12 @@ bool MPlugin::unload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason, PL_UNLOAD_REASO
// Close the file. Note: after this, attempts to reference any memory
// locations in the file will produce a segfault.
if (DLCLOSE(handle) != 0)
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, DLERROR());
META_WARNING("dll: Couldn't close plugin file '%s': %s", file, "invalid handle");
}
handle = NULL;
if (action == PA_UNLOAD)
{
@ -1118,12 +1114,12 @@ bool MPlugin::detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
// 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 (!handle)
if (!sys_module.gethandle())
return true;
if (!(pfn_detach = (META_DETACH_FN) DLSYM(handle, "Meta_Detach")))
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, DLERROR());
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);
}
@ -1279,13 +1275,12 @@ bool MPlugin::clear(void)
}
// If file is open, close the file. Note: after this, attempts to
// reference any memory locations in the file will produce a segfault.
if (handle && DLCLOSE(handle) != 0)
if (!sys_module.unload())
{
META_ERROR("dll: Couldn't close plugin file '%s': %s", file, DLERROR());
META_ERROR("dll: Couldn't close plugin file '%s': %s", file, "invalid handle");
status = PL_FAILED;
RETURN_ERRNO(false, ME_DLERROR);
}
handle = NULL;
if (gamedll_funcs.dllapi_table) Q_free(gamedll_funcs.dllapi_table);
if (gamedll_funcs.newapi_table) Q_free(gamedll_funcs.newapi_table);
@ -1298,7 +1293,6 @@ bool MPlugin::clear(void)
status = PL_EMPTY;
action = PA_NULL;
handle = NULL;
info = NULL;
time_loaded = 0;
dllapi_table = NULL;

View File

@ -5,9 +5,9 @@
// Flags to indicate current "load" state of plugin.
// NOTE: order is important, as greater/less comparisons are made.
enum PLUG_STATUS
enum PLUG_STATUS : uint8
{
PL_EMPTY = 0, // empty slot
PL_EMPTY = 0, // empty slot
PL_VALID, // has valid info in it
PL_BADFILE, // nonexistent file (open failed), or not a valid plugin file (query failed)
PL_OPENED, // dlopened and queried
@ -17,7 +17,7 @@ enum PLUG_STATUS
};
// Action to take for plugin at next opportunity.
enum PLUG_ACTION
enum PLUG_ACTION : uint8
{
PA_NULL = 0,
PA_NONE, // no action needed right now
@ -29,7 +29,7 @@ enum PLUG_ACTION
};
// Flags to indicate from where the plugin was loaded.
enum PLOAD_SOURCE
enum PLOAD_SOURCE : uint8
{
PS_INI = 0, // was loaded from the plugins.ini
PS_CMD, // was loaded via a server command
@ -37,7 +37,7 @@ enum PLOAD_SOURCE
};
// Flags for how to word description of plugin loadtime.
enum STR_LOADTIME
enum STR_LOADTIME : uint8
{
SL_SIMPLE = 0, // single word
SL_SHOW, // for "show" output, 5 chars
@ -46,21 +46,21 @@ enum STR_LOADTIME
};
// Flags for how to format description of status.
enum STR_STATUS
enum STR_STATUS : uint8
{
ST_SIMPLE = 0, // single word
ST_SHOW, // for "show" output, 4 chars
};
// Flags for how to format description of action.
enum STR_ACTION
enum STR_ACTION : uint8
{
SA_SIMPLE = 0, // single word
SA_SHOW, // for "show" output, 4 chars
};
// Flags for how to format description of source.
enum STR_SOURCE
enum STR_SOURCE : uint8
{
SO_SIMPLE = 0, // two words
SO_SHOW, // for "list" output, 3 chars
@ -68,26 +68,13 @@ enum STR_SOURCE
// An individual plugin.
class MPlugin {
private:
bool query();
bool attach(PLUG_LOADTIME now);
bool detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason);
gamedll_funcs_t gamedll_funcs;
mutil_funcs_t mutil_funcs;
public:
int index; // 1-based
char filename[PATH_MAX]; // ie "dlls/mm_test_i386.so", from inifile
char *file; // ie "mm_test_i386.so", ptr from filename
char desc[MAX_DESC_LEN]; // ie "Test metamod plugin", from inifile
char pathname[PATH_MAX]; // UNIQUE, ie "/home/willday/half-life/cstrike/dlls/mm_test_i386.so", built with GameDLL.gamedir
int pfspecific; // level of specific platform affinity, used during load time
PLUG_STATUS status; // current status of plugin (loaded, etc)
PLUG_ACTION action; // what to do with plugin (load, unload, etc)
PLOAD_SOURCE source; // source of the request to load the plugin
DLHANDLE handle; // handle for dlopen, dlsym, etc
int index; // 1-based
plugin_info_t *info; // information plugin provides about itself
CSysModule sys_module;
time_t time_loaded; // when plugin was loaded
int source_plugin_index; // who loaded this plugin
int unloader_index;
@ -100,6 +87,15 @@ public:
enginefuncs_t *engine_table;
enginefuncs_t *engine_post_table;
gamedll_funcs_t gamedll_funcs;
mutil_funcs_t mutil_funcs;
char filename[PATH_MAX]; // ie "dlls/mm_test_i386.so", from inifile
char *file; // ie "mm_test_i386.so", ptr from filename
char desc[MAX_DESC_LEN]; // ie "Test metamod plugin", from inifile
char pathname[PATH_MAX]; // UNIQUE, ie "/home/willday/half-life/cstrike/dlls/mm_test_i386.so", built with GameDLL.gamedir
int pfspecific; // level of specific platform affinity, used during load time
bool ini_parseline(char *line); // parse line from inifile
bool cmd_parseline(const char *line); // parse from console command
bool plugin_parseline(const char *fname, int loader_index); // parse from plugin
@ -107,8 +103,8 @@ public:
bool resolve(); // find a matching file on disk
char *resolve_dirs(char *path);
char *resolve_prefix(char *path);
char *resolve_suffix(char *path);
char *resolve_prefix(char *path) const;
char *resolve_suffix(char *path) const;
bool platform_match(MPlugin* plugin);
@ -151,6 +147,11 @@ public:
if (info) return str_loadtime(info->unloadable, fmt);
else return " -";
};
private:
bool query();
bool attach(PLUG_LOADTIME now);
bool detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason);
};
// Macros used by MPlugin::show(), to list the functions that the plugin

View File

@ -172,14 +172,6 @@ void MRegCmdList::show(int plugin_id) const
int n = 0;
MRegCmd *icmd;
// If OS doesn't support DLFNAME, then we can't know what the plugin's
// registered cvars are.
DLFNAME(NULL);
if (meta_errno == ME_OSNOTSUP)
{
META_CONS("Registered commands: unknown (can't get info under this OS)");
return;
}
META_CONS("Registered commands:");
for (int i = 0; i < endlist; i++)
{
@ -375,15 +367,6 @@ void MRegCvarList::show(int plugin_id) const
MRegCvar *icvar;
char bname[30 + 1], bval[15 + 1]; // +1 for term null
// If OS doesn't support DLFNAME, then we can't know what the plugin's
// registered cvars are.
DLFNAME(NULL);
if (meta_errno == ME_OSNOTSUP)
{
META_CONS("Registered cvars: unknown (can't get info under this OS)");
return;
}
META_CONS("%-*s %*s %s", sizeof(bname) - 1, "Registered cvars:", sizeof(bval) - 1, "float value", "string value");
for (int i = 0; i < endlist; i++)
{

View File

@ -128,4 +128,4 @@ private:
MRegMsg mlist[MAX_REG_MSGS]; // array of registered msgs
int size; // size of list, ie MAX_REG_MSGS
int endlist; // index of last used entry
};
};

View File

@ -119,7 +119,7 @@ qboolean EXT_FUNC mutil_CallGameEntity(plid_t plid, const char *entStr, entvars_
{
plugin_info_t *plinfo = (plugin_info_t *)plid;
META_DEBUG(8, ("Looking up game entity '%s' for plugin '%s'", entStr, plinfo->name));
ENTITY_FN pfnEntity = (ENTITY_FN) DLSYM(GameDLL.handle, entStr);
ENTITY_FN pfnEntity = (ENTITY_FN)GameDLL.sys_module.getsym(entStr);
if (!pfnEntity)
{
META_ERROR("Couldn't find game entity '%s' in game DLL '%s' for plugin '%s'", entStr, GameDLL.name, plinfo->name);
@ -268,7 +268,7 @@ int EXT_FUNC mutil_LoadMetaPlugin(plid_t plid, const char* fname, PLUG_LOADTIME
else
{
if (plugin_handle)
*plugin_handle = (void *)pl_loaded->handle;
*plugin_handle = (void *)pl_loaded->sys_module.gethandle();
return 0;
}
}
@ -310,7 +310,7 @@ int EXT_FUNC mutil_UnloadMetaPluginByHandle(plid_t plid, void *plugin_handle, PL
return ME_ARGUMENT;
}
if (!(findp = g_plugins->find((DLHANDLE)plugin_handle)))
if (!(findp = g_plugins->find((module_handle_t)plugin_handle)))
return ME_NOTFOUND;
meta_errno = ME_NOERROR;

View File

@ -1,36 +1,128 @@
#include "precompiled.h"
bool dlclose_handle_invalid;
CSysModule::CSysModule() : m_handle(0), m_base(0), m_size(0)
{
}
#ifdef _WIN32
// Since windows doesn't provide a verison of strtok_r(), we include one
// here. This may or may not operate exactly like strtok_r(), but does
// what we need it it do.
char *my_strtok_r(char *s, const char *delim, char **ptrptr)
module_handle_t CSysModule::load(const char* filepath)
{
char *begin = nullptr;
char *end = nullptr;
char *rest = nullptr;
if (s)
begin = s;
else
begin = *ptrptr;
if (!begin)
return nullptr;
m_handle = LoadLibrary(filepath);
end = strpbrk(begin, delim);
if (end)
{
*end = '\0';
rest = end + 1;
*ptrptr = rest + strspn(rest, delim);
MODULEINFO module_info;
if (GetModuleInformation(GetCurrentProcess(), m_handle, &module_info, sizeof(module_info))) {
m_base = (uintptr_t)module_info.lpBaseOfDll;
m_size = module_info.SizeOfImage;
}
else
*ptrptr = nullptr;
return begin;
return m_handle;
}
bool CSysModule::unload()
{
bool ret = false;
if (m_handle) {
ret = FreeLibrary(m_handle) != ERROR;
m_handle = 0;
m_base = 0;
m_size = 0;
}
return ret;
}
void* CSysModule::getsym(const char* name) const
{
return GetProcAddress(m_handle, name);
}
#else
static ElfW(Addr) dlsize(void* base)
{
ElfW(Ehdr) *ehdr;
ElfW(Phdr) *phdr;
ElfW(Addr) end;
ehdr = (ElfW(Ehdr) *)base;
/* Find the first program header */
phdr = (ElfW(Phdr)*)((ElfW(Addr))ehdr + ehdr->e_phoff);
/* Find the final PT_LOAD segment's extent */
for (int i = 0; i < ehdr->e_phnum; ++i)
if (phdr[i].p_type == PT_LOAD)
end = phdr[i].p_vaddr + phdr[i].p_memsz;
/* The start (virtual) address is always zero, so just return end.*/
return end;
}
module_handle_t CSysModule::load(const char* filepath)
{
m_handle = dlopen(filepath, RTLD_NOW);
char buf[1024], dummy[1024], path[260];
sprintf(buf, "/proc/%i/maps", getpid());
FILE* fp = fopen(buf, "r");
while (fgets(buf, sizeof(buf), fp)) {
uintptr_t start, end;
int args = sscanf(buf, "%x-%x %128s %128s %128s %128s %255s", &start, &end, dummy, dummy, dummy, dummy, path);
if (args != 7) {
continue;
}
if (!Q_stricmp(path, filepath)) {
m_base = start;
m_size = end - start;
break;
}
}
fclose(fp);
return m_handle;
}
bool CSysModule::unload()
{
bool ret = false;
if (m_handle) {
ret = dlclose(m_handle) != 0;
m_handle = 0;
m_base = 0;
m_size = 0;
}
return ret;
}
void* CSysModule::getsym(const char* name) const
{
return dlsym(m_handle, name);
}
#endif
module_handle_t CSysModule::gethandle() const
{
return m_handle;
}
bool CSysModule::contain(void* addr) const
{
return addr && uintptr_t(addr) > m_base && uintptr_t(addr) < m_base + m_size;
}
const char* CSysModule::getloaderror()
{
#ifdef _WIN32
return str_GetLastError();
#else
return dlerror;
#endif
}
#endif // _WIN32
#ifdef _WIN32
// Windows doesn't provide a functon analagous to dlerr() that returns a
@ -44,70 +136,16 @@ const char *str_GetLastError()
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&buf, MAX_STRBUF_LEN - 1, NULL);
return buf;
}
const char* str_os_error()
{
#ifdef _WIN32
return str_GetLastError();
#else
return strerror(errno);
#endif
// Find the filename of the DLL/shared-lib where the given memory location
// exists.
#if defined(linux) || defined(__APPLE__)
// Errno values:
// - ME_NOTFOUND couldn't find a sharedlib that contains memory location
const char *DLFNAME(void *memptr)
{
Dl_info dli;
Q_memset(&dli, 0, sizeof(dli));
if (dladdr(memptr, &dli))
return dli.dli_fname;
else
RETURN_ERRNO(NULL, ME_NOTFOUND);
}
#elif defined(_WIN32)
// Implementation for win32 provided by Jussi Kivilinna <kijuhe00@rhea.otol.fi>:
//
// 1. Get memory location info on memptr with VirtualQuery.
// 2. Check if memory location info is valid and use MBI.AllocationBase
// as module start point.
// 3. Get module file name with GetModuleFileName.
//
// Simple and should work pretty much same way as 'dladdr' in linux.
// VirtualQuery and GetModuleFileName work even with win32s.
//
// Note that GetModuleFileName returns longfilenames rather than 8.3.
//
// Note also, the returned filename is local static storage, and should be
// copied by caller if it needs to keep it around.
//
// Also note, normalize_pathname() should really be done by the caller, but
// is done here to preserve "const char *" return consistent with linux
// version.
//
// Errno values:
// - ME_NOTFOUND couldn't find a DLL that contains memory location
const char *DLFNAME(void *memptr)
{
MEMORY_BASIC_INFORMATION MBI;
static char fname[PATH_MAX];
if (!VirtualQuery(memptr, &MBI, sizeof(MBI)))
RETURN_ERRNO(NULL, ME_NOTFOUND);
if (MBI.State != MEM_COMMIT)
RETURN_ERRNO(NULL, ME_NOTFOUND);
if (!MBI.AllocationBase)
RETURN_ERRNO(NULL, ME_NOTFOUND);
// MSDN indicates that GetModuleFileName will leave string
// null-terminated, even if it's truncated because buffer is too small.
if (!GetModuleFileName((HMODULE)MBI.AllocationBase, fname, sizeof(fname) - 1))
RETURN_ERRNO(NULL, ME_NOTFOUND);
if (!fname[0])
RETURN_ERRNO(NULL, ME_NOTFOUND);
normalize_pathname(fname);
return fname;
}
#endif // _WIN32
#endif
// Determine whether the given memory location is valid (ie whether we
// should expect to be able to reference strings or functions at this

View File

@ -1,10 +1,5 @@
#pragma once
// Various differences between WIN32 and Linux.
#include "types_meta.h" // bool
#include "mreg.h" // REG_CMD_FN, etc
#include "log_meta.h" // LOG_ERROR, etc
// String describing platform/DLL-type, for matching lines in plugins.ini.
#ifdef _WIN32
#define UNUSED /**/
@ -20,75 +15,31 @@
#define PLATFORM_DLEXT ".so"
#endif
extern bool dlclose_handle_invalid;
#include "mreg.h"
// Functions & types for DLL open/close/etc operations.
#ifdef _WIN32
typedef HINSTANCE DLHANDLE;
typedef FARPROC DLFUNC;
inline DLHANDLE DLOPEN(const char *filename)
{
return LoadLibraryA(filename);
}
inline DLFUNC DLSYM(DLHANDLE handle, const char *string)
{
return GetProcAddress(handle, string);
}
inline int DLCLOSE(DLHANDLE handle)
{
if (!handle)
{
dlclose_handle_invalid = true;
return 1;
}
dlclose_handle_invalid = false;
// NOTE: Windows FreeLibrary returns success=nonzero, fail=zero,
// which is the opposite of the unix convention, thus the '!'.
return !FreeLibrary(handle);
}
// Windows doesn't provide a function corresponding to dlerror(), so
// we make our own.
const char *str_GetLastError();
inline const char *DLERROR()
{
if (dlclose_handle_invalid)
return "Invalid handle.";
return str_GetLastError();
}
typedef HINSTANCE module_handle_t;
#else
typedef void *DLHANDLE;
typedef void *DLFUNC;
inline DLHANDLE DLOPEN(const char *filename)
{
return dlopen(filename, RTLD_NOW);
}
inline DLFUNC DLSYM(DLHANDLE handle, const char *string)
{
return dlsym(handle, string);
}
// dlclose crashes if handle is null.
inline int DLCLOSE(DLHANDLE handle)
{
if (!handle)
{
dlclose_handle_invalid = true;
return 1;
}
dlclose_handle_invalid = false;
return dlclose(handle);
}
inline const char *DLERROR()
{
if (dlclose_handle_invalid)
return "Invalid handle.";
return dlerror();
}
typedef void* module_handle_t;
#endif
const char *DLFNAME(void *memptr);
class CSysModule
{
public:
CSysModule();
module_handle_t load(const char* filename);
bool unload();
void* getsym(const char* name) const;
module_handle_t gethandle() const;
bool contain(void* addr) const;
static const char* getloaderror();
private:
module_handle_t m_handle;
uintptr_t m_base;
uintptr_t m_size;
};
bool IS_VALID_PTR(void *memptr);
// Attempt to call the given function pointer, without segfaulting.
@ -96,15 +47,9 @@ bool os_safe_call(REG_CMD_FN pfn);
// Windows doesn't have an strtok_r() routine, so we write our own.
#ifdef _WIN32
#define strtok_r(s, delim, ptrptr) my_strtok_r(s, delim, ptrptr)
char *my_strtok_r(char *s, const char *delim, char **ptrptr);
#else
// Linux doesn't have an strlwr() routine, so we write our own.
#define strlwr(s) my_strlwr(s)
char *my_strlwr(char *s);
#define strtok_r(s, delim, ptrptr) mm_strtok_r(s, delim, ptrptr)
#endif // _WIN32
// Set filename and pathname maximum lengths. Note some windows compilers
// provide a <limits.h> which is incomplete and/or causes problems; see
// doc/windows_notes.txt for more information.
@ -165,89 +110,8 @@ bool os_safe_call(REG_CMD_FN pfn);
#ifndef S_IWGRP
#define S_IWGRP S_IWUSR
#endif
const char *str_GetLastError();
#endif // _WIN32
// Normalize/standardize a pathname.
// - For win32, this involves:
// - Turning backslashes (\) into slashes (/), so that config files and
// Metamod internal code can be simpler and just use slashes (/).
// - Turning upper/mixed case into lowercase, since windows is
// non-case-sensitive.
// - For linux, this requires no work, as paths uses slashes (/) natively,
// and pathnames are case-sensitive.
#if defined(__linux) || defined(__APPLE__)
#define normalize_pathname(a)
#elif defined(_WIN32)
inline void normalize_pathname(char *path)
{
char *cp;
META_DEBUG(8, ("normalize: %s", path));
for (cp = path; *cp; cp++)
{
if (isupper(*cp)) *cp = tolower(*cp);
if (*cp == '\\') *cp = '/';
}
META_DEBUG(8, ("normalized: %s", path));
}
#endif // _WIN32
// Indicate if pathname appears to be an absolute-path. Under linux this
// is a leading slash (/). Under win32, this can be:
// - a drive-letter path (ie "D:blah" or "C:\blah")
// - a toplevel path (ie "\blah")
// - a UNC network address (ie "\\srv1\blah").
// Also, handle both native and normalized pathnames.
inline int is_absolute_path(const char *path)
{
if (path[0] == '/') return TRUE;
#ifdef _WIN32
if (path[1] == ':') return TRUE;
if (path[0] == '\\') return TRUE;
#endif // _WIN32
return FALSE;
}
#ifdef _WIN32
// Buffer pointed to by resolved_name is assumed to be able to store a
// string of PATH_MAX length.
inline char *realpath(const char *file_name, char *resolved_name)
{
int ret;
ret = GetFullPathName(file_name, PATH_MAX, resolved_name, NULL);
if (ret > PATH_MAX)
{
errno = ENAMETOOLONG;
return NULL;
}
else if (ret > 0)
{
HANDLE handle;
WIN32_FIND_DATA find_data;
handle = FindFirstFile(resolved_name, &find_data);
if (INVALID_HANDLE_VALUE == handle)
{
errno = ENOENT;
return NULL;
}
FindClose(handle);
normalize_pathname(resolved_name);
return resolved_name;
}
return NULL;
}
#endif // _WIN32
// Generic "error string" from a recent OS call. For linux, this is based
// on errno. For win32, it's based on GetLastError.
inline const char *str_os_error()
{
#ifdef _WIN32
return str_GetLastError();
#else
return strerror(errno);
#endif
}
const char* str_os_error();

View File

@ -2,6 +2,8 @@
#include "version/appversion.h"
#define PSAPI_VERSION 1
#if defined(linux) || defined(__APPLE__)
// enable extra routines in system header files, like dladdr
#ifndef _GNU_SOURCE

View File

@ -80,13 +80,12 @@ void EXT_FUNC meta_command_handler()
// to a generic command-handler function (see above).
void EXT_FUNC meta_AddServerCommand(char *cmd_name, void (*function)())
{
MPlugin *iplug = NULL;
MRegCmd *icmd = NULL;
MPlugin *iplug = g_plugins->find_memloc(function);
META_DEBUG(4, ("called: meta_AddServerCommand; cmd_name=%s, function=%d", cmd_name, function));
// try to find which plugin is registering this command
if (!(iplug = g_plugins->find_memloc((void *)function)))
if (!iplug)
{
// if this isn't supported on this OS, don't log an error
if (meta_errno != ME_OSNOTSUP)
@ -94,7 +93,7 @@ void EXT_FUNC meta_AddServerCommand(char *cmd_name, void (*function)())
}
// See if this command was previously registered, ie a "reloaded" plugin.
icmd = g_regCmds->find(cmd_name);
auto icmd = g_regCmds->find(cmd_name);
if (!icmd)
{
// If not found, add.
@ -134,27 +133,18 @@ void EXT_FUNC meta_AddServerCommand(char *cmd_name, void (*function)())
// it will fail to work properly.
void EXT_FUNC meta_CVarRegister(cvar_t *pCvar)
{
MPlugin *iplug = nullptr;
MRegCvar *icvar = nullptr;
MPlugin *iplug = g_plugins->find_memloc(pCvar);
META_DEBUG(4, ("called: meta_CVarRegister; name=%s", pCvar->name));
if (!strncmp(pCvar->name, "iz_", 3))
__asm int 3;
// try to find which plugin is registering this cvar
if (!(iplug = g_plugins->find_memloc((void *)pCvar)))
if (!iplug)
{
// if this isn't supported on this OS, don't log an error
if (meta_errno != ME_OSNOTSUP)
// Note: if cvar_t was malloc'd by the plugin, we can't
// determine the calling plugin. Thus, this becomes a Debug
// rather than Error message.
META_DEBUG(1, ("Failed to find memloc for regcvar '%s'", pCvar->name));
}
// See if this cvar was previously registered, ie a "reloaded" plugin.
icvar = g_regCvars->find(pCvar->name);
auto icvar = g_regCvars->find(pCvar->name);
if (!icvar)
{
// If not found, add.

View File

@ -44,12 +44,11 @@ C_DLLEXPORT int Server_GetBlendingInterface(int version,
if (!getblend)
{
META_DEBUG(6, ("Looking up Server_GetBlendingInterface"));
getblend = (GETBLENDAPI_FN) DLSYM(GameDLL.handle,
"Server_GetBlendingInterface");
getblend = (GETBLENDAPI_FN)GameDLL.sys_module.getsym("Server_GetBlendingInterface");
}
if (!getblend)
{
META_DEBUG(6, ("Couldn't find Server_GetBlendingInterface in game DLL '%s': %s", GameDLL.name, DLERROR()));
META_DEBUG(6, ("Couldn't find Server_GetBlendingInterface in game DLL '%s': %s", GameDLL.name, "function not found"));
missing = 1;
return 0;
}

View File

@ -58,3 +58,83 @@ void execmem_allocator::allocate_page()
m_used = 0;
m_pages.push_back(page);
}
#ifdef _WIN32
// Since windows doesn't provide a verison of strtok_r(), we include one
// here. This may or may not operate exactly like strtok_r(), but does
// what we need it it do.
char *mm_strtok_r(char *s, const char *delim, char **ptrptr)
{
char *begin = nullptr;
char *end = nullptr;
char *rest = nullptr;
if (s)
begin = s;
else
begin = *ptrptr;
if (!begin)
return nullptr;
end = strpbrk(begin, delim);
if (end) {
*end = '\0';
rest = end + 1;
*ptrptr = rest + strspn(rest, delim);
}
else
*ptrptr = nullptr;
return begin;
}
#endif // _WIN32
void normalize_pathname(char *path)
{
#ifdef _WIN32
char *cp;
META_DEBUG(8, ("normalize: %s", path));
for (cp = path; *cp; cp++) {
if (isupper(*cp)) *cp = tolower(*cp);
if (*cp == '\\') *cp = '/';
}
META_DEBUG(8, ("normalized: %s", path));
#endif
}
bool is_absolute_path(const char *path)
{
if (path[0] == '/') return true;
#ifdef _WIN32
if (path[1] == ':') return true;
if (path[0] == '\\') return true;
#endif // _WIN32
return false;
}
char *realpath(const char *file_name, char *resolved_name)
{
int ret = GetFullPathName(file_name, PATH_MAX, resolved_name, NULL);
if (ret > PATH_MAX) {
errno = ENAMETOOLONG;
return NULL;
}
if (ret > 0) {
HANDLE handle;
WIN32_FIND_DATA find_data;
handle = FindFirstFile(resolved_name, &find_data);
if (INVALID_HANDLE_VALUE == handle) {
errno = ENOENT;
return NULL;
}
FindClose(handle);
normalize_pathname(resolved_name);
return resolved_name;
}
return NULL;
}

View File

@ -23,3 +23,11 @@ bool is_yes(const char* str);
bool is_no(const char* str);
const char* LOCALINFO(char* key);
#ifdef _WIN32
char *mm_strtok_r(char *s, const char *delim, char **ptrptr);
#endif
void normalize_pathname(char *path);
bool is_absolute_path(const char *path);
char *realpath(const char *file_name, char *resolved_name);