]>
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 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
) {
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_write_sint(struct saxdb_context
*dest
, const char *name
, long value
) {
308 /* we could optimize this to take advantage of the fact that buf will never need escapes */
309 snprintf(buf
, sizeof(buf
), "%ld", value
);
310 saxdb_write_string(dest
, name
, buf
);
314 saxdb_free(void *data
) {
315 struct saxdb
*db
= data
;
318 free(db
->mondo_section
);
323 saxdb_mondo_read(struct dict
*db
, struct saxdb
*saxdb
) {
327 if (saxdb
->prev
&& (res
= saxdb_mondo_read(db
, saxdb
->prev
))) return res
;
328 if (saxdb
->mondo_section
329 && (subdb
= database_get_data(db
, saxdb
->mondo_section
, RECDB_OBJECT
))
330 && (res
= saxdb
->reader(subdb
))) {
331 log_module(MAIN_LOG
, LOG_INFO
, " mondo section read for %s failed: %d", saxdb
->mondo_section
, res
);
337 static SAXDB_READER(saxdb_mondo_reader
) {
338 return saxdb_mondo_read(db
, last_db
);
342 saxdb_mondo_write(struct saxdb_context
*ctx
, struct saxdb
*saxdb
) {
344 if (saxdb
->prev
&& (res
= saxdb_mondo_write(ctx
, saxdb
->prev
))) return res
;
345 if (saxdb
->mondo_section
) {
346 saxdb_start_record(ctx
, saxdb
->mondo_section
, 1);
347 if ((res
= saxdb
->writer(ctx
))) {
348 log_module(MAIN_LOG
, LOG_INFO
, " mondo section write for %s failed: %d", saxdb
->mondo_section
, res
);
351 saxdb_end_record(ctx
);
352 /* cheat a little here to put a newline between mondo sections */
353 saxdb_put_char(ctx
, '\n');
358 static SAXDB_WRITER(saxdb_mondo_writer
) {
359 return saxdb_mondo_write(ctx
, last_db
);
362 static MODCMD_FUNC(cmd_write
) {
363 struct timeval start
, stop
;
364 unsigned int ii
, written
;
369 for (ii
=1; ii
<argc
; ++ii
) {
370 if (!(db
= dict_find(saxdbs
, argv
[ii
], NULL
))) {
371 reply("MSG_DB_UNKNOWN", argv
[ii
]);
374 if (db
->mondo_section
) {
375 reply("MSG_DB_IS_MONDO", db
->name
);
378 gettimeofday(&start
, NULL
);
379 if (saxdb_write_db(db
)) {
380 reply("MSG_DB_WRITE_ERROR", db
->name
);
382 gettimeofday(&stop
, NULL
);
383 stop
.tv_sec
-= start
.tv_sec
;
384 stop
.tv_usec
-= start
.tv_usec
;
385 if (stop
.tv_usec
< 0) {
387 stop
.tv_usec
+= 1000000;
389 reply("MSG_DB_WROTE_DB", db
->name
, stop
.tv_sec
, stop
.tv_usec
);
396 static MODCMD_FUNC(cmd_writeall
) {
397 struct timeval start
, stop
;
399 gettimeofday(&start
, NULL
);
401 gettimeofday(&stop
, NULL
);
402 stop
.tv_sec
-= start
.tv_sec
;
403 stop
.tv_usec
-= start
.tv_usec
;
404 if (stop
.tv_usec
< 0) {
406 stop
.tv_usec
+= 1000000;
408 reply("MSG_DB_WROTE_ALL", stop
.tv_sec
, stop
.tv_usec
);
412 static MODCMD_FUNC(cmd_stats_databases
) {
413 struct helpfile_table tbl
;
417 tbl
.length
= dict_size(saxdbs
) + 1;
419 tbl
.flags
= TABLE_NO_FREE
;
420 tbl
.contents
= calloc(tbl
.length
, sizeof(tbl
.contents
[0]));
421 tbl
.contents
[0] = calloc(tbl
.width
, sizeof(tbl
.contents
[0][0]));
422 tbl
.contents
[0][0] = "Database";
423 tbl
.contents
[0][1] = "Filename/Section";
424 tbl
.contents
[0][2] = "Interval";
425 tbl
.contents
[0][3] = "Last Written";
426 tbl
.contents
[0][4] = "Last Duration";
427 for (ii
=1, it
=dict_first(saxdbs
); it
; it
=iter_next(it
), ++ii
) {
428 struct saxdb
*db
= iter_data(it
);
429 char *buf
= malloc(INTERVALLEN
*3);
430 tbl
.contents
[ii
] = calloc(tbl
.width
, sizeof(tbl
.contents
[ii
][0]));
431 tbl
.contents
[ii
][0] = db
->name
;
432 tbl
.contents
[ii
][1] = db
->mondo_section
? db
->mondo_section
: db
->filename
;
433 if (db
->write_interval
) {
434 intervalString(buf
, db
->write_interval
, user
->handle_info
);
436 strcpy(buf
, "Never");
438 tbl
.contents
[ii
][2] = buf
;
439 if (db
->last_write
) {
440 intervalString(buf
+INTERVALLEN
, now
- db
->last_write
, user
->handle_info
);
441 intervalString(buf
+INTERVALLEN
*2, db
->last_write_duration
, user
->handle_info
);
443 strcpy(buf
+INTERVALLEN
, "Never");
444 strcpy(buf
+INTERVALLEN
*2, "Never");
446 tbl
.contents
[ii
][3] = buf
+INTERVALLEN
;
447 tbl
.contents
[ii
][4] = buf
+INTERVALLEN
*2;
449 table_send(cmd
->parent
->bot
, user
->nick
, 0, 0, tbl
);
450 free(tbl
.contents
[0]);
451 for (ii
=1; ii
<tbl
.length
; ++ii
) {
452 free((char*)tbl
.contents
[ii
][2]);
453 free(tbl
.contents
[ii
]);
460 saxdb_cleanup(void) {
464 static struct helpfile_expansion
465 saxdb_expand_help(const char *variable
) {
466 struct helpfile_expansion exp
;
467 if (!strcasecmp(variable
, "dblist")) {
469 struct string_buffer sbuf
;
472 exp
.type
= HF_STRING
;
473 string_buffer_init(&sbuf
);
474 for (it
= dict_first(saxdbs
); it
; it
= iter_next(it
)) {
476 if (db
->mondo_section
) continue;
477 if (sbuf
.used
) string_buffer_append_string(&sbuf
, ", ");
478 string_buffer_append_string(&sbuf
, iter_key(it
));
480 exp
.value
.str
= sbuf
.list
;
482 exp
.type
= HF_STRING
;
483 exp
.value
.str
= NULL
;
490 reg_exit_func(saxdb_cleanup
);
492 dict_set_free_data(saxdbs
, saxdb_free
);
493 saxdb_register("mondo", saxdb_mondo_reader
, saxdb_mondo_writer
);
494 saxdb_module
= module_register("saxdb", MAIN_LOG
, "saxdb.help", saxdb_expand_help
);
495 modcmd_register(saxdb_module
, "write", cmd_write
, 2, MODCMD_REQUIRE_AUTHED
, "level", "800", NULL
);
496 modcmd_register(saxdb_module
, "writeall", cmd_writeall
, 0, MODCMD_REQUIRE_AUTHED
, "level", "800", NULL
);
497 modcmd_register(saxdb_module
, "stats databases", cmd_stats_databases
, 0, 0, NULL
);
501 saxdb_finalize(void) {
502 free_database(mondo_db
);
506 write_database_helper(struct saxdb_context
*ctx
, struct dict
*db
) {
508 struct record_data
*rd
;
510 for (it
= dict_first(db
); it
; it
= iter_next(it
)) {
513 case RECDB_INVALID
: break;
514 case RECDB_QSTRING
: saxdb_write_string(ctx
, iter_key(it
), rd
->d
.qstring
); break;
515 case RECDB_STRING_LIST
: saxdb_write_string_list(ctx
, iter_key(it
), rd
->d
.slist
); break;
517 saxdb_start_record(ctx
, iter_key(it
), 1);
518 write_database_helper(ctx
, rd
->d
.object
);
519 saxdb_end_record(ctx
);
526 write_database(FILE *out
, struct dict
*db
) {
527 struct saxdb_context ctx
;
532 int_list_init(&ctx
.complex);
533 if (!(res
= setjmp(ctx
.jbuf
))) {
534 write_database_helper(&ctx
, db
);
536 log_module(MAIN_LOG
, LOG_ERROR
, "Exception %d caught while writing to stream", res
);
537 int_list_clean(&ctx
.complex);
540 assert(ctx
.complex.used
== 0);
541 int_list_clean(&ctx
.complex);
545 struct saxdb_context
*
546 saxdb_open_context(FILE *file
) {
547 struct saxdb_context
*ctx
;
550 ctx
= calloc(1, sizeof(*ctx
));
552 int_list_init(&ctx
->complex);
558 saxdb_close_context(struct saxdb_context
*ctx
) {
559 assert(ctx
->complex.used
== 0);
560 int_list_clean(&ctx
->complex);