unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

whowasdb.c (16308B)

      1 /*
      2  * Stores WHOWAS history in a .db file
      3  * (C) Copyright 2023 Syzop
      4  * License: GPLv2 or later
      5  */
      6 
      7 #include "unrealircd.h"
      8 
      9 ModuleHeader MOD_HEADER = {
     10 	"whowasdb",
     11 	"1.0",
     12 	"Stores and retrieves WHOWAS history",
     13 	"UnrealIRCd Team",
     14 	"unrealircd-6",
     15 };
     16 
     17 /* Our header */
     18 #define WHOWASDB_HEADER		0x57484F57
     19 /* Database version */
     20 #define WHOWASDB_VERSION 100
     21 /* Save whowas of users to file every <this> seconds */
     22 #define WHOWASDB_SAVE_EVERY 300
     23 /* The very first save after boot, apply this delta, this
     24  * so we don't coincide with other (potentially) expensive
     25  * I/O events like saving tkldb.
     26  */
     27 #define WHOWASDB_SAVE_EVERY_DELTA -60
     28 
     29 #define MAGIC_WHOWASDB_START	0x11111111
     30 #define MAGIC_WHOWASDB_END		0x22222222
     31 
     32 // #undef BENCHMARK
     33 
     34 /* Yeah, W_SAFE_PROPERTY raises "the address .. will always evaluate to true" warnings,
     35  * disabling it in the entire file for now...
     36  */
     37 #if defined(__GNUC__)
     38 #pragma GCC diagnostic ignored "-Waddress"
     39 #endif
     40 
     41 #define WARN_WRITE_ERROR(fname) \
     42 	do { \
     43 		unreal_log(ULOG_ERROR, "whowasdb", "WHOWASDB_FILE_WRITE_ERROR", NULL, \
     44 			   "[whowasdb] Error writing to temporary database file $filename: $system_error", \
     45 			   log_data_string("filename", fname), \
     46 			   log_data_string("system_error", unrealdb_get_error_string())); \
     47 	} while(0)
     48 
     49 #define W_SAFE(x) \
     50 	do { \
     51 		if (!(x)) { \
     52 			WARN_WRITE_ERROR(tmpfname); \
     53 			unrealdb_close(db); \
     54 			return 0; \
     55 		} \
     56 	} while(0)
     57 
     58 #define W_SAFE_PROPERTY(db, x, y) \
     59 	do { \
     60 		if (x && y && (!unrealdb_write_str(db, x) || !unrealdb_write_str(db, y))) \
     61 		{ \
     62 			WARN_WRITE_ERROR(tmpfname); \
     63 			unrealdb_close(db); \
     64 			return 0; \
     65 		} \
     66 	} while(0)
     67 
     68 #define IsMDErr(x, y, z) \
     69 	do { \
     70 		if (!(x)) { \
     71 			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
     72 			return MOD_FAILED; \
     73 		} \
     74 	} while(0)
     75 
     76 /* Structs */
     77 struct cfgstruct {
     78 	char *database;
     79 	char *db_secret;
     80 };
     81 
     82 /* Forward declarations */
     83 void whowasdb_moddata_free(ModData *md);
     84 void setcfg(struct cfgstruct *cfg);
     85 void freecfg(struct cfgstruct *cfg);
     86 int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     87 int whowasdb_config_posttest(int *errs);
     88 int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
     89 EVENT(write_whowasdb_evt);
     90 int write_whowasdb(void);
     91 int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e);
     92 int read_whowasdb(void);
     93 
     94 /* External variables */
     95 extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
     96 extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
     97 extern MODVAR int whowas_next;
     98 
     99 /* Global variables */
    100 static uint32_t whowasdb_version = WHOWASDB_VERSION;
    101 static struct cfgstruct cfg;
    102 static struct cfgstruct test;
    103 
    104 static long whowasdb_next_event = 0;
    105 
    106 MOD_TEST()
    107 {
    108 	memset(&cfg, 0, sizeof(cfg));
    109 	memset(&test, 0, sizeof(test));
    110 	setcfg(&test);
    111 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whowasdb_config_test);
    112 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, whowasdb_config_posttest);
    113 	return MOD_SUCCESS;
    114 }
    115 
    116 MOD_INIT()
    117 {
    118 	MARK_AS_OFFICIAL_MODULE(modinfo);
    119 
    120 	LoadPersistentLong(modinfo, whowasdb_next_event);
    121 
    122 	setcfg(&cfg);
    123 
    124 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whowasdb_config_run);
    125 	return MOD_SUCCESS;
    126 }
    127 
    128 MOD_LOAD()
    129 {
    130 	if (!whowasdb_next_event)
    131 	{
    132 		/* If this is the first time that our module is loaded, then read the database. */
    133 		if (!read_whowasdb())
    134 		{
    135 			char fname[512];
    136 			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
    137 			if (rename(cfg.database, fname) == 0)
    138 				config_warn("[whowasdb] Existing database renamed to %s and starting a new one...", fname);
    139 			else
    140 				config_warn("[whowasdb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
    141 		}
    142 		whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY + WHOWASDB_SAVE_EVERY_DELTA;
    143 	}
    144 	EventAdd(modinfo->handle, "whowasdb_write_whowasdb", write_whowasdb_evt, NULL, 1000, 0);
    145 	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
    146 	{
    147 		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
    148 		return MOD_FAILED;
    149 	}
    150 	return MOD_SUCCESS;
    151 }
    152 
    153 MOD_UNLOAD()
    154 {
    155 	if (loop.terminating)
    156 		write_whowasdb();
    157 	freecfg(&test);
    158 	freecfg(&cfg);
    159 	SavePersistentLong(modinfo, whowasdb_next_event);
    160 	return MOD_SUCCESS;
    161 }
    162 
    163 void whowasdb_moddata_free(ModData *md)
    164 {
    165 	if (md->i)
    166 		md->i = 0;
    167 }
    168 
    169 void setcfg(struct cfgstruct *cfg)
    170 {
    171 	// Default: data/whowas.db
    172 	safe_strdup(cfg->database, "whowas.db");
    173 	convert_to_absolute_path(&cfg->database, PERMDATADIR);
    174 }
    175 
    176 void freecfg(struct cfgstruct *cfg)
    177 {
    178 	safe_free(cfg->database);
    179 	safe_free(cfg->db_secret);
    180 }
    181 
    182 int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    183 {
    184 	int errors = 0;
    185 	ConfigEntry *cep;
    186 
    187 	// We are only interested in set::whowasdb::database
    188 	if (type != CONFIG_SET)
    189 		return 0;
    190 
    191 	if (!ce || strcmp(ce->name, "whowasdb"))
    192 		return 0;
    193 
    194 	for (cep = ce->items; cep; cep = cep->next)
    195 	{
    196 		if (!cep->value)
    197 		{
    198 			config_error("%s:%i: blank set::whowasdb::%s without value", cep->file->filename, cep->line_number, cep->name);
    199 			errors++;
    200 		} else
    201 		if (!strcmp(cep->name, "database"))
    202 		{
    203 			convert_to_absolute_path(&cep->value, PERMDATADIR);
    204 			safe_strdup(test.database, cep->value);
    205 		} else
    206 		if (!strcmp(cep->name, "db-secret"))
    207 		{
    208 			const char *err;
    209 			if ((err = unrealdb_test_secret(cep->value)))
    210 			{
    211 				config_error("%s:%i: set::whowasdb::db-secret: %s", cep->file->filename, cep->line_number, err);
    212 				errors++;
    213 				continue;
    214 			}
    215 			safe_strdup(test.db_secret, cep->value);
    216 		} else
    217 		{
    218 			config_error("%s:%i: unknown directive set::whowasdb::%s", cep->file->filename, cep->line_number, cep->name);
    219 			errors++;
    220 		}
    221 	}
    222 
    223 	*errs = errors;
    224 	return errors ? -1 : 1;
    225 }
    226 
    227 int whowasdb_config_posttest(int *errs)
    228 {
    229 	int errors = 0;
    230 	char *errstr;
    231 
    232 	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
    233 	{
    234 		config_error("[whowasdb] %s", errstr);
    235 		errors++;
    236 	}
    237 
    238 	*errs = errors;
    239 	return errors ? -1 : 1;
    240 }
    241 
    242 int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
    243 {
    244 	ConfigEntry *cep;
    245 
    246 	// We are only interested in set::whowasdb::database
    247 	if (type != CONFIG_SET)
    248 		return 0;
    249 
    250 	if (!ce || strcmp(ce->name, "whowasdb"))
    251 		return 0;
    252 
    253 	for (cep = ce->items; cep; cep = cep->next)
    254 	{
    255 		if (!strcmp(cep->name, "database"))
    256 			safe_strdup(cfg.database, cep->value);
    257 		else if (!strcmp(cep->name, "db-secret"))
    258 			safe_strdup(cfg.db_secret, cep->value);
    259 	}
    260 	return 1;
    261 }
    262 
    263 EVENT(write_whowasdb_evt)
    264 {
    265 	if (whowasdb_next_event > TStime())
    266 		return;
    267 	whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY;
    268 	write_whowasdb();
    269 }
    270 
    271 int count_whowas_and_user_entries(void)
    272 {
    273 	int i;
    274 	int cnt = 0;
    275 	Client *client;
    276 
    277 	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
    278 	{
    279 		WhoWas *e = &WHOWAS[i];
    280 		if (e->name)
    281 			cnt++;
    282 	}
    283 
    284 	list_for_each_entry(client, &client_list, client_node)
    285 		if (IsUser(client))
    286 			cnt++;
    287 
    288 	return cnt;
    289 }
    290 
    291 int write_whowasdb(void)
    292 {
    293 	char tmpfname[512];
    294 	UnrealDB *db;
    295 	WhoWas *e;
    296 	Client *client;
    297 	int cnt, i;
    298 #ifdef BENCHMARK
    299 	struct timeval tv_alpha, tv_beta;
    300 
    301 	gettimeofday(&tv_alpha, NULL);
    302 #endif
    303 
    304 	// Write to a tempfile first, then rename it if everything succeeded
    305 	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
    306 	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
    307 	if (!db)
    308 	{
    309 		WARN_WRITE_ERROR(tmpfname);
    310 		return 0;
    311 	}
    312 
    313 	W_SAFE(unrealdb_write_int32(db, WHOWASDB_HEADER));
    314 	W_SAFE(unrealdb_write_int32(db, whowasdb_version));
    315 
    316 	cnt = count_whowas_and_user_entries();
    317 	W_SAFE(unrealdb_write_int64(db, cnt));
    318 
    319 	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
    320 	{
    321 		WhoWas *e = &WHOWAS[i];
    322 		if (e->name)
    323 		{
    324 			if (!write_whowas_entry(db, tmpfname, e))
    325 				return 0;
    326 		}
    327 	}
    328 
    329 	/* Add all the currently connected users to WHOWAS history (as if they left just now) */
    330 	list_for_each_entry(client, &client_list, client_node)
    331 	{
    332 		if (IsUser(client))
    333 		{
    334 			WhoWas *e = safe_alloc(sizeof(WhoWas));
    335 			int ret;
    336 
    337 			create_whowas_entry(client, e, WHOWAS_EVENT_SERVER_TERMINATING);
    338 			ret = write_whowas_entry(db, tmpfname, e);
    339 			free_whowas_fields(e);
    340 			safe_free(e);
    341 
    342 			if (ret == 0)
    343 				return 0;
    344 		}
    345 	}
    346 
    347 
    348 	// Everything seems to have gone well, attempt to close and rename the tempfile
    349 	if (!unrealdb_close(db))
    350 	{
    351 		WARN_WRITE_ERROR(tmpfname);
    352 		return 0;
    353 	}
    354 
    355 #ifdef _WIN32
    356 	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
    357 	unlink(cfg.database);
    358 #endif
    359 	if (rename(tmpfname, cfg.database) < 0)
    360 	{
    361 		config_error("[whowasdb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
    362 		return 0;
    363 	}
    364 #ifdef BENCHMARK
    365 	gettimeofday(&tv_beta, NULL);
    366 	config_status("[whowasdb] Benchmark: SAVE DB: %ld microseconds",
    367 		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
    368 #endif
    369 	return 1;
    370 }
    371 
    372 int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e)
    373 {
    374 	char connected_since[64];
    375 	char logontime[64];
    376 	char logofftime[64];
    377 	char event[16];
    378 
    379 	snprintf(connected_since, sizeof(connected_since), "%lld", (long long)e->connected_since);
    380 	snprintf(logontime, sizeof(logontime), "%lld", (long long)e->logon);
    381 	snprintf(logofftime, sizeof(logofftime), "%lld", (long long)e->logoff);
    382 	snprintf(event, sizeof(event), "%d", e->event);
    383 
    384 	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_START));
    385 	W_SAFE_PROPERTY(db, "nick", e->name);
    386 	W_SAFE_PROPERTY(db, "event", event);
    387 	W_SAFE_PROPERTY(db, "connected_since", connected_since);
    388 	W_SAFE_PROPERTY(db, "logontime", logontime);
    389 	W_SAFE_PROPERTY(db, "logofftime", logofftime);
    390 	W_SAFE_PROPERTY(db, "username", e->username);
    391 	W_SAFE_PROPERTY(db, "hostname", e->hostname);
    392 	W_SAFE_PROPERTY(db, "ip", e->ip);
    393 	W_SAFE_PROPERTY(db, "realname", e->realname);
    394 	W_SAFE_PROPERTY(db, "server", e->servername);
    395 	W_SAFE_PROPERTY(db, "virthost", e->virthost);
    396 	W_SAFE_PROPERTY(db, "account", e->account);
    397 	W_SAFE_PROPERTY(db, "end", "");
    398 	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_END));
    399 	return 1;
    400 }
    401 
    402 #define FreeWhowasEntry() \
    403  	do { \
    404 		/* Some of these might be NULL */ \
    405 		safe_free(key); \
    406 		safe_free(value); \
    407 		safe_free(nick); \
    408 		safe_free(username); \
    409 		safe_free(hostname); \
    410 		safe_free(ip); \
    411 		safe_free(realname); \
    412 		connected_since = logontime = logofftime = 0; \
    413 		event = 0; \
    414 		safe_free(server); \
    415 		safe_free(virthost); \
    416 		safe_free(account); \
    417 	} while(0)
    418 
    419 #define R_SAFE(x) \
    420 	do { \
    421 		if (!(x)) { \
    422 			config_warn("[whowasdb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
    423 			unrealdb_close(db); \
    424 			FreeWhowasEntry(); \
    425 			return 0; \
    426 		} \
    427 	} while(0)
    428 
    429 int read_whowasdb(void)
    430 {
    431 	UnrealDB *db;
    432 	uint32_t version;
    433 	int added = 0;
    434 	int i;
    435 	uint64_t count = 0;
    436 	uint32_t magic;
    437 	char *key = NULL;
    438 	char *value = NULL;
    439 	char *nick = NULL;
    440 	char *username = NULL;
    441 	char *hostname = NULL;
    442 	char *ip = NULL;
    443 	char *realname = NULL;
    444 	long long connected_since = 0;
    445 	long long logontime = 0;
    446 	long long logofftime = 0;
    447 	int event = 0;
    448 	char *server = NULL;
    449 	char *virthost = NULL;
    450 	char *account = NULL;
    451 #ifdef BENCHMARK
    452 	struct timeval tv_alpha, tv_beta;
    453 
    454 	gettimeofday(&tv_alpha, NULL);
    455 #endif
    456 
    457 	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
    458 	if (!db)
    459 	{
    460 		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
    461 		{
    462 			/* Database does not exist. Could be first boot */
    463 			config_warn("[whowasdb] No database present at '%s', will start a new one", cfg.database);
    464 			return 1;
    465 		} else
    466 		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
    467 		{
    468 			/* Re-open as unencrypted */
    469 			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
    470 			if (!db)
    471 			{
    472 				/* This should actually never happen, unless some weird I/O error */
    473 				config_warn("[whowasdb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
    474 				return 0;
    475 			}
    476 		} else
    477 		{
    478 			config_warn("[whowasdb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
    479 			return 0;
    480 		}
    481 	}
    482 
    483 	R_SAFE(unrealdb_read_int32(db, &version));
    484 	if (version != WHOWASDB_HEADER)
    485 	{
    486 		config_warn("[whowasdb] Database '%s' is not a whowas db (incorrect header)", cfg.database);
    487 		unrealdb_close(db);
    488 		return 0;
    489 	}
    490 	R_SAFE(unrealdb_read_int32(db, &version));
    491 	if (version > whowasdb_version)
    492 	{
    493 		config_warn("[whowasdb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, whowasdb_version, version);
    494 		unrealdb_close(db);
    495 		return 0;
    496 	}
    497 
    498 	R_SAFE(unrealdb_read_int64(db, &count));
    499 
    500 	for (i=1; i <= count; i++)
    501 	{
    502 		// Variables
    503 		key = value = NULL;
    504 		nick = username = hostname = ip = realname = virthost = account = server = NULL;
    505 		connected_since = logontime = logofftime = 0;
    506 		event = 0;
    507 
    508 		R_SAFE(unrealdb_read_int32(db, &magic));
    509 		if (magic != MAGIC_WHOWASDB_START)
    510 		{
    511 			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic start is 0x%x. Further reading aborted.", cfg.database, magic);
    512 			break;
    513 		}
    514 		while(1)
    515 		{
    516 			R_SAFE(unrealdb_read_str(db, &key));
    517 			R_SAFE(unrealdb_read_str(db, &value));
    518 			if (!strcmp(key, "nick"))
    519 			{
    520 				safe_strdup(nick, value);
    521 			} else
    522 			if (!strcmp(key, "username"))
    523 			{
    524 				safe_strdup(username, value);
    525 			} else
    526 			if (!strcmp(key, "hostname"))
    527 			{
    528 				safe_strdup(hostname, value);
    529 			} else
    530 			if (!strcmp(key, "ip"))
    531 			{
    532 				safe_strdup(ip, value);
    533 			} else
    534 			if (!strcmp(key, "realname"))
    535 			{
    536 				safe_strdup(realname, value);
    537 			} else
    538 			if (!strcmp(key, "connected_since"))
    539 			{
    540 				connected_since = atoll(value);
    541 				safe_free(value);
    542 			} else
    543 			if (!strcmp(key, "logontime"))
    544 			{
    545 				logontime = atoll(value);
    546 				safe_free(value);
    547 			} else
    548 			if (!strcmp(key, "logofftime"))
    549 			{
    550 				logofftime = atoll(value);
    551 				safe_free(value);
    552 			} else
    553 			if (!strcmp(key, "event"))
    554 			{
    555 				event = atoi(value);
    556 				if ((event < WHOWAS_LOWEST_EVENT) || (event > WHOWAS_HIGHEST_EVENT))
    557 					event = WHOWAS_EVENT_QUIT; /* safety */
    558 				safe_free(value);
    559 			} else
    560 			if (!strcmp(key, "server"))
    561 			{
    562 				safe_strdup(server, value);
    563 			} else
    564 			if (!strcmp(key, "virthost"))
    565 			{
    566 				safe_strdup(virthost, value);
    567 			} else
    568 			if (!strcmp(key, "account"))
    569 			{
    570 				safe_strdup(account, value);
    571 			} else
    572 			if (!strcmp(key, "end"))
    573 			{
    574 				safe_free(key);
    575 				safe_free(value);
    576 				break; /* DONE! */
    577 			}
    578 			safe_free(key);
    579 			safe_free(value);
    580 		}
    581 		R_SAFE(unrealdb_read_int32(db, &magic));
    582 		if (magic != MAGIC_WHOWASDB_END)
    583 		{
    584 			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic end is 0x%x. Further reading aborted.", cfg.database, magic);
    585 			FreeWhowasEntry();
    586 			break;
    587 		}
    588 
    589 		if (nick && username && hostname && realname)
    590 		{
    591 			WhoWas *e = &WHOWAS[whowas_next];
    592 			if (e->hashv != -1)
    593 				free_whowas_fields(e);
    594 			/* Set values */
    595 			//unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_READ_RECORD", NULL,
    596 			//           "[whowasdb] Adding '$nick'...",
    597 			//           log_data_string("nick", nick));
    598 			e->hashv = hash_whowas_name(nick);
    599 			e->event = event;
    600 			e->connected_since = connected_since;
    601 			e->logon = logontime;
    602 			e->logoff = logofftime;
    603 			safe_strdup(e->name, nick);
    604 			safe_strdup(e->username, username);
    605 			safe_strdup(e->hostname, hostname);
    606 			safe_strdup(e->ip, ip);
    607 			if (virthost)
    608 				safe_strdup(e->virthost, virthost);
    609 			else
    610 				safe_strdup(e->virthost, "");
    611 			e->servername = find_or_add(server); /* scache */
    612 			safe_strdup(e->realname, realname);
    613 			safe_strdup(e->account, account);
    614 			e->online = NULL;
    615 			/* Server is special - scache shit */
    616 			/* Add to hash table */
    617 			add_whowas_to_list(&WHOWASHASH[e->hashv], e);
    618 			/* And advance pointer (well, integer) */
    619 			whowas_next++;
    620 			if (whowas_next == NICKNAMEHISTORYLENGTH)
    621 				whowas_next = 0;
    622 		}
    623 
    624 		FreeWhowasEntry();
    625 		added++;
    626 	}
    627 
    628 	unrealdb_close(db);
    629 
    630 	if (added)
    631 		config_status("[whowasdb] Added %d WHOWAS items", added);
    632 #ifdef BENCHMARK
    633 	gettimeofday(&tv_beta, NULL);
    634 	unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_BENCHMARK", NULL,
    635 	           "[whowasdb] Benchmark: LOAD DB: $time_msec microseconds",
    636 	           log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
    637 #endif
    638 	return 1;
    639 }
    640 #undef FreeWhowasEntry
    641 #undef R_SAFE