/*********************************************************************** * libdba - A database agent library * * Copyright (C) 2000,2001 Michael Brownlow * * * * This program is free software; you can redistribute it and/or modify* * it under the terms of the GNU General Public License as published by* * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * * * For questions and comments, please email the author at: * * mike@wsmake.org * ***********************************************************************/ #include #include #include #include #include #include #include #include "dba_csv.h" /* * DBA_DB_CSV - Comma separated values * See csv.txt for implementation information */ #ifdef HAVE_TARGET_CSV typedef struct __CSV_DATA { char *data; size_t len; size_t size; } CSV_DATA; typedef struct __DBA_CSV_ENTRY { CSV_DATA *tag; CSV_DATA *val; struct __DBA_CSV_ENTRY *prev; struct __DBA_CSV_ENTRY *next; } DBA_CSV_ENTRY; typedef struct __DBA_CSV { FILE *io; DBA_CSV_ENTRY *data; DBA_CSV_ENTRY *last; } DBA_CSV; static int __dba_csv_count = 0; DBA_CSV_ENTRY *__dba_csv_find __P((DBA *, const char *)); int __dba_csv_list_add __P((DBA *, DBA_CSV_ENTRY *)); void __dba_csv_list_dump __P((DBA *)); char *__dba_csv_makestr __P((const char *, ...)); CSV_DATA *__dba_csv_appendchar __P((CSV_DATA *, char, size_t)); int __dba_csv_storeentry __P((DBA *, CSV_DATA **)); int __dba_csv_init __P((void)) { __dba_csv_count = 0; return DBA_INIT_SUCCEED; } int __dba_csv_open __P((DBA *dba)) { static int error = 0, reason = 0; DBA_CSV *tdb; error = 0; reason = 0; tdb = (DBA_CSV *)__dba_malloc(sizeof(DBA_CSV)); if(tdb == NULL) { reason = errno; error = 1; } else { dba->db = tdb; tdb->data = NULL; if(!__dba_csv_load_db(dba)) { error = 2; } } if(error != 0) { __dba_print_error("`%s': (csv_open error=`%d' errno=`%s')\n", dba->filename, error, (reason)?strerror(reason):"None"); return 0; } __dba_print_debug("opened agent %#x (type=CSV)\n", dba); __dba_csv_count++; return 1; } CSV_DATA *__dba_csv_appendchar (CSV_DATA *column, char c, size_t chunk) { /* check buffer size and realloc if needed */ if((column->len + 2) > column->size) { column->data = (char *)__dba_realloc(column->data, column->size+chunk); column->size += chunk; } /* store char */ column->data[column->len] = c; column->data[++column->len] = '\0'; return column; } int __dba_csv_storeentry (DBA *dba, CSV_DATA *data[2]) { DBA_CSV_ENTRY *entry; data[0]->size = data[0]->len + 1; data[0]->data = (char *)__dba_realloc(data[0]->data, data[0]->size); data[0]->data[data[0]->len] = '\0'; data[1]->size = data[1]->len + 1; data[1]->data = (char *)__dba_realloc(data[1]->data, data[1]->size); data[1]->data[data[1]->len] = '\0'; /* store buff2 in current entry */ entry = (DBA_CSV_ENTRY *)__dba_malloc(sizeof(DBA_CSV_ENTRY)); entry->next = NULL; entry->prev = NULL; entry->tag = data[0]; entry->val = data[1]; /* store entry in CSV entry list */ if(!__dba_csv_list_add(dba, entry)) { __dba_print_error("error loading entry into memory. quitting.\n"); return 0; } return 1; } /* Because libdba only provides a hash-type interface, this loader only does CSV files with 2 columns. However, it can be easily extended */ int __dba_csv_load_db __P((DBA *dba)) { DBA_CSV *tdb; static int achunk = 32; int c; CSV_DATA *buff[2]; int col = 0; int err; int reason, linenum = 1; int inquotes = 0; buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[0]->data = (char *)__dba_malloc(achunk); /* The key */ buff[1]->data = (char *)__dba_malloc(achunk); /* The value */ buff[0]->size = achunk; buff[1]->size = achunk; buff[0]->len = 0; buff[1]->len = 0; /* FIXME: create tmp file before we mess with things */ tdb = dba->db; tdb->io = fopen(dba->filename, "a+"); if(tdb->io == NULL) { err = errno; __dba_print_error("couldn't open `%s' for writing. (%d; %s)\n", dba->filename, err, strerror(err)); return 0; } fseek(tdb->io, 0, SEEK_SET); c=fgetc(tdb->io); while(c != EOF) { if((c!=',') && (c!='"') && (c!='\n') && (c!=EOF)) { /* Unquoted string */ __dba_csv_appendchar(buff[col], c, achunk); c=fgetc(tdb->io); while((c!=EOF) && (c!='\n') && (c!=',')) { __dba_csv_appendchar(buff[col], c, achunk); c=fgetc(tdb->io); } if((c=='\n')||(c==EOF)) { linenum++; __dba_csv_storeentry(dba, buff); buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[0]->data = (char *)__dba_malloc(achunk); buff[1]->data = (char *)__dba_malloc(achunk); buff[0]->size = achunk; buff[1]->size = achunk; buff[0]->len = 0; buff[1]->len = 0; col = 0; } else if(c==',') { col++; if(col > 1) { __dba_print_warning( "Warning: >2 columns on line %d, overwriting last column", linenum); col = 1; buff[col]->len = 0; } } } else if(c == '"') { /* Quoted string */ inquotes = 1; while(inquotes) { c=fgetc(tdb->io); while((c!=EOF) && (c!='\n') && (c!='"')) { __dba_csv_appendchar(buff[col], c, achunk); c=fgetc(tdb->io); } if(c=='"') { c=fgetc(tdb->io); if(c=='"') { __dba_csv_appendchar(buff[col], c, achunk); } else if(c==',') { col++; if(col > 1) { __dba_print_warning( "Warning: >2 columns on line %d, overwriting last column", linenum); col = 1; buff[col]->len = 0; } inquotes = 0; } else if((c=='\n')||(c==EOF)) { inquotes = 0; linenum++; __dba_csv_storeentry(dba, buff); buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[0]->data = (char *)__dba_malloc(achunk); buff[1]->data = (char *)__dba_malloc(achunk); buff[0]->size = achunk; buff[1]->size = achunk; buff[0]->len = 0; buff[1]->len = 0; col = 0; } } else if((c=='\n')||(c==EOF)) { inquotes = 0; __dba_print_error("bad CSV entry:%s:line %d:`%s':`%s'\n", dba->filename, linenum, buff[0]->data, buff[1]->data); } } } else if(c == ',') { /* Column separator */ col++; if(col > 1) { __dba_print_warning( "Warning: >2 columns on line %d, overwriting last column", linenum); col = 1; buff[col]->len = 0; } } else if((c == '\n')||(c == EOF)) { linenum++; __dba_csv_storeentry(dba, buff); buff[0] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[1] = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); buff[0]->data = (char *)__dba_malloc(achunk); buff[1]->data = (char *)__dba_malloc(achunk); buff[0]->size = achunk; buff[1]->size = achunk; buff[0]->len = 0; buff[1]->len = 0; col = 0; } c=fgetc(tdb->io); } __dba_free(buff[0]->data); __dba_free(buff[0]); __dba_free(buff[1]->data); __dba_free(buff[1]); if(fclose(tdb->io) == EOF) { reason = errno; } return 1; } int __dba_csv_sync_db(DBA *dba) { DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *tmp = NULL; int reason = 0, error = 0; char *p; if(tdb != NULL) { tmp = tdb->data; tdb->io = fopen(dba->filename, "w"); while(tmp != NULL) { fprintf(tdb->io, "\""); /* We always quote our values */ p = tmp->tag->data; while(*p != '\0') { if(*p == '"') { fprintf(tdb->io, "\"\""); } else { fprintf(tdb->io, "%c", *p); } p++; } fprintf(tdb->io, "\",\""); p = tmp->val->data; while(*p != '\0') { if(*p == '"') { fprintf(tdb->io, "\"\""); } else { fprintf(tdb->io, "%c", *p); } p++; } fprintf(tdb->io, "\"\n"); tmp = tmp->next; } if(fclose(tdb->io) == EOF) { reason = errno; } if((reason != EBADF)&&(reason != 0)) { error = 1; } } if(error != 0) { __dba_print_error("`%s': (csv_close error=`%d' errno=`%s')\n", dba->filename, error, (reason)?strerror(reason):"None"); return reason; } return 0; } int __dba_csv_list_add __P((DBA *dba, DBA_CSV_ENTRY *entry)) { DBA_CSV *tdb = dba->db; if(entry == NULL) { __dba_print_error("internal attempt to add a null entry. that's weird.\n"); return 0; } if(tdb == NULL) { __dba_print_error("database doesn't exist??? something is screwed up.\n"); return 0; } if(tdb->data == NULL) { tdb->data = entry; tdb->last = entry; tdb->data->prev = NULL; tdb->data->next = NULL; } else { /* for now we tack it onto the end of the list, later we will make this a sorted add, so that searches will be faster */ tdb->last->next = entry; entry->prev = tdb->last; tdb->last = entry; } __dba_csv_list_dump(dba); return 1; } void __dba_csv_list_dump __P((DBA *dba)) { DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *tmp = tdb->data; __dba_print_debug("list dump: \n"); while(tmp != NULL) { __dba_print_debug("[%s,%s]\n", tmp->tag->data, tmp->val->data); tmp = tmp->next; } } int __dba_csv_close __P((DBA *dba)) { DBA_CSV *tdb; DBA_CSV_ENTRY *tmp, *tmp2 = NULL; static int error = 0, reason = 0; if(dba == NULL) { __dba_print_error("internal attempt to free a NULL dba. " "well that's funny..."); } __dba_print_debug("closing agent %#x (type=CSV)\n", dba); tdb = dba->db; if(tdb != NULL) { tmp = tdb->data; /* Flush to disk a last time */ error = __dba_csv_sync_db(dba); while(tmp != NULL) { __dba_free(tmp->tag->data); __dba_free(tmp->val->data); __dba_free(tmp->tag); __dba_free(tmp->val); tmp2 = tmp->next; __dba_free(tmp); tmp = tmp2; } if((error == EBADF)&&(reason == 0)) { __dba_free(tdb); tdb = NULL; } } __dba_csv_count--; if(error != 0) { return error; } return 0; } int __dba_csv_add __P((DBA *dba, int data_type, const char *addkey, ...)) { DBA_CSV_ENTRY *entry = NULL; va_list ap; long dt_long = 0; char *dt_string = NULL; assert(addkey); va_start(ap, addkey); __dba_print_debug("starting add\n"); if(__dba_csv_exists(dba, addkey)) { __dba_print_warning("key `%s' already exists in database\n", addkey); va_end(ap); return 0; } entry = (DBA_CSV_ENTRY *)__dba_malloc(sizeof(DBA_CSV_ENTRY)); memset(entry,0,sizeof(DBA_CSV_ENTRY)); entry->tag = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); entry->val = (CSV_DATA *)__dba_malloc(sizeof(CSV_DATA)); entry->tag->data = (void *)strdup(addkey); switch(data_type) { case DBA_DT_LONG : dt_long = va_arg(ap, long); entry->val->data = __dba_csv_makestr("%ld", dt_long); __dba_print_debug("added `%s':`%ld'\n", addkey, dt_long); break; case DBA_DT_STRING : dt_string = va_arg(ap, char *); entry->val->data = (void *)strdup(dt_string); __dba_print_debug("added `%s':`%s'\n", addkey, dt_string); break; default : __dba_print_error("unknown type (%d)\n", data_type); va_end(ap); return 0; }; if(!__dba_csv_list_add(dba, entry)) { __dba_print_error("hmm, couldn't add entry to the list. that's bad.\n"); return 0; } __dba_csv_sync_db(dba); va_end(ap); __dba_print_debug("finished add\n"); return 1; } /* this code was snagged from the printf man page. FIXME: optimize memory usage before returning p */ char *__dba_csv_makestr __P((const char *fmt, ...)) { /* Guess we need no more than 128 bytes. */ int n, size = 128; char *p; va_list ap; if ((p = malloc (size)) == NULL) return NULL; while (1) { /* Try to print in the allocated space. */ va_start(ap, fmt); #ifdef HAVE_VSNPRINTF n = vsnprintf (p, size, fmt, ap); #else n = vsprintf (p,fmt,ap); #endif va_end(ap); /* If that worked, return the string. */ if (n > -1 && n < size) return p; /* Else try again with more space. */ if (n > -1) /* glibc 2.1 */ size = n+1; /* precisely what is needed */ else /* glibc 2.0 */ size *= 2; /* twice the old size */ if ((p = realloc (p, size)) == NULL) return NULL; } } int __dba_csv_set __P((DBA *dba, int data_type, const char *setkey, ...)) { va_list ap; DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *entry = NULL; int done = 0; long dt_long = 0; char *dt_string = NULL; assert(setkey); va_start(ap, setkey); if(tdb->data == NULL) { return 0; } entry = tdb->data; while((!done) && (entry != NULL)) { if(!strcmp(setkey,entry->tag->data)) { done = 1; } else { entry = entry->next; } } if(entry == NULL) { return 0; } switch(data_type) { case DBA_DT_LONG : dt_long = va_arg(ap, long); entry->val->data = __dba_csv_makestr("%ld", dt_long); __dba_print_debug("set `%s':`%ld'\n", setkey, dt_long); break; case DBA_DT_STRING : dt_string = va_arg(ap, char *); entry->val->data = (void *)strdup(dt_string); __dba_print_debug("set `%s':`%s'\n", setkey, dt_string); break; } va_end(ap); __dba_csv_sync_db(dba); return 1; } int __dba_csv_del __P((DBA *dba, const char *delkey)) { DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *entry = NULL; assert(delkey); if(tdb->data != NULL) { entry=tdb->data; while(entry != NULL) { if(!strcmp(delkey, entry->tag->data)) { if((entry->prev == NULL)&&(entry->next == NULL)) { tdb->data = NULL; } else if(entry->prev == NULL) { tdb->data = entry->next; tdb->data->prev = NULL; } else if(entry->next == NULL) { entry->prev->next = NULL; } else { entry->prev->next = entry->next; entry->next->prev = entry->prev; } __dba_csv_sync_db(dba); __dba_free(entry->tag->data); __dba_free(entry->val->data); __dba_free(entry); return 1; } entry = entry->next; } } return 0; } void *__dba_csv_get __P((DBA *dba, int data_type, const char *testkey, void *value)) { int data = 0; long dt_long = 0; DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *entry = NULL; assert(testkey); if(tdb!=NULL) { if(tdb->data == NULL) { __dba_print_warning("database has no entries\n", testkey); return NULL; } if((entry = __dba_csv_find(dba, testkey)) == NULL) { __dba_print_warning("key `%s' was not found in database\n", testkey); return NULL; } /* set value based on returned data */ switch(data_type) { case DBA_DT_LONG : dt_long = strtol(entry->val->data,NULL,10); memcpy(value,&dt_long,sizeof(dt_long)); break; case DBA_DT_STRING : value = NULL; value = (void *)__dba_malloc(strlen(entry->val->data) + 1); if(value == NULL) { __dba_print_warning("couldn't allocate memory for dba_csv_get.\n"); return NULL; } memset(value,0,strlen(entry->val->data)+1); memcpy(value,entry->val->data,strlen(entry->val->data)+1); break; } if(data == 0) { return value; } else { /* This shouldn't happen, assert */ assert(0); } } return NULL; /* database was null */ } int __dba_csv_exists __P((DBA *dba, const char *testkey)) { size_t len; DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *entry = NULL; assert(testkey); if(tdb->data == NULL) { return 0; } entry=tdb->data; len = strlen(testkey); while(entry != NULL) { if(strlen(entry->tag->data) == len) { if(!strcmp(testkey, entry->tag->data)) { return 1; } } entry = entry->next; } return 0; } DBA_CSV_ENTRY *__dba_csv_find __P((DBA *dba, const char *testkey)) { DBA_CSV *tdb = dba->db; DBA_CSV_ENTRY *entry = NULL; assert(testkey); if(tdb->data == NULL) { return NULL; } entry=tdb->data; while(entry != NULL) { if(!strcmp(testkey, entry->tag->data)) { return entry; } entry = entry->next; } return NULL; } int __dba_csv_register __P((DBA *dba)) { dba->open = (int (*)(DBA *))(__dba_csv_open); dba->close = (int (*)(DBA *))(__dba_csv_close); dba->add = (int (*)(DBA *, int, const char *, ...))(__dba_csv_add); dba->set = (int (*)(DBA *, int, const char *, ...))(__dba_csv_set); dba->del = (int (*)(DBA *, const char *))(__dba_csv_del); dba->get = (void *(*)(DBA *, int, const char *, void *))(__dba_csv_get); dba->exists = (int (*)(DBA *, const char *))(__dba_csv_exists); return 1; } #endif