00001
00013 #include <assert.h>
00014 #include <ctype.h>
00015 #include <dirent.h>
00016 #include <errno.h>
00017 #include <limits.h>
00018 #include <stdarg.h>
00019 #include <stdio.h>
00020 #include <stdlib.h>
00021 #include <string.h>
00022 #include <sys/stat.h>
00023 #include <sys/types.h>
00024
00025 #include "boris.h"
00026 #include "plugin.h"
00027
00028 #define MKDIR(d) mkdir(d, 0777)
00029 #define PERROR(m) perror(m)
00030 #define VERBOSE printf
00031
00032 #define FDB_VALUE_MAX 4096
00033
00037 struct plugin_fdb_class {
00038 struct plugin_basic_class base_class;
00039 struct plugin_fdb_interface fdb_interface;
00040 };
00041 extern const struct plugin_fdb_class plugin_class;
00042
00047 struct fdb_write_handle {
00048 FILE *f;
00049 char *filename_tmp;
00050 char *domain, *id;
00051 int error_fl;
00052 };
00053
00058 struct fdb_read_handle {
00059 FILE *f;
00060 char *filename;
00061 int line_number;
00062 int error_fl;
00063 size_t alloc_len;
00064 char *line;
00065 };
00066
00071 struct fdb_iterator {
00072 DIR *d;
00073 char *pathname;
00074 char *curr_id;
00075 char *domain;
00076 };
00077
00078
00082 static int ishex(const char code[2]) {
00083 return isxdigit(code[0]) && isxdigit(code[0]);
00084 }
00085
00089 static unsigned unhex(const char code[2]) {
00090 const char hextab[] = {
00091 ['0']=0, ['1']=1, ['2']=2, ['3']=3, ['4']=4,
00092 ['5']=5, ['6']=6, ['7']=7, ['8']=8, ['9']=9,
00093 ['a']=0xa, ['b']=0xc, ['c']=0xc, ['d']=0xd, ['e']=0xe, ['f']=0xf,
00094 ['A']=0xa, ['B']=0xb, ['C']=0xc, ['D']=0xd, ['E']=0xe, ['F']=0xf,
00095 };
00096
00097 return hextab[(unsigned)code[0]]*16 + hextab[(unsigned)code[1]];
00098 }
00099
00103 static void unescape(char *str) {
00104 char *e;
00105
00106 for(e=str+strlen(str);e>str && isspace(e[-1]);e--) ;
00107 *e=0;
00108
00109
00110
00111
00112
00113 for(e=str;*str;str++) {
00114 if(*str=='%') {
00115 if(ishex(str+1)) {
00116 *e++=unhex(str+1);
00117 str+=2;
00118 } else {
00119 *e++=*str;
00120 }
00121 } else {
00122 *e++=*str;
00123 }
00124 }
00125 *e=0;
00126
00127 }
00128
00132 static char *fdb_basepath(const char *domain) {
00133 char path[PATH_MAX];
00134 snprintf(path, sizeof path, "data/%s", domain);
00135 return strdup(path);
00136 }
00137
00141 static char *fdb_makepath(const char *domain, const char *id) {
00142 char path[PATH_MAX];
00143 snprintf(path, sizeof path, "data/%s/%s", domain, id);
00144 return strdup(path);
00145 }
00146
00150 static char *fdb_makepath_tmp(const char *domain, const char *id) {
00151 char path[PATH_MAX];
00152 snprintf(path, sizeof path, "data/%s/%s.tmp", domain, id);
00153 return strdup(path);
00154 }
00155
00160 static int fdb_istempname(const char *filename) {
00161 size_t len, extlen=strlen(".tmp");
00162 if(!filename) return 0;
00163 len=strlen(filename);
00164 if(len>extlen && !strcmp(filename+len-extlen, ".tmp"))
00165 return 1;
00166 return 0;
00167 }
00168
00172 static void fdb_write_handle_free(struct fdb_write_handle *h) {
00173 free(h->filename_tmp); h->filename_tmp=NULL;
00174 free(h->domain); h->domain=NULL;
00175 free(h->id); h->id=NULL;
00176 free(h);
00177 }
00178
00182 static void fdb_read_handle_free(struct fdb_read_handle *h) {
00183 free(h->filename); h->filename=NULL;
00184 free(h->line); h->line=NULL;
00185 free(h);
00186 }
00187
00192 static int fdb_parse_line(char *line, const char **name, const char **value) {
00193 char *e, *b;
00194
00195 while(isspace(*line)) line++;
00196 *name=line;
00197
00198
00199 for(e=line;*e && *e != '=';e++) ;
00200 if(!*e) return 0;
00201
00202
00203 for(b=e;b>line && isspace(b[-1]);b--) ;
00204 *b=0;
00205
00206 *(e++)=0;
00207 line=e;
00208
00209
00210 while(isspace(*line)) line++;
00211 *value=line;
00212
00213
00214 unescape(line);
00215
00216 return 1;
00217 }
00218
00223 static int fdb_domain_init(const char *domain) {
00224 char *pathname;
00225 pathname=fdb_basepath(domain);
00226 if(MKDIR(pathname)==-1 && errno!=EEXIST) {
00227 PERROR(pathname);
00228 free(pathname);
00229 return 0;
00230 }
00231 free(pathname);
00232 return 1;
00233 }
00237 static struct fdb_write_handle *fdb_write_begin(const char *domain, const char *id) {
00238 struct fdb_write_handle *ret;
00239 FILE *f;
00240 char *filename_tmp;
00241
00242 filename_tmp=fdb_makepath_tmp(domain, id);
00243 f=fopen(filename_tmp, "w");
00244 if(!f) {
00245 PERROR(filename_tmp);
00246 free(filename_tmp);
00247 return 0;
00248 }
00249
00250 ret=calloc(1, sizeof *ret);
00251 ret->f=f;
00252 ret->filename_tmp=filename_tmp;
00253 ret->domain=strdup(domain);
00254 ret->id=strdup(id);
00255 ret->error_fl=0;
00256
00257 return ret;
00258 }
00259
00263 static struct fdb_write_handle *fdb_write_begin_uint(const char *domain, unsigned id) {
00264 char numbuf[22];
00265 snprintf(numbuf, sizeof numbuf, "%u", id);
00266 return fdb_write_begin(domain, numbuf);
00267 }
00268
00273 static int fdb_write_pair(struct fdb_write_handle *h, const char *name, const char *value_str) {
00274 int res;
00275 size_t escaped_len, i;
00276 char *escaped_value;
00277
00278 assert(h != NULL);
00279 assert(name != NULL);
00280 assert(value_str != NULL);
00281
00282 if(h->error_fl) return 0;
00283
00284
00285 for(i=0,escaped_len=0;value_str[i];i++) {
00286 if(isprint(value_str[i]) && !isspace(value_str[i]) && value_str[i]!='%' && value_str[i]!='"') {
00287 escaped_len++;
00288 } else {
00289 escaped_len+=3;
00290 }
00291 }
00292
00293
00294
00295
00296 escaped_value=malloc(escaped_len+1);
00297 if(!escaped_value) {
00298 PERROR("malloc()");
00299 return 0;
00300 }
00301
00302 for(i=0;*value_str;value_str++) {
00303 if(isprint(*value_str) && !isspace(*value_str) && *value_str!='%' && *value_str!='"') {
00304 escaped_value[i++]=*value_str;
00305 } else {
00306
00307 escaped_value[i]='%';
00308 sprintf(escaped_value+i+1, "%02hhX", (unsigned char)*value_str);
00309 i+=3;
00310 }
00311 }
00312 assert(escaped_value != NULL);
00313 escaped_value[i]=0;
00314
00315 res=fprintf(h->f, "%-12s= %s\n", name, escaped_value);
00316 free(escaped_value);
00317 if(res<0) h->error_fl=1;
00318 return res >= 0;
00319 }
00320
00326 static int fdb_write_format(struct fdb_write_handle *h, const char *name, const char *value_fmt, ...) {
00327 char buf[FDB_VALUE_MAX];
00328 va_list ap;
00329
00330 if(h->error_fl) return 0;
00331
00332 va_start(ap, value_fmt);
00333 vsnprintf(buf, sizeof buf, value_fmt, ap);
00334 va_end(ap);
00335 return fdb_write_pair(h, name, buf);
00336 }
00337
00341 static int fdb_write_end(struct fdb_write_handle *h) {
00342 char *filename;
00343
00344 assert(h != NULL);
00345 assert(h->f != NULL);
00346 assert(h->filename_tmp != NULL);
00347 assert(h->domain != NULL);
00348 assert(h->id != NULL);
00349
00350
00351 if(h->f) {
00352 if(fclose(h->f)) {
00353 perror(h->filename_tmp);
00354 h->error_fl=1;
00355 }
00356 h->f=NULL;
00357 }
00358
00359 if(h->error_fl) {
00360 free(filename);
00361
00362 if(!remove(h->filename_tmp)) {
00363 perror(h->filename_tmp);
00364 }
00365
00366 fdb_write_handle_free(h);
00367 return 0;
00368 } else {
00369
00370
00371
00372 filename=fdb_makepath(h->domain, h->id);
00373 if(rename(h->filename_tmp, filename)) {
00374 perror(h->filename_tmp);
00375 free(filename);
00376 fdb_write_handle_free(h);
00377 return 0;
00378 }
00379 free(filename);
00380
00381
00382 fdb_write_handle_free(h);
00383 return 1;
00384 }
00385 }
00386
00391 static void fdb_write_abort(struct fdb_write_handle *h) {
00392 h->error_fl=1;
00393 }
00394
00398 static struct fdb_read_handle *fdb_read_begin(const char *domain, const char *id) {
00399 struct fdb_read_handle *ret;
00400 FILE *f;
00401 char *filename;
00402
00403 filename=fdb_makepath(domain, id);
00404 f=fopen(filename, "r");
00405 if(!f) {
00406 PERROR(filename);
00407 free(filename);
00408 return 0;
00409 }
00410
00411 ret=calloc(1, sizeof *ret);
00412 ret->f=f;
00413 ret->filename=filename;
00414 ret->line_number=0;
00415 ret->error_fl=0;
00416 ret->alloc_len=4;
00417 ret->line=malloc(ret->alloc_len);
00418
00419 return ret;
00420 }
00421
00422 static struct fdb_read_handle *fdb_read_begin_uint(const char *domain, unsigned id) {
00423 char numbuf[22];
00424 snprintf(numbuf, sizeof numbuf, "%u", id);
00425 return fdb_read_begin(domain, numbuf);
00426 }
00427
00431 static int fdb_read_next(struct fdb_read_handle *h, const char **name, const char **value) {
00432 size_t ofs, newofs;
00433
00434 assert(h != NULL);
00435 assert(h->f != NULL);
00436 assert(h->alloc_len > 0);
00437
00438 h->line_number++;
00439 ofs=0;
00440 while(!feof(h->f)) {
00441 if(!fgets(h->line+ofs, h->alloc_len-ofs, h->f)) {
00442 if(ofs) {
00443 VERBOSE("%s:%d:missing newline before EOF.\n", h->filename, h->line_number);
00444 h->error_fl=1;
00445 }
00446 return 0;
00447 }
00448 newofs=ofs+strlen(h->line+ofs);
00449 if(strchr(h->line+ofs, '\n')) {
00450
00451 return fdb_parse_line(h->line, name, value);
00452 }
00453 ofs=newofs;
00454
00455 if(newofs*2 >= h->alloc_len) {
00456 char *newline;
00457 size_t newlen;
00458
00459 newlen=((newofs*2)+4096-1)*4096/4096;
00460 newline=realloc(h->line, newlen);
00461 if(!newline) {
00462 PERROR(h->filename);
00463 h->error_fl=1;
00464 return 0;
00465 }
00466 h->line=newline;
00467 h->alloc_len=newlen;
00468 }
00469 }
00470 return 0;
00471 }
00472
00476 static int fdb_read_end(struct fdb_read_handle *h) {
00477 int ret;
00478
00479 assert(h != NULL);
00480 assert(h->f != NULL);
00481
00482 ret=!h->error_fl;
00483 if(h->f) {
00484 fclose(h->f);
00485 h->f=NULL;
00486 }
00487
00488 fdb_read_handle_free(h);
00489 return ret;
00490 }
00491
00495 static struct fdb_iterator *fdb_iterator_begin(const char *domain) {
00496 char *pathname;
00497 DIR *d;
00498 struct fdb_iterator *it;
00499
00500 assert(domain != NULL);
00501
00502 pathname=fdb_basepath(domain);
00503
00504 d=opendir(pathname);
00505 if(!d) {
00506 PERROR(pathname);
00507 free(pathname);
00508 return 0;
00509 }
00510
00511 it=calloc(1, sizeof *it);
00512 if(!it) {
00513 PERROR("calloc()");
00514 free(pathname);
00515 return 0;
00516 }
00517
00518 it->d=d;
00519 it->pathname=pathname;
00520 it->curr_id=NULL;
00521 it->domain=strdup(domain);
00522 return it;
00523 }
00524
00529 static const char *fdb_iterator_next(struct fdb_iterator *it) {
00530 struct dirent *de;
00531 struct stat st;
00532 char *filename;
00533
00534 assert(it != NULL);
00535 assert(it->d != NULL);
00536
00537
00538 next:
00539 de=readdir(it->d);
00540 if(!de) return NULL;
00541 if(de->d_name[0]=='.') goto next;
00542 if(fdb_istempname(de->d_name)) {
00543 goto next;
00544 }
00545 if(de->d_name[0] && de->d_name[strlen(de->d_name)-1]=='~') {
00546 VERBOSE("skip things that don't look like data files:%s\n", de->d_name);
00547 goto next;
00548 }
00549
00550
00551 filename=fdb_makepath(it->domain, de->d_name);
00552 if(stat(filename, &st)) {
00553 PERROR(filename);
00554 free(filename);
00555 goto next;
00556 }
00557 free(filename);
00558
00559 if(!S_ISREG(st.st_mode)) {
00560 VERBOSE("Ignoring directories and other non-regular files:%s\n", de->d_name);
00561 goto next;
00562 }
00563
00564 free(it->curr_id);
00565 return it->curr_id=strdup(de->d_name);
00566 }
00567
00571 static void fdb_iterator_end(struct fdb_iterator *it) {
00572 assert(it != NULL);
00573 closedir(it->d);
00574 free(it->pathname); it->pathname=NULL;
00575 free(it->curr_id); it->curr_id=NULL;
00576 free(it->domain); it->domain=NULL;
00577 free(it);
00578 }
00579
00580
00581 #ifdef STAND_ALONE_TEST
00582 static int fdb_test1(void) {
00583 struct fdb_write_handle *h;
00584
00585 fdb_domain_init("room");
00586
00587 h=fdb_write_begin("room", "123");
00588 if(!h) return 0;
00589
00590 fdb_write_format(h, "id", "%d", 123);
00591 fdb_write_pair(h, "owner", "orange");
00592 fdb_write_pair(h, "description", " Hello World\nThis is great stuff.");
00593
00594 fdb_write_end(h);
00595 return 1;
00596 }
00597
00598 static int fdb_test2(void) {
00599 struct fdb_iterator *it;
00600 const char *id;
00601 it=fdb_iterator_begin("users");
00602 if(!it) return 0;
00603
00604 while((id=fdb_iterator_next(it))) {
00605 VERBOSE("Found item \"%s\"\n", id);
00606 }
00607
00608 fdb_iterator_end(it);
00609 return 1;
00610 }
00611
00612 static int fdb_test3(void) {
00613 struct fdb_read_handle *h;
00614 const char *name, *id;
00615 int res;
00616
00617 fdb_domain_init("room");
00618
00619 h=fdb_read_begin("room", "123");
00620 if(!h) return 0;
00621
00622 while(fdb_read_next(h, &name, &id)) {
00623 VERBOSE("Read \"%s\"=\"%s\"\n", name, id);
00624 }
00625
00626 res=fdb_read_end(h);
00627 if(!res) {
00628 VERBOSE("Read failure.\n");
00629 }
00630 return res;
00631 }
00632
00636 int main() {
00637 VERBOSE("*** TEST 1 ***\n");
00638 if(!fdb_test1()) goto failure;
00639 VERBOSE("*** TEST 2 ***\n");
00640 if(!fdb_test2()) goto failure;
00641 VERBOSE("*** TEST 3 ***\n");
00642 if(!fdb_test3()) goto failure;
00643 return 0;
00644 failure:
00645 fprintf(stderr, "It didn't work.\n");
00646 return 1;
00647 }
00648 #else
00649 static int initialize(void) {
00650 fprintf(stderr, "loaded %s\n", plugin_class.base_class.class_name);
00651 service_attach_fdb(&plugin_class.base_class, &plugin_class.fdb_interface);
00652 b_log(B_LOG_INFO, "logging", "FDB-file system loaded (" __FILE__ " compiled " __TIME__ " " __DATE__ ")");
00653 return 1;
00654 }
00655
00656 static int shutdown(void) {
00657 service_detach_fdb(&plugin_class.base_class);
00658 return 1;
00659 }
00660
00661 const struct plugin_fdb_class plugin_class = {
00662 .base_class = { PLUGIN_API, "fdb", initialize, shutdown },
00663 .fdb_interface = {
00664 fdb_domain_init,
00665 fdb_write_begin,
00666 fdb_write_begin_uint,
00667 fdb_write_pair,
00668 fdb_write_format,
00669 fdb_write_end,
00670 fdb_write_abort,
00671 fdb_read_begin,
00672 fdb_read_begin_uint,
00673 fdb_read_next,
00674 fdb_read_end,
00675 fdb_iterator_begin,
00676 fdb_iterator_next,
00677 fdb_iterator_end,
00678 },
00679 };
00680 #endif