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