/* 
 * Mach Operating System
 * Copyright (c) 1991,1990,1989,1988 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 * HISTORY
 * $Log:	load.c,v $
 * Revision 2.2  92/01/03  19:57:44  dbg
 * 	Close startup file after loading it.
 * 	[91/09/25            dbg]
 * 
 * 	Move outside of kernel.
 * 	[91/09/04            dbg]
 * 
 * Revision 2.14  91/09/04  11:27:54  jsb
 * 	Fixed a printf in read_symtab_from_file (following failure of
 * 	get_symtab) by not printing unset variable ``result''.
 * 	[91/09/01  07:48:45  jsb]
 * 
 * Revision 2.13  91/08/28  11:09:49  jsb
 * 	Fixed to check vm_allocate return codes, esp in read_symtab_from_file.
 * 	[91/08/26            rpd]
 * 
 * Revision 2.12  91/07/31  17:25:29  dbg
 * 	Replace blkclr with bzero.
 * 	[91/07/26            dbg]
 * 
 * 	Moved read_symtab_from_file here and made independent of load
 * 	format.
 * 	[91/06/25            dbg]
 * 
 * 
 * Revision 2.11  91/05/14  15:23:41  mrt
 * 	Correcting copyright
 * 
 * Revision 2.10  91/03/16  14:41:37  rpd
 * 	Fixed read_emulator_symbols to not panic if there is no emulator.
 * 	[91/03/13            rpd]
 * 
 * Revision 2.9  91/02/05  17:01:45  mrt
 * 	Changed to new copyright
 * 	[91/01/28  14:55:09  mrt]
 * 
 * Revision 2.8  90/10/25  14:42:05  rwd
 * 	Modified boot_load_program and read_emulator_symbols
 * 	to allocate arrays from heap, not stack.
 * 	[90/10/23            rpd]
 * 
 * Revision 2.7  90/08/27  21:46:02  dbg
 * 	Reduce lint.
 * 	[90/08/13            dbg]
 * 
 * 	Use new error codes.  Use new file_io package.
 * 	[90/07/16            dbg]
 * 
 * Revision 2.6  90/06/02  14:45:31  rpd
 * 	Added read_emulator_symbols.
 * 	[90/05/11  17:08:19  rpd]
 * 
 * 	From jsb: to support debugging, read in the server's symbol table.
 * 	[90/04/23            rpd]
 * 	Converted to new IPC.
 * 	[90/03/26  21:31:33  rpd]
 * 
 * Revision 2.5  90/01/11  11:41:20  dbg
 * 	Use bootstrap-task print routines.
 * 	[89/12/20            dbg]
 * 
 * Revision 2.4  89/11/14  10:28:13  dbg
 * 	Restore read-only protection for text segment.
 * 	[89/11/01            dbg]
 * 
 * Revision 2.3  89/09/08  11:22:13  dbg
 * 	Use new open_file routine.  Change 'struct inode' to
 * 	'struct file' for new stand-alone IO package.
 * 	[89/08/31            dbg]
 * 
 * 26-May-89  Randall Dean (rwd) at Carnegie-Mellon University
 *	Fixed separate text/data case.  Added copyright to version
 *	generated by dbg
 *
 */
#include <mach/mach_interface.h>

#include <varargs.h>

#include "file_io.h"
#include "loader_info.h"

/*
 * Loader for a.out format.
 * Loads the file into the user space of the specified task,
 * and sets up the starting thread's registers.
 */

extern char *set_regs();
extern int  ex_get_header();

boolean_t	load_protect_text = TRUE;

