/*
 * Script utility for parsing the /etc/badmem.conf file into a kernel compilable .c file
 *
 * License: GPL as all of the kernel
 * Author: Nico Schmoigl, nico@writemail.com
 * 
 * Changes: V1.0 release version
 * 
 * 
 * NOTE: If you make any changes to this file, please note them at the CHANGES section above. Thanks.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <badmemlib.h>

#define DELIMS "\n #,;"
#define MODULE_TABLE_NAME "badmem_modtab"
#define ABSOLUTE_MODULENAME "_absolute"

FILE *fz;

typedef struct {
   char name[100];
   int mdflen;
   unsigned long base;
} moddata;

struct {
   int openstruct;

   char thisline[600];
   moddata modnames[100];
   unsigned int lastmodule;
   int absolutemode, hadbeenamode;
   mdf_cmdset *mdf;
} ivar;

unsigned long parse_ulong (char *s) {
   register int i = 0, countdigits = 0;
   unsigned long res = 0L;
   char shifter = 0;
   
   for (;i<strlen(s);i++)
     if (isdigit(s[i])) countdigits++;
   
   if (countdigits == strlen(s)) {
      sscanf (s, "%lu", &res);
      return res;
   }
   
   countdigits = 0;
   
   for (i=2; i< strlen(s);i++) 
     if (isxdigit(s[i])) countdigits++;
   
   if (countdigits == strlen(s)-2 && s[0] == '0' && s[1] == 'x') {
      sscanf (s, "%lx", &res);
      return res;
   }
   
   i = strlen(s)-1;
   
   if (s[i] != 'm' && s[i] != 'g' && s[i] != 'b' && s[i] != 'p' && s[i] != 'k' && s[i] != 't')
     return 0;

   shifter = s[i];
   s[i] = 0;
   
   sscanf (s, "%lu", &res);
   
   switch (shifter) {
    case 'm': res <<= 20; break;
    case 'g': res <<= 30; break;
    case 'b': res <<= 9; break;
    case 'p': res <<= 12; break;
    case 'k': res <<= 10; break;
   }
   
   return res;
}

void __inline__ internal_hexreturn (char *b, const char c) {
   char dummy[5];
   char *ptr = dummy;
   
   sprintf (dummy, "%02hx", (char) c);
   if (strlen(dummy) == 4)
     ptr += 2;
   
   strcpy (b, ptr);
}

void hexdumpram (char *buf, const int len) {
   register int i = 0;
   char dummy[3];
   
   if (len == 0)
     return;
   
   printf ("  ");
   while (len > i) {
      char dummy[3];
      
      internal_hexreturn (dummy, buf[i]);
      printf ("0x%s, ", dummy);
      
      i++;
      if (i % 10 == 0)
	printf ("\n  ");
   }
   internal_hexreturn (dummy, buf[len-1]);
   printf ("0x%s", dummy);
}


void close_module (void) {
     if (ivar.openstruct) {
	// There is an 'old' mdf_cmdset to be written into the output file.
	char buf[1000];
	int mdflen;
	mdf_cmd cmd;
	
	cmd.cmd = MDFCMD_END;
	cmd.subcmd = 0;
	cmd.len = 0;
	cmd.data = NULL;
	mdf_appendcmd (ivar.mdf, cmd);
	
	// print it in host order!
	printf ("/* MDF CMD set in clear text is as follows:\n");
	mdf_print_cmdset_out(ivar.mdf);

	mdf_convert2networkorder (ivar.mdf);
	
	mdflen = mdf_gettotalsize(ivar.mdf);
	if (mdflen > sizeof(buf)) {
	   fprintf (stderr, "Error: MDF File too large (%u)... breaking!", mdflen);
	   exit(1);
	}
 	mdflen = mdf_writetomem(buf, sizeof(buf), ivar.mdf);
	
	printf ("total length in memory: %u\n", mdflen);
 	printf ("*/\n\n");
	ivar.modnames[ivar.lastmodule-1].mdflen = mdflen;

	free_mdfset (ivar.mdf);
	ivar.mdf = NULL;
	
	printf ("char mdf_%s[] = {\n", ivar.modnames[ivar.lastmodule-1].name); 
	
	hexdumpram (buf, mdflen);
	
	printf ("};\n\n");

     }

     ivar.openstruct = 0;
}

void close_absolute (void) {
   close_module();
   
   ivar.absolutemode = 0;
   ivar.hadbeenamode = 1;
}


