1
0
mirror of https://github.com/anope/anope.git synced 2026-06-27 16:26:39 +02:00
Files
anope/src/modules.c
T
2009-06-26 20:01:26 +00:00

941 lines
23 KiB
C

/* Modular support
*
* (C) 2003-2009 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*
* Based on the original code of Epona by Lara.
* Based on the original code of Services by Andy Church.
*
* $Id$
*
*/
#include "modules.h"
#include "language.h"
#include "version.h"
/**
* Declare all the list's we want to use here
**/
CommandHash *HOSTSERV[MAX_CMD_HASH];
CommandHash *BOTSERV[MAX_CMD_HASH];
CommandHash *MEMOSERV[MAX_CMD_HASH];
CommandHash *NICKSERV[MAX_CMD_HASH];
CommandHash *CHANSERV[MAX_CMD_HASH];
CommandHash *OPERSERV[MAX_CMD_HASH];
MessageHash *IRCD[MAX_CMD_HASH];
ModuleHash *MODULE_HASH[MAX_CMD_HASH];
char *mod_current_buffer = NULL;
char *ModuleGetErrStr(int status)
{
const char *module_err_str[] = {
"Module, Okay - No Error", /* MOD_ERR_OK */
"Module Error, Allocating memory", /* MOD_ERR_MEMORY */
"Module Error, Not enough parameters", /* MOD_ERR_PARAMS */
"Module Error, Already loaded", /* MOD_ERR_EXISTS */
"Module Error, File does not exist", /* MOD_ERR_NOEXIST */
"Module Error, No User", /* MOD_ERR_NOUSER */
"Module Error, Error during load time or module returned MOD_STOP", /* MOD_ERR_NOLOAD */
"Module Error, Unable to unload", /* MOD_ERR_NOUNLOAD */
"Module Error, Incorrect syntax", /* MOD_ERR_SYNTAX */
"Module Error, Unable to delete", /* MOD_ERR_NODELETE */
"Module Error, Unknown Error occuried", /* MOD_ERR_UNKOWN */
"Module Error, File I/O Error", /* MOD_ERR_FILE_IO */
"Module Error, No Service found for request", /* MOD_ERR_NOSERVICE */
"Module Error, No module name for request" /* MOD_ERR_NO_MOD_NAME */
};
return const_cast<char *>(module_err_str[status]);
}
/************************************************/
/**
*
**/
int encryption_module_init() {
int ret = 0;
alog("Loading Encryption Module: [%s]", EncModule);
ret = ModuleManager::LoadModule(EncModule, NULL);
if (ret == MOD_ERR_OK)
findModule(EncModule)->SetType(ENCRYPTION);
return ret;
}
/**
* Load the ircd protocol module up
**/
int protocol_module_init()
{
int ret = 0;
alog("Loading IRCD Protocol Module: [%s]", IRCDModule);
ret = ModuleManager::LoadModule(IRCDModule, NULL);
if (ret == MOD_ERR_OK)
{
findModule(IRCDModule)->SetType(PROTOCOL);
/* This is really NOT the correct place to do config checks, but
* as we only have the ircd struct filled here, we have to over
* here. -GD
*/
if (ircd->ts6)
{
if (!Numeric)
{
alog("This IRCd protocol requires a server id to be set in Anope's configuration.");
ret = -1;
}
else
{
ts6_uid_init();
}
}
}
return ret;
}
/**
* Unload ALL loaded modules, no matter what kind of module it is.
* Do NEVER EVER, and i mean NEVER (and if that isn't clear enough
* yet, i mean: NEVER AT ALL) call this unless we're shutting down,
* or we'll fuck up Anope badly (protocol handling won't work for
* example). If anyone calls this function without a justified need
* for it, i reserve the right to break their legs in a painful way.
* And if that isn't enough discouragement, you'll wake up with your
* both legs broken tomorrow ;) -GD
*/
void modules_unload_all(bool unload_proto)
{
int idx;
ModuleHash *mh, *next;
for (idx = 0; idx < MAX_CMD_HASH; idx++) {
mh = MODULE_HASH[idx];
while (mh) {
next = mh->next;
if (unload_proto || (mh->m->type != PROTOCOL))
ModuleManager::UnloadModule(mh->m, NULL);
mh = next;
}
}
}
void Module::InsertLanguage(int langNumber, int ac, const char **av)
{
int i;
if (debug)
alog("debug: %s Adding %d texts for language %d", this->name.c_str(), ac, langNumber);
if (this->lang[langNumber].argc > 0) {
this->DeleteLanguage(langNumber);
}
this->lang[langNumber].argc = ac;
this->lang[langNumber].argv = new char *[ac];
for (i = 0; i < ac; i++) {
this->lang[langNumber].argv[i] = sstrdup(av[i]);
}
}
/**
* Search the list of loaded modules for the given name.
* @param name the name of the module to find
* @return a pointer to the module found, or NULL
*/
Module *findModule(const char *name)
{
int idx;
ModuleHash *current = NULL;
if (!name) {
return NULL;
}
idx = CMD_HASH(name);
for (current = MODULE_HASH[idx]; current; current = current->next) {
if (stricmp(name, current->name) == 0) {
return current->m;
}
}
return NULL;
}
/*******************************************************************************
* Command Functions
*******************************************************************************/
/** Add a command to a command table. Only for internal use.
* only add if were unique, pos = 0;
* if we want it at the "head" of that command, pos = 1
* at the tail, pos = 2
* @param cmdTable the table to add the command to
* @param c the command to add
* @param pos the position in the cmd call stack to add the command
* @return MOD_ERR_OK will be returned on success.
*/
static int internal_addCommand(Module *m, CommandHash * cmdTable[], Command * c, int pos)
{
/* We can assume both param's have been checked by this point.. */
int index = 0;
CommandHash *current = NULL;
CommandHash *newHash = NULL;
CommandHash *lastHash = NULL;
Command *tail = NULL;
if (!cmdTable || !c || (pos < 0 || pos > 2)) {
return MOD_ERR_PARAMS;
}
index = CMD_HASH(c->name);
for (current = cmdTable[index]; current; current = current->next) {
if ((c->service) && (current->c) && (current->c->service)
&& (!strcmp(c->service, current->c->service) == 0)) {
continue;
}
if ((stricmp(c->name.c_str(), current->name) == 0))
{
/* the cmd exists, throw an error */
return MOD_ERR_EXISTS;
}
lastHash = current;
}
newHash = new CommandHash;
newHash->next = NULL;
newHash->name = sstrdup(c->name.c_str());
newHash->c = c;
if (lastHash == NULL)
cmdTable[index] = newHash;
else
lastHash->next = newHash;
return MOD_ERR_OK;
}
int Module::AddCommand(CommandHash * cmdTable[], Command * c, int pos)
{
int status;
if (!cmdTable || !c) {
return MOD_ERR_PARAMS;
}
c->core = 0;
if (!c->mod_name) {
c->mod_name = sstrdup(this->name.c_str());
}
if (cmdTable == HOSTSERV) {
if (s_HostServ) {
c->service = sstrdup(s_HostServ);
} else {
return MOD_ERR_NOSERVICE;
}
} else if (cmdTable == BOTSERV) {
if (s_BotServ) {
c->service = sstrdup(s_BotServ);
} else {
return MOD_ERR_NOSERVICE;
}
} else if (cmdTable == MEMOSERV) {
if (s_MemoServ) {
c->service = sstrdup(s_MemoServ);
} else {
return MOD_ERR_NOSERVICE;
}
} else if (cmdTable == CHANSERV) {
if (s_ChanServ) {
c->service = sstrdup(s_ChanServ);
} else {
return MOD_ERR_NOSERVICE;
}
} else if (cmdTable == NICKSERV) {
if (s_NickServ) {
c->service = sstrdup(s_NickServ);
} else {
return MOD_ERR_NOSERVICE;
}
} else if (cmdTable == OPERSERV) {
if (s_OperServ) {
c->service = sstrdup(s_OperServ);
} else {
return MOD_ERR_NOSERVICE;
}
} else
c->service = sstrdup("Unknown");
status = internal_addCommand(this, cmdTable, c, pos);
if (status != MOD_ERR_OK)
{
alog("ERROR! [%d]", status);
}
return status;
}
/** Remove a command from the command hash. Only for internal use.
* @param cmdTable the command table to remove the command from
* @param c the command to remove
* @param mod_name the name of the module who owns the command
* @return MOD_ERR_OK will be returned on success
*/
static int internal_delCommand(CommandHash * cmdTable[], Command * c, const char *mod_name)
{
int index = 0;
CommandHash *current = NULL;
CommandHash *lastHash = NULL;
Command *tail = NULL, *last = NULL;
if (!c || !cmdTable) {
return MOD_ERR_PARAMS;
}
index = CMD_HASH(c->name);
for (current = cmdTable[index]; current; current = current->next) {
if (stricmp(c->name.c_str(), current->name) == 0) {
if (!lastHash) {
tail = current->c;
if (tail->next) {
while (tail) {
if (mod_name && tail->mod_name
&& (stricmp(mod_name, tail->mod_name) == 0)) {
if (last) {
last->next = tail->next;
} else {
current->c = tail->next;
}
return MOD_ERR_OK;
}
last = tail;
tail = tail->next;
}
} else {
cmdTable[index] = current->next;
delete [] current->name;
return MOD_ERR_OK;
}
} else {
tail = current->c;
if (tail->next) {
while (tail) {
if (mod_name && tail->mod_name
&& (stricmp(mod_name, tail->mod_name) == 0)) {
if (last) {
last->next = tail->next;
} else {
current->c = tail->next;
}
return MOD_ERR_OK;
}
last = tail;
tail = tail->next;
}
} else {
lastHash->next = current->next;
delete [] current->name;
return MOD_ERR_OK;
}
}
}
lastHash = current;
}
return MOD_ERR_NOEXIST;
}
/**
* Delete a command from the service given.
* @param cmdTable the cmdTable for the services to remove the command from
* @param name the name of the command to delete from the service
* @return returns MOD_ERR_OK on success
*/
int Module::DelCommand(CommandHash * cmdTable[], const char *dname)
{
Command *c = NULL;
Command *cmd = NULL;
int status = 0;
c = findCommand(cmdTable, dname);
if (!c) {
return MOD_ERR_NOEXIST;
}
for (cmd = c; cmd; cmd = cmd->next)
{
if (cmd->mod_name && cmd->mod_name == this->name)
{
status = internal_delCommand(cmdTable, cmd, this->name.c_str());
}
}
return status;
}
/**
* Search the command table gieven for a command.
* @param cmdTable the name of the command table to search
* @param name the name of the command to look for
* @return returns a pointer to the found command struct, or NULL
*/
Command *findCommand(CommandHash * cmdTable[], const char *name)
{
int idx;
CommandHash *current = NULL;
if (!cmdTable || !name) {
return NULL;
}
idx = CMD_HASH(name);
for (current = cmdTable[idx]; current; current = current->next) {
if (stricmp(name, current->name) == 0) {
return current->c;
}
}
return NULL;
}
/*******************************************************************************
* Message Functions
*******************************************************************************/
/**
* Create a new Message struct.
* @param name the name of the message
* @param func a pointer to the function to call when we recive this message
* @return a new Message object
**/
Message *createMessage(const char *name,
int (*func) (const char *source, int ac, const char **av))
{
Message *m = NULL;
if (!name || !func) {
return NULL;
}
if (!(m = new Message)) {
fatal("Out of memory!");
}
m->name = sstrdup(name);
m->func = func;
m->core = 0;
m->next = NULL;
return m;
}
/**
* find a message in the given table.
* Looks up the message <name> in the MessageHash given
* @param MessageHash the message table to search for this command, will almost always be IRCD
* @param name the name of the command were looking for
* @return NULL if we cant find it, or a pointer to the Message if we can
**/
Message *findMessage(MessageHash * msgTable[], const char *name)
{
int idx;
MessageHash *current = NULL;
if (!msgTable || !name) {
return NULL;
}
idx = CMD_HASH(name);
for (current = msgTable[idx]; current; current = current->next) {
if (stricmp(name, current->name) == 0) {
return current->m;
}
}
return NULL;
}
/**
* Add a message to the MessageHash.
* @param msgTable the MessageHash we want to add a message to
* @param m the Message we want to add
* @param pos the position we want to add the message to, E.G. MOD_HEAD, MOD_TAIL, MOD_UNIQUE
* @return MOD_ERR_OK on a successful add.
**/
int addMessage(MessageHash * msgTable[], Message * m, int pos)
{
/* We can assume both param's have been checked by this point.. */
int index = 0;
MessageHash *current = NULL;
MessageHash *newHash = NULL;
MessageHash *lastHash = NULL;
Message *tail = NULL;
int match = 0;
if (!msgTable || !m || (pos < 0 || pos > 2)) {
return MOD_ERR_PARAMS;
}
index = CMD_HASH(m->name);
for (current = msgTable[index]; current; current = current->next) {
match = stricmp(m->name, current->name);
if (match == 0) { /* the msg exist's we are a addHead */
if (pos == 1) {
m->next = current->m;
current->m = m;
if (debug)
alog("debug: existing msg: (0x%p), new msg (0x%p)",
static_cast<void *>(m->next), static_cast<void *>(m));
return MOD_ERR_OK;
} else if (pos == 2) {
tail = current->m;
while (tail->next)
tail = tail->next;
if (debug)
alog("debug: existing msg: (0x%p), new msg (0x%p)",
static_cast<void *>(tail), static_cast<void *>(m));
tail->next = m;
m->next = NULL;
return MOD_ERR_OK;
} else
return MOD_ERR_EXISTS;
}
lastHash = current;
}
if (!(newHash = new MessageHash)) {
fatal("Out of memory");
}
newHash->next = NULL;
newHash->name = sstrdup(m->name);
newHash->m = m;
if (lastHash == NULL)
msgTable[index] = newHash;
else
lastHash->next = newHash;
return MOD_ERR_OK;
}
/**
* Add the given message (m) to the MessageHash marking it as a core command
* @param msgTable the MessageHash we want to add to
* @param m the Message we are adding
* @return MOD_ERR_OK on a successful add.
**/
int addCoreMessage(MessageHash * msgTable[], Message * m)
{
if (!msgTable || !m) {
return MOD_ERR_PARAMS;
}
m->core = 1;
return addMessage(msgTable, m, 0);
}
/**
* remove the given message from the given message hash, for the given module
* @param msgTable which MessageHash we are removing from
* @param m the Message we want to remove
* @return MOD_ERR_OK on success, althing else on fail.
**/
int delMessage(MessageHash * msgTable[], Message * m)
{
int index = 0;
MessageHash *current = NULL;
MessageHash *lastHash = NULL;
Message *tail = NULL, *last = NULL;
if (!m || !msgTable) {
return MOD_ERR_PARAMS;
}
index = CMD_HASH(m->name);
for (current = msgTable[index]; current; current = current->next) {
if (stricmp(m->name, current->name) == 0) {
if (!lastHash) {
tail = current->m;
if (tail->next) {
while (tail) {
if (last) {
last->next = tail->next;
} else {
current->m = tail->next;
}
return MOD_ERR_OK;
last = tail;
tail = tail->next;
}
} else {
msgTable[index] = current->next;
delete [] current->name;
return MOD_ERR_OK;
}
} else {
tail = current->m;
if (tail->next) {
while (tail) {
if (last) {
last->next = tail->next;
} else {
current->m = tail->next;
}
return MOD_ERR_OK;
last = tail;
tail = tail->next;
}
} else {
lastHash->next = current->next;
delete [] current->name;
return MOD_ERR_OK;
}
}
}
lastHash = current;
}
return MOD_ERR_NOEXIST;
}
/**
* Destory a message, freeing its memory.
* @param m the message to be destroyed
* @return MOD_ERR_SUCCESS on success
**/
int destroyMessage(Message * m)
{
if (!m) {
return MOD_ERR_PARAMS;
}
if (m->name) {
delete [] m->name;
}
m->func = NULL;
m->next = NULL;
return MOD_ERR_OK;
}
/*******************************************************************************
* Module Callback Functions
*******************************************************************************/
/**
* Adds a timer to the current module
* The timer handling will take care of everything for this timer, this is only here
* so we have a list of timers to destroy when this module is unloaded
* @param t A timer derived class
*/
void Module::AddCallBack(Timer *t)
{
this->CallBacks.push_back(t);
}
/**
* Deletes a timer for the current module
* @param t The timer
*/
bool Module::DelCallBack(Timer *t)
{
std::list<Timer *>::iterator it;
Timer *t2;
for (it = this->CallBacks.begin(); it != this->CallBacks.end(); ++it)
{
t2 = *it;
if (t == t2)
{
TimerManager::DelTimer(t2);
this->CallBacks.erase(it);
return true;
}
}
return false;
}
/**
* Display any extra module help for the given service.
* @param services which services is help being dispalyed for?
* @param u which user is requesting the help
**/
void moduleDisplayHelp(const char *service, User * u)
{
int idx;
ModuleHash *current = NULL;
if (!service)
return;
for (idx = 0; idx != MAX_CMD_HASH; idx++) {
for (current = MODULE_HASH[idx]; current; current = current->next) {
if (!strcmp(s_NickServ, service))
current->m->NickServHelp(u);
else if (!strcmp(s_ChanServ, service))
current->m->ChanServHelp(u);
else if (!strcmp(s_MemoServ, service))
current->m->MemoServHelp(u);
else if (!strcmp(s_BotServ, service))
current->m->BotServHelp(u);
else if (!strcmp(s_OperServ, service))
current->m->OperServHelp(u);
else if (!strcmp(s_HostServ, service))
current->m->HostServHelp(u);
}
}
}
/**
* Check the current version of anope against a given version number
* Specifiying -1 for minor,patch or build
* @param major The major version of anope, the first part of the verison number
* @param minor The minor version of anope, the second part of the version number
* @param patch The patch version of anope, the third part of the version number
* @param build The build revision of anope from SVN
* @return True if the version newer than the version specified.
**/
bool moduleMinVersion(int major, int minor, int patch, int build)
{
bool ret = false;
if (VERSION_MAJOR > major) { /* Def. new */
ret = true;
} else if (VERSION_MAJOR == major) { /* Might be newer */
if (minor == -1) {
return true;
} /* They dont care about minor */
if (VERSION_MINOR > minor) { /* Def. newer */
ret = true;
} else if (VERSION_MINOR == minor) { /* Might be newer */
if (patch == -1) {
return true;
} /* They dont care about patch */
if (VERSION_PATCH > patch) {
ret = true;
} else if (VERSION_PATCH == patch) {
#if 0
// XXX
if (build == -1) {
return true;
} /* They dont care about build */
if (VERSION_BUILD >= build) {
ret = true;
}
#endif
}
}
}
return ret;
}
/**
* Allow ircd protocol files to update the protect level info tables.
**/
void updateProtectDetails(const char *level_info_protect_word,
const char *level_info_protectme_word,
const char *fant_protect_add, const char *fant_protect_del,
const char *level_protect_word, const char *protect_set_mode,
const char *protect_unset_mode)
{
int i = 0;
CSModeUtil ptr;
LevelInfo l_ptr;
ptr = csmodeutils[i];
while (ptr.name) {
if (strcmp(ptr.name, "PROTECT") == 0) {
csmodeutils[i].bsname = sstrdup(fant_protect_add);
csmodeutils[i].mode = sstrdup(protect_set_mode);
} else if (strcmp(ptr.name, "DEPROTECT") == 0) {
csmodeutils[i].bsname = sstrdup(fant_protect_del);
csmodeutils[i].mode = sstrdup(protect_unset_mode);
}
ptr = csmodeutils[++i];
}
i = 0;
l_ptr = levelinfo[i];
while (l_ptr.what != -1) {
if (l_ptr.what == CA_PROTECT) {
levelinfo[i].name = sstrdup(level_info_protect_word);
} else if (l_ptr.what == CA_PROTECTME) {
levelinfo[i].name = sstrdup(level_info_protectme_word);
} else if (l_ptr.what == CA_AUTOPROTECT) {
levelinfo[i].name = sstrdup(level_protect_word);
}
l_ptr = levelinfo[++i];
}
}
void updateOwnerDetails(const char *fant_owner_add, const char *fant_owner_del, const char *owner_set_mode, const char *owner_del_mode)
{
CSModeUtil ptr;
int i = 0;
ptr = csmodeutils[i];
while (ptr.name) {
if (!strcmp(ptr.name, "OWNER")) {
csmodeutils[i].bsname = sstrdup(fant_owner_add);
csmodeutils[i].mode = sstrdup(owner_set_mode);
}
else if (!strcmp(ptr.name, "DEOWNER")) {
csmodeutils[i].bsname = sstrdup(fant_owner_del);
csmodeutils[i].mode = sstrdup(owner_del_mode);
}
ptr = csmodeutils[++i];
}
}
void Module::NoticeLang(char *source, User * u, int number, ...)
{
va_list va;
char buffer[4096], outbuf[4096];
char *fmt = NULL;
int mlang = NSDefLanguage;
char *s, *t, *buf;
/* Find the users lang, and use it if we can */
if (u && u->nc) {
mlang = u->nc->language;
}
/* If the users lang isnt supported, drop back to English */
if (this->lang[mlang].argc == 0)
{
mlang = LANG_EN_US;
}
/* If the requested lang string exists for the language */
if (this->lang[mlang].argc > number) {
fmt = this->lang[mlang].argv[number];
buf = sstrdup(fmt);
va_start(va, number);
vsnprintf(buffer, 4095, buf, va);
va_end(va);
s = buffer;
while (*s) {
t = s;
s += strcspn(s, "\n");
if (*s)
*s++ = '\0';
strscpy(outbuf, t, sizeof(outbuf));
u->SendMessage(source, "%s", outbuf);
}
delete [] buf;
} else {
alog("%s: INVALID language string call, language: [%d], String [%d]", this->name.c_str(), mlang, number);
}
}
const char *Module::GetLangString(User * u, int number)
{
int mlang = NSDefLanguage;
/* Find the users lang, and use it if we can */
if (u && u->nc)
mlang = u->nc->language;
/* If the users lang isnt supported, drop back to English */
if (this->lang[mlang].argc == 0)
mlang = LANG_EN_US;
/* If the requested lang string exists for the language */
if (this->lang[mlang].argc > number) {
return this->lang[mlang].argv[number];
/* Return an empty string otherwise, because we might be used without
* the return value being checked. If we would return NULL, bad things
* would happen!
*/
} else {
alog("%s: INVALID language string call, language: [%d], String [%d]", this->name.c_str(), mlang, number);
return "";
}
}
void Module::DeleteLanguage(int langNumber)
{
if (this->lang[langNumber].argc)
{
for (int idx = 0; idx > this->lang[langNumber].argc; idx++)
delete [] this->lang[langNumber].argv[idx];
delete [] this->lang[langNumber].argv;
this->lang[langNumber].argc = 0;
}
}
void ModuleRunTimeDirCleanUp()
{
#ifndef _WIN32
DIR *dirp;
struct dirent *dp;
#else
BOOL fFinished;
HANDLE hList;
TCHAR szDir[MAX_PATH + 1];
WIN32_FIND_DATA FileData;
char buffer[_MAX_PATH];
#endif
char dirbuf[BUFSIZE];
char filebuf[BUFSIZE];
snprintf(dirbuf, BUFSIZE, "%s/modules/runtime", services_dir.c_str());
if (debug) {
alog("debug: Cleaning out Module run time directory (%s) - this may take a moment please wait", dirbuf);
}
#ifndef _WIN32
if ((dirp = opendir(dirbuf)) == NULL) {
if (debug) {
alog("debug: cannot open directory (%s)", dirbuf);
}
return;
}
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_ino == 0) {
continue;
}
if (!stricmp(dp->d_name, ".") || !stricmp(dp->d_name, "..")) {
continue;
}
snprintf(filebuf, BUFSIZE, "%s/%s", dirbuf, dp->d_name);
unlink(filebuf);
}
closedir(dirp);
#else
/* Get the current working directory: */
if (_getcwd(buffer, _MAX_PATH) == NULL) {
if (debug) {
alog("debug: Unable to set Current working directory");
}
}
snprintf(szDir, sizeof(szDir), "%s\\%s\\*", buffer, dirbuf);
hList = FindFirstFile(szDir, &FileData);
if (hList != INVALID_HANDLE_VALUE) {
fFinished = FALSE;
while (!fFinished) {
if (!(FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
snprintf(filebuf, BUFSIZE, "%s/%s", dirbuf, FileData.cFileName);
DeleteFile(filebuf);
}
if (!FindNextFile(hList, &FileData)) {
if (GetLastError() == ERROR_NO_MORE_FILES) {
fFinished = TRUE;
}
}
}
} else {
if (debug) {
alog("debug: Invalid File Handle. GetLastError reports %d\n", static_cast<int>(GetLastError()));
}
}
FindClose(hList);
#endif
if (debug) {
alog("debug: Module run time directory has been cleaned out");
}
}
/* EOF */