/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
#ifndef RLD
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <mach/mach.h>
#include <libc.h>

#include "stuff/bool.h"
#include "stuff/errors.h"
#include "stuff/allocate.h"
#include "stuff/guess_short_name.h"
#include "stuff/seg_addr_table.h"

/*
 * parse_default_seg_addr_table() simply calls parse_seg_addr_table() with
 * the default file that contains the segment address table.
 */
struct seg_addr_table *
parse_default_seg_addr_table(
char **seg_addr_table_name,
unsigned long *table_size)
{
#ifdef __GONZO_BUNSEN_BEAKER__
	*seg_addr_table_name = "/Local/Developer/seg_addr_table";
#else
	*seg_addr_table_name = "/AppleInternal/Developer/seg_addr_table";
#endif
	return(parse_seg_addr_table(*seg_addr_table_name,
				"default", "segment address table", table_size));
}

/*
 * parse_seg_addr_table() opens the file_name passed to it and parses it as a
 * segment address table.  The flag and argument parameters are used for
 * error messages if there is a problem parsing the file.  The file should
 * contains lines of the form:
 *	<hex address> <install name>
 * or
 *	<hex address> <hex address> <install name>
 * The first form with one <hex address> specifies the -seg1addr of the
 * dynamic shared library with the specified <install name>.  The second form
 * specifies the -segs_read_only_addr and the -segs_read_write_addr addresses
 * of the dynamic shared library.  The fields are to be separated by spaces or
 * tabs.  Comment lines starting with a '#' character are ignored as well as
 * lines with just spaces and tabs.  This routine returns the parsed table as an
 * array of seg_addr_table structs.  The last entry in the table has a NULL
 * install_name.  For lines with two addresses the field split will be TRUE
 * otherwise it will be FALSE.
 */
struct seg_addr_table *
parse_seg_addr_table(
char *file_name,/* file name of the seg_addr_table file */
char *flag,	/* "-seg_addr_table" or "default" */
char *argument, /* -seg_addr_table argument or "segment address table" */
unsigned long *table_size)
{
    int fd;
    kern_return_t r;
    struct stat stat_buf;
    unsigned long j, k, file_size, seg_addr_table_size, line;
    char *file_addr, *endp;
    struct seg_addr_table *new_seg_addr_table;

	if((fd = open(file_name, O_RDONLY, 0)) == -1)
	    system_fatal("Can't open: %s for %s %s",
		    file_name, flag, argument);
	if(fstat(fd, &stat_buf) == -1)
	    system_fatal("Can't stat file: %s for %s %s",
		    file_name, flag, argument);
	/*
	 * For some reason mapping files with zero size fails
	 * so it has to be handled specially.
	 */
	if(stat_buf.st_size != 0){
	    if((r = map_fd((int)fd, (vm_offset_t)0,
		(vm_offset_t *)&file_addr, (boolean_t)TRUE,
		(vm_size_t)stat_buf.st_size)) != KERN_SUCCESS)
		mach_fatal(r, "can't map file: %s for %s %s",
		    file_name, flag, argument);
	}
	else
	    fatal("Empty file: %s for %s %s", file_name, flag, argument);
	close(fd);
	file_size = stat_buf.st_size;

	/*
	 * Got the file mapped now parse it.
	 */
	if(file_addr[file_size - 1] != '\n')
	    fatal("file: %s for %s %s does not end in new line",
		  file_name, flag, argument);
	seg_addr_table_size = 0;
	for(j = 1; j < file_size; j++){
	    if(file_addr[j] == '\n'){
		seg_addr_table_size++;
	    }
	}
	seg_addr_table_size++;
	new_seg_addr_table = allocate(sizeof(struct seg_addr_table) *
				      seg_addr_table_size);
	k = 0;
	line = 1;
	for(j = 0; j < file_size; /* no increment expression */ ){
	    /* Skip lines that start with '#' */
	    if(file_addr[j] == '#'){
		j++;
		while(file_addr[j] != '\n')
		    j++;
		continue;
	    }
	    /* Skip blank lines */
	    while(file_addr[j] == ' ' || file_addr[j] == '\t')
		j++;
	    if(file_addr[j] == '\n'){
		j++;
		line++;
		continue;
	    }
	    new_seg_addr_table[k].seg1addr =
		strtoul(file_addr + j, &endp, 16);
	    if(endp == NULL)
		fatal("improper hexadecimal number on line %lu in "
		      "file: %s for %s %s", j, file_name, flag, argument);
	    j = endp - file_addr;
	    if(j == file_size)
		fatal("missing library install name on line %lu in file: "
		      "%s for %s %s", j, file_name, flag, argument);
	    /*
	     * Since we checked to see the file ends in a '\n' we can
	     * be assured this won't run off the end of the file.
	     */
	    while(file_addr[j] == ' ' || file_addr[j] == '\t')
		j++;
	    if(file_addr[j] == '\n')
		fatal("missing library install name on line %lu in file: "
		      "%s for %s %s", j, file_name, flag, argument);

	    new_seg_addr_table[k].segs_read_write_addr =
		strtoul(file_addr + j, &endp, 16);
	    if(endp == NULL || endp == file_addr + j){
		new_seg_addr_table[k].split = FALSE;
		new_seg_addr_table[k].segs_read_write_addr = ULONG_MAX;
	    }
	    else{
		j = endp - file_addr;
		new_seg_addr_table[k].split = TRUE;
		new_seg_addr_table[k].segs_read_only_addr =
		    new_seg_addr_table[k].seg1addr;
		new_seg_addr_table[k].seg1addr =  ULONG_MAX;
		while(file_addr[j] == ' ' || file_addr[j] == '\t')
		    j++;
	    }

	    new_seg_addr_table[k].install_name = file_addr + j;
	    new_seg_addr_table[k].line = line;
	    k++;
	    while(file_addr[j] != '\n')
		j++;
	    file_addr[j] = '\0';
	    line++;
	    j++;
	}

	new_seg_addr_table[k].install_name = NULL;
	new_seg_addr_table[k].seg1addr = 0;
	new_seg_addr_table[k].split = FALSE;
	new_seg_addr_table[k].segs_read_only_addr = ULONG_MAX;
	new_seg_addr_table[k].segs_read_write_addr = ULONG_MAX;
	new_seg_addr_table[k].line = line;

	*table_size = k;
	return(new_seg_addr_table);
}