void define_module (const char *modname) {
   mdf_cmd cmd;
   char *mname, *dummy;
   
   if (ivar.openstruct) 
     close_module();
     
   if (ivar.absolutemode) 
     close_absolute();
   
   ivar.openstruct = 1;
   ivar.modnames[ivar.lastmodule].base = 0;
   strcpy (ivar.modnames[ivar.lastmodule++].name, modname);
   
   mname = (char *) malloc (strlen(modname)+1);
   strcpy (mname, modname); 
   
   ivar.mdf = new_mdfset(32);       // 32 is the standard area size; allocation will be automatically increased by the library if necessary.
   
   cmd.cmd = MDFCMD_VERSION;
   cmd.subcmd = 1;
   cmd.data = NULL;
   cmd.len = 0;
   mdf_appendcmd (ivar.mdf, cmd);
   
   dummy = (char *) malloc(100);
   cmd.cmd = MDFCMD_MODULENAME;
   cmd.subcmd = 0;
   cmd.data = mname;
   cmd.len = strlen(mname)+1;
   mdf_appendcmd (ivar.mdf, cmd);
   
   free (mname);
   free (dummy);
}

void define_absolute (void) {
   if (ivar.openstruct) {
      fprintf (stderr, "ERROR: Absolute definitions must be defined before *ANY* other module!\n");
      exit(1);
   }
   
   if (ivar.hadbeenamode) {
      fprintf (stderr, "ERROR: Definition of absolute values only allowed once!\n");
      exit(1);
   }
   
   if (ivar.absolutemode)
     return;
   
   define_module (ABSOLUTE_MODULENAME);
   
   ivar.absolutemode = 1;
}

void define_base (const char *base) {
   char *s = (char *) malloc (strlen(base)+10);
   
   strcpy (s, base);
   
   ivar.modnames[ivar.lastmodule-1].base = parse_ulong (s);
   
   free (s);
}

void define_size (const char *sizestr) { 
   char *s = (char *) malloc (strlen(sizestr)+10);
   unsigned long size;
   mdf_cmd cmd;
   
   strcpy (s, sizestr);
   
   size = parse_ulong(s);

   free (s);
   
   cmd.cmd = MDFCMD_MODULESIZE;
   cmd.subcmd = 0;
   cmd.len = sizeof(size);
   cmd.data = &(size);
   mdf_appendcmd (ivar.mdf, cmd);
}


void define_badmem (const badmem_patternset *bps) {
   if ((!ivar.openstruct) && (!ivar.absolutemode))
     // ups; we may not write here!
     return;

   if (bps == NULL)
     return;
   
   mdf_append_patternset (ivar.mdf, bps);
   
}

void make_moduletable (void) {
   register int i = 0;
   
/*   if (!ivar.hadbeenamode) {
      define_absolute();
      close_absolute();
   }*/

   printf ("struct badmem_modules " MODULE_TABLE_NAME "[] = {\n");
   
   for (;i < ivar.lastmodule; i++) 
      printf ("   { \"%s\", %u, mdf_%s },\n", ivar.modnames[i].name, ivar.modnames[i].mdflen, ivar.modnames[i].name);
   
   printf ("   { NULL, 0, NULL }\n};\n\n");
   
   printf ("int absolutemode = %i;\n\n",  ivar.hadbeenamode ? 1 : 0);
}

void insert_mdffile (const char *fname) {
	FILE *fz = fopen (fname, "rb");
	mdf_cmdset *set;
	char modname[100];
	int begin = 0, end = 0;
	int i;

	fprintf (stderr, "Inserting mdf file: %s...", fname);
	
	if (!fz) {
		fprintf (stderr, "Could not open %s, exiting\n", fname);
		exit(2);
	}
	
	set = mdf_readfromfile (fz);
        mdf_convert2hostorder (set);
	fclose (fz);
	
	printf ("/* Dump of the mdf file %s:\n", fname);
	mdf_print_cmdset_out(set);
	printf ("*/\n\n");
	
	while (mdf_getnextindexpair (set, &begin, &end)) {
	        badmem_patternset *bps;     
	        char size[20];
	   
		if (mdf_extract_modulename (modname, set, begin, end) == NULL) {
			free_mdfset(set);
			fprintf (stderr, "An internal module name error occured!\nPlease report this bug!\n");
			exit(1); // a sort of internal error!
		}
	   
	        if (strcmp (modname, ABSOLUTE_MODULENAME) == 0) {
		   fprintf (stderr, "Invalid module name found: %s\tSkipping this module!\n", modname);
		   continue;
		}

		// In modname we now have the module's name
	        define_module (modname);
	        sprintf (size, "%u",mdf_extract_modulesize(set, begin, end)); 
	        define_size (size);
	        bps = mdf_extract_patternset (set, begin, end);

	        define_badmem (bps);
	   
	        close_module();
	}
	free_mdfset (set);
	fprintf (stderr, "done.\n");
}

