/**************************************************************** -*- 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, y;
	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);
    }
}