/* db.c : part of The Bastard disassembly environment =========================
 *
 * Contains routines for interfacing with the Typhoon database module. Typhoon
 * is pretty self-sufficient, so there will not be much here. 
 */
#include <sys/stat.h>
#include <db/db_main.h>
#include <db/typhoon.h>
#include <debug.h>


extern struct DISASM_ENV disasm_env;
struct DB_DESCRIPTOR  __db_config = {0}, __db_target = {0};

/* NOTE: This file contains a lot of redundancy due to the different DB 
         tables. This should be rewritten or, perhaps, implemented as 
         Seer scripts that set table-specific info dynamically 
*/
         
int InitDB(){
      d_dbdpath("./.bdb"); d_dbfpath("./.bdb");
      initSequences();
      db_config = &__db_config;
      db_target = &__db_target;
      return(0);
}
void initSequences() {
   struct addr_exp a;
   struct code_effect ce;
   struct int_code ic;
   struct fin_code fc;
   struct xref x;
   struct comment cmt;
   struct function f;
   struct func_param fp;
   struct func_local fl;
   struct func_effect fe;
   struct f_inline fi;
   struct structure s;
   struct struct_member sm;
   struct lib l;
   struct data_type dt;
   struct constant c;
   struct bc_macro m;
   
   
   if (GetEnvFlag(DB_LOADED)) {
      if (d_keylast(ADDR_EXP_ID) == S_OKAY) {
         d_recread(&a);
         seq.addr_exp = a.id;
      } else seq.addr_exp = 0;
      if (d_keylast(CODE_EFFECT_ID) == S_OKAY) {
         d_recread(&ce);
         seq.code_effect = ce.id;
      } else seq.code_effect = 0;
      if (d_keylast(INT_CODE_ID) == S_OKAY) {
         d_recread(&ic);
         seq.int_code = ic.id;
      } else seq.int_code = 0;
      if (d_keylast(FIN_CODE_ID) == S_OKAY) {
         d_recread(&fc);
         seq.fin_code = fc.id;
      } else seq.fin_code = 0 ;
      if (d_keylast(XREF_ID) == S_OKAY) {
         d_recread(&x);
         seq.xref = x.id;
      } else seq.comment = 0;
      if (d_keylast(COMMENT_ID) == S_OKAY) {
         d_recread(&c);
         seq.comment = c.id;
      } else seq.comment = 0;
      if (d_keylast(FUNCTION_ID) == S_OKAY) {
         d_recread(&f);
         seq.func = f.id;
      } else seq.func = 0;
      if (d_keylast(FUNC_PARAM_ID) == S_OKAY) {
         d_recread(&fp);
         seq.func_param = fp.id;
      } else seq.func_param = 0;
      if (d_keylast(FUNC_LOCAL_ID) == S_OKAY) {
         d_recread(&fl);
         seq.func_local = fl.id;
      } else seq.func_local = 0;
      if (d_keylast(FUNC_EFFECT_ID) == S_OKAY) {
         d_recread(&fe);
         seq.func_effect = fe.id;
      } else seq.func_effect = 0;
      if (d_keylast(F_INLINE_ID) == S_OKAY) {
         d_recread(&fi);
         seq.f_inline = fi.id;
      } else seq.f_inline = 0;
      if (d_keylast(STRUCTURE_ID) == S_OKAY) {
         d_recread(&s);
         seq.structure = s.id;
      } else seq.structure = 0;
      if (d_keylast(STRUCT_MEMBER_ID) == S_OKAY) {
         d_recread(&sm);
         seq.struct_member = sm.id;
      } else seq.struct_member = 0;
      if (d_keylast(DATA_TYPE_ID) == S_OKAY) {
         d_recread(&dt);
         seq.data_type = dt.id;
      } else seq.data_type = 0;
      if (d_keylast(LIB_ID) == S_OKAY) {
         d_recread(&l);
         seq.library = l.id;
      } else seq.library = 0;
      if (d_keylast(CONSTANT_ID) == S_OKAY) {
         d_recread(&c);
         seq.constant = c.id;
      } else seq.constant = 0;
      if (d_keylast(BC_MACRO_ID) == S_OKAY) {
         d_recread(&m);
         seq.bc_macro = m.id;
      } else seq.bc_macro = 0;
   } else {
      memset( &seq, 0, sizeof( struct DB_SEQUENCE ));
   }
   return;
}

/* ++++++++++++++++++++ WARNING These are dangerous
 *             Support for them will be added to eic_wrappers
 *             and a .h file provided, but they are *NOT* bastard API
 */
