/*
    Copyright 2001 (c) by Claudio Jeker,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
	
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Tools/PatchBinaries.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3039 $
    Last Date of Change:   $Date: 2007-06-12 21:43:31 +0200 (Die, 12 Jun 2007) $  by: $Author: sryffel $
 
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "../Startup/headerLayout.h"

#define BIG 1
#define LITTLE -1

#define ARCH_NONE 0
#define ARCH_MIPS 1
#define ARCH_MIPS_ECOFF 2
#define ARCH_IXP  3
#define ARCH_IA32 4
#define ARCH_IXP2400 5

char *progname;

char* archstring[] = {
                       "NONE",
                       "mips",
                       "mips",
                       "ixp",
                       "ia32",
                       "ixp2400",
                       NULL
                     };

int archoffset[] = {
                     0,
                     0x0,
                     0x1000,
                     0x0,
                     0x0,
                     0x0,
                     0
                   };

static void
usage()
{
  fprintf(stderr,
          "usage: %s -a mips|ixp|ia32 -f binaryfile -s sizefile [-o optsegmentfile -l address]\n",
          progname);
  fprintf(stderr, "\
          -a  binary file is compiled for specified architecture.\n\
          Specified architectures are: \n\
          mips        mips binary file\n\
          mips-ecoff  mips ecoff file\n\
          ixp         ixp binary file\n\
          ixp2400     ixp2400 binary file\n\
          ia32        ia32 binary file\n\
          -f  path to the binaryfile.\n\
          -s  path to the corresponding size file (in System V stile)\n\
          -o  path to the file building the optional segment\n\
          -l  address where the optional segment is loaded at\n");
  exit(1);
}

static char* basename(char *path)
{
  char *s;

  s = path + strlen(path);
  while ( *s != '/' && s >= path )
    s--;
  return ++s;

}

static void check_binary(FILE *fp, const char *name, int arch)
{
  char c[17];
  int len = 0;

  /* Find the keyword in file. */
  if (fseek(fp, archoffset[arch] + ORG_MARKER, SEEK_SET))
  {
    perror(name);
    exit(EXIT_FAILURE);
  }
  fread(c, sizeof(c) - 1, 1, fp);
  if (ferror(fp))
  {
    perror(name);
    exit(EXIT_FAILURE);
  }

  len = strlen(archstring[arch]);
  if (memcmp("TOPSY", c, 5) == 0 && memcmp(archstring[arch], c+5, len) == 0)
  {
    /* correct binary, seek to correct position */
    if (fseek(fp, archoffset[arch] + ORG_TEXT_START, SEEK_SET))
    {
      perror(name);
      exit(EXIT_FAILURE);
    }
    return;
  }
  (void)fprintf(stderr, " !!!! NO VALID >TOPSY %s< binary !!!!\n",
                archstring[arch]);
  exit(EXIT_FAILURE);
}

/* Write a 32 bit integer. Acctualy we write 64bits */
static void write_int(FILE *fp, const char *name, int order, unsigned long x)
{
  if ( order == BIG )
  {
    fputc(0, fp);
    fputc(0, fp);
    fputc(0, fp);
    fputc(0, fp);
    fputc((x >> 24) & 0xff, fp);
    fputc((x >> 16) & 0xff, fp);
    fputc((x >> 8)  & 0xff, fp);
    fputc((x)       & 0xff, fp);
  }
  else
  {
    fputc((x)       & 0xff, fp);
    fputc((x >> 8)  & 0xff, fp);
    fputc((x >> 16) & 0xff, fp);
    fputc((x >> 24) & 0xff, fp);
    fputc(0, fp);
    fputc(0, fp);
    fputc(0, fp);
    fputc(0, fp);
  }

  if (ferror(fp))
  {
    perror(name);
    exit(EXIT_FAILURE);
  }
}

static void parse_size_line(FILE *in, const char *in_name,
                            const char *keyword, unsigned long *addr, unsigned long *size)
{
  size_t n = strlen(keyword);
  char s[2048];
  char *t1 = NULL;
  char *t2 = NULL;

  /* Find the keyword in file. */
  if (fseek(in, 0, SEEK_SET))
  {
    perror(in_name);
    exit(EXIT_FAILURE);
  }
  while (fgets(s, sizeof(s), in))
  {
    int x, y;
    if (memcmp(keyword, s, n) == 0 && isspace(*(s + n + 1)))
    {
      /* Match found. Read the following two integers. */
      t1 = s + n;
      x = strtoul(t1, &t2, 0);
      if ( *t1 == '\0' || !isspace(*t2) )
      {
        fprintf(stderr, "%s: size-file syntax error\ncan not parse first number:\n%s\n", in_name, s);
        exit(EXIT_FAILURE);
      }
      t1 = t2;
      y = strtoul(t1, &t2, 0);
      if ( *t1 == '\0' || !isspace(*t2) )
      {
        fprintf(stderr, "%s: size-file syntax error\ncan not parse second number:\n%s\n", in_name, s);
        exit(EXIT_FAILURE);
      }
      /* Adjust size. */
      //		   if (y && *addr) *size += y - *addr;
      //		   else *size += x;
      //		   printf("%i\n", *size);
      *size += x;

      /* If new minimum start address found, adjust addr. */
      if (*addr == 0 || (unsigned)*addr > (unsigned)y)
        *addr = y;
      return;
    }
  }

  if (ferror(in))
  {
    perror(in_name);
    exit(EXIT_FAILURE);
  }
}