int load_program_file(fp, user_task, user_thread, arg_size, arg_pos_p)
	register struct file *	fp;
	task_t			user_task;
	thread_t		user_thread;
	int			arg_size;
	char *			*arg_pos_p;	/* out */
{
	struct loader_info	lp;
	kern_return_t		result;
	vm_offset_t		text_page_start,
				text_page_end,
				data_page_start,
				data_page_end,
				area_start;
	vm_size_t		resid;

	if ((fp->i_mode & IFMT) != IFREG ||
	    (fp->i_mode & (IEXEC|IEXEC>>3|IEXEC>>6)) == 0)
		return (EX_NOT_EXECUTABLE);

	/*
	 * Read in the header and get the file pointers.
	 */
	result = ex_get_header(fp, &lp);
	if (result)
		return (result);

	/*
	 * Allocate space.
	 */
	text_page_start = trunc_page(lp.text_start);
	text_page_end   = round_page(lp.text_start + lp.text_size);
	data_page_start = trunc_page(lp.data_start);
	data_page_end   = round_page(lp.data_start + lp.data_size
					+ lp.bss_size);

	if (text_page_end >= data_page_start) {
	    /*
	     * One contiguous area for text and data.
	     */
	    result = vm_allocate(mach_task_self(),
				&area_start,
				(vm_size_t)(data_page_end - text_page_start),
				TRUE);
	    if (result)
		return (result);

	    result = read_file(fp,
				lp.text_offset,
				area_start + (lp.text_start - text_page_start),
				lp.text_size,
				&resid);
	    if (result)
		return (result);
	    if (resid)
		return (EX_NOT_EXECUTABLE);

	    result = read_file(fp,
				lp.data_offset,
				area_start + (lp.data_start - text_page_start),
				lp.data_size,
				&resid);
	    if (result)
		return (result);
	    if (resid)
		return (EX_NOT_EXECUTABLE);

	    bzero((char *)(area_start +
				(lp.data_start + lp.data_size
					 - text_page_start)),
				lp.bss_size);

	    result = vm_allocate(user_task,
				&text_page_start,
				(vm_size_t)(data_page_end - text_page_start),
				FALSE);
	    if (result)
		return (result);

	    result = vm_write(user_task,
				text_page_start,
				(pointer_t)area_start,
				(vm_size_t)(data_page_end - text_page_start));
	    if (result)
		return (result);

	    result = vm_deallocate(mach_task_self(),
				area_start,
				(vm_size_t)(data_page_end - text_page_start));
	    if (result)
		return (result);

	}
	else {
	    /*
	     * Separated text and data areas.
	     */
	    /*
	     * First read in the text
	     */
	    result = vm_allocate(mach_task_self(),
				&area_start,
				(vm_size_t)(text_page_end - text_page_start),
				TRUE);
	    if (result)
		return (result);

	    result = read_file(fp,
				lp.text_offset,
				area_start + (lp.text_start - text_page_start),
				lp.text_size,
				&resid);
	    if (result)
		return (result);
	    if (resid)
		return (EX_NOT_EXECUTABLE);

	    result = vm_allocate(user_task,
				&text_page_start,
				(vm_size_t)(text_page_end - text_page_start),
				FALSE);
	    if (result)
		return (result);

	    result = vm_write(user_task,
				text_page_start,
				(pointer_t)area_start,
				(vm_size_t)(text_page_end - text_page_start));
	    if (result)
		return (result);

	    result = vm_deallocate(mach_task_self(),
				area_start,
				(vm_size_t)(text_page_end - text_page_start));
	    if (result)
		return (result);

	    /*
	     * Then the data
	     */
	    result = vm_allocate(mach_task_self(),
				&area_start,
				(vm_size_t)(data_page_end - data_page_start),
				TRUE);
	    if (result)
		return (result);

	    result = read_file(fp,
				lp.data_offset,
				area_start + (lp.data_start - data_page_start),
				lp.data_size,
				&resid);
	    if (result)
		return (result);
	    if (resid)
		return (EX_NOT_EXECUTABLE);

	    bzero((char *)(area_start + 
				(lp.data_start + lp.data_size
					- data_page_start)),
		   lp.bss_size);

	    result = vm_allocate(user_task,
				&data_page_start,
				(vm_size_t)(data_page_end - data_page_start),
				FALSE);
	    if (result)
		return (result);

	    result = vm_write(user_task,
				data_page_start,
				(pointer_t)area_start,
				(vm_size_t)(data_page_end - data_page_start));
	    if (result)
		return (result);

	    result = vm_deallocate(mach_task_self(),
				area_start,
				(vm_size_t)(data_page_end - data_page_start));
	    if (result)
		return (result);

	}
	/*
	 * Protect the text.
	 */
	if (load_protect_text) {
	    result = vm_protect(user_task,
			    text_page_start,
			    (vm_size_t)(trunc_page(lp.text_start+lp.text_size)
					- text_page_start),
			    FALSE,
			    VM_PROT_READ|VM_PROT_EXECUTE);
	    if (result)
		return (result);
	}

	/*
	 * Set up the stack and user registers.
	 */
	*arg_pos_p = set_regs(user_task, user_thread, &lp, arg_size);

	return (KERN_SUCCESS);
}

