unrealircd/src/modules/channeldb.c

568 lines
15 KiB
C
Raw Normal View History

2020-03-29 02:16:53 -07:00
/*
* Stores channel settings for +P channels in a .db file
* (C) Copyright 2019 Syzop, Gottem and the UnrealIRCd team
2022-11-19 20:12:40 -08:00
* License: GPLv2 or later
2020-03-29 02:16:53 -07:00
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER = {
"channeldb",
"1.0",
"Stores and retrieves channel settings for persistent (+P) channels",
"UnrealIRCd Team",
2022-01-14 21:16:34 -08:00
"unrealircd-6",
2020-03-29 02:16:53 -07:00
};
2021-03-21 10:05:35 -07:00
/* Database version */
2020-03-29 02:16:53 -07:00
#define CHANNELDB_VERSION 100
2021-03-21 10:05:35 -07:00
/* Save channels to file every <this> seconds */
#define CHANNELDB_SAVE_EVERY 300
/* The very first save after boot, apply this delta, this
* so we don't coincide with other (potentially) expensive
* I/O events like saving tkldb.
*/
#define CHANNELDB_SAVE_EVERY_DELTA -15
2020-03-29 02:16:53 -07:00
#define MAGIC_CHANNEL_START 0x11111111
#define MAGIC_CHANNEL_END 0x22222222
2022-01-14 21:16:34 -08:00
// #undef BENCHMARK
2020-03-29 02:16:53 -07:00
#define WARN_WRITE_ERROR(fname) \
do { \
2022-01-14 21:16:34 -08:00
unreal_log(ULOG_ERROR, "channeldb", "CHANNELDB_FILE_WRITE_ERROR", NULL, \
"[channeldb] Error writing to temporary database file $filename: $system_error", \
log_data_string("filename", fname), \
log_data_string("system_error", unrealdb_get_error_string())); \
2020-03-29 02:16:53 -07:00
} while(0)
#define W_SAFE(x) \
do { \
if (!(x)) { \
WARN_WRITE_ERROR(tmpfname); \
2021-06-19 08:52:51 -07:00
unrealdb_close(db); \
2020-03-29 02:16:53 -07:00
return 0; \
} \
} while(0)
#define IsMDErr(x, y, z) \
do { \
if (!(x)) { \
config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
return MOD_FAILED; \
} \
} while(0)
2021-06-19 08:52:51 -07:00
/* Structs */
struct cfgstruct {
char *database;
char *db_secret;
};
2020-03-29 02:16:53 -07:00
/* Forward declarations */
void channeldb_moddata_free(ModData *md);
2021-06-19 08:52:51 -07:00
void setcfg(struct cfgstruct *cfg);
void freecfg(struct cfgstruct *cfg);
int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int channeldb_config_posttest(int *errs);
int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
2020-03-29 02:16:53 -07:00
EVENT(write_channeldb_evt);
int write_channeldb(void);
2021-06-19 08:52:51 -07:00
int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel);
2020-03-29 02:16:53 -07:00
int read_channeldb(void);
/* Global variables */
static uint32_t channeldb_version = CHANNELDB_VERSION;
static struct cfgstruct cfg;
2021-06-19 08:52:51 -07:00
static struct cfgstruct test;
2020-03-29 02:16:53 -07:00
2020-05-28 19:06:50 -07:00
static long channeldb_next_event = 0;
2020-03-29 02:16:53 -07:00
MOD_TEST()
{
memset(&cfg, 0, sizeof(cfg));
2021-06-19 08:52:51 -07:00
memset(&test, 0, sizeof(test));
setcfg(&test);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, channeldb_config_test);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, channeldb_config_posttest);
2020-03-29 02:16:53 -07:00
return MOD_SUCCESS;
}
MOD_INIT()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
2021-06-19 08:52:51 -07:00
/* We must unload early, when all channel modes and such are still in place: */
2023-05-05 15:12:01 -07:00
ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
2020-03-29 02:16:53 -07:00
2020-05-28 19:06:50 -07:00
LoadPersistentLong(modinfo, channeldb_next_event);
2020-03-29 02:16:53 -07:00
2021-06-19 08:52:51 -07:00
setcfg(&cfg);
2020-03-29 02:16:53 -07:00
2021-06-19 08:52:51 -07:00
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_config_run);
2020-04-20 12:12:33 -07:00
return MOD_SUCCESS;
}
MOD_LOAD()
{
2020-05-28 19:06:50 -07:00
if (!channeldb_next_event)
2020-03-29 02:16:53 -07:00
{
/* If this is the first time that our module is loaded, then read the database. */
if (!read_channeldb())
{
char fname[512];
snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
if (rename(cfg.database, fname) == 0)
config_warn("[channeldb] Existing database renamed to %s and starting a new one...", fname);
else
config_warn("[channeldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
}
2021-03-21 10:05:35 -07:00
channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY + CHANNELDB_SAVE_EVERY_DELTA;
2020-03-29 02:16:53 -07:00
}
2020-05-28 19:06:50 -07:00
EventAdd(modinfo->handle, "channeldb_write_channeldb", write_channeldb_evt, NULL, 1000, 0);
2020-03-29 02:16:53 -07:00
if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
{
config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
return MOD_FAILED;
}
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
2022-01-14 21:16:34 -08:00
if (loop.terminating)
2021-06-19 08:52:51 -07:00
write_channeldb();
freecfg(&test);
freecfg(&cfg);
2020-05-28 19:06:50 -07:00
SavePersistentLong(modinfo, channeldb_next_event);
2020-03-29 02:16:53 -07:00
return MOD_SUCCESS;
}
void channeldb_moddata_free(ModData *md)
{
if (md->i)
md->i = 0;
}
2021-06-19 08:52:51 -07:00
void setcfg(struct cfgstruct *cfg)
2020-03-29 02:16:53 -07:00
{
// Default: data/channel.db
2021-06-19 08:52:51 -07:00
safe_strdup(cfg->database, "channel.db");
convert_to_absolute_path(&cfg->database, PERMDATADIR);
2020-03-29 02:16:53 -07:00
}
2021-06-19 08:52:51 -07:00
void freecfg(struct cfgstruct *cfg)
2020-03-29 02:16:53 -07:00
{
2021-06-19 08:52:51 -07:00
safe_free(cfg->database);
safe_free(cfg->db_secret);
2020-03-29 02:16:53 -07:00
}
2021-06-19 08:52:51 -07:00
int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
2020-03-29 02:16:53 -07:00
{
int errors = 0;
ConfigEntry *cep;
// We are only interested in set::channeldb::database
if (type != CONFIG_SET)
return 0;
2022-01-14 21:16:34 -08:00
if (!ce || strcmp(ce->name, "channeldb"))
2020-03-29 02:16:53 -07:00
return 0;
2022-01-14 21:16:34 -08:00
for (cep = ce->items; cep; cep = cep->next)
2020-03-29 02:16:53 -07:00
{
2022-01-14 21:16:34 -08:00
if (!cep->value)
2021-06-19 08:52:51 -07:00
{
2022-01-14 21:16:34 -08:00
config_error("%s:%i: blank set::channeldb::%s without value", cep->file->filename, cep->line_number, cep->name);
2020-03-29 02:16:53 -07:00
errors++;
2021-06-19 08:52:51 -07:00
} else
2022-01-14 21:16:34 -08:00
if (!strcmp(cep->name, "database"))
2021-06-19 08:52:51 -07:00
{
2022-01-14 21:16:34 -08:00
convert_to_absolute_path(&cep->value, PERMDATADIR);
safe_strdup(test.database, cep->value);
2021-06-19 08:52:51 -07:00
} else
2022-01-14 21:16:34 -08:00
if (!strcmp(cep->name, "db-secret"))
2021-06-19 08:52:51 -07:00
{
2022-01-14 21:16:34 -08:00
const char *err;
if ((err = unrealdb_test_secret(cep->value)))
2021-06-19 08:52:51 -07:00
{
2022-01-14 21:16:34 -08:00
config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
2021-06-19 08:52:51 -07:00
errors++;
continue;
}
2022-01-14 21:16:34 -08:00
safe_strdup(test.db_secret, cep->value);
2021-06-19 08:52:51 -07:00
} else
{
2022-01-14 21:16:34 -08:00
config_error("%s:%i: unknown directive set::channeldb::%s", cep->file->filename, cep->line_number, cep->name);
2021-06-19 08:52:51 -07:00
errors++;
2020-03-29 02:16:53 -07:00
}
2021-06-19 08:52:51 -07:00
}
*errs = errors;
return errors ? -1 : 1;
}
int channeldb_config_posttest(int *errs)
{
int errors = 0;
char *errstr;
if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
{
config_error("[channeldb] %s", errstr);
2020-03-29 02:16:53 -07:00
errors++;
}
*errs = errors;
return errors ? -1 : 1;
}
2021-06-19 08:52:51 -07:00
int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
2020-03-29 02:16:53 -07:00
{
ConfigEntry *cep;
// We are only interested in set::channeldb::database
if (type != CONFIG_SET)
return 0;
2022-01-14 21:16:34 -08:00
if (!ce || strcmp(ce->name, "channeldb"))
2020-03-29 02:16:53 -07:00
return 0;
2022-01-14 21:16:34 -08:00
for (cep = ce->items; cep; cep = cep->next)
2020-03-29 02:16:53 -07:00
{
2022-01-14 21:16:34 -08:00
if (!strcmp(cep->name, "database"))
safe_strdup(cfg.database, cep->value);
else if (!strcmp(cep->name, "db-secret"))
safe_strdup(cfg.db_secret, cep->value);
2020-03-29 02:16:53 -07:00
}
return 1;
}
EVENT(write_channeldb_evt)
{
2020-05-28 19:06:50 -07:00
if (channeldb_next_event > TStime())
return;
channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY;
2020-03-29 02:16:53 -07:00
write_channeldb();
}
int write_channeldb(void)
{
char tmpfname[512];
2021-06-19 08:52:51 -07:00
UnrealDB *db;
2020-03-29 02:16:53 -07:00
Channel *channel;
int cnt = 0;
#ifdef BENCHMARK
struct timeval tv_alpha, tv_beta;
gettimeofday(&tv_alpha, NULL);
#endif
// Write to a tempfile first, then rename it if everything succeeded
2021-03-21 10:05:35 -07:00
snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
2021-06-19 08:52:51 -07:00
db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
if (!db)
2020-03-29 02:16:53 -07:00
{
WARN_WRITE_ERROR(tmpfname);
return 0;
}
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_int32(db, channeldb_version));
2020-03-29 02:16:53 -07:00
/* First, count +P channels and write the count to the database */
for (channel = channels; channel; channel=channel->nextch)
if (has_channel_mode(channel, 'P'))
cnt++;
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_int64(db, cnt));
2020-03-29 02:16:53 -07:00
for (channel = channels; channel; channel=channel->nextch)
{
/* We only care about +P (persistent) channels */
if (has_channel_mode(channel, 'P'))
{
2021-06-19 08:52:51 -07:00
if (!write_channel_entry(db, tmpfname, channel))
2020-03-29 02:16:53 -07:00
return 0;
}
}
// Everything seems to have gone well, attempt to close and rename the tempfile
2021-06-19 08:52:51 -07:00
if (!unrealdb_close(db))
2020-03-29 02:16:53 -07:00
{
WARN_WRITE_ERROR(tmpfname);
return 0;
}
#ifdef _WIN32
/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
unlink(cfg.database);
#endif
if (rename(tmpfname, cfg.database) < 0)
{
2022-01-14 21:16:34 -08:00
config_error("[channeldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
2020-03-29 02:16:53 -07:00
return 0;
}
#ifdef BENCHMARK
gettimeofday(&tv_beta, NULL);
config_status("[channeldb] Benchmark: SAVE DB: %ld microseconds",
((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
#endif
return 1;
}
2021-06-19 08:52:51 -07:00
int write_listmode(UnrealDB *db, const char *tmpfname, Ban *lst)
2020-03-29 02:16:53 -07:00
{
Ban *l;
int cnt = 0;
/* First count and write the list count */
for (l = lst; l; l = l->next)
cnt++;
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_int32(db, cnt));
2020-03-29 02:16:53 -07:00
for (l = lst; l; l = l->next)
{
/* The entry, setby, seton */
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_str(db, l->banstr));
W_SAFE(unrealdb_write_str(db, l->who));
W_SAFE(unrealdb_write_int64(db, l->when));
2020-03-29 02:16:53 -07:00
}
return 1;
}
2021-06-19 08:52:51 -07:00
int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel)
2020-03-29 02:16:53 -07:00
{
2022-01-14 21:16:34 -08:00
char modebuf[BUFSIZE], parabuf[BUFSIZE];
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_START));
2020-03-29 02:16:53 -07:00
/* Channel name */
2022-01-14 21:16:34 -08:00
W_SAFE(unrealdb_write_str(db, channel->name));
2020-03-29 02:16:53 -07:00
/* Channel creation time */
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_int64(db, channel->creationtime));
2020-03-29 02:16:53 -07:00
/* Topic (topic, setby, seton) */
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_str(db, channel->topic));
W_SAFE(unrealdb_write_str(db, channel->topic_nick));
W_SAFE(unrealdb_write_int64(db, channel->topic_time));
2020-03-29 02:16:53 -07:00
/* Basic channel modes (eg: +sntkl key 55) */
2022-01-14 21:16:34 -08:00
channel_modes(&me, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_str(db, modebuf));
W_SAFE(unrealdb_write_str(db, parabuf));
2020-03-29 02:16:53 -07:00
/* Mode lock */
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_str(db, channel->mode_lock));
2020-03-29 02:16:53 -07:00
/* List modes (bans, exempts, invex) */
2021-06-19 08:52:51 -07:00
if (!write_listmode(db, tmpfname, channel->banlist))
2020-03-29 02:16:53 -07:00
return 0;
2021-06-19 08:52:51 -07:00
if (!write_listmode(db, tmpfname, channel->exlist))
2020-03-29 02:16:53 -07:00
return 0;
2021-06-19 08:52:51 -07:00
if (!write_listmode(db, tmpfname, channel->invexlist))
2020-03-29 02:16:53 -07:00
return 0;
2021-06-19 08:52:51 -07:00
W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_END));
2020-03-29 02:16:53 -07:00
return 1;
}
#define R_SAFE(x) \
do { \
if (!(x)) { \
2021-06-19 08:52:51 -07:00
config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
2020-03-29 02:16:53 -07:00
if (e) \
{ \
safe_free(e->banstr); \
safe_free(e->who); \
safe_free(e); \
} \
return 0; \
} \
} while(0)
2021-06-19 08:52:51 -07:00
int read_listmode(UnrealDB *db, Ban **lst)
2020-03-29 02:16:53 -07:00
{
uint32_t total;
uint64_t when;
int i;
Ban *e = NULL;
2021-06-19 08:52:51 -07:00
R_SAFE(unrealdb_read_int32(db, &total));
2020-03-29 02:16:53 -07:00
for (i = 0; i < total; i++)
{
2022-04-03 08:09:29 -07:00
const char *str;
2020-03-29 02:16:53 -07:00
e = safe_alloc(sizeof(Ban));
2021-06-19 08:52:51 -07:00
R_SAFE(unrealdb_read_str(db, &e->banstr));
R_SAFE(unrealdb_read_str(db, &e->who));
R_SAFE(unrealdb_read_int64(db, &when));
2022-04-03 08:09:29 -07:00
str = clean_ban_mask(e->banstr, MODE_ADD, &me, 0);
if (str == NULL)
{
/* Skip this item */
config_warn("[channeldb] listmode skipped (no longer valid?): %s", e->banstr);
safe_free(e->banstr);
safe_free(e->who);
safe_free(e);
continue;
}
safe_strdup(e->banstr, str);
2023-05-05 15:12:01 -07:00
if (ban_exists(*lst, e->banstr))
2022-11-19 20:12:40 -08:00
{
/* Free again - duplicate item */
safe_free(e->banstr);
safe_free(e->who);
safe_free(e);
} else {
/* Add to list */
e->when = when;
e->next = *lst;
*lst = e;
}
2020-03-29 02:16:53 -07:00
}
return 1;
}
#undef R_SAFE
#define FreeChannelEntry() \
do { \
/* Some of these might be NULL */ \
safe_free(chname); \
safe_free(topic); \
safe_free(topic_nick); \
safe_free(modes1); \
safe_free(modes2); \
safe_free(mode_lock); \
} while(0)
#define R_SAFE(x) \
do { \
if (!(x)) { \
2021-06-19 08:52:51 -07:00
config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
unrealdb_close(db); \
2020-03-29 02:16:53 -07:00
FreeChannelEntry(); \
return 0; \
} \
} while(0)
int read_channeldb(void)
{
2021-06-19 08:52:51 -07:00
UnrealDB *db;
2020-03-29 02:16:53 -07:00
uint32_t version;
int added = 0;
int i;
uint64_t count = 0;
uint32_t magic;
// Variables for the channels
// Some of them need to be declared and NULL initialised early due to the macro FreeChannelEntry() being used by R_SAFE() on error
char *chname = NULL;
uint64_t creationtime = 0;
char *topic = NULL;
char *topic_nick = NULL;
uint64_t topic_time = 0;
char *modes1 = NULL;
char *modes2 = NULL;
char *mode_lock = NULL;
#ifdef BENCHMARK
struct timeval tv_alpha, tv_beta;
gettimeofday(&tv_alpha, NULL);
#endif
2021-06-19 08:52:51 -07:00
db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
if (!db)
2020-03-29 02:16:53 -07:00
{
2021-06-19 08:52:51 -07:00
if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
2020-03-29 02:16:53 -07:00
{
/* Database does not exist. Could be first boot */
config_warn("[channeldb] No database present at '%s', will start a new one", cfg.database);
return 1;
2021-06-19 08:52:51 -07:00
} else
if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
{
/* Re-open as unencrypted */
db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
if (!db)
{
/* This should actually never happen, unless some weird I/O error */
config_warn("[channeldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
return 0;
}
} else
{
config_warn("[channeldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
2020-03-29 02:16:53 -07:00
return 0;
}
}
2021-06-19 08:52:51 -07:00
R_SAFE(unrealdb_read_int32(db, &version));
2020-03-29 02:16:53 -07:00
if (version > channeldb_version)
{
config_warn("[channeldb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, channeldb_version, version);
2021-06-19 08:52:51 -07:00
unrealdb_close(db);
2020-03-29 02:16:53 -07:00
return 0;
}
2021-06-19 08:52:51 -07:00
R_SAFE(unrealdb_read_int64(db, &count));
2020-03-29 02:16:53 -07:00
for (i=1; i <= count; i++)
{
// Variables
chname = NULL;
creationtime = 0;
topic = NULL;
topic_nick = NULL;
topic_time = 0;
modes1 = NULL;
modes2 = NULL;
mode_lock = NULL;
Channel *channel;
2021-06-19 08:52:51 -07:00
R_SAFE(unrealdb_read_int32(db, &magic));
2020-03-29 02:16:53 -07:00
if (magic != MAGIC_CHANNEL_START)
{
config_error("[channeldb] Corrupt database (%s) - channel magic start is 0x%x. Further reading aborted.", cfg.database, magic);
break;
}
2021-06-19 08:52:51 -07:00
R_SAFE(unrealdb_read_str(db, &chname));
R_SAFE(unrealdb_read_int64(db, &creationtime));
R_SAFE(unrealdb_read_str(db, &topic));
R_SAFE(unrealdb_read_str(db, &topic_nick));
R_SAFE(unrealdb_read_int64(db, &topic_time));
R_SAFE(unrealdb_read_str(db, &modes1));
R_SAFE(unrealdb_read_str(db, &modes2));
R_SAFE(unrealdb_read_str(db, &mode_lock));
2020-03-29 02:16:53 -07:00
/* If we got this far, we can create/initialize the channel with the above */
2022-01-14 21:16:34 -08:00
channel = make_channel(chname);
if (IsInvalidChannelTS(creationtime))
channel->creationtime = TStime();
else
channel->creationtime = creationtime;
2020-03-29 02:16:53 -07:00
safe_strdup(channel->topic, topic);
safe_strdup(channel->topic_nick, topic_nick);
channel->topic_time = topic_time;
safe_strdup(channel->mode_lock, mode_lock);
2023-05-05 15:12:01 -07:00
set_channel_mode(channel, NULL, modes1, modes2);
2021-06-19 08:52:51 -07:00
R_SAFE(read_listmode(db, &channel->banlist));
R_SAFE(read_listmode(db, &channel->exlist));
R_SAFE(read_listmode(db, &channel->invexlist));
R_SAFE(unrealdb_read_int32(db, &magic));
2020-03-29 02:16:53 -07:00
FreeChannelEntry();
added++;
if (magic != MAGIC_CHANNEL_END)
{
config_error("[channeldb] Corrupt database (%s) - channel magic end is 0x%x. Further reading aborted.", cfg.database, magic);
break;
}
}
2021-06-19 08:52:51 -07:00
unrealdb_close(db);
2020-03-29 02:16:53 -07:00
if (added)
2022-01-14 21:16:34 -08:00
config_status("[channeldb] Added %d persistent channels (+P)", added);
2020-03-29 02:16:53 -07:00
#ifdef BENCHMARK
gettimeofday(&tv_beta, NULL);
2022-01-14 21:16:34 -08:00
unreal_log(ULOG_DEBUG, "channeldb", "CHANNELDB_BENCHMARK", NULL,
"[channeldb] Benchmark: LOAD DB: $time_msec microseconds",
log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
2020-03-29 02:16:53 -07:00
#endif
return 1;
}
#undef FreeChannelEntry
#undef R_SAFE