]> jfr.im git - irc/evilnet/x3.git/blame - src/saxdb.c
Author:
[irc/evilnet/x3.git] / src / saxdb.c
CommitLineData
d76ed9a9 1/* saxdb.c - srvx database manager
2 * Copyright 2002-2004 srvx Development Team
3 *
4 * This file is part of srvx.
5 *
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.
10 *
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.
15 *
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.
19 */
20
21#include "conf.h"
22#include "hash.h"
23#include "modcmd.h"
24#include "saxdb.h"
25#include "timeq.h"
26
27DEFINE_LIST(int_list, int);
28
29struct saxdb {
30 char *name;
31 char *filename;
32 char *mondo_section;
33 saxdb_reader_func_t *reader;
34 saxdb_writer_func_t *writer;
35 unsigned int write_interval;
36 time_t last_write;
37 unsigned int last_write_duration;
38 struct saxdb *prev;
39};
40
41#define COMPLEX(CTX) ((CTX)->complex.used ? ((CTX)->complex.list[(CTX)->complex.used-1]) : 1)
42
43static struct saxdb *last_db;
44static struct dict *saxdbs; /* -> struct saxdb */
45static struct dict *mondo_db;
46static struct module *saxdb_module;
47
48static SAXDB_WRITER(saxdb_mondo_writer);
49static void saxdb_timed_write(void *data);
50
51static void
52saxdb_read_db(struct saxdb *db) {
53 struct dict *data;
54
55 assert(db);
56 assert(db->filename);
57 data = parse_database(db->filename);
58 if (!data)
59 return;
60 if (db->writer == saxdb_mondo_writer) {
61 mondo_db = data;
62 } else {
63 db->reader(data);
64 free_database(data);
65 }
66}
67
68struct saxdb *
69saxdb_register(const char *name, saxdb_reader_func_t *reader, saxdb_writer_func_t *writer) {
70 struct saxdb *db;
71 struct dict *conf;
72 int ii;
73 const char *filename = NULL, *str;
74 char conf_path[MAXLEN];
75
76 db = calloc(1, sizeof(*db));
77 db->name = strdup(name);
78 db->reader = reader;
79 db->writer = writer;
80 /* Look up configuration */
81 sprintf(conf_path, "dbs/%s", name);
82 if ((conf = conf_get_data(conf_path, RECDB_OBJECT))) {
83 if ((str = database_get_data(conf, "mondo_section", RECDB_QSTRING))) {
84 db->mondo_section = strdup(str);
85 }
86 str = database_get_data(conf, "frequency", RECDB_QSTRING);
87 db->write_interval = str ? ParseInterval(str) : 1800;
88 filename = database_get_data(conf, "filename", RECDB_QSTRING);
89 } else {
90 db->write_interval = 1800;
91 }
92 /* Schedule database writes */
93 if (db->write_interval && !db->mondo_section) {
94 timeq_add(now + db->write_interval, saxdb_timed_write, db);
95 }
96 /* Insert filename */
97 if (filename) {
98 db->filename = strdup(filename);
99 } else {
100 db->filename = malloc(strlen(db->name)+4);
101 for (ii=0; db->name[ii]; ++ii) db->filename[ii] = tolower(db->name[ii]);
102 strcpy(db->filename+ii, ".db");
103 }
104 /* Read from disk (or mondo DB) */
105 if (db->mondo_section) {
106 if (mondo_db && (conf = database_get_data(mondo_db, db->mondo_section, RECDB_OBJECT))) {
107 db->reader(conf);
108 }
109 } else {
110 saxdb_read_db(db);
111 }
112 /* Remember the database */
113 dict_insert(saxdbs, db->name, db);
114 db->prev = last_db;
115 last_db = db;
116 return db;
117}
118
119static int
120saxdb_write_db(struct saxdb *db) {
121 struct saxdb_context ctx;
122 char tmp_fname[MAXLEN];
123 int res, res2;
124 time_t start, finish;
125
126 assert(db->filename);
127 sprintf(tmp_fname, "%s.new", db->filename);
128 memset(&ctx, 0, sizeof(ctx));
129 ctx.output = fopen(tmp_fname, "w+");
130 int_list_init(&ctx.complex);
131 if (!ctx.output) {
132 log_module(MAIN_LOG, LOG_ERROR, "Unable to write to %s: %s", tmp_fname, strerror(errno));
133 int_list_clean(&ctx.complex);
134 return 1;
135 }
136 start = time(NULL);
137 if ((res = setjmp(ctx.jbuf)) || (res2 = db->writer(&ctx))) {
138 if (res) {
139 log_module(MAIN_LOG, LOG_ERROR, "Error writing to %s: %s", tmp_fname, strerror(res));
140 } else {
141 log_module(MAIN_LOG, LOG_ERROR, "Internal error %d while writing to %s", res2, tmp_fname);
142 }
143 int_list_clean(&ctx.complex);
144 fclose(ctx.output);
145 remove(tmp_fname);
146 return 2;
147 }
148 finish = time(NULL);
149 assert(ctx.complex.used == 0);
150 int_list_clean(&ctx.complex);
151 fclose(ctx.output);
152 if (rename(tmp_fname, db->filename) < 0) {
153 log_module(MAIN_LOG, LOG_ERROR, "Unable to rename %s to %s: %s", tmp_fname, db->filename, strerror(errno));
154 }
155 db->last_write = now;
156 db->last_write_duration = finish - start;
157 log_module(MAIN_LOG, LOG_INFO, "Wrote %s database to disk.", db->name);
158 return 0;
159}
160
161static void
162saxdb_timed_write(void *data) {
163 struct saxdb *db = data;
164 saxdb_write_db(db);
165 timeq_add(now + db->write_interval, saxdb_timed_write, db);
166}
167
168void
169saxdb_write(const char *db_name) {
170 struct saxdb *db;
171 db = dict_find(saxdbs, db_name, NULL);
172 if (db) saxdb_write_db(db);
173}
174
175void
176saxdb_write_all(void) {
177 dict_iterator_t it;
178 struct saxdb *db;
179
180 for (it = dict_first(saxdbs); it; it = iter_next(it)) {
181 db = iter_data(it);
182 if (!db->mondo_section)
183 saxdb_write_db(db);
184 }
185}
186
187#define saxdb_put_char(DEST, CH) do { \
188 if (fputc(CH, (DEST)->output) == EOF) \
189 longjmp((DEST)->jbuf, errno); \
190 } while (0)
191#define saxdb_put_string(DEST, CH) do { \
192 if (fputs(CH, (DEST)->output) == EOF) \
193 longjmp((DEST)->jbuf, errno); \
194 } while (0)
195
196static inline void
197saxdb_put_nchars(struct saxdb_context *dest, const char *name, int len) {
198 while (len--)
199 if (fputc(*name++, dest->output) == EOF)
200 longjmp(dest->jbuf, errno);
201}
202
203static void
204saxdb_put_qstring(struct saxdb_context *dest, const char *str) {
205 const char *esc;
206
207 assert(str);
208 saxdb_put_char(dest, '"');
209 while ((esc = strpbrk(str, "\\\a\b\t\n\v\f\r\""))) {
210 if (esc != str)
211 saxdb_put_nchars(dest, str, esc-str);
212 saxdb_put_char(dest, '\\');
213 switch (*esc) {
214 case '\a': saxdb_put_char(dest, 'a'); break;
215 case '\b': saxdb_put_char(dest, 'b'); break;
216 case '\t': saxdb_put_char(dest, 't'); break;
217 case '\n': saxdb_put_char(dest, 'n'); break;
218 case '\v': saxdb_put_char(dest, 'v'); break;
219 case '\f': saxdb_put_char(dest, 'f'); break;
220 case '\r': saxdb_put_char(dest, 'r'); break;
221 case '\\': saxdb_put_char(dest, '\\'); break;
222 case '"': saxdb_put_char(dest, '"'); break;
223 }
224 str = esc + 1;
225 }
226 saxdb_put_string(dest, str);
227 saxdb_put_char(dest, '"');
228}
229
230#ifndef NDEBUG
231static void
232saxdb_pre_object(struct saxdb_context *dest) {
233 unsigned int ii;
234 if (COMPLEX(dest)) {
235 for (ii=0; ii<dest->indent; ++ii) saxdb_put_char(dest, '\t');
236 }
237}
238#else
239#define saxdb_pre_object(DEST)
240#endif
241
242static inline void
243saxdb_post_object(struct saxdb_context *dest) {
244 saxdb_put_char(dest, ';');
245 saxdb_put_char(dest, COMPLEX(dest) ? '\n' : ' ');
246}
247
248void
249saxdb_start_record(struct saxdb_context *dest, const char *name, int complex) {
250 saxdb_pre_object(dest);
251 saxdb_put_qstring(dest, name);
252 saxdb_put_string(dest, " { ");
253 int_list_append(&dest->complex, complex);
254 if (complex) {
255 dest->indent++;
256 saxdb_put_char(dest, '\n');
257 }
258}
259
260void
261saxdb_end_record(struct saxdb_context *dest) {
262 assert(dest->complex.used > 0);
263 if (COMPLEX(dest)) dest->indent--;
264 saxdb_pre_object(dest);
265 dest->complex.used--;
266 saxdb_put_char(dest, '}');
267 saxdb_post_object(dest);
268}
269
270void
271saxdb_write_string_list(struct saxdb_context *dest, const char *name, struct string_list *list) {
272 unsigned int ii;
273
274 saxdb_pre_object(dest);
275 saxdb_put_qstring(dest, name);
276 saxdb_put_string(dest, " (");
277 if (list->used) {
278 for (ii=0; ii<list->used-1; ++ii) {
279 saxdb_put_qstring(dest, list->list[ii]);
280 saxdb_put_string(dest, ", ");
281 }
282 saxdb_put_qstring(dest, list->list[list->used-1]);
283 }
284 saxdb_put_string(dest, ")");
285 saxdb_post_object(dest);
286}
287
288void
289saxdb_write_string(struct saxdb_context *dest, const char *name, const char *value) {
290 saxdb_pre_object(dest);
291 saxdb_put_qstring(dest, name);
292 saxdb_put_char(dest, ' ');
293 saxdb_put_qstring(dest, value);
294 saxdb_post_object(dest);
295}
296
297void
298saxdb_write_int(struct saxdb_context *dest, const char *name, unsigned long value) {
299 unsigned char buf[16];
300 /* we could optimize this to take advantage of the fact that buf will never need escapes */
301 snprintf(buf, sizeof(buf), "%lu", value);
302 saxdb_write_string(dest, name, buf);
303}
304
305static void
306saxdb_free(void *data) {
307 struct saxdb *db = data;
308 free(db->name);
309 free(db->filename);
310 free(db->mondo_section);
311 free(db);
312}
313
314static int
315saxdb_mondo_read(struct dict *db, struct saxdb *saxdb) {
316 int res;
317 struct dict *subdb;
318
319 if (saxdb->prev && (res = saxdb_mondo_read(db, saxdb->prev))) return res;
320 if (saxdb->mondo_section
321 && (subdb = database_get_data(db, saxdb->mondo_section, RECDB_OBJECT))
322 && (res = saxdb->reader(subdb))) {
323 log_module(MAIN_LOG, LOG_INFO, " mondo section read for %s failed: %d", saxdb->mondo_section, res);
324 return res;
325 }
326 return 0;
327}
328
329static SAXDB_READER(saxdb_mondo_reader) {
330 return saxdb_mondo_read(db, last_db);
331}
332
333static int
334saxdb_mondo_write(struct saxdb_context *ctx, struct saxdb *saxdb) {
335 int res;
336 if (saxdb->prev && (res = saxdb_mondo_write(ctx, saxdb->prev))) return res;
337 if (saxdb->mondo_section) {
338 saxdb_start_record(ctx, saxdb->mondo_section, 1);
339 if ((res = saxdb->writer(ctx))) {
340 log_module(MAIN_LOG, LOG_INFO, " mondo section write for %s failed: %d", saxdb->mondo_section, res);
341 return res;
342 }
343 saxdb_end_record(ctx);
344 /* cheat a little here to put a newline between mondo sections */
345 saxdb_put_char(ctx, '\n');
346 }
347 return 0;
348}
349
350static SAXDB_WRITER(saxdb_mondo_writer) {
351 return saxdb_mondo_write(ctx, last_db);
352}
353
354static MODCMD_FUNC(cmd_write) {
355 struct timeval start, stop;
356 unsigned int ii, written;
357 struct saxdb *db;
358
359 assert(argc >= 2);
360 written = 0;
361 for (ii=1; ii<argc; ++ii) {
362 if (!(db = dict_find(saxdbs, argv[ii], NULL))) {
363 reply("MSG_DB_UNKNOWN", argv[ii]);
364 continue;
365 }
366 if (db->mondo_section) {
367 reply("MSG_DB_IS_MONDO", db->name);
368 continue;
369 }
370 gettimeofday(&start, NULL);
371 if (saxdb_write_db(db)) {
372 reply("MSG_DB_WRITE_ERROR", db->name);
373 } else {
374 gettimeofday(&stop, NULL);
375 stop.tv_sec -= start.tv_sec;
376 stop.tv_usec -= start.tv_usec;
377 if (stop.tv_usec < 0) {
378 stop.tv_sec -= 1;
379 stop.tv_usec += 1000000;
380 }
381 reply("MSG_DB_WROTE_DB", db->name, stop.tv_sec, stop.tv_usec);
382 written++;
383 }
384 }
385 return written;
386}
387
388static MODCMD_FUNC(cmd_writeall) {
389 struct timeval start, stop;
390
391 gettimeofday(&start, NULL);
392 saxdb_write_all();
393 gettimeofday(&stop, NULL);
394 stop.tv_sec -= start.tv_sec;
395 stop.tv_usec -= start.tv_usec;
396 if (stop.tv_usec < 0) {
397 stop.tv_sec -= 1;
398 stop.tv_usec += 1000000;
399 }
400 reply("MSG_DB_WROTE_ALL", stop.tv_sec, stop.tv_usec);
401 return 1;
402}
403
404static MODCMD_FUNC(cmd_stats_databases) {
405 struct helpfile_table tbl;
406 dict_iterator_t it;
407 unsigned int ii;
408
409 tbl.length = dict_size(saxdbs) + 1;
410 tbl.width = 5;
411 tbl.flags = TABLE_NO_FREE;
412 tbl.contents = calloc(tbl.length, sizeof(tbl.contents[0]));
413 tbl.contents[0] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
414 tbl.contents[0][0] = "Database";
415 tbl.contents[0][1] = "Filename/Section";
416 tbl.contents[0][2] = "Interval";
417 tbl.contents[0][3] = "Last Written";
418 tbl.contents[0][4] = "Last Duration";
419 for (ii=1, it=dict_first(saxdbs); it; it=iter_next(it), ++ii) {
420 struct saxdb *db = iter_data(it);
421 char *buf = malloc(INTERVALLEN*3);
422 tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0]));
423 tbl.contents[ii][0] = db->name;
424 tbl.contents[ii][1] = db->mondo_section ? db->mondo_section : db->filename;
425 if (db->write_interval) {
426 intervalString(buf, db->write_interval, user->handle_info);
427 } else {
428 strcpy(buf, "Never");
429 }
430 tbl.contents[ii][2] = buf;
431 if (db->last_write) {
432 intervalString(buf+INTERVALLEN, now - db->last_write, user->handle_info);
433 intervalString(buf+INTERVALLEN*2, db->last_write_duration, user->handle_info);
434 } else {
435 strcpy(buf+INTERVALLEN, "Never");
436 strcpy(buf+INTERVALLEN*2, "Never");
437 }
438 tbl.contents[ii][3] = buf+INTERVALLEN;
439 tbl.contents[ii][4] = buf+INTERVALLEN*2;
440 }
441 table_send(cmd->parent->bot, user->nick, 0, 0, tbl);
442 free(tbl.contents[0]);
443 for (ii=1; ii<tbl.length; ++ii) {
444 free((char*)tbl.contents[ii][2]);
445 free(tbl.contents[ii]);
446 }
447 free(tbl.contents);
448 return 0;
449}
450
451static void
452saxdb_cleanup(void) {
453 dict_delete(saxdbs);
454}
455
456static struct helpfile_expansion
457saxdb_expand_help(const char *variable) {
458 struct helpfile_expansion exp;
459 if (!strcasecmp(variable, "dblist")) {
460 dict_iterator_t it;
461 struct string_buffer sbuf;
462 struct saxdb *db;
463
464 exp.type = HF_STRING;
465 string_buffer_init(&sbuf);
466 for (it = dict_first(saxdbs); it; it = iter_next(it)) {
467 db = iter_data(it);
468 if (db->mondo_section) continue;
469 if (sbuf.used) string_buffer_append_string(&sbuf, ", ");
470 string_buffer_append_string(&sbuf, iter_key(it));
471 }
472 exp.value.str = sbuf.list;
473 } else {
474 exp.type = HF_STRING;
475 exp.value.str = NULL;
476 }
477 return exp;
478}
479
480void
481saxdb_init(void) {
482 reg_exit_func(saxdb_cleanup);
483 saxdbs = dict_new();
484 dict_set_free_data(saxdbs, saxdb_free);
485 saxdb_register("mondo", saxdb_mondo_reader, saxdb_mondo_writer);
486 saxdb_module = module_register("saxdb", MAIN_LOG, "saxdb.help", saxdb_expand_help);
487 modcmd_register(saxdb_module, "write", cmd_write, 2, MODCMD_REQUIRE_AUTHED, "level", "800", NULL);
488 modcmd_register(saxdb_module, "writeall", cmd_writeall, 0, MODCMD_REQUIRE_AUTHED, "level", "800", NULL);
489 modcmd_register(saxdb_module, "stats databases", cmd_stats_databases, 0, 0, NULL);
490}
491
492void
493saxdb_finalize(void) {
494 free_database(mondo_db);
495}
496
497static void
498write_database_helper(struct saxdb_context *ctx, struct dict *db) {
499 dict_iterator_t it;
500 struct record_data *rd;
501
502 for (it = dict_first(db); it; it = iter_next(it)) {
503 rd = iter_data(it);
504 switch (rd->type) {
505 case RECDB_INVALID: break;
506 case RECDB_QSTRING: saxdb_write_string(ctx, iter_key(it), rd->d.qstring); break;
507 case RECDB_STRING_LIST: saxdb_write_string_list(ctx, iter_key(it), rd->d.slist); break;
508 case RECDB_OBJECT:
509 saxdb_start_record(ctx, iter_key(it), 1);
510 write_database_helper(ctx, rd->d.object);
511 saxdb_end_record(ctx);
512 break;
513 }
514 }
515}
516
517int
518write_database(FILE *out, struct dict *db) {
519 struct saxdb_context ctx;
520 int res;
521
522 ctx.output = out;
523 ctx.indent = 0;
524 int_list_init(&ctx.complex);
525 if (!(res = setjmp(ctx.jbuf))) {
526 write_database_helper(&ctx, db);
527 } else {
528 log_module(MAIN_LOG, LOG_ERROR, "Exception %d caught while writing to stream", res);
529 int_list_clean(&ctx.complex);
530 return 1;
531 }
532 assert(ctx.complex.used == 0);
533 int_list_clean(&ctx.complex);
534 return 0;
535}
536
537struct saxdb_context *
538saxdb_open_context(FILE *file) {
539 struct saxdb_context *ctx;
540
541 assert(file);
542 ctx = calloc(1, sizeof(*ctx));
543 ctx->output = file;
544 int_list_init(&ctx->complex);
545
546 return ctx;
547}
548
549void
550saxdb_close_context(struct saxdb_context *ctx) {
551 assert(ctx->complex.used == 0);
552 int_list_clean(&ctx->complex);
553 free(ctx);
554}