1 /* saxdb.c - srvx database manager
2 * Copyright 2002-2004,2007-2008 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 #if !defined(SAXDB_BUFFER_SIZE)
28 # define SAXDB_BUFFER_SIZE (32 * 1024)
31 DEFINE_LIST(int_list, int)
37 saxdb_reader_func_t *reader;
38 saxdb_writer_func_t *writer;
39 unsigned int write_interval;
40 unsigned long last_write;
41 unsigned int last_write_duration;
45 struct saxdb_context {
46 struct string_buffer obuf;
49 struct int_list complex;
53 #define COMPLEX(CTX) ((CTX)->complex.used ? ((CTX)->complex.list[(CTX)->complex.used-1]) : 1)
55 static struct saxdb *last_db;
56 static struct dict *saxdbs; /* -> struct saxdb */
57 static struct dict *mondo_db;
58 static struct module *saxdb_module;
60 static SAXDB_WRITER(saxdb_mondo_writer);
61 static void saxdb_timed_write(void *data);
64 saxdb_read_db(struct saxdb *db) {
69 data = parse_database(db->filename);
72 if (db->writer == saxdb_mondo_writer) {
73 free_database(mondo_db);
82 saxdb_register(const char *name, saxdb_reader_func_t *reader, saxdb_writer_func_t *writer) {
86 const char *filename = NULL, *str;
87 char conf_path[MAXLEN];
89 db = calloc(1, sizeof(*db));
90 db->name = strdup(name);
93 /* Look up configuration */
94 sprintf(conf_path, "dbs/%s", name);
95 if ((conf = conf_get_data(conf_path, RECDB_OBJECT))) {
96 if ((str = database_get_data(conf, "mondo_section", RECDB_QSTRING))) {
97 db->mondo_section = strdup(str);
99 str = database_get_data(conf, "frequency", RECDB_QSTRING);
100 db->write_interval = str ? ParseInterval(str) : 1800;
101 filename = database_get_data(conf, "filename", RECDB_QSTRING);
103 db->write_interval = 1800;
105 /* Schedule database writes */
106 if (db->write_interval && !db->mondo_section) {
107 timeq_add(now + db->write_interval, saxdb_timed_write, db);
109 /* Insert filename */
111 db->filename = strdup(filename);
113 db->filename = malloc(strlen(db->name)+4);
114 for (ii=0; db->name[ii]; ++ii) db->filename[ii] = tolower(db->name[ii]);
115 strcpy(db->filename+ii, ".db");
117 /* Read from disk (or mondo DB) */
118 if (db->mondo_section) {
119 if (mondo_db && (conf = database_get_data(mondo_db, db->mondo_section, RECDB_OBJECT))) {
125 /* Remember the database */
126 dict_insert(saxdbs, db->name, db);
133 saxdb_write_db(struct saxdb *db) {
134 struct saxdb_context *ctx;
136 char tmp_fname[MAXLEN];
138 unsigned long start, finish;
140 assert(db->filename);
141 sprintf(tmp_fname, "%s.new", db->filename);
142 output = fopen(tmp_fname, "w+");
144 log_module(MAIN_LOG, LOG_ERROR, "Unable to write to %s: %s", tmp_fname, strerror(errno));
147 ctx = saxdb_open_context(output);
149 if ((res = setjmp(*saxdb_jmp_buf(ctx))) || (res2 = db->writer(ctx))) {
151 log_module(MAIN_LOG, LOG_ERROR, "Error writing to %s: %s", tmp_fname, strerror(res));
153 log_module(MAIN_LOG, LOG_ERROR, "Internal error %d while writing to %s", res2, tmp_fname);
155 ctx->complex.used = 0; /* Squelch asserts about unbalanced output. */
156 saxdb_close_context(ctx, 1);
161 saxdb_close_context(ctx, 1);
162 if (rename(tmp_fname, db->filename) < 0) {
163 log_module(MAIN_LOG, LOG_ERROR, "Unable to rename %s to %s: %s", tmp_fname, db->filename, strerror(errno));
165 db->last_write = now;
166 db->last_write_duration = finish - start;
167 log_module(MAIN_LOG, LOG_INFO, "Wrote %s database to disk.", db->name);
172 saxdb_timed_write(void *data) {
173 struct saxdb *db = data;
175 timeq_add(now + db->write_interval, saxdb_timed_write, db);
179 saxdb_write(const char *db_name) {
181 db = dict_find(saxdbs, db_name, NULL);
182 if (db) saxdb_write_db(db);
186 saxdb_write_all(void) {
190 for (it = dict_first(saxdbs); it; it = iter_next(it)) {
192 if (!db->mondo_section)
198 saxdb_flush(struct saxdb_context *dest) {
203 assert(dest->obuf.used <= dest->obuf.size);
204 fd = fileno(dest->output);
205 for (ofs = 0; ofs < dest->obuf.used; ofs += nbw) {
206 nbw = write(fd, dest->obuf.list + ofs, dest->obuf.used);
208 longjmp(dest->jbuf, errno);
215 saxdb_put_char(struct saxdb_context *dest, char ch) {
216 dest->obuf.list[dest->obuf.used] = ch;
217 if (++dest->obuf.used == dest->obuf.size)
222 saxdb_put_nchars(struct saxdb_context *dest, const char *name, int len) {
226 for (ofs = 0; ofs < len; ofs += frag) {
227 frag = dest->obuf.size - dest->obuf.used;
228 if (frag > len - ofs)
230 memcpy(dest->obuf.list + dest->obuf.used, name + ofs, frag);
231 dest->obuf.used += frag;
232 if (dest->obuf.used == dest->obuf.size)
237 #define saxdb_put_string(DEST, STR) do { \
238 saxdb_put_nchars((DEST), (STR), strlen(STR)); \
242 saxdb_put_qstring(struct saxdb_context *dest, const char *str) {
247 saxdb_put_char(dest, '"');
248 for (ofs = 0; str[ofs] != '\0'; ) {
250 span = strcspn(str + ofs, "\\\a\b\t\n\v\f\r\"");
251 saxdb_put_nchars(dest, str + ofs, span);
256 case '\a': stop = 'a'; break;
257 case '\b': stop = 'b'; break;
258 case '\t': stop = 't'; break;
259 case '\n': stop = 'n'; break;
260 case '\v': stop = 'v'; break;
261 case '\f': stop = 'f'; break;
262 case '\r': stop = 'r'; break;
264 saxdb_put_char(dest, '\\');
265 saxdb_put_char(dest, stop);
268 saxdb_put_char(dest, '"');
273 saxdb_pre_object(struct saxdb_context *dest) {
276 for (ii=0; ii<dest->indent; ++ii) saxdb_put_char(dest, '\t');
280 #define saxdb_pre_object(DEST)
284 saxdb_post_object(struct saxdb_context *dest) {
285 saxdb_put_char(dest, ';');
286 saxdb_put_char(dest, COMPLEX(dest) ? '\n' : ' ');
290 saxdb_start_record(struct saxdb_context *dest, const char *name, int complex) {
291 saxdb_pre_object(dest);
292 saxdb_put_qstring(dest, name);
293 saxdb_put_string(dest, " {");
294 int_list_append(&dest->complex, complex);
297 saxdb_put_char(dest, '\n');
299 saxdb_put_char(dest, ' ');
304 saxdb_end_record(struct saxdb_context *dest) {
305 assert(dest->complex.used > 0);
306 if (COMPLEX(dest)) dest->indent--;
307 saxdb_pre_object(dest);
308 dest->complex.used--;
309 saxdb_put_char(dest, '}');
310 saxdb_post_object(dest);
314 saxdb_write_string_list(struct saxdb_context *dest, const char *name, struct string_list *list) {
317 saxdb_pre_object(dest);
318 saxdb_put_qstring(dest, name);
319 saxdb_put_string(dest, " (");
321 for (ii=0; ii<list->used-1; ++ii) {
322 saxdb_put_qstring(dest, list->list[ii]);
323 saxdb_put_string(dest, ", ");
325 saxdb_put_qstring(dest, list->list[list->used-1]);
327 saxdb_put_string(dest, ")");
328 saxdb_post_object(dest);
332 saxdb_write_string(struct saxdb_context *dest, const char *name, const char *value) {
333 saxdb_pre_object(dest);
334 saxdb_put_qstring(dest, name);
335 saxdb_put_char(dest, ' ');
336 saxdb_put_qstring(dest, value);
337 saxdb_post_object(dest);
341 saxdb_write_int(struct saxdb_context *dest, const char *name, unsigned long value) {
343 /* we could optimize this to take advantage of the fact that buf will never need escapes */
344 snprintf(buf, sizeof(buf), "%lu", value);
345 saxdb_write_string(dest, name, buf);
349 saxdb_write_sint(struct saxdb_context *dest, const char *name, long value) {
351 /* we could optimize this to take advantage of the fact that buf will never need escapes */
352 snprintf(buf, sizeof(buf), "%ld", value);
353 saxdb_write_string(dest, name, buf);
357 saxdb_free(void *data) {
358 struct saxdb *db = data;
361 free(db->mondo_section);
366 saxdb_mondo_read(struct dict *db, struct saxdb *saxdb) {
370 if (saxdb->prev && (res = saxdb_mondo_read(db, saxdb->prev))) return res;
371 if (saxdb->mondo_section
372 && (subdb = database_get_data(db, saxdb->mondo_section, RECDB_OBJECT))
373 && (res = saxdb->reader(subdb))) {
374 log_module(MAIN_LOG, LOG_INFO, " mondo section read for %s failed: %d", saxdb->mondo_section, res);
380 static SAXDB_READER(saxdb_mondo_reader) {
381 return saxdb_mondo_read(db, last_db);
385 saxdb_mondo_write(struct saxdb_context *ctx, struct saxdb *saxdb) {
387 if (saxdb->prev && (res = saxdb_mondo_write(ctx, saxdb->prev))) return res;
388 if (saxdb->mondo_section) {
389 saxdb_start_record(ctx, saxdb->mondo_section, 1);
390 if ((res = saxdb->writer(ctx))) {
391 log_module(MAIN_LOG, LOG_INFO, " mondo section write for %s failed: %d", saxdb->mondo_section, res);
394 saxdb_end_record(ctx);
395 /* cheat a little here to put a newline between mondo sections */
396 saxdb_put_char(ctx, '\n');
401 static SAXDB_WRITER(saxdb_mondo_writer) {
402 return saxdb_mondo_write(ctx, last_db);
405 static MODCMD_FUNC(cmd_write) {
406 struct timeval start, stop;
407 unsigned int ii, written;
412 for (ii=1; ii<argc; ++ii) {
413 if (!(db = dict_find(saxdbs, argv[ii], NULL))) {
414 reply("MSG_DB_UNKNOWN", argv[ii]);
417 if (db->mondo_section) {
418 reply("MSG_DB_IS_MONDO", db->name);
421 gettimeofday(&start, NULL);
422 if (saxdb_write_db(db)) {
423 reply("MSG_DB_WRITE_ERROR", db->name);
425 gettimeofday(&stop, NULL);
426 stop.tv_sec -= start.tv_sec;
427 stop.tv_usec -= start.tv_usec;
428 if (stop.tv_usec < 0) {
430 stop.tv_usec += 1000000;
432 reply("MSG_DB_WROTE_DB", db->name, (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec);
439 static MODCMD_FUNC(cmd_writeall) {
440 struct timeval start, stop;
442 gettimeofday(&start, NULL);
444 gettimeofday(&stop, NULL);
445 stop.tv_sec -= start.tv_sec;
446 stop.tv_usec -= start.tv_usec;
447 if (stop.tv_usec < 0) {
449 stop.tv_usec += 1000000;
451 reply("MSG_DB_WROTE_ALL", (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec);
455 static MODCMD_FUNC(cmd_stats_databases) {
456 struct helpfile_table tbl;
460 tbl.length = dict_size(saxdbs) + 1;
462 tbl.flags = TABLE_NO_FREE;
463 tbl.contents = calloc(tbl.length, sizeof(tbl.contents[0]));
464 tbl.contents[0] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
465 tbl.contents[0][0] = "Database";
466 tbl.contents[0][1] = "Filename/Section";
467 tbl.contents[0][2] = "Interval";
468 tbl.contents[0][3] = "Last Written";
469 tbl.contents[0][4] = "Last Duration";
470 for (ii=1, it=dict_first(saxdbs); it; it=iter_next(it), ++ii) {
471 struct saxdb *db = iter_data(it);
472 if (db->mondo_section) {
476 char *buf = malloc(INTERVALLEN*3);
477 tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0]));
478 tbl.contents[ii][0] = db->name;
479 tbl.contents[ii][1] = db->mondo_section ? db->mondo_section : db->filename;
480 if (db->write_interval) {
481 intervalString(buf, db->write_interval, user->handle_info);
483 strcpy(buf, "Never");
485 tbl.contents[ii][2] = buf;
486 if (db->last_write) {
487 intervalString(buf+INTERVALLEN, now - db->last_write, user->handle_info);
488 intervalString(buf+INTERVALLEN*2, db->last_write_duration, user->handle_info);
490 strcpy(buf+INTERVALLEN, "Never");
491 strcpy(buf+INTERVALLEN*2, "Never");
493 tbl.contents[ii][3] = buf+INTERVALLEN;
494 tbl.contents[ii][4] = buf+INTERVALLEN*2;
497 table_send(cmd->parent->bot, user->nick, 0, 0, tbl);
498 free(tbl.contents[0]);
499 for (ii=1; ii<tbl.length; ++ii) {
500 free((char*)tbl.contents[ii][2]);
501 free(tbl.contents[ii]);
508 saxdb_cleanup(void) {
512 static struct helpfile_expansion
513 saxdb_expand_help(const char *variable) {
514 struct helpfile_expansion exp;
515 if (!strcasecmp(variable, "dblist")) {
517 struct string_buffer sbuf;
520 exp.type = HF_STRING;
521 string_buffer_init(&sbuf);
522 for (it = dict_first(saxdbs); it; it = iter_next(it)) {
524 if (db->mondo_section) continue;
525 if (sbuf.used) string_buffer_append_string(&sbuf, ", ");
526 string_buffer_append_string(&sbuf, iter_key(it));
528 exp.value.str = sbuf.list;
530 exp.type = HF_STRING;
531 exp.value.str = NULL;
538 reg_exit_func(saxdb_cleanup);
540 dict_set_free_data(saxdbs, saxdb_free);
541 saxdb_register("mondo", saxdb_mondo_reader, saxdb_mondo_writer);
542 saxdb_module = module_register("saxdb", MAIN_LOG, "saxdb.help", saxdb_expand_help);
543 modcmd_register(saxdb_module, "write", cmd_write, 2, MODCMD_REQUIRE_AUTHED, "level", "800", NULL);
544 modcmd_register(saxdb_module, "writeall", cmd_writeall, 0, MODCMD_REQUIRE_AUTHED, "level", "800", NULL);
545 modcmd_register(saxdb_module, "stats databases", cmd_stats_databases, 0, 0, NULL);
549 saxdb_finalize(void) {
550 free_database(mondo_db);
554 write_database_helper(struct saxdb_context *ctx, struct dict *db) {
556 struct record_data *rd;
558 for (it = dict_first(db); it; it = iter_next(it)) {
561 case RECDB_INVALID: break;
562 case RECDB_QSTRING: saxdb_write_string(ctx, iter_key(it), rd->d.qstring); break;
563 case RECDB_STRING_LIST: saxdb_write_string_list(ctx, iter_key(it), rd->d.slist); break;
565 saxdb_start_record(ctx, iter_key(it), 1);
566 write_database_helper(ctx, rd->d.object);
567 saxdb_end_record(ctx);
574 write_database(FILE *out, struct dict *db) {
575 struct saxdb_context *ctx;
578 ctx = saxdb_open_context(out);
579 if (!(res = setjmp(*saxdb_jmp_buf(ctx)))) {
580 write_database_helper(ctx, db);
582 log_module(MAIN_LOG, LOG_ERROR, "Exception %d caught while writing to stream", res);
583 ctx->complex.used = 0; /* Squelch asserts about unbalanced output. */
584 saxdb_close_context(ctx, 0);
587 saxdb_close_context(ctx, 0);
591 struct saxdb_context *
592 saxdb_open_context(FILE *file) {
593 struct saxdb_context *ctx;
596 ctx = calloc(1, sizeof(*ctx));
598 ctx->obuf.size = SAXDB_BUFFER_SIZE;
599 ctx->obuf.list = calloc(1, ctx->obuf.size);
600 int_list_init(&ctx->complex);
606 saxdb_jmp_buf(struct saxdb_context *ctx) {
612 saxdb_close_context(struct saxdb_context *ctx, int close_file) {
613 assert(ctx->complex.used == 0);
615 int_list_clean(&ctx->complex);
616 free(ctx->obuf.list);