]>
jfr.im git - irc/evilnet/x3.git/blob - src/saxdb.c
1 /* saxdb.c - srvx database manager
2 * Copyright 2002-2004 srvx Development Team
4 * This file is part of x3.
6 * x3 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 3 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
;
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 time_t start
, finish
;
140 assert(db
->filename
);
141 sprintf(tmp_fname
, "%s.new", db
->filename
);
142 output
= fopen(tmp_fname
, "w+");
145 log_module(MAIN_LOG
, LOG_ERROR
, "Unable to write to %s: %s", tmp_fname
, strerror(errno
));
148 ctx
= saxdb_open_context(output
);
150 if ((res
= setjmp(*saxdb_jmp_buf(ctx
))) || (res2
= db
->writer(ctx
))) {
152 log_module(MAIN_LOG
, LOG_ERROR
, "Error writing to %s: %s", tmp_fname
, strerror(res
));
154 log_module(MAIN_LOG
, LOG_ERROR
, "Internal error %d while writing to %s", res2
, tmp_fname
);
156 ctx
->complex.used
= 0; /* Squelch asserts about unbalanced output. */
157 saxdb_close_context(ctx
, 1);
162 saxdb_close_context(ctx
, 1);
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
));
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
);
173 saxdb_timed_write(void *data
) {
174 struct saxdb
*db
= data
;
176 timeq_add(now
+ db
->write_interval
, saxdb_timed_write
, db
);
180 saxdb_write(const char *db_name
) {
182 db
= dict_find(saxdbs
, db_name
, NULL
);
183 if (db
) saxdb_write_db(db
);
187 saxdb_write_all(UNUSED_ARG(void* extra
)) {
191 for (it
= dict_first(saxdbs
); it
; it
= iter_next(it
)) {
193 if (!db
->mondo_section
)
199 saxdb_flush(struct saxdb_context
*dest
) {
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
);
209 longjmp(dest
->jbuf
, errno
);
216 saxdb_put_char(struct saxdb_context
*dest
, char ch
) {
217 dest
->obuf
.list
[dest
->obuf
.used
] = ch
;
218 if (++dest
->obuf
.used
== dest
->obuf
.size
)
223 saxdb_put_nchars(struct saxdb_context
*dest
, const char *name
, int len
) {
227 for (ofs
= 0; ofs
< len
; ofs
+= frag
) {
228 frag
= dest
->obuf
.size
- dest
->obuf
.used
;
229 if (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
)
238 #define saxdb_put_string(DEST, STR) do { \
239 saxdb_put_nchars((DEST), (STR), strlen(STR)); \
243 saxdb_put_qstring(struct saxdb_context
*dest
, const char *str
) {
248 saxdb_put_char(dest
, '"');
249 for (ofs
= 0; str
[ofs
] != '\0'; ) {
251 span
= strcspn(str
+ ofs
, "\\\a\b\t\n\v\f\r\"");
252 saxdb_put_nchars(dest
, str
+ ofs
, span
);
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;
265 saxdb_put_char(dest
, '\\');
266 saxdb_put_char(dest
, stop
);
270 saxdb_put_char(dest
, '"');
275 saxdb_pre_object(struct saxdb_context
*dest
) {
278 for (ii
=0; ii
<dest
->indent
; ++ii
) saxdb_put_char(dest
, '\t');
282 #define saxdb_pre_object(DEST)
286 saxdb_post_object(struct saxdb_context
*dest
) {
287 saxdb_put_char(dest
, ';');
288 saxdb_put_char(dest
, COMPLEX(dest
) ? '\n' : ' ');
292 saxdb_start_record(struct saxdb_context
*dest
, const char *name
, int complex) {
293 saxdb_pre_object(dest
);
294 saxdb_put_qstring(dest
, name
);
295 saxdb_put_string(dest
, " {");
296 int_list_append(&dest
->complex, complex);
299 saxdb_put_char(dest
, '\n');
301 saxdb_put_char(dest
, ' ');
306 saxdb_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
);
316 saxdb_write_string_list(struct saxdb_context
*dest
, const char *name
, struct string_list
*list
) {
319 saxdb_pre_object(dest
);
320 saxdb_put_qstring(dest
, name
);
321 saxdb_put_string(dest
, " (");
323 for (ii
=0; ii
<list
->used
-1; ++ii
) {
324 saxdb_put_qstring(dest
, list
->list
[ii
]);
325 saxdb_put_string(dest
, ", ");
327 saxdb_put_qstring(dest
, list
->list
[list
->used
-1]);
329 saxdb_put_string(dest
, ")");
330 saxdb_post_object(dest
);
334 saxdb_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
);
343 saxdb_write_int(struct saxdb_context
*dest
, const char *name
, unsigned long value
) {
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
);
351 saxdb_write_sint(struct saxdb_context
*dest
, const char *name
, long value
) {
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
);
359 saxdb_free(void *data
) {
360 struct saxdb
*db
= data
;
363 free(db
->mondo_section
);
368 saxdb_mondo_read(struct dict
*db
, struct saxdb
*saxdb
) {
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
);
382 static SAXDB_READER(saxdb_mondo_reader
) {
383 return saxdb_mondo_read(db
, last_db
);
387 saxdb_mondo_write(struct saxdb_context
*ctx
, struct saxdb
*saxdb
) {
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
);
396 saxdb_end_record(ctx
);
397 /* cheat a little here to put a newline between mondo sections */
398 saxdb_put_char(ctx
, '\n');
403 static SAXDB_WRITER(saxdb_mondo_writer
) {
404 return saxdb_mondo_write(ctx
, last_db
);
407 static MODCMD_FUNC(cmd_write
) {
408 struct timeval start
, stop
;
409 unsigned int ii
, written
;
414 for (ii
=1; ii
<argc
; ++ii
) {
415 if (!(db
= dict_find(saxdbs
, argv
[ii
], NULL
))) {
416 reply("MSG_DB_UNKNOWN", argv
[ii
]);
419 if (db
->mondo_section
) {
420 reply("MSG_DB_IS_MONDO", db
->name
);
423 gettimeofday(&start
, NULL
);
424 if (saxdb_write_db(db
)) {
425 reply("MSG_DB_WRITE_ERROR", db
->name
);
427 gettimeofday(&stop
, NULL
);
428 stop
.tv_sec
-= start
.tv_sec
;
429 stop
.tv_usec
-= start
.tv_usec
;
430 if (stop
.tv_usec
< 0) {
432 stop
.tv_usec
+= 1000000;
434 reply("MSG_DB_WROTE_DB", db
->name
, stop
.tv_sec
, stop
.tv_usec
);
441 static MODCMD_FUNC(cmd_writeall
) {
442 struct timeval start
, stop
;
444 gettimeofday(&start
, NULL
);
445 saxdb_write_all(NULL
);
446 gettimeofday(&stop
, NULL
);
447 stop
.tv_sec
-= start
.tv_sec
;
448 stop
.tv_usec
-= start
.tv_usec
;
449 if (stop
.tv_usec
< 0) {
451 stop
.tv_usec
+= 1000000;
453 reply("MSG_DB_WROTE_ALL", stop
.tv_sec
, stop
.tv_usec
);
457 static MODCMD_FUNC(cmd_stats_databases
) {
458 struct helpfile_table tbl
;
462 tbl
.length
= dict_size(saxdbs
) + 1;
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
);
474 if (db
->mondo_section
) {
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
);
485 strcpy(buf
, "Never");
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
);
492 strcpy(buf
+INTERVALLEN
, "Never");
493 strcpy(buf
+INTERVALLEN
*2, "Never");
495 tbl
.contents
[ii
][3] = buf
+INTERVALLEN
;
496 tbl
.contents
[ii
][4] = buf
+INTERVALLEN
*2;
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
]);
510 saxdb_cleanup(UNUSED_ARG(void *extra
)) {
514 static struct helpfile_expansion
515 saxdb_expand_help(const char *variable
) {
516 struct helpfile_expansion exp
;
517 if (!strcasecmp(variable
, "dblist")) {
519 struct string_buffer sbuf
;
522 exp
.type
= HF_STRING
;
523 string_buffer_init(&sbuf
);
524 for (it
= dict_first(saxdbs
); it
; it
= iter_next(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
));
530 exp
.value
.str
= sbuf
.list
;
532 exp
.type
= HF_STRING
;
533 exp
.value
.str
= NULL
;
540 reg_exit_func(saxdb_cleanup
, NULL
);
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
);
551 saxdb_finalize(void) {
552 free_database(mondo_db
);
556 write_database_helper(struct saxdb_context
*ctx
, struct dict
*db
) {
558 struct record_data
*rd
;
560 for (it
= dict_first(db
); it
; it
= iter_next(it
)) {
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;
567 saxdb_start_record(ctx
, iter_key(it
), 1);
568 write_database_helper(ctx
, rd
->d
.object
);
569 saxdb_end_record(ctx
);
576 write_database(FILE *out
, struct dict
*db
) {
577 struct saxdb_context
*ctx
;
580 ctx
= saxdb_open_context(out
);
581 if (!(res
= setjmp(*saxdb_jmp_buf(ctx
)))) {
582 write_database_helper(ctx
, db
);
584 log_module(MAIN_LOG
, LOG_ERROR
, "Exception %d caught while writing to stream", res
);
585 ctx
->complex.used
= 0; /* Squelch asserts about unbalanced output. */
586 saxdb_close_context(ctx
, 0);
589 saxdb_close_context(ctx
, 0);
593 struct saxdb_context
*
594 saxdb_open_context(FILE *file
) {
595 struct saxdb_context
*ctx
;
598 ctx
= calloc(1, sizeof(*ctx
));
600 ctx
->obuf
.size
= SAXDB_BUFFER_SIZE
;
601 ctx
->obuf
.list
= calloc(1, ctx
->obuf
.size
);
602 int_list_init(&ctx
->complex);
608 saxdb_jmp_buf(struct saxdb_context
*ctx
) {
613 saxdb_close_context(struct saxdb_context
*ctx
, int close_file
) {
614 assert(ctx
->complex.used
== 0);
616 int_list_clean(&ctx
->complex);
617 free(ctx
->obuf
.list
);