1 /* recdb.c - recursive/record database implementation
2 * Copyright 2000-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 #ifdef HAVE_SYS_MMAN_H
30 #ifdef HAVE_SYS_STAT_H
35 #define MMAP_MAP_LENGTH (getpagesize()*1024)
37 /* file format (grammar in Backus-Naur Form):
40 * record := qstring [ '=' ] ( qstring | object | stringlist ) ';'
41 * qstring := '"' ( [^\\] | ('\\' [\\n]) )* '"'
42 * object := '{' record* '}'
43 * stringlist := '(' [ qstring [',' qstring]* ] ')'
47 /* when a database or object is read from disk, it is represented as
48 * a dictionary object, keys are names (what's left of the '=') and
49 * values are 'struct record_data's
52 struct recdb_context {
63 typedef struct recdb_file {
65 FILE *f; /* For RECDB_FILE, RECDB_MMAP */
66 char *s; /* For RECDB_STRING, RECDB_MMAP */
67 enum recdb_filetype type;
70 struct recdb_context ctx;
74 typedef struct recdb_outfile {
75 FILE *f; /* For RECDB_FILE, RECDB_MMAP */
76 char *s; /* For RECDB_STRING, RECDB_MMAP */
78 struct { /* For RECDB_STRING */
82 struct { /* For RECDB_MMAP */
87 enum recdb_filetype type;
96 static int mmap_error;
102 #define ABORT(recdb, code, ch) longjmp((recdb)->env, ((code) << 8) | (ch))
105 ABORT(RECDB *recdb, int code, unsigned char ch) {
106 longjmp(recdb->env, code << 8 | ch);
112 UNTERMINATED_COMMENT,
117 EXPECTED_START_RECORD_DATA,
122 static void parse_record_int(RECDB *recdb, char **pname, struct record_data **prd);
124 /* allocation functions */
126 #define alloc_record_data_int() malloc(sizeof(struct record_data))
129 alloc_record_data_qstring(const char *string)
131 struct record_data *rd;
132 rd = alloc_record_data_int();
133 SET_RECORD_QSTRING(rd, string);
138 alloc_record_data_object(dict_t obj)
140 struct record_data *rd;
141 rd = alloc_record_data_int();
142 SET_RECORD_OBJECT(rd, obj);
147 alloc_record_data_string_list(struct string_list *slist)
149 struct record_data *rd;
150 rd = alloc_record_data_int();
151 SET_RECORD_STRING_LIST(rd, slist);
156 alloc_string_list(int size)
158 struct string_list *slist;
159 slist = malloc(sizeof(struct string_list));
162 slist->list = slist->size ? malloc(size*sizeof(char*)) : NULL;
169 dict_t db = dict_new();
170 dict_set_free_data(db, free_record_data);
174 /* misc. operations */
177 string_list_append(struct string_list *slist, char *string)
179 if (slist->used == slist->size) {
182 slist->list = realloc(slist->list, slist->size*sizeof(char*));
185 slist->list = malloc(slist->size*sizeof(char*));
188 slist->list[slist->used++] = string;
192 string_list_copy(struct string_list *slist)
194 struct string_list *new_list;
196 new_list = alloc_string_list(slist->size);
197 new_list->used = slist->used;
198 for (i=0; i<new_list->used; i++) {
199 new_list->list[i] = strdup(slist->list[i]);
204 int slist_compare_two(const void *pa, const void *pb)
206 return irccasecmp(*(const char**)pa, *(const char **)pb);
210 string_list_sort(struct string_list *slist)
212 qsort(slist->list, slist->used, sizeof(slist->list[0]), slist_compare_two);
216 database_get_path(dict_t db, const char *path)
218 char *new_path = strdup(path), *orig_path = new_path;
220 struct record_data *rd;
222 for (part=new_path; *new_path; new_path++) {
223 if (*new_path != '/') continue;
226 rd = dict_find(db, part, NULL);
227 if (!rd || rd->type != RECDB_OBJECT) {
236 rd = dict_find(db, part, NULL);
242 database_get_data(dict_t db, const char *path, enum recdb_type type)
244 struct record_data *rd = database_get_path(db, path);
245 return (rd && rd->type == type) ? rd->d.whatever : NULL;
251 free_string_list(struct string_list *slist)
256 for (i=0; i<slist->used; i++)
257 free(slist->list[i]);
263 free_record_data(void *rdata)
265 struct record_data *r = rdata;
267 case RECDB_INVALID: break;
268 case RECDB_QSTRING: free(r->d.qstring); break;
269 case RECDB_OBJECT: dict_delete(r->d.object); break;
270 case RECDB_STRING_LIST: free_string_list(r->d.slist); break;
275 /* parse functions */
280 switch (recdb->type) {
282 return feof(recdb->f);
288 return ((size_t)recdb->pos >= recdb->length);
300 switch (recdb->type) {
302 res = fgetc(recdb->f);
306 res = dbeof(recdb) ? EOF : recdb->s[recdb->pos++];
312 if (res == EOL) recdb->ctx.line++, recdb->ctx.col=1;
313 else if (res != EOF) recdb->ctx.col++;
318 dbungetc(int c, RECDB *recdb)
320 switch (recdb->type) {
326 recdb->s[--recdb->pos] = c;
329 if (c == EOL) recdb->ctx.line--, recdb->ctx.col=-1;
330 else recdb->ctx.col--;
333 /* returns first non-whitespace, non-comment character (-1 for EOF found) */
335 parse_skip_ws(RECDB *recdb)
337 int c, d, in_comment = 0;
338 while (!dbeof(recdb)) {
340 if (c == EOF) return EOF;
341 if (isspace(c)) continue;
342 if (c != '/') return c;
343 if ((d = dbgetc(recdb)) == '*') {
344 /* C style comment, with slash star comment star slash */
349 if (c == EOF) ABORT(recdb, UNTERMINATED_COMMENT, c);
351 if ((c = dbgetc(recdb)) == '/') in_comment = 0;
352 if (c == EOF) ABORT(recdb, UNTERMINATED_COMMENT, c);
353 } while (in_comment);
354 } else if (d == '/') {
355 /* C++ style comment, with slash slash comment newline */
358 } while (c != EOF && c != EOL);
360 if (d != EOF) dbungetc(d, recdb);
368 parse_qstring(RECDB *recdb)
371 int used=0, size=8, c;
372 struct recdb_context start_ctx;
375 if ((c = parse_skip_ws(recdb)) == EOF) return NULL;
376 start_ctx = recdb->ctx;
377 if (c != '"') ABORT(recdb, EXPECTED_OPEN_QUOTE, c);
379 while (!dbeof(recdb) && (c = dbgetc(recdb)) != '"') {
381 /* There should never be a literal newline, as it is saved as a \n */
384 ABORT(recdb, UNTERMINATED_STRING, ' ');
388 switch (c = dbgetc(recdb)) {
389 case '0': /* \<octal>, 000 through 377 */
398 char digits[3] = { (char)c, '\0', '\0' };
399 for (i=1; i < 3; i++) {
400 /* Maximum of \377, so there's a max of 2 digits
401 * if digits[0] > '3' (no \400, but \40 is fine) */
402 if (i == 2 && digits[0] > '3') {
405 if ((c = dbgetc(recdb)) == EOF) {
408 if ((c < '0') || (c > '7')) {
415 c = (int)strtol(digits, NULL, 8);
424 char digits[3] = { '\0', '\0', '\0' };
425 for (i=0; i < 2; i++) {
426 if ((c = dbgetc(recdb)) == EOF) {
436 c = (int)strtol(digits, NULL, 16);
444 case 'a': buff[used++] = '\a'; break;
445 case 'b': buff[used++] = '\b'; break;
446 case 't': buff[used++] = '\t'; break;
447 case 'n': buff[used++] = EOL; break;
448 case 'v': buff[used++] = '\v'; break;
449 case 'f': buff[used++] = '\f'; break;
450 case 'r': buff[used++] = '\r'; break;
451 case '\\': buff[used++] = '\\'; break;
452 case '"': buff[used++] = '"'; break;
453 default: buff[used++] = '\\'; buff[used++] = c; break;
458 buff = realloc(buff, size);
461 if (c != '"' && dbeof(recdb)) {
463 recdb->ctx = start_ctx;
464 ABORT(recdb, UNTERMINATED_STRING, EOF);
471 parse_object(RECDB *recdb)
475 struct record_data *rd;
477 if ((c = parse_skip_ws(recdb)) == EOF) return NULL;
478 if (c != '{') ABORT(recdb, EXPECTED_OPEN_BRACE, c);
479 obj = alloc_object();
480 dict_set_free_keys(obj, free);
481 while (!dbeof(recdb)) {
482 if ((c = parse_skip_ws(recdb)) == '}') break;
485 parse_record_int(recdb, &name, &rd);
486 dict_insert(obj, name, rd);
492 parse_string_list(RECDB *recdb)
494 struct string_list *slist;
496 if ((c = parse_skip_ws(recdb)) == EOF) return NULL;
497 if (c != '(') ABORT(recdb, EXPECTED_OPEN_PAREN, c);
498 slist = alloc_string_list(4);
500 c = parse_skip_ws(recdb);
501 if (c == EOF || c == ')') break;
503 string_list_append(slist, parse_qstring(recdb));
504 c = parse_skip_ws(recdb);
505 if (c == EOF || c == ')') break;
506 if (c != ',') ABORT(recdb, EXPECTED_COMMA, c);
512 parse_record_int(RECDB *recdb, char **pname, struct record_data **prd)
515 *pname = parse_qstring(recdb);
516 c = parse_skip_ws(recdb);
520 ABORT(recdb, EXPECTED_RECORD_DATA, EOF);
522 if (c == '=') c = parse_skip_ws(recdb);
524 *prd = malloc(sizeof(**prd));
527 /* Don't use SET_RECORD_QSTRING, since that does an extra strdup() of the string. */
528 (*prd)->type = RECDB_QSTRING;
529 (*prd)->d.qstring = parse_qstring(recdb);
531 case '{': SET_RECORD_OBJECT(*prd, parse_object(recdb)); break;
532 case '(': SET_RECORD_STRING_LIST(*prd, parse_string_list(recdb)); break;
533 default: ABORT(recdb, EXPECTED_START_RECORD_DATA, c);
535 if ((c = parse_skip_ws(recdb)) != ';') ABORT(recdb, EXPECTED_SEMICOLON, c);
539 parse_database_int(RECDB *recdb)
542 struct record_data *rd;
543 dict_t db = alloc_database();
544 dict_set_free_keys(db, free);
545 while (!dbeof(recdb)) {
546 parse_record_int(recdb, &name, &rd);
547 if (name) dict_insert(db, name, rd);
553 failure_reason(int code)
557 case UNTERMINATED_STRING: reason = "Unterminated string"; break;
558 case UNTERMINATED_COMMENT: reason = "Unterminated comment"; break;
559 case EXPECTED_OPEN_QUOTE: reason = "Expected '\"'"; break;
560 case EXPECTED_OPEN_BRACE: reason = "Expected '{'"; break;
561 case EXPECTED_OPEN_PAREN: reason = "Expected '('"; break;
562 case EXPECTED_COMMA: reason = "Expected ','"; break;
563 case EXPECTED_START_RECORD_DATA: reason = "Expected start of some record data"; break;
564 case EXPECTED_SEMICOLON: reason = "Expected ';'"; break;
565 case EXPECTED_RECORD_DATA: reason = "Expected record data"; break;
566 default: reason = "Unknown error";
568 if (code == -1) reason = "Premature end of file";
573 explain_failure(RECDB *recdb, int code)
575 static char msg[1024];
576 snprintf(msg, sizeof(msg), "%s (got '%c') at %s line %d column %d.",
577 failure_reason(code), code & 255,
578 recdb->source, recdb->ctx.line, recdb->ctx.col);
579 if (MAIN_LOG == NULL) {
584 log_module(MAIN_LOG, LOG_ERROR, "%s", msg);
588 parse_record(const char *text, char **pname, struct record_data **prd)
594 recdb.source = "<user-supplied text>";
596 recdb.s = strdup(text);
597 recdb.length = strlen(text);
599 recdb.type = RECDB_STRING;
600 recdb.ctx.line = recdb.ctx.col = 1;
601 if ((res = setjmp(recdb.env)) == 0) {
602 parse_record_int(&recdb, pname, prd);
607 return failure_reason(res);
612 parse_database(const char *filename)
617 struct stat statinfo;
619 recdb.source = filename;
620 if (!(recdb.f = fopen(filename, "r"))) {
621 log_module(MAIN_LOG, LOG_ERROR, "Unable to open database file '%s' for reading: %s", filename, strerror(errno));
625 if (fstat(fileno(recdb.f), &statinfo)) {
626 log_module(MAIN_LOG, LOG_ERROR, "Unable to fstat database file '%s': %s", filename, strerror(errno));
630 recdb.length = (size_t)statinfo.st_size;
631 if (recdb.length == 0) {
633 return alloc_database();
638 if (!mmap_error && (recdb.s = mmap(NULL, recdb.length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fileno(recdb.f), 0)) != MAP_FAILED) {
639 recdb.type = RECDB_MMAP;
640 madvise(recdb.s, recdb.length, MADV_SEQUENTIAL);
642 /* Fall back to stdio */
644 log_module(MAIN_LOG, LOG_WARNING, "Unable to mmap database file '%s' (falling back to stdio): %s", filename, strerror(errno));
651 recdb.type = RECDB_FILE;
654 recdb.ctx.line = recdb.ctx.col = 1;
657 if ((res = setjmp(recdb.env)) == 0) {
658 db = parse_database_int(&recdb);
660 explain_failure(&recdb, res);
664 switch (recdb.type) {
667 munmap(recdb.s, recdb.length);