/**************************************************************** -*- C -*- **
** Copyright (C) 1999 
**   Patryk Zadarnowski, University of New South Wales. All rights reserved.
** 
** Redistribution  and  use  in  source  and binary  forms,  with  or  without
** modification, are permitted provided that the following conditions are met:
** (1) Redistributions of source code  must retain the above copyright notice,
** this list of conditions  and the following disclaimer.  (2) Redistributions
** in  binary form must  reproduce the  above copyright  notice, this  list of
** conditions and  the following disclaimer in the  documentation and/or other
** materials provided with  the distribution.  (3) The name  of the author may
** not  be used  to endorse  or promote  products derived  from  this software
** without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED ``AS  IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT  NOT LIMITED TO,  THE IMPLIED WARRANTIES  OF MERCHANTABILITY
** AND FITNESS  FOR A PARTICULAR PURPOSE,  ARE DISCLAIMED.  IN  NO EVENT SHALL
** THE  AUTHOR  OR  THE  CONTRIBUTORS  BE LIABLE  FOR  ANY  DIRECT,  INDIRECT,
** INCIDENTAL,  SPECIAL, EXEMPLARY, OR  CONSEQUENTIAL DAMAGES  (INCLUDING, BUT
** NOT LIMITED TO,  PROCUREMENT OF SUBSTITUTE GOODS OR  SERVICES; LOSS OF USE,
** DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
** OF  LIABILITY, WHETHER IN  CONTRACT, STRICT  LIABILITY, OR  TORT (INCLUDING
** NEGLIGENCE  OR  OTHERWISE) ARISING  IN  ANY  WAY OUT  OF  THE  USE OF  THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
** 
 * -*- Created:       Mon Feb  8 09:20:04 1999 -*-
 * -*- Last modified: Mon Feb  8 12:43:07 1999 -*-
 *
 * Any comments and bug reports should be sent to patrykz@cse.unsw.edu.au.
 */

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

/*****************************************************************************
 * This is a quick and dirty port of the BootLinker.java file to C.
 * BootLinker.java is part of the original Topsy distribution. 
 */

const char *outfile = "segmap.bin";
int completed = 0; /* non-zero when processing completed */

static void cleanup(void);
static void write_integer(FILE *fp, const char *name, unsigned long x);
static void parse_size_file(FILE *in, const char *in_name,
                            FILE *out, const char *out_name);
static void parse_size_line(FILE *in, const char *in_name,
                            const char *keyword, int *addr, int *size);

int
main(int argc, char *argv[])
{
  unsigned long x;
  char *endptr;
  char s[2048];
  FILE *segmap_file, *ksize_file, *isize_file, *user_srec;

  if (argc != 6)
  {
    fprintf(stderr,
            "Usage:\n"
            "\tbootlinker <kSizeFile> <iSizeFile> <user.srec> \n"
            "\t           <segmap address> <user in kernel at>\n",
            (argc < 5 ? "Not enough" : "Too many"));
    return EXIT_FAILURE;
  }

  /* Create files. */
  ksize_file = fopen(argv[1], "r");
  if (ksize_file == NULL)
  {
    perror(argv[1]);
    return EXIT_FAILURE;
  }
  isize_file = fopen(argv[2], "r");
  if (isize_file == NULL)
  {
    perror(argv[2]);
    return EXIT_FAILURE;
  }
  user_srec = fopen(argv[3], "r");
  if (user_srec == NULL)
  {
    perror(argv[3]);
    return EXIT_FAILURE;
  }

  /* Open the output file and register cleanup handler. */
  segmap_file = fopen(outfile, "w+");
  if (segmap_file == NULL)
  {
    perror(outfile);
    return EXIT_FAILURE;
  }
  atexit(cleanup);

  /* Write the segment map  (sizes and start addresses of kernel and
   * user segments).  We assume that the size file was  generated by
   * gnu-size with options -Ax (addresses, hex) ex.: size -Ax */
  parse_size_file(ksize_file, argv[1], segmap_file, outfile);
  parse_size_file(isize_file, argv[2], segmap_file, outfile);

  /* Fetch the S7 record from user.srec and dump it to the segment map. */
  while (fgets(s, sizeof(s), user_srec))
  {
    if (s[0] == 'S' && s[1] == '7')
    {
      /* Write the following 8-digit hexadecimal integer. */
      s[12] = '\0';
      endptr = NULL;
      x = strtoul(s + 4, &endptr, 16);
      if (endptr == NULL || *endptr != '\0')
      {
        fprintf(stderr, "%s: invalid srec file\n", argv[3]);
        return EXIT_FAILURE;
      }
      write_integer(segmap_file, outfile, x);
    }
  }

  if (ferror(user_srec))
  {
    perror(argv[3]);
    return EXIT_FAILURE;
  }

  /* Write the user-in-kernel address at the end. */
  endptr = NULL;
  x = strtoul(argv[5], &endptr, 16);
  if (endptr == NULL || *endptr != '\0')
  {
    fprintf(stderr, "bootlinker: invalid user-in-kernel-addr \"%s\"\n",
            argv[5]);
    return EXIT_FAILURE;
  }
  write_integer(segmap_file, outfile, x);

  completed = 1;
  return EXIT_SUCCESS;
}

/* Write a 32 bit big-endian integer. */
static void
write_integer(FILE *fp, const char *name, unsigned long x)
{
  fputc((x >> 24) & 0xff, fp);
  fputc((x >> 16) & 0xff, fp);
  fputc((x >> 8)  & 0xff, fp);
  fputc((x)       & 0xff, fp);
  if (ferror(fp))
  {
    perror(name);
    exit(EXIT_FAILURE);
  }
}

/* Remove a partial output file. */
static void
cleanup(void)
{
  if (!completed)
    remove(outfile);
}

/* Parse a size file. */
static void
parse_size_file(FILE *in, const char *in_name,
                FILE *out, const char *out_name)
{
  int text_addr = 0, addr = 0;
  int text_size = 0, size = 0;

  /* Read the text segment and store the information in the segment map. */
  parse_size_line(in, in_name, ".text", &text_addr, &text_size);
  write_integer(out, out_name, text_size);
  write_integer(out, out_name, text_addr);

  /* Read data segments, add up the sizes and find the start address
   * as a minimum of all start addresses. */
  parse_size_line(in, in_name, ".data", &addr, &size);
  parse_size_line(in, in_name, ".rdata", &addr, &size);
  parse_size_line(in, in_name, ".bss", &addr, &size);
  parse_size_line(in, in_name, ".sbss", &addr, &size);

  /* Write the summary to the segment map. */
  write_integer(out, out_name, size);
  write_integer(out, out_name, addr);
}

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

  /* 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 = 0, y = 0;
    if (memcmp(keyword, s, n) == 0)
    {
      /* Match found. Read the following two integers. */
      if (sscanf(s + n, "%x%x", &x, &y) != 2)
      {
        fprintf(stderr, "%s: size-file syntax error\n", in_name);
        exit(EXIT_FAILURE);
      }
      /* Adjust 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);
  }
}