/*
 * search_seg_addr_table() searches the specified segment address table for the
 * specified name and returns the entry to it if it is found.  If it is not
 * found NULL is returned.
 */
struct seg_addr_table *
search_seg_addr_table(
struct seg_addr_table *seg_addr_table,
char *install_name)
{
    struct seg_addr_table *p;

	if(seg_addr_table == NULL)
	    return(NULL);

	for(p = seg_addr_table; p->install_name != NULL; p++){
	    if(strcmp(p->install_name, install_name) == 0)
		return(p);
	}
	return(NULL);
}

/*
 * process_seg_addr_table() is used to create an new segment address table from
 * an existing segment address table.  It opens and parses "file_name" as a
 * segment address table.  It copies the comment lines that don't start with
 * "comment_prefix" to "out_fp".  For entries in the file it calls the function
 * processor() pass to it and passes it the parsed entry from the file, the
 * "out_fp" and the "cookie" passed to process_seg_addr_table().
 */
void
process_seg_addr_table(
char *file_name,
FILE *out_fp,
char *comment_prefix,
void (*processor)(struct seg_addr_table *entry, FILE *out_fp, void *cookie),
void *cookie)
{
    int fd;
    kern_return_t r;
    struct stat stat_buf;
    unsigned long i, file_size, line, comment_prefix_length;
    char *file_addr, *endp;
    struct seg_addr_table entry;

	if((fd = open(file_name, O_RDONLY, 0)) == -1)
	    system_fatal("can't open file: %s", file_name);
	if(fstat(fd, &stat_buf) == -1)
	    system_fatal("can't stat file: %s", file_name);
	/*
	 * For some reason mapping files with zero size fails
	 * so it has to be handled specially.
	 */
	if(stat_buf.st_size != 0){
	    if((r = map_fd((int)fd, (vm_offset_t)0,
		(vm_offset_t *)&file_addr, (boolean_t)TRUE,
		(vm_size_t)stat_buf.st_size)) != KERN_SUCCESS)
		mach_fatal(r, "can't map file: %s", file_name);
	}
	else
	    fatal("empty file: %s ", file_name);
	close(fd);
	file_size = stat_buf.st_size;

	/*
	 * Got the file mapped now parse and process it.
	 */
	if(file_addr[file_size - 1] != '\n')
	    fatal("file: %s does not end in new line", file_name);

	line = 1;
	comment_prefix_length = strlen(comment_prefix);
	for(i = 0; i < file_size; /* no increment expression */ ){
	    /* Copy comment lines that start with '#' */
	    if(file_addr[i] == '#'){
		if(strncmp(comment_prefix, file_addr + i + 1,
			   comment_prefix_length) == 0){
		    i++;
		    while(file_addr[i] != '\n')
			i++;
		    if(file_addr[i] == '\n'){
			i++;
			line++;
		    }
		    continue;
		}
		fputc(file_addr[i], out_fp);
		i++;
		while(file_addr[i] != '\n'){
		    fputc(file_addr[i], out_fp);
		    i++;
		}
		continue;
	    }
	    /* Copy blank lines */
	    while(file_addr[i] == ' ' || file_addr[i] == '\t'){
		fputc(file_addr[i], out_fp);
		i++;
	    }
	    if(file_addr[i] == '\n'){
		fputc(file_addr[i], out_fp);
		i++;
		line++;
		continue;
	    }
	    entry.seg1addr = strtoul(file_addr + i, &endp, 16);
	    if(endp == NULL)
		fatal("improper hexadecimal number on line %lu in file: %s",
		      line, file_name);
	    i = endp - file_addr;
	    if(i == file_size)
		fatal("missing library install name on line %lu in file: %s",
		      line, file_name);
	    /*
	     * Since we checked to see the file ends in a '\n' we can
	     * be assured this won't run off the end of the file.
	     */
	    while(file_addr[i] == ' ' || file_addr[i] == '\t')
		i++;
	    if(file_addr[i] == '\n')
		fatal("missing library install name on line %lu in file: %s",
		      line, file_name);

	    entry.segs_read_write_addr = strtoul(file_addr + i, &endp, 16);
	    if(endp == NULL || endp == file_addr + i){
		entry.split = FALSE;
		entry.segs_read_write_addr = ULONG_MAX;
	    }
	    else{
		i = endp - file_addr;
		entry.split = TRUE;
		entry.segs_read_only_addr = entry.seg1addr;
		entry.seg1addr = ULONG_MAX;
		while(file_addr[i] == ' ' || file_addr[i] == '\t')
		    i++;
	    }

	    entry.install_name = file_addr + i;
	    entry.line = line;
	    while(file_addr[i] != '\n')
		i++;
	    file_addr[i] = '\0';

	    processor(&entry, out_fp, cookie);

	    line++;
	    i++;
	}
}
#endif /* !defined(RLD) */