/*VARARGS4*/
int boot_load_program(master_host_port,
		      master_device_port,
		      user_task,
		      user_thread,
		      server_dir_name,
		      va_alist)	/* first component is program name */
	mach_port_t	master_host_port;
	mach_port_t	master_device_port;
	thread_t	user_thread;
	char		*server_dir_name;
	va_dcl
{
	va_list			argv_ptr;
	char *			arg_ptr;

	int			arg_len;
	int			arg_count;
	char *			arg_pos;
	unsigned int		arg_item_len;

	kern_return_t		result;
	struct file		file;
	char *			file_name;

	char			namebuf[MAXPATHLEN+1];

	extern char *	strbuild();

	/*
	 * Get the target file name
	 */
	va_start(argv_ptr);
	file_name = va_arg(argv_ptr, char *);
	va_end(argv_ptr);

	/*
	 * Build file name: "$server_dir_name/$filename"
	 */
	(void) strbuild(namebuf, server_dir_name,
				 "/",
				 file_name,
				 (char *)0);

	/*
	 * Open the file
	 */
	bzero((char *)&file, sizeof(file));

	result = open_file(master_device_port, namebuf, &file);
	if (result != 0) {
	    panic("openi %d", result);
	}

	/*
	 * Calculate the size of the argument list.
	 */
	va_start(argv_ptr);
	arg_len = 0;
	arg_count = 0;
	for (;;) {
	    arg_ptr = va_arg(argv_ptr, char *);
	    if (arg_ptr == (char *)0)
		break;
	    arg_count++;
	    arg_len += strlen(arg_ptr) + 1;	/* space for '\0' */
	}
	va_end(argv_ptr);

	/*
	 * Add space for:
	 *    arg_count
	 *    pointers to arguments
	 *    trailing 0 pointer
	 *    dummy 0 pointer to environment variables
	 *    and align to integer boundary
	 */
	arg_len += sizeof(int) + (2 + arg_count) * sizeof(char *);
	arg_len = (arg_len + (sizeof(int) - 1)) & ~(sizeof(int)-1);

	/*
	 * Load the file
	 */
	result = load_program_file(&file, user_task, user_thread, arg_len,
				   &arg_pos);
	if (result)
	    panic("load_program_file %d", result);

	/*
	 * Read symbols from file
	 */
	read_symtab_from_file(&file, master_host_port, user_task, "unix");

	/*
	 * Copy out the arguments.
	 */
	{
	    vm_offset_t	u_arg_start;
				/* user start of argument list block */
	    vm_offset_t	k_arg_start;
				/* kernel start of argument list block */
	    vm_offset_t u_arg_page_start;
				/* user start of args, page-aligned */
	    vm_size_t	arg_page_size;
				/* page_aligned size of args */
	    vm_offset_t	k_arg_page_start;
				/* kernel start of args, page-aligned */

	    register
	    char **	k_ap;	/* kernel arglist address */
	    char *	u_cp;	/* user argument string address */
	    register
	    char *	k_cp;	/* kernel argument string address */
	    register
	    int		i;

	    /*
	     * Get address of argument list in user space
	     */
	    u_arg_start = (vm_offset_t)arg_pos;

	    /*
	     * Round to page boundaries, and allocate kernel copy
	     */
	    u_arg_page_start = trunc_page(u_arg_start);
	    arg_page_size = (vm_size_t)(round_page(u_arg_start + arg_len)
					- u_arg_page_start);

	    result = vm_allocate(mach_task_self(),
				 &k_arg_page_start,
				 (vm_size_t)arg_page_size,
				 TRUE);
	    if (result)
		panic("boot_load_program: arg size");

	    /*
	     * Set up addresses corresponding to user pointers
	     * in the kernel block
	     */
	    k_arg_start = k_arg_page_start + (u_arg_start - u_arg_page_start);

	    k_ap = (char **)k_arg_start;

	    /*
	     * Start the strings after the arg-count and pointers
	     */
	    u_cp = (char *)u_arg_start + arg_count * sizeof(char *)
					+ 2 * sizeof(char *)
					+ sizeof(int);
	    k_cp = (char *)k_arg_start + arg_count * sizeof(char *)
					+ 2 * sizeof(char *)
					+ sizeof(int);

	    /*
	     * first the argument count
	     */
	    *k_ap++ = (char *)arg_count;

	    /*
	     * Then the strings and string pointers for each argument
	     */
	    va_start(argv_ptr);
	    for (i = 0; i < arg_count; i++) {
		arg_ptr = va_arg(argv_ptr, char *);
		arg_item_len = strlen(arg_ptr) + 1; /* include trailing 0 */

		/* set string pointer */
		*k_ap++ = u_cp;

		/* copy string */
		bcopy(arg_ptr, k_cp, arg_item_len);
		k_cp += arg_item_len;
		u_cp += arg_item_len;
	    }
	    va_end(argv_ptr);

	    /*
	     * last, the trailing 0 argument and a null environment pointer.
	     */
	    *k_ap++ = (char *)0;
	    *k_ap   = (char *)0;

	    /*
	     * Now write all of this to user space.
	     */
	    (void) vm_write(user_task,
			    u_arg_page_start,
			    k_arg_page_start,
			    arg_page_size);

	    (void) vm_deallocate(mach_task_self(),
				 k_arg_page_start,
				 arg_page_size);
	}

	/*
	 * Close the file.
	 */
	close_file(&file);

	return (0);
}