int LoadDB( char *dbname, char *filename, struct DB_DESCRIPTOR *db ){
   /* name is a fully-qualified path to a .tgz which will extract
    * to a subdirectory. Perhaps the .tgz specifies how to load/what subdir.
    * This returns the ID of the DB.
    * Note that one should switch back to the Target DB after using this!
    */
   /* params: (loc of ,dbd files (./)), name of DB, .bdb filename */
   /* dbname = cfg || bdb; filename = .bdb file */
   FILE *tmp;
   DIR *dirBDB;
   int db_old, idx;
   struct stat s;
   char db_path[256], cmdBuf[256], *pos;

   if (! db) return(0);
   /* set location of .dbd files --> $BASTARD_HOME */
   snprintf(db_path, 256, "%s/db", disasm_env.home);
   d_dbdpath( db_path );

   pos = strrchr(filename, '/');
   idx = (pos == 0) ? 0 :  pos - filename + 1;


   /* set up DB_DESCRIPTOR */
   strncpy(db->name, dbname, 64);
   strncpy(db->filename, filename, 155);
   snprintf(db_path, 256, "%s/.%s", disasm_env.dbpath, &filename[idx]);
   DEBUG_PrintMsg("stat %s\n", db_path);
   DEBUG_PrintMsg("stat %s\n", filename);
   /* 1. Check if dir exists : if so, use it */
   if (! stat(db_path, &s)) {
      ; /* we're OK, it'll sort itself out ;P */
   /* 2. Check if .bdb exists; if so use it */
   } else if (! stat(filename, &s) ) {
      sprintf(cmdBuf, "tar -zxf %s -C%s/", filename, disasm_env.dbpath);
      DEBUG_PrintVar(cmdBuf, "%s");
      if (! ((tmp=popen(cmdBuf, "r")) && (pclose(tmp) == 0)) ) {
         DEBUG_PrintMsg("Unable to execute cmd %s\n", cmdBuf);
         return(0);
      }
   } else  {
   /* 3. Create directory and such */
      if ( mkdir(db_path, 0777) == -1 ){
         DEBUG_PrintMsg("Unable to mkdir %s\n", db_path);
         return(SetLastError(4510));
      }
      if ( (dirBDB = opendir(db_path)) == NULL ) {
         DEBUG_PrintMsg("Unable to opendir %s\n", db_path);
         return(SetLastError(4520));   
      } 
      else closedir(dirBDB);
   }
   /* Set location of Db data files: ./.$TARGET.bdb */
   d_dbfpath( db_path );

   if ( d_dbget(&db_old) == S_NOCD) db_old = 0;
   /* open DB */
   
   if ( d_open(dbname, "s") != S_OKAY) {
      sprintf(cmdBuf, "DB error: Cannot open database (db_status %d)\n",
                        db_status);
      ErrorMsg( 4550, cmdBuf);
      return(SetLastError(4550));
   }

   /* Get ID of the DB just opened */
   d_dbget(&db->db_id);

   if (db_old > -1) 
      if (d_dbset(db_old) == S_INVPARM)
         printf("Invalid ID loading DB %s\n", dbname);
   return(db->db_id);
}

int SwitchDB( int db ){
   /* use to switch to a different DB . Remember to switch back!
    * SwitchDB( NULL ) will switch back to the target DB
    */
   int rv;
   if ( d_dbget(&rv) == S_NOCD) rv = -1;
   if (rv == db) return(rv);
   if (d_dbset(db) != S_OKAY) return(0);
   else if (db_status == S_INVPARM) 
   DEBUG_PrintMsg("Switching to db %x\n", db);
   DEBUG_PrintMsg("Switched from db %x\n", rv);
   return(rv);
}

int UnloadDB(struct DB_DESCRIPTOR *db) {
   int tmp;
   
   if (! db) return(0);
   tmp = SwitchDB(db->db_id);

   d_close();
   db->db_id = -1;
   db->name[0] = '\0';
   db->filename[0] = '\0';
   if (tmp > -1 && tmp != db->db_id)  SwitchDB(tmp);
   return(1);
}
/* ===================================================== LOW LEVEL ROUTINES */
/* These provide a low-level wrapper to the typhoon DB. The intent is to
 * allow the user/controlling process full control over the DB; however, 
 * it is assumed that anyone who uses these functions knows what they are
 * doing. */

void * DBSaveState(  ){
   DB_ADDR *curr_rec;
   curr_rec = (DB_ADDR *) calloc(sizeof(DB_ADDR),1);
   d_crget( curr_rec);
   return(curr_rec);   
}

int DBRestoreState(void *state){
   DB_ADDR *curr_rec = (DB_ADDR *) state;
   d_crset(curr_rec);
   free(curr_rec);
   return(1);
}

int dbError(){
   int err = 2000 + db_status;
   SetLastError(err);
   return(err);
}

