]>
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 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
;
37 unsigned int last_write_duration
;
41 #define COMPLEX(CTX) ((CTX)->complex.used ? ((CTX)->complex.list[(CTX)->complex.used-1]) : 1)
43 static struct saxdb
*last_db
;
44 static struct dict
*saxdbs
; /* -> struct saxdb */
45 static struct dict
*mondo_db
;
46 static struct module *saxdb_module
;
48 static SAXDB_WRITER(saxdb_mondo_writer
);
49 static void saxdb_timed_write(void *data
);
52 saxdb_read_db(struct saxdb
*db
) {
57 data
= parse_database(db
->filename
);
60 if (db
->writer
== saxdb_mondo_writer
) {
69 saxdb_register(const char *name
, saxdb_reader_func_t
*reader
, saxdb_writer_func_t
*writer
) {
73 const char *filename
= NULL
, *str
;
74 char conf_path
[MAXLEN
];
76 db
= calloc(1, sizeof(*db
));
77 db
->name
= strdup(name
);
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
);
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
);
90 db
->write_interval
= 1800;
92 /* Schedule database writes */
93 if (db
->write_interval
&& !db
->mondo_section
) {
94 timeq_add(now
+ db
->write_interval
, saxdb_timed_write
, db
);
98 db
->filename
= strdup(filename
);
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");
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
))) {
112 /* Remember the database */
113 dict_insert(saxdbs
, db
->name
, db
);
120 saxdb_write_db(struct saxdb
*db
) {
121 struct saxdb_context ctx
;
122 char tmp_fname
[MAXLEN
];
124 time_t start
, finish
;
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);
132 log_module(MAIN_LOG
, LOG_ERROR
, "Unable to write to %s: %s", tmp_fname
, strerror(errno
));
133 int_list_clean(&ctx
.complex);
137 if ((res
= setjmp(ctx
.jbuf
)) || (res2
= db
->writer(&ctx
))) {
139 log_module(MAIN_LOG
, LOG_ERROR
, "Error writing to %s: %s", tmp_fname
, strerror(res
));
141 log_module(MAIN_LOG
, LOG_ERROR
, "Internal error %d while writing to %s", res2
, tmp_fname
);
143 int_list_clean(&ctx
.complex);
149 assert(ctx
.complex.used
== 0);
150 int_list_clean(&ctx
.complex);
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
));
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
);
162 saxdb_timed_write(void *data
) {
163 struct saxdb
*db
= data
;
165 timeq_add(now
+ db
->write_interval
, saxdb_timed_write
, db
);
169 saxdb_write(const char *db_name
) {
171 db
= dict_find(saxdbs
, db_name
, NULL
);
172 if (db
) saxdb_write_db(db
);
176 saxdb_write_all(void) {
180 for (it
= dict_first(saxdbs
); it
; it
= iter_next(it
)) {
182 if (!db
->mondo_section
)
187 #define saxdb_put_char(DEST, CH) do { \
188 if (fputc(CH, (DEST)->output) == EOF) \
189 longjmp((DEST)->jbuf, errno); \
191 #define saxdb_put_string(DEST, CH) do { \
192 if (fputs(CH, (DEST)->output) == EOF) \
193 longjmp((DEST)->jbuf, errno); \
197 saxdb_put_nchars(struct saxdb_context
*dest
, const char *name
, int len
) {
199 if (fputc(*name
++, dest
->output
) == EOF
)
200 longjmp(dest
->jbuf
, errno
);
204 saxdb_put_qstring(struct saxdb_context
*dest
, const char *str
) {
208 saxdb_put_char(dest
, '"');
209 while ((esc
= strpbrk(str
, "\\\a\b\t\n\v\f\r\""))) {
211 saxdb_put_nchars(dest
, str
, esc
-str
);
212 saxdb_put_char(dest
, '\\');
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;
226 saxdb_put_string(dest
, str
);
227 saxdb_put_char(dest
, '"');
232 saxdb_pre_object(struct saxdb_context
*dest
) {
235 for (ii
=0; ii
<dest
->indent
; ++ii
) saxdb_put_char(dest
, '\t');
239 #define saxdb_pre_object(DEST)
243 saxdb_post_object(struct saxdb_context
*dest
) {
244 saxdb_put_char(dest
, ';');
245 saxdb_put_char(dest
, COMPLEX(dest
) ? '\n' : ' ');
249 saxdb_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);
256 saxdb_put_char(dest
, '\n');
261 saxdb_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
);
271 saxdb_write_string_list(struct saxdb_context
*dest
, const char *name
, struct string_list
*list
) {
274 saxdb_pre_object(dest
);
275 saxdb_put_qstring(dest
, name
);
276 saxdb_put_string(dest
, " (");
278 for (ii
=0; ii
<list
->used
-1; ++ii
) {
279 saxdb_put_qstring(dest
, list
->list
[ii
]);
280 saxdb_put_string(dest
, ", ");
282 saxdb_put_qstring(dest
, list
->list
[list
->used
-1]);
284 saxdb_put_string(dest
, ")");
285 saxdb_post_object(dest
);
289 saxdb_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
);
298 saxdb_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
);
306 saxdb_free(void *data
) {
307 struct saxdb
*db
= data
;
310 free(db
->mondo_section
);
315 saxdb_mondo_read(struct dict
*db
, struct saxdb
*saxdb
) {
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
);
329 static SAXDB_READER(saxdb_mondo_reader
) {
330 return saxdb_mondo_read(db
, last_db
);
334 saxdb_mondo_write(struct saxdb_context
*ctx
, struct saxdb
*saxdb
) {
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
);
343 saxdb_end_record(ctx
);
344 /* cheat a little here to put a newline between mondo sections */
345 saxdb_put_char(ctx
, '\n');
350 static SAXDB_WRITER(saxdb_mondo_writer
) {
351 return saxdb_mondo_write(ctx
, last_db
);
354 static MODCMD_FUNC(cmd_write
) {
355 struct timeval start
, stop
;
356 unsigned int ii
, written
;
361 for (ii
=1; ii
<argc
; ++ii
) {
362 if (!(db
= dict_find(saxdbs
, argv
[ii
], NULL
))) {
363 reply("MSG_DB_UNKNOWN", argv
[ii
]);
366 if (db
->mondo_section
) {
367 reply("MSG_DB_IS_MONDO", db
->name
);
370 gettimeofday(&start
, NULL
);
371 if (saxdb_write_db(db
)) {
372 reply("MSG_DB_WRITE_ERROR", db
->name
);
374 gettimeofday(&stop
, NULL
);
375 stop
.tv_sec
-= start
.tv_sec
;
376 stop
.tv_usec
-= start
.tv_usec
;
377 if (stop
.tv_usec
< 0) {
379 stop
.tv_usec
+= 1000000;
381 reply("MSG_DB_WROTE_DB", db
->name
, stop
.tv_sec
, stop
.tv_usec
);
388 static MODCMD_FUNC(cmd_writeall
) {
389 struct timeval start
, stop
;
391 gettimeofday(&start
, NULL
);
393 gettimeofday(&stop
, NULL
);
394 stop
.tv_sec
-= start
.tv_sec
;
395 stop
.tv_usec
-= start
.tv_usec
;
396 if (stop
.tv_usec
< 0) {
398 stop
.tv_usec
+= 1000000;
400 reply("MSG_DB_WROTE_ALL", stop
.tv_sec
, stop
.tv_usec
);
404 static MODCMD_FUNC(cmd_stats_databases
) {
405 struct helpfile_table tbl
;
409 tbl
.length
= dict_size(saxdbs
) + 1;
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
);
428 strcpy(buf
, "Never");
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
);
435 strcpy(buf
+INTERVALLEN
, "Never");
436 strcpy(buf
+INTERVALLEN
*2, "Never");
438 tbl
.contents
[ii
][3] = buf
+INTERVALLEN
;
439 tbl
.contents
[ii
][4] = buf
+INTERVALLEN
*2;
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
]);
452 saxdb_cleanup(void) {
456 static struct helpfile_expansion
457 saxdb_expand_help(const char *variable
) {
458 struct helpfile_expansion exp
;
459 if (!strcasecmp(variable
, "dblist")) {
461 struct string_buffer sbuf
;
464 exp
.type
= HF_STRING
;
465 string_buffer_init(&sbuf
);
466 for (it
= dict_first(saxdbs
); it
; it
= iter_next(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
));
472 exp
.value
.str
= sbuf
.list
;
474 exp
.type
= HF_STRING
;
475 exp
.value
.str
= NULL
;
482 reg_exit_func(saxdb_cleanup
);
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
);
493 saxdb_finalize(void) {
494 free_database(mondo_db
);
498 write_database_helper(struct saxdb_context
*ctx
, struct dict
*db
) {
500 struct record_data
*rd
;
502 for (it
= dict_first(db
); it
; it
= iter_next(it
)) {
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;
509 saxdb_start_record(ctx
, iter_key(it
), 1);
510 write_database_helper(ctx
, rd
->d
.object
);
511 saxdb_end_record(ctx
);
518 write_database(FILE *out
, struct dict
*db
) {
519 struct saxdb_context ctx
;
524 int_list_init(&ctx
.complex);
525 if (!(res
= setjmp(ctx
.jbuf
))) {
526 write_database_helper(&ctx
, db
);
528 log_module(MAIN_LOG
, LOG_ERROR
, "Exception %d caught while writing to stream", res
);
529 int_list_clean(&ctx
.complex);
532 assert(ctx
.complex.used
== 0);
533 int_list_clean(&ctx
.complex);
537 struct saxdb_context
*
538 saxdb_open_context(FILE *file
) {
539 struct saxdb_context
*ctx
;
542 ctx
= calloc(1, sizeof(*ctx
));
544 int_list_init(&ctx
->complex);
550 saxdb_close_context(struct saxdb_context
*ctx
) {
551 assert(ctx
->complex.used
== 0);
552 int_list_clean(&ctx
->complex);