read_emulator_symbols(master_host_port, master_device_port,
		server_dir_name, file_name)
	mach_port_t	master_host_port;
	mach_port_t	master_device_port;
	char		*server_dir_name;
	char		*file_name;
{
	kern_return_t	result;
	struct file	file;
	char		namebuf[MAXPATHLEN+1];
	extern char *	strbuild();

	/*
	 * Build file name: "$server_dir_name/$filename"
	 */
	(void) strbuild(namebuf, server_dir_name,
				 "/",
				 file_name,
				 (char *)0);

	/*
	 * Open the file
	 */
	bzero((char *)&file, sizeof(file));
	result = open_file(master_device_port, namebuf, &file);
	if (result != 0) {
	    printf("[ no emulator symbols ]\n");
	    return;
	}

	/*
	 * Read symbols from file
	 */
	read_symtab_from_file(&file, master_host_port,
			MACH_PORT_NULL,		/* in all tasks */
			"emulator");

	/*
	 * Close the file.
	 */
	close_file(&file);
}

/*
 * Load symbols from file into kernel debugger.
 */
read_symtab_from_file(fp, host_port, task, symtab_name)
	struct file	*fp;
	mach_port_t	host_port;
	task_t		task;
	char *		symtab_name;
{
	/*
	 * The symbol table is read from the file.
	 * Machine-dependent code can specify an
	 * optional header to be prefixed to the
	 * symbol table, containing information
	 * that cannot directly be read from the file.
	 */
#define	HEADER_MAX	(64*sizeof(int))

	vm_size_t	resid;
	kern_return_t	result;
	vm_offset_t	sym_off;
	vm_size_t	sym_size;
	char		header[HEADER_MAX];
	vm_size_t	header_size;
	vm_size_t	table_size;
	vm_offset_t	symtab;
	boolean_t	size_prefix = FALSE;

	/*
	 * Read the header from the file to find the symbols.
	 */
	header_size = HEADER_MAX;
	if (!get_symtab(fp, &sym_off, &sym_size, &header[0], &header_size)) {
	    printf("[ no valid symbol table header present for %s ]\n",
			symtab_name);
	    return;
	}

	/*
	 * Allocate space for the symbol table, preceding it
	 * by the optional header if there is one.
	 */
	table_size = sym_size + header_size;
	if (table_size == 0)
	    return;	/* no symbols */

	result= vm_allocate(mach_task_self(),
			    &symtab,
			    table_size,
			    TRUE);
	if (result) {
	    printf("[ error %d allocating space for %s symbol table ]\n",
		result, symtab_name);
	    return;
	}

	/*
	 * Copy the header to the allocated area, then
	 * read in the symbol table.
	 */
	bcopy(header, (char *)symtab, header_size);

	result = read_file(fp, sym_off,
			symtab + header_size, sym_size, &resid);
	if (result || resid) {
	    printf("[ no valid symbol table present for %s ]\n",
		symtab_name);
	}
	else {
	    /*
	     * Load the symbols into the kernel.
	     */
	    result = host_load_symbol_table(
			host_port,
			task,
			symtab_name,
			symtab,
			table_size);
	}
	(void) vm_deallocate(mach_task_self(), symtab, table_size);
}
