1 /***********************************************************************
2 * libdba - A database agent library *
3 * Copyright (C) 2000,2001 Michael Brownlow *
4 * *
5 * This program is free software; you can redistribute it and/or modify*
6 * it under the terms of the GNU General Public License as published by*
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software *
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
18 * *
19 * For questions and comments, please email the author at: *
20 * mike@wsmake.org *
21 ***********************************************************************/
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <assert.h>
28 #include <limits.h>
29
30 #include "dba_csv.h"
31
32 /*
33 * DBA_DB_CSV - Comma separated values
34 * See csv.txt for implementation information
35 */
36
37 #ifdef HAVE_TARGET_CSV
38
39 typedef struct __CSV_DATA {
40 char *data;
41 size_t len;
42 size_t size;
43 } CSV_DATA;
44
45 typedef struct __DBA_CSV_ENTRY {
46 CSV_DATA *tag;
47 CSV_DATA *val;
48 struct __DBA_CSV_ENTRY *prev;
49 struct __DBA_CSV_ENTRY *next;
50 } DBA_CSV_ENTRY;
51
52 typedef struct __DBA_CSV {
53 FILE *io;
54 DBA_CSV_ENTRY *data;
55 DBA_CSV_ENTRY *last;
56 } DBA_CSV;
57
58 static int __dba_csv_count = 0;
59 DBA_CSV_ENTRY *__dba_csv_find __P((DBA *, const char *));
60 int __dba_csv_list_add __P((DBA *, DBA_CSV_ENTRY *));
61 void __dba_csv_list_dump __P((DBA *));
62 char *__dba_csv_makestr __P((const char *, ...));
63 CSV_DATA *__dba_csv_appendchar __P((CSV_DATA *, char, size_t));
64 int __dba_csv_storeentry __P((DBA *, CSV_DATA **));
65
__dba_csv_init(void)66 int __dba_csv_init __P((void))
67 {
68 __dba_csv_count = 0;
69 return DBA_INIT_SUCCEED;
70 }
71
__dba_csv_open(DBA * dba)72 int __dba_csv_open __P((DBA *dba))
73 {
74 static int error = 0, reason = 0;
75 DBA_CSV *tdb;
76
77 error = 0;
78 reason = 0;
79
80 tdb = (DBA_CSV *)__dba_malloc(sizeof(DBA_CSV));
81 if(tdb == NULL) {
82 reason = errno;
83 error = 1;
84 } else {
85 dba->db = tdb;
86 tdb->data = NULL;
87 if(!__dba_csv_load_db(dba)) {
88 error = 2;
89 }
90 }
91
92 if(error != 0) {
93 __dba_print_error("`%s': (csv_open error=`%d' errno=`%s')\n",
94 dba->filename, error, (reason)?strerror(reason):"None");
95 return 0;
96 }
97
98 __dba_print_debug("opened agent %#x (type=CSV)\n", dba);
99 __dba_csv_count++;
100
101 return 1;
102 }
103
__dba_csv_appendchar(CSV_DATA * column,char c,size_t chunk)104 CSV_DATA *__dba_csv_appendchar
105 (CSV_DATA *column, char c, size_t chunk)
106 {
107 /* check buffer size and realloc if needed */
108 if((column->len + 2) > column->size) {
109 column->data = (char *)__dba_realloc(column->data, column->size+chunk);
110 column->size += chunk;
111 }
112
113 /* store char */
114 column->data[column->len] = c;
115 column->data[++column->len] = '\0';
116
117 return column;
118 }
119
__dba_csv_storeentry(DBA * dba,CSV_DATA * data[2])120 int __dba_csv_storeentry
121 (DBA *dba, CSV_DATA *data[2])
122 {
123 DBA_CSV_ENTRY *entry;
124
125 data[0]->size = data[0]->len + 1;
126 data[0]->data = (char *)__dba_realloc(data[0]->data, data[0]->size);
127 data[0]->data[data[0]->len] = '\0';
128 data[1]->size = data[1]->len + 1;
129 data[1]->data = (char *)__dba_realloc(data[1]->data, data[1]->size);
130 data[1]->data[data[1]->len] = '\0';
131
132 /* store buff2 in current entry */
133 entry = (DBA_CSV_ENTRY *)__dba_malloc(sizeof(DBA_CSV_ENTRY));
134 entry->next = NULL;
135 entry->prev = NULL;
136 entry->tag = data[0];
137 entry->val = data[1];
138
139 /* store entry in CSV entry list */
140 if(!__dba_csv_list_add(dba, entry)) {
141 __dba_print_error("error loading entry into memory. quitting.\n");
142 return 0;
143 }
144
145 return 1;
146 }
147
148 /* Because libdba only provides a hash-type interface, this loader
149 only does CSV files with 2 columns. However, it can be easily
150 extended */
__dba_csv_load_db(DBA * dba)151 int __dba_csv_load_db __P((DBA *dba))
152 {
153 DBA_CSV *tdb;
154 static int achunk = 32;
155 int c;
156 CSV_DATA *buff[2];
157 int col = 0;
158 int err;
159 int reason, linenum = 1;
160 int inquotes = 0;
161
162 buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
163 buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
164 buff[0]->data = (char *)__dba_malloc(achunk); /* The key */
165 buff[1]->data = (char *)__dba_malloc(achunk); /* The value */
166 buff[0]->size = achunk;
167 buff[1]->size = achunk;
168 buff[0]->len = 0;
169 buff[1]->len = 0;
170
171 /* FIXME: create tmp file before we mess with things */
172
173 tdb = dba->db;
174 tdb->io = fopen(dba->filename, "a+");
175 if(tdb->io == NULL) {
176 err = errno;
177 __dba_print_error("couldn't open `%s' for writing. (%d; %s)\n",
178 dba->filename, err, strerror(err));
179 return 0;
180 }
181 fseek(tdb->io, 0, SEEK_SET);
182
183 c=fgetc(tdb->io);
184 while(c != EOF) {
185 if((c!=',') && (c!='"') && (c!='\n') && (c!=EOF)) { /* Unquoted string */
186 __dba_csv_appendchar(buff[col], c, achunk);
187 c=fgetc(tdb->io);
188 while((c!=EOF) && (c!='\n') && (c!=',')) {
189 __dba_csv_appendchar(buff[col], c, achunk);
190 c=fgetc(tdb->io);
191 }
192 if((c=='\n')||(c==EOF)) {
193 linenum++;
194 __dba_csv_storeentry(dba, buff);
195 buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
196 buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
197 buff[0]->data = (char *)__dba_malloc(achunk);
198 buff[1]->data = (char *)__dba_malloc(achunk);
199 buff[0]->size = achunk;
200 buff[1]->size = achunk;
201 buff[0]->len = 0;
202 buff[1]->len = 0;
203 col = 0;
204 } else if(c==',') {
205 col++;
206 if(col > 1) {
207 __dba_print_warning(
208 "Warning: >2 columns on line %d, overwriting last column",
209 linenum);
210 col = 1;
211 buff[col]->len = 0;
212 }
213 }
214 } else if(c == '"') { /* Quoted string */
215 inquotes = 1;
216 while(inquotes) {
217 c=fgetc(tdb->io);
218 while((c!=EOF) && (c!='\n') && (c!='"')) {
219 __dba_csv_appendchar(buff[col], c, achunk);
220 c=fgetc(tdb->io);
221 }
222 if(c=='"') {
223 c=fgetc(tdb->io);
224 if(c=='"') {
225 __dba_csv_appendchar(buff[col], c, achunk);
226 } else if(c==',') {
227 col++;
228 if(col > 1) {
229 __dba_print_warning(
230 "Warning: >2 columns on line %d, overwriting last column",
231 linenum);
232 col = 1;
233 buff[col]->len = 0;
234 }
235 inquotes = 0;
236 } else if((c=='\n')||(c==EOF)) {
237 inquotes = 0;
238 linenum++;
239 __dba_csv_storeentry(dba, buff);
240 buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
241 buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
242 buff[0]->data = (char *)__dba_malloc(achunk);
243 buff[1]->data = (char *)__dba_malloc(achunk);
244 buff[0]->size = achunk;
245 buff[1]->size = achunk;
246 buff[0]->len = 0;
247 buff[1]->len = 0;
248 col = 0;
249 }
250 } else if((c=='\n')||(c==EOF)) {
251 inquotes = 0;
252 __dba_print_error("bad CSV entry:%s:line %d:`%s':`%s'\n",
253 dba->filename, linenum, buff[0]->data, buff[1]->data);
254 }
255 }
256 } else if(c == ',') { /* Column separator */
257 col++;
258 if(col > 1) {
259 __dba_print_warning(
260 "Warning: >2 columns on line %d, overwriting last column",
261 linenum);
262 col = 1;
263 buff[col]->len = 0;
264 }
265 } else if((c == '\n')||(c == EOF)) {
266 linenum++;
267 __dba_csv_storeentry(dba, buff);
268 buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
269 buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
270 buff[0]->data = (char *)__dba_malloc(achunk);
271 buff[1]->data = (char *)__dba_malloc(achunk);
272 buff[0]->size = achunk;
273 buff[1]->size = achunk;
274 buff[0]->len = 0;
275 buff[1]->len = 0;
276 col = 0;
277 }
278 c=fgetc(tdb->io);
279 }
280
281 __dba_free(buff[0]->data);
282 __dba_free(buff[0]);
283 __dba_free(buff[1]->data);
284 __dba_free(buff[1]);
285
286 if(fclose(tdb->io) == EOF) {
287 reason = errno;
288 }
289
290 return 1;
291 }
292
__dba_csv_sync_db(DBA * dba)293 int __dba_csv_sync_db(DBA *dba)
294 {
295 DBA_CSV *tdb = dba->db;
296 DBA_CSV_ENTRY *tmp = NULL;
297 int reason = 0, error = 0;
298 char *p;
299
300 if(tdb != NULL) {
301 tmp = tdb->data;
302
303 tdb->io = fopen(dba->filename, "w");
304
305 while(tmp != NULL) {
306 fprintf(tdb->io, "\""); /* We always quote our values */
307 p = tmp->tag->data;
308 while(*p != '\0') {
309 if(*p == '"') {
310 fprintf(tdb->io, "\"\"");
311 } else {
312 fprintf(tdb->io, "%c", *p);
313 }
314 p++;
315 }
316 fprintf(tdb->io, "\",\"");
317 p = tmp->val->data;
318 while(*p != '\0') {
319 if(*p == '"') {
320 fprintf(tdb->io, "\"\"");
321 } else {
322 fprintf(tdb->io, "%c", *p);
323 }
324 p++;
325 }
326 fprintf(tdb->io, "\"\n");
327 tmp = tmp->next;
328 }
329
330 if(fclose(tdb->io) == EOF) {
331 reason = errno;
332 }
333
334 if((reason != EBADF)&&(reason != 0)) {
335 error = 1;
336 }
337 }
338
339 if(error != 0) {
340 __dba_print_error("`%s': (csv_close error=`%d' errno=`%s')\n",
341 dba->filename, error, (reason)?strerror(reason):"None");
342 return reason;
343 }
344
345 return 0;
346 }
347
__dba_csv_list_add(DBA * dba,DBA_CSV_ENTRY * entry)348 int __dba_csv_list_add __P((DBA *dba, DBA_CSV_ENTRY *entry))
349 {
350 DBA_CSV *tdb = dba->db;
351
352 if(entry == NULL) {
353 __dba_print_error("internal attempt to add a null entry. that's weird.\n");
354 return 0;
355 }
356 if(tdb == NULL) {
357 __dba_print_error("database doesn't exist??? something is screwed up.\n");
358 return 0;
359 }
360
361 if(tdb->data == NULL) {
362 tdb->data = entry;
363 tdb->last = entry;
364 tdb->data->prev = NULL;
365 tdb->data->next = NULL;
366 } else {
367 /* for now we tack it onto the end of the list, later we will
368 make this a sorted add, so that searches will be faster */
369 tdb->last->next = entry;
370 entry->prev = tdb->last;
371 tdb->last = entry;
372 }
373
374 __dba_csv_list_dump(dba);
375
376 return 1;
377 }
378
__dba_csv_list_dump(DBA * dba)379 void __dba_csv_list_dump __P((DBA *dba))
380 {
381 DBA_CSV *tdb = dba->db;
382 DBA_CSV_ENTRY *tmp = tdb->data;
383
384 __dba_print_debug("list dump: \n");
385 while(tmp != NULL) {
386 __dba_print_debug("[%s,%s]\n", tmp->tag->data, tmp->val->data);
387 tmp = tmp->next;
388 }
389 }
390
__dba_csv_close(DBA * dba)391 int __dba_csv_close __P((DBA *dba))
392 {
393 DBA_CSV *tdb;
394 DBA_CSV_ENTRY *tmp, *tmp2 = NULL;
395 static int error = 0, reason = 0;
396
397 if(dba == NULL) { __dba_print_error("internal attempt to free a NULL dba. "
398 "well that's funny..."); }
399 __dba_print_debug("closing agent %#x (type=CSV)\n", dba);
400 tdb = dba->db;
401 if(tdb != NULL) {
402
403 tmp = tdb->data;
404
405 /* Flush to disk a last time */
406 error = __dba_csv_sync_db(dba);
407
408 while(tmp != NULL) {
409 __dba_free(tmp->tag->data);
410 __dba_free(tmp->val->data);
411 __dba_free(tmp->tag);
412 __dba_free(tmp->val);
413 tmp2 = tmp->next;
414 __dba_free(tmp);
415 tmp = tmp2;
416 }
417
418 if((error == EBADF)&&(reason == 0)) {
419 __dba_free(tdb);
420 tdb = NULL;
421 }
422 }
423
424 __dba_csv_count--;
425
426 if(error != 0) {
427 return error;
428 }
429
430 return 0;
431 }
432
__dba_csv_add(DBA * dba,int data_type,const char * addkey,...)433 int __dba_csv_add __P((DBA *dba, int data_type, const char *addkey, ...))
434 {
435 DBA_CSV_ENTRY *entry = NULL;
436 va_list ap;
437 long dt_long = 0;
438 char *dt_string = NULL;
439
440 assert(addkey);
441
442 va_start(ap, addkey);
443 __dba_print_debug("starting add\n");
444
445
446 if(__dba_csv_exists(dba, addkey)) {
447 __dba_print_warning("key `%s' already exists in database\n", addkey);
448 va_end(ap);
449 return 0;
450 }
451
452 entry = (DBA_CSV_ENTRY *)__dba_malloc(sizeof(DBA_CSV_ENTRY));
453 memset(entry,0,sizeof(DBA_CSV_ENTRY));
454 entry->tag = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
455 entry->val = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA));
456 entry->tag->data = (void *)strdup(addkey);
457
458 switch(data_type) {
459 case DBA_DT_LONG :
460 dt_long = va_arg(ap, long);
461 entry->val->data = __dba_csv_makestr("%ld", dt_long);
462 __dba_print_debug("added `%s':`%ld'\n", addkey, dt_long);
463 break;
464 case DBA_DT_STRING :
465 dt_string = va_arg(ap, char *);
466 entry->val->data = (void *)strdup(dt_string);
467 __dba_print_debug("added `%s':`%s'\n", addkey, dt_string);
468 break;
469 default :
470 __dba_print_error("unknown type (%d)\n", data_type);
471 va_end(ap);
472 return 0;
473 };
474
475 if(!__dba_csv_list_add(dba, entry)) {
476 __dba_print_error("hmm, couldn't add entry to the list. that's bad.\n");
477 return 0;
478 }
479 __dba_csv_sync_db(dba);
480
481 va_end(ap);
482 __dba_print_debug("finished add\n");
483
484 return 1;
485 }
486
487 /* this code was snagged from the printf man page.
488 FIXME: optimize memory usage before returning p */
__dba_csv_makestr(const char * fmt,...)489 char *__dba_csv_makestr __P((const char *fmt, ...))
490 {
491 /* Guess we need no more than 128 bytes. */
492 int n, size = 128;
493 char *p;
494 va_list ap;
495 if ((p = malloc (size)) == NULL)
496 return NULL;
497 while (1) {
498 /* Try to print in the allocated space. */
499 va_start(ap, fmt);
500 #ifdef HAVE_VSNPRINTF
501 n = vsnprintf (p, size, fmt, ap);
502 #else
503 n = vsprintf (p,fmt,ap);
504 #endif
505 va_end(ap);
506 /* If that worked, return the string. */
507 if (n > -1 && n < size)
508 return p;
509 /* Else try again with more space. */
510 if (n > -1) /* glibc 2.1 */
511 size = n+1; /* precisely what is needed */
512 else /* glibc 2.0 */
513 size *= 2; /* twice the old size */
514 if ((p = realloc (p, size)) == NULL)
515 return NULL;
516 }
517 }
518
__dba_csv_set(DBA * dba,int data_type,const char * setkey,...)519 int __dba_csv_set __P((DBA *dba, int data_type, const char *setkey, ...))
520 {
521 va_list ap;
522 DBA_CSV *tdb = dba->db;
523 DBA_CSV_ENTRY *entry = NULL;
524 int done = 0;
525 long dt_long = 0;
526 char *dt_string = NULL;
527
528 assert(setkey);
529
530 va_start(ap, setkey);
531
532 if(tdb->data == NULL) {
533 return 0;
534 }
535
536 entry = tdb->data;
537 while((!done) && (entry != NULL)) {
538 if(!strcmp(setkey,entry->tag->data)) {
539 done = 1;
540 } else {
541 entry = entry->next;
542 }
543 }
544
545 if(entry == NULL) {
546 return 0;
547 }
548
549 switch(data_type) {
550 case DBA_DT_LONG :
551 dt_long = va_arg(ap, long);
552 entry->val->data = __dba_csv_makestr("%ld", dt_long);
553 __dba_print_debug("set `%s':`%ld'\n", setkey, dt_long);
554 break;
555 case DBA_DT_STRING :
556 dt_string = va_arg(ap, char *);
557 entry->val->data = (void *)strdup(dt_string);
558 __dba_print_debug("set `%s':`%s'\n", setkey, dt_string);
559 break;
560 }
561
562 va_end(ap);
563
564 __dba_csv_sync_db(dba);
565
566 return 1;
567 }
568
__dba_csv_del(DBA * dba,const char * delkey)569 int __dba_csv_del __P((DBA *dba, const char *delkey))
570 {
571 DBA_CSV *tdb = dba->db;
572 DBA_CSV_ENTRY *entry = NULL;
573
574 assert(delkey);
575
576 if(tdb->data != NULL) {
577 entry=tdb->data;
578 while(entry != NULL) {
579 if(!strcmp(delkey, entry->tag->data)) {
580 if((entry->prev == NULL)&&(entry->next == NULL)) {
581 tdb->data = NULL;
582 } else if(entry->prev == NULL) {
583 tdb->data = entry->next;
584 tdb->data->prev = NULL;
585 } else if(entry->next == NULL) {
586 entry->prev->next = NULL;
587 } else {
588 entry->prev->next = entry->next;
589 entry->next->prev = entry->prev;
590 }
591 __dba_csv_sync_db(dba);
592 __dba_free(entry->tag->data);
593 __dba_free(entry->val->data);
594 __dba_free(entry);
595 return 1;
596 }
597 entry = entry->next;
598 }
599 }
600
601 return 0;
602 }
603
__dba_csv_get(DBA * dba,int data_type,const char * testkey,void * value)604 void *__dba_csv_get __P((DBA *dba, int data_type, const char *testkey,
605 void *value))
606 {
607 int data = 0;
608 long dt_long = 0;
609 DBA_CSV *tdb = dba->db;
610 DBA_CSV_ENTRY *entry = NULL;
611
612 assert(testkey);
613
614 if(tdb!=NULL) {
615 if(tdb->data == NULL) {
616 __dba_print_warning("database has no entries\n", testkey);
617 return NULL;
618 }
619
620 if((entry = __dba_csv_find(dba, testkey)) == NULL) {
621 __dba_print_warning("key `%s' was not found in database\n", testkey);
622 return NULL;
623 }
624
625 /* set value based on returned data */
626 switch(data_type) {
627 case DBA_DT_LONG :
628 dt_long = strtol(entry->val->data,NULL,10);
629 memcpy(value,&dt_long,sizeof(dt_long));
630 break;
631 case DBA_DT_STRING :
632 value = NULL;
633 value = (void *)__dba_malloc(strlen(entry->val->data) + 1);
634 if(value == NULL) {
635 __dba_print_warning("couldn't allocate memory for dba_csv_get.\n");
636 return NULL;
637 }
638 memset(value,0,strlen(entry->val->data)+1);
639 memcpy(value,entry->val->data,strlen(entry->val->data)+1);
640 break;
641 }
642
643 if(data == 0) {
644 return value;
645 } else {
646 /* This shouldn't happen, assert */
647 assert(0);
648 }
649 }
650
651 return NULL; /* database was null */
652 }
653
__dba_csv_exists(DBA * dba,const char * testkey)654 int __dba_csv_exists __P((DBA *dba, const char *testkey))
655 {
656 DBA_CSV *tdb = dba->db;
657 DBA_CSV_ENTRY *entry = NULL;
658
659 assert(testkey);
660
661 if(tdb->data == NULL) {
662 return 0;
663 }
664
665 entry=tdb->data;
666
667 while(entry != NULL) {
668 if(!strncmp(testkey, entry->tag->data, strlen(entry->tag->data))) {
669 return 1;
670 }
671 entry = entry->next;
672 }
673
674 return 0;
675 }
676
__dba_csv_find(DBA * dba,const char * testkey)677 DBA_CSV_ENTRY *__dba_csv_find __P((DBA *dba, const char *testkey))
678 {
679 DBA_CSV *tdb = dba->db;
680 DBA_CSV_ENTRY *entry = NULL;
681
682 assert(testkey);
683
684 if(tdb->data == NULL) {
685 return NULL;
686 }
687
688 entry=tdb->data;
689
690 while(entry != NULL) {
691 if(!strcmp(testkey, entry->tag->data)) {
692 return entry;
693 }
694 entry = entry->next;
695 }
696
697 return NULL;
698 }
699
__dba_csv_register(DBA * dba)700 int __dba_csv_register __P((DBA *dba))
701 {
702 dba->open = (int (*)(DBA *))(__dba_csv_open);
703 dba->close = (int (*)(DBA *))(__dba_csv_close);
704 dba->add = (int (*)(DBA *, int, const char *, ...))(__dba_csv_add);
705 dba->set = (int (*)(DBA *, int, const char *, ...))(__dba_csv_set);
706 dba->del = (int (*)(DBA *, const char *))(__dba_csv_del);
707 dba->get = (void *(*)(DBA *, int, const char *, void *))(__dba_csv_get);
708 dba->exists = (int (*)(DBA *, const char *))(__dba_csv_exists);
709
710 return 1;
711 }
712
713 #endif
714
715