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