/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <libc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <mach/mach.h>

#include "stuff/bool.h"
#include "stuff/ofile.h"
#include "stuff/errors.h"
#include "stuff/allocate.h"
#include "stuff/dylib_table.h"
/* This may change to "/MacOSX/System" someday */
#define SYSTEM_LIBRARY "/System/Library"

/*
 * parse_dylib_table() opens the file_name passed to it and parses it as a
 * dylib 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> <short name>
 * The fields are to be separated by spaces or tabs.  Lines with just spaces
 * and tabs are ignored.  This routine returns the parsed table as an
 * array of dylib_table structs and returns a pointer to the table.  The last
 * entry in the table has a NULL name.
 */
struct dylib_table *
parse_dylib_table(
char *file_name,/* file name of dylib table file */
char *flag,	/* "-dylib_file" or "default" */
char *argument) /* -dylib_file argument or "dylib table" */
{
    int fd;
    struct stat stat_buf;
    uint32_t j, k, file_size, new_dylib_table_size;
    char *file_addr, *endp;
    struct dylib_table *new_dylib_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.
	 */
	file_addr = NULL;
	if(stat_buf.st_size != 0){
	    file_addr = mmap(0, stat_buf.st_size, PROT_READ|PROT_WRITE,
			     MAP_FILE|MAP_PRIVATE, fd, 0);
	    if((intptr_t)file_addr == -1)
		system_error("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);
	new_dylib_table_size = 0;
	for(j = 1; j < file_size; j++){
	    if(file_addr[j] == '\n'){
		new_dylib_table_size++;
	    }
	}
	new_dylib_table_size++;
	new_dylib_table = allocate(sizeof(struct dylib_table) *
				   new_dylib_table_size);
	k = 0;
	for(j = 0; j < file_size; /* no increment expression */ ){
	    /* Skip blank lines */
	    while(file_addr[j] == ' ' || file_addr[j] == '\t')
		j++;
	    if(file_addr[j] == '\n'){
		j++;
		continue;
	    }
	    new_dylib_table[k].seg1addr =
		strtoul(file_addr + j, &endp, 16);
	    if(endp == NULL)
		fatal("improper hexadecimal number on line %u in "
		      "file: %s for %s %s", j, file_name, flag, argument);
	    j = endp - file_addr;
	    if(j == file_size)
		fatal("missing library name on line %u 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 name on line %u in file: "
		      "%s for %s %s", j, file_name, flag, argument);
	    new_dylib_table[k].name = file_addr + j;
	    k++;
	    while(file_addr[j] != '\n')
		j++;
	    file_addr[j] = '\0';
	    j++;
	}
	new_dylib_table[k].seg1addr = 0;
	new_dylib_table[k].name = NULL;
	return(new_dylib_table);
}

/*
 * parse_default_dylib_table() opens and parses ~rc/Data/DylibTable .
 */
struct dylib_table *
parse_default_dylib_table(
char **file_name)
{
    uint32_t i;
    FILE *fp;

	*file_name = allocate(MAXPATHLEN+1);
	fp = popen("/bin/echo ~rc/Data/DylibTable", "r");
	if(fp == NULL)
	    fatal("must use -dylib_table (popen failed on \"/bin/echo "
		  "~rc/Data/DylibTable\"");
	if(fgets(*file_name, MAXPATHLEN, fp) == NULL)
	    fatal("must use -dylib_table (fgets failed from popen of "
		  "\"/bin/echo ~rc/Data/DylibTable\"");
	i = strlen(*file_name);
	if(i == 0 || (*file_name)[i-1] != '\n')
	    fatal("must use -dylib_table (file name from popen of "
		  "\"/bin/echo ~rc/Data/DylibTable\" greater than "
		  "MAXPATHLEN");
	(*file_name)[i-1] = '\0';
	pclose(fp);
	return(parse_dylib_table(*file_name, "default", "dylib table"));
}

/*
 * search_dylib_table() searches the specified dylib table for the specified
 * name and returns the entry to it if it is found.  If it is not found NULL
 * is returned.
 */
struct dylib_table *
search_dylib_table(
struct dylib_table *dylib_table,
char *name)
{
    struct dylib_table *p;

	if(dylib_table == NULL)
	    return(NULL);

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

static char *versions[] = { "A", "B", "C", "0", NULL };

/*
 * guess_dylib_install_name() is passed a name from the dylib file and then
 * the install name is guessed based on the files on the system the program is
 * running on.  This routines makes up conventional install names base on the 
 * name passed in and then sees if there is a file on the system with that
 * install_name.  If it finds one it returns the install_name else it returns
 * NULL.
 */
char *
guess_dylib_install_name(
char *name)
{
    uint32_t i;
    char *guess;
    struct stat stat_buf;

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(
		    SYSTEM_LIBRARY,"/Frameworks/JavaVM.framework/Libraries/lib",
		    name, ".", versions[i], ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/", name,
			".framework/Versions/", versions[i], "/", name, NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/PrivateFrameworks/", name,
			".framework/Versions/", versions[i], "/", name, NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"Printers/", name, ".",
			    versions[i],".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
#ifdef __GONZO_BUNSEN_BEAKER__
	    guess = makestr("/Local/Library/Frameworks/", name,
			".framework/Versions/", versions[i], "/", name, NULL);
#else
	    guess = makestr("/MacOSX/Library/Frameworks/", name,
			".framework/Versions/", versions[i], "/", name, NULL);
#endif
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

#ifndef __GONZO_BUNSEN_BEAKER__
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr("/AppleInternal/Library/Frameworks/", name,
			".framework/Versions/", versions[i], "/", name, NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
#endif

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr("/lib/", name, ".", versions[i], ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr("/usr/lib/", name, ".", versions[i], ".dylib",
			NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr("/usr/lib/java/", name, ".", versions[i],
			    ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr("/usr/local/lib/", name, ".", versions[i],
			    ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr("/usr/canna/dylib/", name, ".", versions[i],
			    ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
/* From ALLOW_MACOSX_PR1_PATHS */
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/CarbonCore.framework/"
			    "Versions/", versions[i], "/Support/", name,
			    ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"Frameworks/Carbon.framework/"
			    "Versions/", versions[i], "/Libraries/", name,
			    ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/CoreGraphics.framework/"
			    "Versions/", versions[i], "/Libraries/", name, ".",
			    versions[i], ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
/* From ALLOW_MACOSX_DP3_PATHS */
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Components/", name, ".qtx", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/Carbon.framework/"
			    "Versions/", versions[i], "/Resources//", name,
			    ".qtx", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/CoreGraphics.framework/"
			    "Versions/", versions[i], "/Resources/", name, ".",
			    versions[i], ".qtx", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/JavaVM.framework/"
			    "Versions/1.2/Libraries/", name, ".", versions[i],
			    ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/PrintingCore.framework/"
			    "Versions/", versions[i], "/Libraries/", name, ".",
			    versions[i], ".dylib", NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}
	for(i = 0; versions[i] != NULL; i++){
	    guess = makestr(SYSTEM_LIBRARY,"/Frameworks/QuickTime.framework/"
			    "Versions/", versions[i], "/", name, NULL);
	    if(stat(guess, &stat_buf) != -1)
		return(guess);
	    free(guess);
	}

	return(NULL);
}
#endif /* !defined(RLD) */
