1 /* saxdb.c - srvx database manager
2 * Copyright 2002-2004 srvx Development Team
4 * This file is part of srvx.
6 * srvx is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
27 DEFINE_LIST(int_list, int)
33 saxdb_reader_func_t *reader;
34 saxdb_writer_func_t *writer;
35 unsigned int write_interval;
36 unsigned long last_write;
37 unsigned int last_write_duration;
41 struct saxdb_context {
44 struct int_list complex;
48 #define COMPLEX(CTX) ((CTX)->complex.used ? ((CTX)->complex.list[(CTX)->complex.used-1]) : 1)
50 static struct saxdb *last_db;
51 static struct dict *saxdbs; /* -> struct saxdb */
52 static struct dict *mondo_db;
53 static struct module *saxdb_module;
55 static SAXDB_WRITER(saxdb_mondo_writer);
56 static void saxdb_timed_write(void *data);
59 saxdb_read_db(struct saxdb *db) {
64 data = parse_database(db->filename);
67 if (db->writer == saxdb_mondo_writer) {
68 free_database(mondo_db);
77 saxdb_register(const char *name, saxdb_reader_func_t *reader, saxdb_writer_func_t *writer) {
81 const char *filename = NULL, *str;
82 char conf_path[MAXLEN];
84 db = calloc(1, sizeof(*db));
85 db->name = strdup(name);
88 /* Look up configuration */
89 sprintf(conf_path, "dbs/%s", name);
90 if ((conf = conf_get_data(conf_path, RECDB_OBJECT))) {
91 if ((str = database_get_data(conf, "mondo_section", RECDB_QSTRING))) {
92 db->mondo_section = strdup(str);
94 str = database_get_data(conf, "frequency", RECDB_QSTRING);
95 db->write_interval = str ? ParseInterval(str) : 1800;
96 filename = database_get_data(conf, "filename", RECDB_QSTRING);
98 db->write_interval = 1800;
100 /* Schedule database writes */
101 if (db->write_interval && !db->mondo_section) {
102 timeq_add(now + db->write_interval, saxdb_timed_write, db);
104 /* Insert filename */
106 db->filename = strdup(filename);
108 db->filename = malloc(strlen(db->name)+4);
109 for (ii=0; db->name[ii]; ++ii) db->filename[ii] = tolower(db->name[ii]);
110 strcpy(db->filename+ii, ".db");
112 /* Read from disk (or mondo DB) */
113 if (db->mondo_section) {
114 if (mondo_db && (conf = database_get_data(mondo_db, db->mondo_section, RECDB_OBJECT))) {
120 /* Remember the database */
121 dict_insert(saxdbs, db->name, db);
128 saxdb_write_db(struct saxdb *db) {
129 struct saxdb_context ctx;
130 char tmp_fname[MAXLEN];
132 unsigned long start, finish;
134 assert(db->filename);
135 sprintf(tmp_fname, "%s.new", db->filename);
136 memset(&ctx, 0, sizeof(ctx));
137 ctx.output = fopen(tmp_fname, "w+");
138 int_list_init(&ctx.complex);
140 log_module(MAIN_LOG, LOG_ERROR, "Unable to write to %s: %s", tmp_fname, strerror(errno));
141 int_list_clean(&ctx.complex);
145 if ((res = setjmp(*saxdb_jmp_buf(&ctx))) || (res2 = db->writer(&ctx))) {
147 log_module(MAIN_LOG, LOG_ERROR, "Error writing to %s: %s", tmp_fname, strerror(res));
149 log_module(MAIN_LOG, LOG_ERROR, "Internal error %d while writing to %s", res2, tmp_fname);
151 int_list_clean(&ctx.complex);
157 assert(ctx.complex.used == 0);
158 int_list_clean(&ctx.complex);
160 if (rename(tmp_fname, db->filename) < 0) {
161 log_module(MAIN_LOG, LOG_ERROR, "Unable to rename %s to %s: %s", tmp_fname, db->filename, strerror(errno));
163 db->last_write = now;
164 db->last_write_duration = finish - start;
165 log_module(MAIN_LOG, LOG_INFO, "Wrote %s database to disk.", db->name);
170 saxdb_timed_write(void *data) {
171 struct saxdb *db = data;
173 timeq_add(now + db->write_interval, saxdb_timed_write, db);
177 saxdb_write(const char *db_name) {
179 db = dict_find(saxdbs, db_name, NULL);
180 if (db) saxdb_write_db(db);
184 saxdb_write_all(void) {
188 for (it = dict_first(saxdbs); it; it = iter_next(it)) {
190 if (!db->mondo_section)
195 #define saxdb_put_char(DEST, CH) do { \
196 if (fputc(CH, (DEST)->output) == EOF) \
197 longjmp((DEST)->jbuf, errno); \
199 #define saxdb_put_string(DEST, CH) do { \
200 if (fputs(CH, (DEST)->output) == EOF) \
201 longjmp((DEST)->jbuf, errno); \
205 saxdb_put_nchars(struct saxdb_context *dest, const char *name, int len) {
207 if (fputc(*name++, dest->output) == EOF)
208 longjmp(dest->jbuf, errno);
212 saxdb_put_qstring(struct saxdb_context *dest, const char *str) {
216 saxdb_put_char(dest, '"');
217 while ((esc = strpbrk(str, "\\\a\b\t\n\v\f\r\""))) {
219 saxdb_put_nchars(dest, str, esc-str);
220 saxdb_put_char(dest, '\\');
222 case '\a': saxdb_put_char(dest, 'a'); break;
223 case '\b': saxdb_put_char(dest, 'b'); break;
224 case '\t': saxdb_put_char(dest, 't'); break;
225 case '\n': saxdb_put_char(dest, 'n'); break;
226 case '\v': saxdb_put_char(dest, 'v'); break;
227 case '\f': saxdb_put_char(dest, 'f'); break;
228 case '\r': saxdb_put_char(dest, 'r'); break;
229 case '\\': saxdb_put_char(dest, '\\'); break;
230 case '"': saxdb_put_char(dest, '"'); break;
234 saxdb_put_string(dest, str);
235 saxdb_put_char(dest, '"');
240 saxdb_pre_object(struct saxdb_context *dest) {
243 for (ii=0; ii<dest->indent; ++ii) saxdb_put_char(dest, '\t');
247 #define saxdb_pre_object(DEST)
251 saxdb_post_object(struct saxdb_context *dest) {
252 saxdb_put_char(dest, ';');
253 saxdb_put_char(dest, COMPLEX(dest) ? '\n' : ' ');
257 saxdb_start_record(struct saxdb_context *dest, const char *name, int complex) {
258 saxdb_pre_object(dest);
259 saxdb_put_qstring(dest, name);
260 saxdb_put_string(dest, " { ");
261 int_list_append(&dest->complex, complex);
264 saxdb_put_char(dest, '\n');
269 saxdb_end_record(struct saxdb_context *dest) {
270 assert(dest->complex.used > 0);
271 if (COMPLEX(dest)) dest->indent--;
272 saxdb_pre_object(dest);
273 dest->complex.used--;
274 saxdb_put_char(dest, '}');
275 saxdb_post_object(dest);
279 saxdb_write_string_list(struct saxdb_context *dest, const char *name, struct string_list *list) {
282 saxdb_pre_object(dest);
283 saxdb_put_qstring(dest, name);
284 saxdb_put_string(dest, " (");
286 for (ii=0; ii<list->used-1; ++ii) {
287 saxdb_put_qstring(dest, list->list[ii]);
288 saxdb_put_string(dest, ", ");
290 saxdb_put_qstring(dest, list->list[list->used-1]);
292 saxdb_put_string(dest, ")");
293 saxdb_post_object(dest);
297 saxdb_write_string(struct saxdb_context *dest, const char *name, const char *value) {
298 saxdb_pre_object(dest);
299 saxdb_put_qstring(dest, name);
300 saxdb_put_char(dest, ' ');
301 saxdb_put_qstring(dest, value);
302 saxdb_post_object(dest);
306 saxdb_write_int(struct saxdb_context *dest, const char *name, unsigned long value) {
308 /* we could optimize this to take advantage of the fact that buf will never need escapes */
309 snprintf(buf, sizeof(buf), "%lu", value);
310 saxdb_write_string(dest, name, buf);
314 saxdb_write_sint(struct saxdb_context *dest, const char *name, long value) {
316 /* we could optimize this to take advantage of the fact that buf will never need escapes */
317 snprintf(buf, sizeof(buf), "%ld", value);
318 saxdb_write_string(dest, name, buf);
322 saxdb_free(void *data) {
323 struct saxdb *db = data;
326 free(db->mondo_section);
331 saxdb_mondo_read(struct dict *db, struct saxdb *saxdb) {
335 if (saxdb->prev && (res = saxdb_mondo_read(db, saxdb->prev))) return res;
336 if (saxdb->mondo_section
337 && (subdb = database_get_data(db, saxdb->mondo_section, RECDB_OBJECT))
338 && (res = saxdb->reader(subdb))) {
339 log_module(MAIN_LOG, LOG_INFO, " mondo section read for %s failed: %d", saxdb->mondo_section, res);
345 static SAXDB_READER(saxdb_mondo_reader) {
346 return saxdb_mondo_read(db, last_db);
350 saxdb_mondo_write(struct saxdb_context *ctx, struct saxdb *saxdb) {
352 if (saxdb->prev && (res = saxdb_mondo_write(ctx, saxdb->prev))) return res;
353 if (saxdb->mondo_section) {
354 saxdb_start_record(ctx, saxdb->mondo_section, 1);
355 if ((res = saxdb->writer(ctx))) {
356 log_module(MAIN_LOG, LOG_INFO, " mondo section write for %s failed: %d", saxdb->mondo_section, res);
359 saxdb_end_record(ctx);
360 /* cheat a little here to put a newline between mondo sections */
361 saxdb_put_char(ctx, '\n');
366 static SAXDB_WRITER(saxdb_mondo_writer) {
367 return saxdb_mondo_write(ctx, last_db);
370 static MODCMD_FUNC(cmd_write) {
371 struct timeval start, stop;
372 unsigned int ii, written;
377 for (ii=1; ii<argc; ++ii) {
378 if (!(db = dict_find(saxdbs, argv[ii], NULL))) {
379 reply("MSG_DB_UNKNOWN", argv[ii]);
382 if (db->mondo_section) {
383 reply("MSG_DB_IS_MONDO", db->name);
386 gettimeofday(&start, NULL);
387 if (saxdb_write_db(db)) {
388 reply("MSG_DB_WRITE_ERROR", db->name);
390 gettimeofday(&stop, NULL);
391 stop.tv_sec -= start.tv_sec;
392 stop.tv_usec -= start.tv_usec;
393 if (stop.tv_usec < 0) {
395 stop.tv_usec += 1000000;
397 reply("MSG_DB_WROTE_DB", db->name, (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec);
404 static MODCMD_FUNC(cmd_writeall) {
405 struct timeval start, stop;
407 gettimeofday(&start, NULL);
409 gettimeofday(&stop, NULL);
410 stop.tv_sec -= start.tv_sec;
411 stop.tv_usec -= start.tv_usec;
412 if (stop.tv_usec < 0) {
414 stop.tv_usec += 1000000;
416 reply("MSG_DB_WROTE_ALL", (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec);
420 static MODCMD_FUNC(cmd_stats_databases) {
421 struct helpfile_table tbl;
425 tbl.length = dict_size(saxdbs) + 1;
427 tbl.flags = TABLE_NO_FREE;
428 tbl.contents = calloc(tbl.length, sizeof(tbl.contents[0]));
429 tbl.contents[0] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
430 tbl.contents[0][0] = "Database";
431 tbl.contents[0][1] = "Filename/Section";
432 tbl.contents[0][2] = "Interval";
433 tbl.contents[0][3] = "Last Written";
434 tbl.contents[0][4] = "Last Duration";
435 for (ii=1, it=dict_first(saxdbs); it; it=iter_next(it), ++ii) {
436 struct saxdb *db = iter_data(it);
437 if (db->mondo_section) {
441 char *buf = malloc(INTERVALLEN*3);
442 tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0]));
443 tbl.contents[ii][0] = db->name;
444 tbl.contents[ii][1] = db->mondo_section ? db->mondo_section : db->filename;
445 if (db->write_interval) {
446 intervalString(buf, db->write_interval, user->handle_info);
448 strcpy(buf, "Never");
450 tbl.contents[ii][2] = buf;
451 if (db->last_write) {
452 intervalString(buf+INTERVALLEN, now - db->last_write, user->handle_info);
453 intervalString(buf+INTERVALLEN*2, db->last_write_duration, user->handle_info);
455 strcpy(buf+INTERVALLEN, "Never");
456 strcpy(buf+INTERVALLEN*2, "Never");
458 tbl.contents[ii][3] = buf+INTERVALLEN;
459 tbl.contents[ii][4] = buf+INTERVALLEN*2;
462 table_send(cmd->parent->bot, user->nick, 0, 0, tbl);
463 free(tbl.contents[0]);
464 for (ii=1; ii<tbl.length; ++ii) {
465 free((char*)tbl.contents[ii][2]);
466 free(tbl.contents[ii]);
473 saxdb_cleanup(void) {
477 static struct helpfile_expansion
478 saxdb_expand_help(const char *variable) {
479 struct helpfile_expansion exp;
480 if (!strcasecmp(variable, "dblist")) {
482 struct string_buffer sbuf;
485 exp.type = HF_STRING;
486 string_buffer_init(&sbuf);
487 for (it = dict_first(saxdbs); it; it = iter_next(it)) {
489 if (db->mondo_section) continue;
490 if (sbuf.used) string_buffer_append_string(&sbuf, ", ");
491 string_buffer_append_string(&sbuf, iter_key(it));
493 exp.value.str = sbuf.list;
495 exp.type = HF_STRING;
496 exp.value.str = NULL;
503 reg_exit_func(saxdb_cleanup);
505 dict_set_free_data(saxdbs, saxdb_free);
506 saxdb_register("mondo", saxdb_mondo_reader, saxdb_mondo_writer);
507 saxdb_module = module_register("saxdb", MAIN_LOG, "saxdb.help", saxdb_expand_help);
508 modcmd_register(saxdb_module, "write", cmd_write, 2, MODCMD_REQUIRE_AUTHED, "level", "800", NULL);
509 modcmd_register(saxdb_module, "writeall", cmd_writeall, 0, MODCMD_REQUIRE_AUTHED, "level", "800", NULL);
510 modcmd_register(saxdb_module, "stats databases", cmd_stats_databases, 0, 0, NULL);
514 saxdb_finalize(void) {
515 free_database(mondo_db);
519 write_database_helper(struct saxdb_context *ctx, struct dict *db) {
521 struct record_data *rd;
523 for (it = dict_first(db); it; it = iter_next(it)) {
526 case RECDB_INVALID: break;
527 case RECDB_QSTRING: saxdb_write_string(ctx, iter_key(it), rd->d.qstring); break;
528 case RECDB_STRING_LIST: saxdb_write_string_list(ctx, iter_key(it), rd->d.slist); break;
530 saxdb_start_record(ctx, iter_key(it), 1);
531 write_database_helper(ctx, rd->d.object);
532 saxdb_end_record(ctx);
539 write_database(FILE *out, struct dict *db) {
540 struct saxdb_context ctx;
545 int_list_init(&ctx.complex);
546 if (!(res = setjmp(*saxdb_jmp_buf(&ctx)))) {
547 write_database_helper(&ctx, db);
549 log_module(MAIN_LOG, LOG_ERROR, "Exception %d caught while writing to stream", res);
550 int_list_clean(&ctx.complex);
553 assert(ctx.complex.used == 0);
554 int_list_clean(&ctx.complex);
558 struct saxdb_context *
559 saxdb_open_context(FILE *file) {
560 struct saxdb_context *ctx;
563 ctx = calloc(1, sizeof(*ctx));
565 int_list_init(&ctx->complex);
571 saxdb_jmp_buf(struct saxdb_context *ctx) {
577 saxdb_close_context(struct saxdb_context *ctx) {
578 assert(ctx->complex.used == 0);
579 int_list_clean(&ctx->complex);