/*
 * Copyright (c) 1992, 1993, 1996
 *	Berkeley Software Design, Inc.  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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Berkeley Software
 *	Design, Inc.
 *
 * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``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 Berkeley Software Design, Inc. 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.
 *
 *	BSDI exe.c,v 2.2 1996/04/08 19:32:34 bostic Exp
 */

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ctype.h>
#include "doscmd.h"

int	pspseg;
int	curpsp = 0;
int	psp_s[10] = { 0 };
int	env_s[10];
struct sigcontext frames[10];

char *env_block;

int
make_environment (char *cmdname, char **env)
{
	int i;
	int total;
	int len;
	int envseg;
	char *p;
	char *env_block;

	total = 0;
	for (i = 0; env[i]; i++) {
	    debug (D_EXEC,"env: %s\n", env[i]);
	    len = strlen (env[i]);
	    if (total + len >= 32 * 1024)
		break;
	    total += len + 1;
	}

	total++; /* terminating null */
	total += 2; /* word count */
	total += strlen (cmdname) + 1;
	total += 4; /* some more zeros, just in case */

	if ((envseg = mem_alloc(total/16 + 1, 1, NULL)) == 0)
		fatal("out of memory for env\n");

	env_block = (char *)MAKEPTR(envseg, 0);
	memset (env_block, 0, total);

	p = env_block;
	total = 0;
	for (i = 0; env[i]; i++) {
		len = strlen (env[i]);
		if (total + len >= 32 * 1024)
			break;
		total += len + 1;
		strcpy (p, env[i]);
		p += strlen (p) + 1;
	}
	*p++ = 0;
	*(short *)p = strlen(cmdname);
	p += 2;
	strcpy (p, cmdname);
	while(*p) {
		if (*p == '/')
			*p = '\\';
		else if (islower(*p))
		    	*p = toupper(*p);
		p++;
	}
	*p = '\0';
	return envseg;
}

struct exehdr {
	u_short magic;
	u_short bytes_on_last_page;
	u_short size; /* 512 byte blocks */
	u_short nreloc;
	u_short hdr_size; /* paragraphs */
	u_short min_memory; /* paragraphs */
	u_short max_memory; /* pargraphs */
	u_short init_ss;
	u_short init_sp;
	u_short checksum;
	u_short init_ip;
	u_short init_cs;
	u_short reloc_offset;
	u_short overlay_num;
};

struct reloc_entry {
	u_short off;
	u_short seg;
};

void
load_com(int fd, int start_segment)
{
	char *start_addr;
	int i;

	start_addr = (char *)MAKEPTR(start_segment, 0);

	lseek (fd, 0, 0);
	i = read (fd, start_addr, 0xff00);

	debug(D_EXEC, "Read %05x into %04x\n",
		       i, start_segment);

}

void
load_exe(int fd, int start_segment, int reloc_segment, struct exehdr *hdr, int text_size)
{
	char *start_addr;
	int reloc_size;
	struct reloc_entry *reloc_tbl, *rp;
	u_short *segp;
	int i;

	start_addr = (char *)MAKEPTR(start_segment, 0);

	lseek (fd, hdr->hdr_size * 16, 0);
	if (read (fd, start_addr, text_size) != text_size)
		fatal ("error reading program text\n");
	debug(D_EXEC, "Read %05x into %04x\n",
		       text_size, start_segment);

    	if (hdr->nreloc) {
	    reloc_size = hdr->nreloc * sizeof (struct reloc_entry);

	    if ((reloc_tbl = (struct reloc_entry *)malloc (reloc_size)) == NULL)
		    fatal ("out of memory for program\n");

	    lseek (fd, hdr->reloc_offset, 0);
	    if (read (fd, reloc_tbl, reloc_size) != reloc_size)
		    fatal ("error reading reloc table\n");

	    for (i = 0, rp = reloc_tbl; i < hdr->nreloc; i++, rp++) {
		    segp = (u_short *)MAKEPTR(start_segment + rp->seg, rp->off);
		    *segp += start_segment;
	    }
	    free((char *)reloc_tbl);
    	}
}