int is_addr(const char *t) {
   register int i = 0, countdigits = 0;
   
   // let's check for a hex first.
   if (t[0] == '0' && t[1] == 'x' && strlen(t) > 2)
     return 1;
   
   // we need the number of digits in the string
   for (;i<strlen(t);i++)
     if (isdigit(t[i]))
       countdigits++;
   
   // if all chars are digits then this is a decimal (or an ocatal... ;-) )
   if (countdigits == strlen(t))
     return 1;
   
   // now this could be a abbreviated decimal (ie. "128m")
   if (countdigits-1 == strlen(t)) {
      i = strlen(t)-1;
      if (t[i] == 'm') return 1;  // megabytes = 1048576 bytes
      if (t[i] == 'b') return 1;  // blocks = 512 bytes
      if (t[i] == 'k') return 1;  // kilobytes = 1024 bytes
      if (t[i] == 'p') return 1;  // pages = 4096 bytes
      if (t[i] == 'g') return 1;  // gigabytes = ehm... something huge =;-)
      if (t[i] == 't') return 1;  // terabytes = that is gigabytes * 1024
   }
   
   // wow, then this is no number...
   return 0;
}

int main (int argc, char *argv[]) {
   
   ivar.openstruct = 0;
   ivar.lastmodule = 0;
   ivar.absolutemode = 0;
   ivar.hadbeenamode = 0;
   
   if (argc != 2)
     return 1;
   
   fz = fopen (argv[1], "rt");

   if (!fz) {
      fprintf (stderr, "ERROR: Could not open %s - did you configure BadMEM at all?!\n", argv[1]);
      return 255;
   }
   
   printf ("#include <linux/badmem.h>\n\n");
   
   while (!feof(fz)) {
      char *token;
      register int i = 0;
      char *pos = NULL;
      char securecopy[sizeof(ivar.thisline)];
      
      fgets (ivar.thisline, sizeof(ivar.thisline), fz);
      strcpy (securecopy, ivar.thisline);
      
      // let's check it this could be a comment...therefor we skip some bs or tabs
      while (ivar.thisline[i] == ' ' || ivar.thisline[i] == '\t') 
	i++;
      
      // now we can check if this line is a comment or not
      if (ivar.thisline[i] == '#' || ivar.thisline[i] == ';')
	continue;

      // the # or the ; may also be at the end of a line
      pos = (char *) rindex (ivar.thisline, '#');
      if (pos != NULL)
	*pos = '\0';
      
      // the ; too!
      pos = (char *) rindex (ivar.thisline, ';');
      if (pos != NULL)
	*pos = '\0';

      // get the first token from this string
      token = (char *) strtok (&ivar.thisline[i], DELIMS);

#define NEXTTOKEN \
      token = (char *) strtok(NULL, DELIMS);\
      continue
      
      while (token != NULL) {
	 char *param2;
	 badmem_patternset *bps;
	 
	 if (strcasecmp (token, "mdf") == 0) {
	    char *param1 = (char *) strtok (NULL, DELIMS);
	    
	    insert_mdffile (param1);
	    NEXTTOKEN;
	 }
	      
	 if (strcasecmp (token, "module") == 0) {
	    char *param1 = (char *) strtok (NULL, DELIMS);
	    
	    
	    define_module (param1);
	    NEXTTOKEN;
	 }
	 if (strcasecmp (token, "base") == 0){
	    char *param1 = (char *) strtok (NULL, DELIMS);
	    define_base (param1);
	    NEXTTOKEN;
	 }

	 if (strcasecmp (token, "size") == 0) {
	    char *param1 = (char *) strtok (NULL, DELIMS);
	    define_size (param1);
	    NEXTTOKEN;
	 }
	 
	 if (strcasecmp (token, "absolute") == 0) {
	    define_absolute();
	    NEXTTOKEN;
	 }
	 
	 if (!is_addr(token)) {
	    fprintf (stderr, "Unknown or unexpected token '%s'\n", token);
	    return 2;
	 }
	 
	 // in securecopy we have the whole line we are currently analyzing!
	 bps = badmem_get_patternset_from_string (&securecopy[i]);
	 token = NULL;

	 if (bps == NULL)
	   continue;
	 
	 for (i = 0; i<bps->used_patterns;i++) {
	    // the base (the same idea as the 'offset') must be added here!
	    // this is double-offsetting!
	    bps->pattern[i]->offset += ivar.modnames[ivar.lastmodule-1].base;
	 }

	 define_badmem (bps);
	 
	 free_badmem_patternset(bps);
      }

#undef NEXTTOKEN
   }
 
   if (ivar.openstruct)
	   close_module();
	 
   make_moduletable();
   
   
   fclose (fz);
   return 0;
}