void dbErrorMsg( ){
   PrintErrorMsg(dbError());
   return;
}

/* TODO : keep track of curr_rec in these */
int DBIndexFirst(int index, void *dest){
   if (d_keyfrst(index) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBIndexNext(int index, void *dest){
   if (d_keynext(index) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBIndexPrev(int index, void *dest){
   if (d_keyprev(index) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBIndexLast(int index, void *dest){
   if (d_keylast(index) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBIndexFind(int index, void *value, void *dest){
   if (d_keyfind(index, value) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}

int DBTableFirst(int table, void *dest){
   if (d_recfrst(table) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
   
int DBTableNext(int table, void *dest){
   if (d_recnext(table) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBTablePrev(int table, void *dest){
   if (d_recprev(table) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBTableLast(int table, void *dest){
   if (d_reclast(table) == S_OKAY) {
      d_recread(dest);
   }
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
int DBRecordInsert(int table, void *src){
   void *state = DBSaveState();
   d_fillnew(table, src);
   DBRestoreState(state);
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}

/* These use the specified index to find the record to update/delete */
int DBRecordUpdate(int index, void *value, void *src){
   void *state = DBSaveState();
   if (d_keyfind(index, value) == S_OKAY) {
      d_recwrite(src);
   }
   DBRestoreState(state);
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}
   
int DBRecordDelete(int index, void *value){
   void *state = DBSaveState();
   if (d_keyfind(index, value) == S_OKAY) {
      d_delete();
   }
   DBRestoreState(state);
   if (db_status != S_OKAY) return(SetLastError(dbError()));
   return(1);
}

/* find closest record to key -- prev = closest-before, next = closest-after */
DBFindClosestPrev(int index, void *value, void *dest){
   void *state;
   int found = 0;
   
   state = DBSaveState();
   if( d_keyfind(index, value) == S_NOTFOUND){
      if (d_keynext(index) == S_OKAY) {
         if (d_keyprev(index) == S_OKAY){
               found = 1; 
               d_recread(dest);
         }
      }
   } else if (db_status == S_OKAY){
      found = 1;
      d_recread(dest);
   }
   DBRestoreState(state);
   return(found);
}
   
DBFindClosestNext(int index, void *value, void *dest){
   void *state;
   int found = 0;
   
   state = DBSaveState();
   if( d_keyfind(index, value) == S_NOTFOUND){
      if (d_keynext(index) == S_OKAY) {
         found = 1; 
         d_recread(dest);
      }
   } else if (db_status == S_OKAY){
      found = 1;
      d_recread(dest);
   }
   DBRestoreState(state);
   return(found);   
}


/* ==================================================== GENERIC ACCESS */
/* These are the recommended DB search routines. They are one level below
 * the bastard API -- useful for debugging or for direct DB queries. Users
 * are expected to use these when the 'normal' API functions are too 
 * limiting.
 */
struct address * GetAddressObject(long rva){
   struct address *a = malloc(sizeof(struct address));
   if ( a && DBIndexFind(ADDRESS_RVA, &rva, a)) return(a);
   if(a) free(a);
   return((void *)SetLastError(4020));
}
   
struct code * GetCodeObject(long rva){
   struct code *c = malloc(sizeof( struct code));
   if (c && DBIndexFind(CODE_RVA, &rva, c) ) return(c);
   if(c) free(c);
   return((void *)SetLastError(4110));
}
   
struct addr_exp * GetAddrExpObject(int id){
   struct addr_exp *a = malloc(sizeof( struct addr_exp));
   if (a && DBIndexFind(ADDR_EXP_ID, &id, a) ) return(a);
   if(a) free(a);
   return((void *)SetLastError(4110));
}
   
struct code_effect * GetCodeEffectObject(int id){
   struct code_effect *e = malloc(sizeof( struct code_effect));
   if (e && DBIndexFind(CODE_EFFECT_ID, &id, e) ) return(e);
   if(e) free(e);
   return((void *)SetLastError(4110));
}
   
struct section * GetSectionObject(char *name){
   struct section *s = malloc(sizeof( struct section));
   if (s && DBIndexFind(SECTION_NAME, &name, s) ) return(s);
   if(s) free(s);
   return((void *)SetLastError(4820));
}
   
struct xref * GetXrefObject(int id){
   struct xref *x = malloc(sizeof( struct xref));
   if (x && DBIndexFind(XREF_ID, &id, x) ) return(x);
   if(x) free(x);
   return((void *)SetLastError(5310));
}
   
struct int_code * GetIntCodeObject(int id){
   struct int_code *i = malloc(sizeof( struct int_code));
   if (i && DBIndexFind(INT_CODE_ID, &id, i) ) return(i);
   if(i) free(i);
   return((void *)SetLastError(4150));
}
   
struct fin_code * GetFinCodeObject(int id){
   struct fin_code *f = malloc(sizeof( struct fin_code));
   if (f && DBIndexFind(FIN_CODE_ID, &id, f) ) return(f);
   if(f) free(f);
   return((void *)SetLastError(4170));
}
   
struct export_addr * GetExportObject(long rva){
   struct export_addr *e = malloc(sizeof( struct export_addr));
   if (e && DBIndexFind(EXPORT_ADDR_RVA, &rva, e) ) return(e);
   if(e) free(e);
   return((void *)SetLastError(4450));
}
   
struct import_addr * GetImportObject(long rva){
   struct import_addr *i = malloc(sizeof( struct import_addr));
   if (i && DBIndexFind(IMPORT_ADDR_RVA, &rva, i) ) return(i);
   if(i) free(i);
   return((void *)SetLastError(4400));
}
   
struct string * GetStringObject(long rva){
   struct string *s = malloc(sizeof( struct string));
   if (s && DBIndexFind(STRING_RVA, &rva, s) ) return(s);
   if(s) free(s);
   return((void *)SetLastError(5000));
}

struct lib * GetLibObject(int id){
   struct lib *l = malloc(sizeof( struct lib));
   if (l && DBIndexFind(LIB_ID, &id, l) ) return(l);
   if(l) free(l);
   return((void *)SetLastError(4411));
}
   
struct name * GetNameObject(long rva){
   struct name *n = malloc(sizeof( struct name));
   if (n && DBIndexFind(NAME_RVA, &rva, n) ) return(n);
   if(n) free(n);
   return((void *)SetLastError(4710));
}
   
struct comment * GetCommentObject(int id){
   struct comment *c = malloc(sizeof( struct comment));
   if (c && DBIndexFind(COMMENT_ID, &id, c) ) return(c);
   if(c) free(c);
   return((void *)SetLastError(4730));
}
   
struct function * GetFunctionObject(int id){
   struct function *f = malloc(sizeof( struct function));
   if (f && DBIndexFind(FUNCTION_ID, &id, f) ) return(f);
   if(f) free(f);
   return((void *)SetLastError(4300));
}
   
struct func_param * GetFuncParamObject(int id){
   struct func_param *f = malloc(sizeof( struct func_param));
   if (f && DBIndexFind(FUNC_PARAM_ID, &id, f) ) return(f);
   if(f) free(f);
   return((void *)SetLastError(4330));
}
   
struct func_local * GetFuncLocalObject(int id){
   struct func_local *f = malloc(sizeof( struct func_local));
   if (f && DBIndexFind(FUNC_LOCAL_ID, &id, f) ) return(f);
   if(f) free(f);
   return((void *)SetLastError(4340));
}
   
struct func_effect * GetFuncEffectObject(int id){
   struct func_effect *e = malloc(sizeof( struct func_effect));
   if (e && DBIndexFind(FUNC_EFFECT_ID, &id, e) ) return(e);
   if(e) free(e);
   return((void *)SetLastError(4110));
}
   
struct f_inline * GetFInlineObject(int id){
   struct f_inline *f = malloc(sizeof( struct f_inline));
   if (f && DBIndexFind(F_INLINE_ID, &id, f) ) return(f);
   if(f) free(f);
   return((void *)GetLastError(4350));
}
   
   
struct structure * GetStructureObject(int id){
   struct structure *s = malloc(sizeof( struct structure));
   if (s && DBIndexFind(STRUCTURE_ID, &id, s) ) return(s);
   if(s) free(s);
   return((void *)SetLastError(5150));
}
   
struct struct_member * GetStructMemberObject(int id){
   struct struct_member *s = malloc(sizeof( struct struct_member));
   if (s && DBIndexFind(STRUCT_MEMBER_ID, &id, s) ) return(s);
   if(s) free(s);
   return((void *)SetLastError(5160));
}
   
struct data_type * GetDataTypeObject(int id){
   struct data_type *d = malloc(sizeof( struct data_type));
   if (d && DBIndexFind(DATA_TYPE_ID, &id, d) ) return(d);
   if(d) free(d);
   return((void *)SetLastError(5170));
}
   
struct constant * GetConstantObject(int id){
   struct constant *c = malloc(sizeof( struct constant));
   if (c && DBIndexFind(CONSTANT_ID, &id, c) ) return(c);
   if(c) free(c);
   return((void *)SetLastError(5100));
}
   
struct bc_macro * GetBCMacroObject(int id){
   struct bc_macro *s = malloc(sizeof( struct bc_macro));
   if (s && DBIndexFind(BC_MACRO_ID, &id, s) ) return(s);
   if(s) free(s);
   return((void *)SetLastError(4600));
}