int main(int argc, char *argv[])
{
  struct stat st;
  unsigned long addr, size;
  unsigned long opt_addr, opt_size, opt;
  unsigned long bss_addr = 0, bss_size = 0;
  int ch, arch, byteorder, i;
  char* binname = NULL;
  char* sizename = NULL;
  char* s = NULL;
  FILE *bin_F, *size_F;

  progname = basename(*argv);

  arch = ARCH_NONE;
  opt = 0;
  opt_addr = 0;
  opt_size = 0;
  while ((ch = getopt(argc, argv, "a:f:s:o:l:")) != -1 )
  {
    switch (ch)
    {
    case 'a':
      if ( strcasecmp(optarg, "mips") == 0 )
      {
        arch = ARCH_MIPS;
        byteorder = BIG;
        break;
      }
      else if ( strcasecmp( optarg, "mips-ecoff") == 0 )
      {
        arch = ARCH_MIPS_ECOFF;
        byteorder = BIG;
        break;
      }
      else if ( strcasecmp( optarg, "ixp") == 0 )
      {
        arch = ARCH_IXP;
        byteorder = BIG;
        break;
      }
      else if ( strcasecmp( optarg, "ia32") == 0 )
      {
        arch = ARCH_IA32;
        byteorder = LITTLE;
        break;
      }
      else if ( strcasecmp( optarg, "ixp2400") == 0 )
      {
        arch = ARCH_IXP2400;
        byteorder = BIG;
        break;
      }
      else
      {
        usage();
      }
      assert(0);
    case 'f':
      binname = optarg;
      break;
    case 's':
      sizename = optarg;
      break;
    case 'o':
      if ( stat(optarg, &st) != 0 )
      {
        perror(optarg);
        usage();
      }
      opt_size = st.st_size;
      opt++;
      break;
    case 'l':
      opt_addr = strtoul(optarg, &s, 0);
      if ( *optarg == '\0' || *s != '\0' )
      {
        fprintf(stderr, "%s: not a valid number\n", optarg);
        usage();
      }
      opt++;
      break;
    default:
      usage();
    }
  }

  if ( arch == ARCH_NONE || binname == NULL || sizename == NULL )
    usage();
  if ( opt != 0 && opt != 2 )
    usage();

  /* OK we parsed the arguments,
  	 lets start patching
   */
  bin_F = fopen(binname, "r+");
  if ( bin_F == NULL )
  {
    perror(binname);
    exit(EXIT_FAILURE);
  }

  size_F = fopen(sizename, "r");
  if ( size_F == NULL )
  {
    perror(sizename);
    exit(EXIT_FAILURE);
  }

  /* First check the binary if it is a correct one and
  	 position the FILE stream so that the segmap can be written.
   */
  check_binary(bin_F, binname, arch);

  printf("Topsy PatchBinaries v0.31\npatching %s\n", binname);

  /* XXX these next steps need to be generalized */
  /* Read the text segment and store the information in the segment map. */
  addr = size = 0;
  parse_size_line(size_F, sizename, ".text", &addr, &size);
  write_int(bin_F, binname, byteorder, addr);
  write_int(bin_F, binname, byteorder, size);

  printf("\tcode text    @ %#x size %7u (%#x)\n", addr, size, size);
  addr = size = 0;

  /* Read data segments, add up the sizes and find the start address
  * as a minimum of all start addresses. */
  parse_size_line(size_F, sizename, ".rodata.str1.4", &addr, &size);
  parse_size_line(size_F, sizename, ".rodata", &addr, &size);
  parse_size_line(size_F, sizename, ".data", &addr, &size);
  parse_size_line(size_F, sizename, ".rdata", &addr, &size);
  write_int(bin_F, binname, byteorder, addr);
  write_int(bin_F, binname, byteorder, size);

  printf("\tcode data    @ %#x size %7u (%#x)\n", addr, size, size);
  addr = size = 0;

  parse_size_line(size_F, sizename, ".bss", &bss_addr, &bss_size);
  parse_size_line(size_F, sizename, ".sbss", &bss_addr, &bss_size);
  write_int(bin_F, binname, byteorder, bss_addr);
  write_int(bin_F, binname, byteorder, bss_size);

  printf("\tcode bss     @ %#x size %7u (%#x)\n", bss_addr, bss_size, bss_size);
  addr = size = 0;

  if ( opt == 2 )
  {
    write_int(bin_F, binname, byteorder, opt_addr);
    write_int(bin_F, binname, byteorder, opt_size);
    printf("\topt. segment @ %#x size %7u (%#x)\n", opt_addr, opt_size, opt_size);
  }

  if (arch == ARCH_IA32)  /* patch for disk drives */
  {
    if (fseek(bin_F, 0, SEEK_END))
    {
      perror(binname);
      exit(EXIT_FAILURE);
    }
    size = ftell(bin_F) / (18 * 512) + 1; // No. of Tracks to read
    if (fseek(bin_F, archoffset[arch] + ORG_PLATFORM_EXTENSIONS, SEEK_SET))
    {
      perror(binname);
      exit(EXIT_FAILURE);
    }
    write_int(bin_F, binname, byteorder, size);
    printf("\ttrack to read   %7u\n", size);
  }

  if (arch == ARCH_IXP2400)
  {
    fseek(bin_F,0,SEEK_END);
    for (i = 0; i < bss_size; i++)
    {
      fputc(0, bin_F);
      fputc(0, bin_F);
      fputc(0, bin_F);
      fputc(0, bin_F);
    }
  }

  fclose(bin_F);
  fclose(size_F);
  printf("succesfully done\n");
  return 0;
}