load_command(
    struct sigcontext *sc, int run,
    int fd, char *cmdname, u_short *param, char **argv, char **envs)
{
	struct exehdr hdr;
	int min_memory, max_memory;
	int biggest;
	int envseg;
	char *psp;
	int text_size;
	int i;
	int start_segment;
	int exe_file;
	char *p;
	int used, n;
	char *fcb;
	int newpsp;
	u_short init_cs,
		init_ip,
		init_ss,
		init_sp,
		init_ds,
		init_es;

	if (envs)
		envseg = make_environment(cmdname, envs);
	else
		envseg = env_s[curpsp];

	/* read exe header */
	if (read (fd, &hdr, sizeof hdr) != sizeof hdr)
		fatal ("can't read header\n");

	/* proper header ? */
	if (hdr.magic == 0x5a4d) {
		exe_file = 1;
		text_size = (hdr.size - 1) * 512 + hdr.bytes_on_last_page
			- hdr.hdr_size * 16;
		min_memory = hdr.min_memory + (text_size + 15)/16;
		max_memory = hdr.max_memory + (text_size + 15)/16;
	} else {
		exe_file = 0;
		min_memory = 64 * (1024/16);
		max_memory = 0xffff;
	}

	/* alloc mem block */
	pspseg = mem_alloc(max_memory, 1, &biggest);
	if (pspseg == 0) {
		if (biggest < min_memory ||
		   (pspseg = mem_alloc(biggest, 1, NULL)) == 0)
			fatal("not enough memory: needed %d have %d\n",
			      min_memory, biggest);

		max_memory = biggest;
	}

	mem_change_owner(pspseg, pspseg);
	mem_change_owner(envseg, pspseg);

	/* create psp */
	newpsp = curpsp + 1;
	psp_s[newpsp] = pspseg;
	env_s[newpsp] = envseg;

	psp = (char *)MAKEPTR(pspseg, 0);
	memset(psp, 0, 256);

	psp[0] = 0xcd;
	psp[1] = 0x20;

	*(u_short *)&psp[2] = pspseg + max_memory;

	/*
	 * this is supposed to be a long call to dos ... try to fake it
	 */
	psp[5] = 0xcd;
	psp[6] = 0x99;
	psp[7] = 0xc3;

	*(u_short *)&psp[0x16] = psp_s[curpsp];
	psp[0x18] = 1;
	psp[0x19] = 1;
	psp[0x1a] = 1;
	psp[0x1b] = 0;
	psp[0x1c] = 2;
	memset(psp + 0x1d, 0xff, 15);

	*(u_short *)&psp[0x2c] = envseg;

	*(u_short *)&psp[0x32] = 20;
	*(u_long *)&psp[0x34] = MAKEVEC(pspseg, 0x18);
	*(u_long *)&psp[0x38] = 0xffffffff;

	psp[0x50] = 0xcd;
	psp[0x51] = 0x98;
	psp[0x52] = 0xc3;

	p = psp + 0x81;
    	*p = 0;
	used = 0;
	for (i = 0; argv[i]; i++) {
		n = strlen(argv[i]);
		if (used + 1 + n > 0x7d)
			break;
		*p++ = ' ';
		memcpy(p, argv[i], n);
		p += n;
		used += n;
	}

	psp[0x80] = strlen(psp + 0x81);
	psp[0x81 + psp[0x80]] = 0x0d;
	psp[0x82 + psp[0x80]] = 0;

	p = psp + 0x81;
	parse_filename(0x00, p, psp + 0x5c, &n);
	p += n;
	parse_filename(0x00, p, psp + 0x6c, &n);

	if (param[4]) {
		fcb = (char *)MAKEPTR(param[4], param[3]);
		memcpy(psp + 0x5c, fcb, 16);
	}
	if (param[6]) {
		fcb = (char *)MAKEPTR(param[6], param[5]);
		memcpy(psp + 0x6c, fcb, 16);
	}

#if 0
	printf("005c:");
	for (n = 0; n < 16; n++)
		printf(" %02x", psp[0x5c + n]);
	printf("\n");
	printf("006c:");
	for (n = 0; n < 16; n++)
		printf(" %02x", psp[0x6c + n]);
	printf("\n");
#endif

	disk_transfer_addr = MAKEVEC(pspseg, 0x80);

	start_segment = pspseg + 0x10;

	if (!exe_file) {
		load_com(fd, start_segment);

		init_cs = pspseg;
		init_ip = 0x100;
		init_ss = init_cs;
		init_sp = 0xfffe;
		init_ds = init_cs;
		init_es = init_cs;
	} else {
		load_exe(fd, start_segment, start_segment, &hdr, text_size);

		init_cs = hdr.init_cs + start_segment;
		init_ip = hdr.init_ip;
		init_ss = hdr.init_ss + start_segment;
		init_sp = hdr.init_sp;
		init_ds = pspseg;
		init_es = init_ds;
	}

	debug(D_EXEC, "cs:ip = %04x:%04x, ss:sp = %04x:%04x, "
			 "ds = %04x, es = %04x\n",
		init_cs, init_ip, init_ss, init_sp, init_ds, init_es);

	if (run) {
		frames[newpsp] = *sc;
		curpsp = newpsp;

		sc->sc_eflags = 0x20202;
		SET16(sc->sc_cs, init_cs);
		SET16(sc->sc_eip, init_ip);
		SET16(sc->sc_ss, init_ss);
		SET16(sc->sc_esp, init_sp);

		SET16(sc->sc_ds, init_ds);
		SET16(sc->sc_es, init_es);

		SET16(sc->sc_eax, 0);
		SET16(sc->sc_ebx, 0);
		SET16(sc->sc_ecx, 0);
		SET16(sc->sc_edx, 0);
		SET16(sc->sc_esi, 0);
		SET16(sc->sc_edi, 0);
		SET16(sc->sc_ebp, 0);
	} else {
		param[7] = init_sp;
		param[8] = init_ss;
		param[9] = init_ip;
		param[10] = init_cs;
	}
}

