mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-30 17:46:38 +02:00
d2ccba80c5
The LoadPersistent*()/SavePersistent*() functions caused moddata to be tagged with ->unloaded=1. Though it seems it caused no real issues this is not good... we now properly tag them as 0 and the like. Also did a code cleanup / overhaul on that system as well. For other ModData we now handle the case where a module is loaded with with a newer version and that newer version is no longer having certain moddata, eg the name changed or it no longer needs it. KNOWN ISSUE: Unfortunately we cannot call the free function for the old moddata that is no longer being handled by the newer version of the module, since the module is already unloaded. So this will result in a memory leak, but not in a crash. KNOWN ISSUE: Similarly, for SavePersistentPointer() there is a free function, again this is called just fine if the module is permanently unloaded but NOT if the module is reloaded with the same name and no longer is interested in the persistent pointer object. Again, here too, that would result in a memory leak but not in a crash. Fortunately the "known issues" are rare. Fixing these is impossible with the current module API because modules are unloaded after MOD_TEST and before MOD_INIT, and only after MOD_INIT we know which moddata is handled by the new version of the module. To change that we would need to keep the old module around until after MOD_INIT of the new module (so we can call free functions in the old module), but that means delaying the MOD_UNLOAD for the old modules until after MOD_INIT of the new modules, which changes the sequence too much that i don't dare to do that. For example, it would mean a database save routine in the old module would only be called after MOD_INIT finished in the new module, which may be unexpected since right now MOD_UNLOAD is called before MOD_INIT and maybe the db loading is done in MOD_INIT, which would need to be moved to MOD_LOAD. That's just one example, there may be others. I think such a change can only be done on a major UnrealIRCd version change, so we will have to live this for now. As said, fortunately it is a corner case.
511 lines
12 KiB
C
511 lines
12 KiB
C
/************************************************************************
|
|
* IRC - Internet Relay Chat, src/api-moddata.c
|
|
* (C) 2003-2019 Bram Matthys (Syzop) and the UnrealIRCd Team
|
|
*
|
|
* See file AUTHORS in IRC package for additional names of
|
|
* the programmers.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 1, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
MODVAR ModDataInfo *MDInfo = NULL;
|
|
|
|
MODVAR ModData local_variable_moddata[MODDATA_MAX_LOCAL_VARIABLE];
|
|
MODVAR ModData global_variable_moddata[MODDATA_MAX_GLOBAL_VARIABLE];
|
|
|
|
ModDataInfo *ModDataAdd(Module *module, ModDataInfo req)
|
|
{
|
|
int slotav = 0; /* highest available slot */
|
|
ModDataInfo *m;
|
|
int new_struct = 0;
|
|
|
|
/* Hunt for highest available slot */
|
|
for (m = MDInfo; m ; m = m->next)
|
|
if (m->type == req.type)
|
|
{
|
|
/* Does an entry already exist with this name? */
|
|
if (!strcmp(m->name, req.name))
|
|
{
|
|
/* If old module is unloading (so reloading), then OK to take this slot */
|
|
if (m->unloaded)
|
|
{
|
|
slotav = m->slot;
|
|
m->unloaded = 0;
|
|
goto moddataadd_isok;
|
|
}
|
|
/* Otherwise, name collision */
|
|
if (module)
|
|
module->errorcode = MODERR_EXISTS;
|
|
return NULL;
|
|
}
|
|
/* Update next available slot */
|
|
slotav = MAX(slotav, m->slot+1);
|
|
}
|
|
|
|
/* Now check if we are within bounds (if we really have a free slot available) */
|
|
if (((req.type == MODDATATYPE_LOCAL_VARIABLE) && (slotav >= MODDATA_MAX_LOCAL_VARIABLE)) ||
|
|
((req.type == MODDATATYPE_GLOBAL_VARIABLE) && (slotav >= MODDATA_MAX_GLOBAL_VARIABLE)) ||
|
|
((req.type == MODDATATYPE_CLIENT) && (slotav >= MODDATA_MAX_CLIENT)) ||
|
|
((req.type == MODDATATYPE_LOCAL_CLIENT) && (slotav >= MODDATA_MAX_LOCAL_CLIENT)) ||
|
|
((req.type == MODDATATYPE_CHANNEL) && (slotav >= MODDATA_MAX_CHANNEL)) ||
|
|
((req.type == MODDATATYPE_MEMBER) && (slotav >= MODDATA_MAX_MEMBER)) ||
|
|
((req.type == MODDATATYPE_MEMBERSHIP) && (slotav >= MODDATA_MAX_MEMBERSHIP)))
|
|
{
|
|
unreal_log(ULOG_ERROR, "module", "MOD_DATA_OUT_OF_SPACE", NULL,
|
|
"ModDataAdd: out of space!!!");
|
|
if (module)
|
|
module->errorcode = MODERR_NOSPACE;
|
|
return NULL;
|
|
}
|
|
|
|
new_struct = 1;
|
|
m = safe_alloc(sizeof(ModDataInfo));
|
|
safe_strdup(m->name, req.name);
|
|
m->slot = slotav;
|
|
m->type = req.type;
|
|
moddataadd_isok:
|
|
m->free = req.free;
|
|
m->serialize = req.serialize;
|
|
m->unserialize = req.unserialize;
|
|
m->sync = req.sync;
|
|
m->remote_write = req.remote_write;
|
|
m->self_write = req.self_write;
|
|
m->owner = module;
|
|
|
|
if (new_struct)
|
|
AddListItem(m, MDInfo);
|
|
|
|
if (module)
|
|
{
|
|
ModuleObject *mobj = safe_alloc(sizeof(ModuleObject));
|
|
mobj->object.moddata = m;
|
|
mobj->type = MOBJ_MODDATA;
|
|
AddListItem(mobj, module->objects);
|
|
module->errorcode = MODERR_NOERROR;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
void moddata_free_client(Client *client)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if (md->type == MODDATATYPE_CLIENT)
|
|
{
|
|
if (md->free && moddata_client(client, md).ptr)
|
|
md->free(&moddata_client(client, md));
|
|
}
|
|
|
|
memset(client->moddata, 0, sizeof(client->moddata));
|
|
}
|
|
|
|
void moddata_free_local_client(Client *client)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if (md->type == MODDATATYPE_LOCAL_CLIENT)
|
|
{
|
|
if (md->free && moddata_local_client(client, md).ptr)
|
|
md->free(&moddata_local_client(client, md));
|
|
}
|
|
|
|
memset(client->moddata, 0, sizeof(client->moddata));
|
|
}
|
|
|
|
void moddata_free_channel(Channel *channel)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if (md->type == MODDATATYPE_CHANNEL)
|
|
{
|
|
if (md->free && moddata_channel(channel, md).ptr)
|
|
md->free(&moddata_channel(channel, md));
|
|
}
|
|
|
|
memset(channel->moddata, 0, sizeof(channel->moddata));
|
|
}
|
|
|
|
void moddata_free_member(Member *m)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if (md->type == MODDATATYPE_MEMBER)
|
|
{
|
|
if (md->free && moddata_member(m, md).ptr)
|
|
md->free(&moddata_member(m, md));
|
|
}
|
|
|
|
memset(m->moddata, 0, sizeof(m->moddata));
|
|
}
|
|
|
|
void moddata_free_membership(Membership *m)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if (md->type == MODDATATYPE_MEMBERSHIP)
|
|
{
|
|
if (md->free && moddata_membership(m, md).ptr)
|
|
md->free(&moddata_membership(m, md));
|
|
}
|
|
|
|
memset(m->moddata, 0, sizeof(m->moddata));
|
|
}
|
|
|
|
/** Actually free all the ModData from all objects */
|
|
void unload_moddata_commit(ModDataInfo *md)
|
|
{
|
|
switch(md->type)
|
|
{
|
|
case MODDATATYPE_LOCAL_VARIABLE:
|
|
if (md->free && moddata_local_variable(md).ptr)
|
|
md->free(&moddata_local_variable(md));
|
|
memset(&moddata_local_variable(md), 0, sizeof(ModData));
|
|
break;
|
|
case MODDATATYPE_GLOBAL_VARIABLE:
|
|
if (md->free && moddata_global_variable(md).ptr)
|
|
md->free(&moddata_global_variable(md));
|
|
memset(&moddata_global_variable(md), 0, sizeof(ModData));
|
|
break;
|
|
case MODDATATYPE_CLIENT:
|
|
{
|
|
Client *client;
|
|
list_for_each_entry(client, &client_list, client_node)
|
|
{
|
|
if (md->free && moddata_client(client, md).ptr)
|
|
md->free(&moddata_client(client, md));
|
|
memset(&moddata_client(client, md), 0, sizeof(ModData));
|
|
}
|
|
list_for_each_entry(client, &unknown_list, lclient_node)
|
|
{
|
|
if (md->free && moddata_client(client, md).ptr)
|
|
md->free(&moddata_client(client, md));
|
|
memset(&moddata_client(client, md), 0, sizeof(ModData));
|
|
}
|
|
break;
|
|
}
|
|
case MODDATATYPE_LOCAL_CLIENT:
|
|
{
|
|
Client *client;
|
|
list_for_each_entry(client, &lclient_list, lclient_node)
|
|
{
|
|
if (md->free && moddata_local_client(client, md).ptr)
|
|
md->free(&moddata_local_client(client, md));
|
|
memset(&moddata_local_client(client, md), 0, sizeof(ModData));
|
|
}
|
|
list_for_each_entry(client, &unknown_list, lclient_node)
|
|
{
|
|
if (md->free && moddata_local_client(client, md).ptr)
|
|
md->free(&moddata_local_client(client, md));
|
|
memset(&moddata_local_client(client, md), 0, sizeof(ModData));
|
|
}
|
|
break;
|
|
}
|
|
case MODDATATYPE_CHANNEL:
|
|
{
|
|
Channel *channel;
|
|
for (channel = channels; channel; channel=channel->nextch)
|
|
{
|
|
if (md->free && moddata_channel(channel, md).ptr)
|
|
md->free(&moddata_channel(channel, md));
|
|
memset(&moddata_channel(channel, md), 0, sizeof(ModData));
|
|
}
|
|
break;
|
|
}
|
|
case MODDATATYPE_MEMBER:
|
|
{
|
|
Channel *channel;
|
|
Member *m;
|
|
for (channel = channels; channel; channel=channel->nextch)
|
|
{
|
|
for (m = channel->members; m; m = m->next)
|
|
{
|
|
if (md->free && moddata_member(m, md).ptr)
|
|
md->free(&moddata_member(m, md));
|
|
memset(&moddata_member(m, md), 0, sizeof(ModData));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MODDATATYPE_MEMBERSHIP:
|
|
{
|
|
Client *client;
|
|
Membership *m;
|
|
list_for_each_entry(client, &lclient_list, lclient_node)
|
|
{
|
|
if (!client->user)
|
|
continue;
|
|
for (m = client->user->channel; m; m = m->next)
|
|
{
|
|
if (md->free && moddata_membership(m, md).ptr)
|
|
md->free(&moddata_membership(m, md));
|
|
memset(&moddata_membership(m, md), 0, sizeof(ModData));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
DelListItem(md, MDInfo);
|
|
safe_free(md->name);
|
|
safe_free(md);
|
|
}
|
|
|
|
void ModDataDel(ModDataInfo *md)
|
|
{
|
|
/* Delete the reference to us first */
|
|
if (md->owner)
|
|
{
|
|
ModuleObject *mdobj;
|
|
for (mdobj = md->owner->objects; mdobj; mdobj = mdobj->next)
|
|
{
|
|
if ((mdobj->type == MOBJ_MODDATA) && (mdobj->object.moddata == md))
|
|
{
|
|
DelListItem(mdobj, md->owner->objects);
|
|
safe_free(mdobj);
|
|
break;
|
|
}
|
|
}
|
|
/* Module is unloading, so owner will be gone: */
|
|
md->owner = NULL;
|
|
}
|
|
|
|
if (loop.rehashing)
|
|
{
|
|
/* Since the module is unloaded, these are not safe anymore either: */
|
|
md->free = NULL;
|
|
md->serialize = NULL;
|
|
md->unserialize = NULL;
|
|
md->unloaded = 1;
|
|
} else {
|
|
/* We need the free function and the like,
|
|
* and then completely destroy 'md'.
|
|
*/
|
|
unload_moddata_commit(md);
|
|
}
|
|
}
|
|
|
|
void unload_all_unused_moddata(void)
|
|
{
|
|
ModDataInfo *md, *md_next;
|
|
|
|
for (md = MDInfo; md; md = md_next)
|
|
{
|
|
md_next = md->next;
|
|
if (md->unloaded)
|
|
{
|
|
//config_status("UNLOADING: md %s (owner %p, type %d, slot %d)",
|
|
// md->name, md->owner, md->type, md->slot);
|
|
unload_moddata_commit(md);
|
|
} else {
|
|
//config_status("loaded: md %s (owner %p, type %d, slot %d)",
|
|
// md->name, md->owner, md->type, md->slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
ModDataInfo *findmoddata_byname(const char *name, ModDataType type)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if ((md->type == type) && !strcmp(name, md->name))
|
|
return md;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int module_has_moddata(Module *mod)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
for (md = MDInfo; md; md = md->next)
|
|
if (md->owner == mod)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Set ModData for client (via variable name, string value) */
|
|
int moddata_client_set(Client *client, const char *varname, const char *value)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
|
|
|
|
if (!md)
|
|
return 0;
|
|
|
|
if (value)
|
|
{
|
|
/* SET */
|
|
md->unserialize(value, &moddata_client(client, md));
|
|
}
|
|
else
|
|
{
|
|
/* UNSET */
|
|
md->free(&moddata_client(client, md));
|
|
memset(&moddata_client(client, md), 0, sizeof(ModData));
|
|
}
|
|
|
|
/* If 'sync' field is set and the client is not in pre-registered
|
|
* state then broadcast the new setting.
|
|
*/
|
|
if (md->sync && (IsUser(client) || IsServer(client) || IsMe(client)))
|
|
broadcast_md_client_cmd(NULL, &me, client, md->name, value);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Get ModData for client (via variable name) */
|
|
const char *moddata_client_get(Client *client, const char *varname)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
|
|
|
|
if (!md)
|
|
return NULL;
|
|
|
|
return md->serialize(&moddata_client(client, md)); /* can be NULL */
|
|
}
|
|
|
|
/** Get ModData for client (via variable name) */
|
|
ModData *moddata_client_get_raw(Client *client, const char *varname)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
|
|
|
|
if (!md)
|
|
return NULL;
|
|
|
|
return &moddata_client(client, md); /* can be NULL */
|
|
}
|
|
|
|
/** Set ModData for LocalClient (via variable name, string value) */
|
|
int moddata_local_client_set(Client *client, const char *varname, const char *value)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
if (!MyConnect(client))
|
|
abort();
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_LOCAL_CLIENT);
|
|
|
|
if (!md)
|
|
return 0;
|
|
|
|
if (value)
|
|
{
|
|
/* SET */
|
|
md->unserialize(value, &moddata_local_client(client, md));
|
|
}
|
|
else
|
|
{
|
|
/* UNSET */
|
|
md->free(&moddata_local_client(client, md));
|
|
memset(&moddata_local_client(client, md), 0, sizeof(ModData));
|
|
}
|
|
|
|
/* If 'sync' field is set and the client is not in pre-registered
|
|
* state then broadcast the new setting.
|
|
*/
|
|
if (md->sync && (IsUser(client) || IsServer(client) || IsMe(client)))
|
|
broadcast_md_client_cmd(NULL, &me, client, md->name, value);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Get ModData for LocalClient (via variable name) */
|
|
const char *moddata_local_client_get(Client *client, const char *varname)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
if (!MyConnect(client))
|
|
abort();
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_LOCAL_CLIENT);
|
|
|
|
if (!md)
|
|
return NULL;
|
|
|
|
return md->serialize(&moddata_local_client(client, md)); /* can be NULL */
|
|
}
|
|
|
|
/** Set local variable moddata (via variable name, string value) */
|
|
int moddata_local_variable_set(const char *varname, const char *value)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_LOCAL_VARIABLE);
|
|
|
|
if (!md)
|
|
return 0;
|
|
|
|
if (value)
|
|
{
|
|
/* SET */
|
|
md->unserialize(value, &moddata_local_variable(md));
|
|
}
|
|
else
|
|
{
|
|
/* UNSET */
|
|
md->free(&moddata_local_variable(md));
|
|
memset(&moddata_local_variable(md), 0, sizeof(ModData));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Set global variable moddata (via variable name, string value) */
|
|
int moddata_global_variable_set(const char *varname, const char *value)
|
|
{
|
|
ModDataInfo *md;
|
|
|
|
md = findmoddata_byname(varname, MODDATATYPE_GLOBAL_VARIABLE);
|
|
|
|
if (!md)
|
|
return 0;
|
|
|
|
if (value)
|
|
{
|
|
/* SET */
|
|
md->unserialize(value, &moddata_global_variable(md));
|
|
}
|
|
else
|
|
{
|
|
/* UNSET */
|
|
md->free(&moddata_global_variable(md));
|
|
memset(&moddata_global_variable(md), 0, sizeof(ModData));
|
|
}
|
|
|
|
if (md->sync)
|
|
broadcast_md_globalvar_cmd(NULL, &me, md->name, value);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* The rest of the MD related functions, the send/receive functions,
|
|
* are in src/modules/md.c
|
|
*/
|