load_overlay (int fd, int start_segment, int reloc_segment)
{
	struct exehdr hdr;
	int text_size;
	int exe_file;

	/* read exe header */
	if (read (fd, &hdr, sizeof hdr) != sizeof hdr)
		fatal ("can't read header\n");

	/* proper header ? */
	if (hdr.magic == 0x5a4d) {
		exe_file = 1;
		text_size = (hdr.size - 1) * 512 + hdr.bytes_on_last_page
			- hdr.hdr_size * 16;
	} else {
		exe_file = 0;
	}

	if (!exe_file)
		load_com(fd, start_segment);
	else
		load_exe(fd, start_segment, reloc_segment, &hdr, text_size);
}

int
get_psp(void)
{
	return(psp_s[curpsp]);
}

int
get_env(void)
{
	return(env_s[curpsp]);
}

void
exec_command(
    struct sigcontext *sc, int run,
    int fd, char *cmdname, u_short *param)
{
    char *arg;
    char *env;
    char *argv[2];
    char *envs[100];

    env = (char *)MAKEPTR(param[0], 0);
    arg = (char *)MAKEPTR(param[2], param[1]);

    if (arg) {
	int nbytes = *arg++;
	arg[nbytes] = 0;
	if (!*arg)
	    arg = NULL;
    }
    argv[0] = arg;
    argv[1] = NULL;

    debug (D_EXEC, "exec_command: cmdname = %s\n"
		   "env = 0x0%x, arg = %04x:%04x(%s)\n",
    	cmdname, param[0], param[2], param[1], arg);

    if (env) {
	int i;
	for ( i=0; i < 99 && *env; ++i ) {
	    envs[i] = env;
	    env += strlen(env)+1;
	}
	envs[i] = NULL;
	load_command(sc, run, fd, cmdname, param, argv, envs);
    } else
	load_command(sc, run, fd, cmdname, param, argv, NULL);
}

void
exec_return(struct sigcontext *sc, int code)
{
    debug(D_EXEC, "Returning from exec\n");
    mem_free_owner(psp_s[curpsp]);
    *sc = frames[curpsp--];
    SET16(sc->sc_eax, code);
    sc->sc_eflags &= ~PSL_C;	/* It must have worked */
}
