/*
 * 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 int21.c,v 2.2 1996/04/08 19:32:51 bostic Exp
 */

#include "doscmd.h"
#include <dirent.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>
#include <glob.h>
#include <errno.h>
#include <ctype.h>
#include <stddef.h>

u_long upcase_vector;

/* Country Info */
struct {
        ushort  ciDateFormat;
        char    ciCurrency[5];
        char    ciThousands[2];
        char    ciDecimal[2];
        char    ciDateSep[2];
        char    ciTimeSep[2];
        char    ciCurrencyFormat;
        char    ciCurrencyPlaces;
	char	ciTimeFormat;
	ushort	ciCaseMapOffset;
	ushort	ciCaseMapSegment;
        char    ciDataSep[2];
#if 0
        char    ciReserved[10];
#endif
} countryinfo = {
        0, "$", ",", ".", "-", ":", 0, 2, 0, 0xffff, 0xffff, "?"
};

/* DOS File Control Block */
struct fcb {
#if 1
	u_char	fcbMagic;
	u_char	fcbResoived[5];
	u_char	fcbAttribute;
#endif
        u_char  fcbDriveID;
        u_char  fcbFileName[8];
        u_char  fcbExtent[3];
        u_short fcbCurBlockNo;
        u_short fcbRecSize;
        u_long  fcbFileSize;
        u_short fcbFileDate;     
        u_short fcbFileTime;
        int     fcbReserved;
        int     fcb_fd;                 /* hide UNIX FD here */
        u_char  fcbCurRecNo; 
        u_long  fcbRandomRecNo;
} __attribute__((__packed__));

void encode_dos_file_time (time_t, u_short *, u_short *);
time_t decode_dos_file_time (u_short, u_short);
char *convert_filename ();
static void openfcb(struct fcb*);
static int getfcb_rec(struct fcb*, int);
static int setfcb_rec(struct fcb*, int);
void fcb_to_string(struct fcb *, u_char *);
void dosdir_to_dta(dosdir_t *, find_block_t *);

int ctrl_c_flag = 0;
int return_status = 0;
int diskdrive = 2;	/* C: */
int doserrno = 0;

char *InDOS;

extern time_t delta_clock;

u_char upc_table[0x80] = {
	0x80, 0x9a, 'E',  'A',  0x8e, 'A',  0x8f, 0x80,
	'E',  'E',  'E',  'I',  'I',  'I',  0x8e, 0x8f,
	0x90, 0x92, 0x92, 'O',  0x99, 'O',  'U',  'U', 
	'Y',  0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
	'A',  'I',  'O',  'U',  0xa5, 0xa5, 0xa6, 0xa7,
	0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
	0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
	0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
	0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
	0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
	0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
	0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
	0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
};

u_char
upcase(u_char c)
{

	if (islower(c))
		return (toupper(c));
	else if (c >= 0x80)
		return (upc_table[c - 0x80]);
	else
		return (c);
}

void
upcase_entry(struct sigcontext *sc)
{
	u_char c;

	c = GET8L(sc->sc_eax);
	SET8L(sc->sc_eax, upcase(c));
}

static inline
from_dos_attr(int attr)
{
	return((attr & READ_ONLY_FILE) ? 0444 : 0666);
}

void
int20(struct sigcontext *sc)
{
	/* int 20 = exit(0) */
	done (sc, 0);
}

void
int21(struct sigcontext *sc)
{
	int error;

	error = msdos_call(sc);
	debug(D_FILE_OPS, "msdos call %02x returns %d\n", GET8H(sc->sc_eax),
	    error);
	if (error) {
		doserrno = error;
		if (GET8H(sc->sc_eax) >= 0x2f)
			SET16(sc->sc_eax, error);
		else
			SET8L(sc->sc_eax, 0xff);
		sc->sc_eflags |= PSL_C;
	} else
		sc->sc_eflags &= ~PSL_C;
}

void
int29(struct sigcontext *sc)
{
	tty_write(GET8L(sc->sc_eax), TTYF_REDIRECT);
}

int
msdos_call(struct sigcontext *sc)
{
	u_char filename1[1024], filename2[1024];
	char *addr;
	int nbytes;
	char buf[1000];
	char *p;
	int n;
	int memseg;
	u_short action;
	int mode;
	u_short attr;
	char *name;
	int fd, fd2;
	int avail;
	struct timeval tv;
	struct timezone tz;
	struct tm tm;
	time_t now;
	struct fcb *fcbp;
    	off_t offset;
	u_short *param;
	struct stat sb;
	int drive;
	find_block_t *dta;
	dosdir_t dosdir;
	int whence;
	int error;
	short v;

	switch (GET8H(sc->sc_eax)) {
	case 0x00:
		done(sc, 0);

	case 0x01: /* single character input, with echo */
	func01:
	    	if ((n = tty_read(sc, TTYF_BLOCKALL)) < 0)
			return (0);
    	    	SET8L(sc->sc_eax, n);
		break;

	case 0x02: /* print char */
		tty_write(GET8L(sc->sc_edx), TTYF_REDIRECT);
		break;

	case 0x06: /* Direct console IO */
	func06:
		/* XXX - should be able to read a file */
		if (GET8L(sc->sc_edx) == 0xff) {
			n = tty_read(sc, TTYF_ECHO|TTYF_REDIRECT);
    	    	    	if (n < 0) {
			    sc->sc_eflags &= ~PSL_Z;
			} else {
#if 0 /*XXXXX*/
			    GET8L(sc->sc_eax) &= 0xff;
#endif
			    sc->sc_eflags |= PSL_Z;
			}
		} else {
			tty_write(GET8L(sc->sc_edx), TTYF_REDIRECT);
		}
		break;
	case 0x07: /* single character input, no echo, no controls */
	func07:
		SET8L(sc->sc_eax, tty_read(sc, TTYF_BLOCK|TTYF_REDIRECT) & 0xff);
		break;
	case 0x08: /* single character input, no echo */
	func08:
		if ((n = tty_read(sc, TTYF_BLOCK|TTYF_CTRL|TTYF_REDIRECT)) < 0)
			return (0);
		SET8L(sc->sc_eax, n);
		break;

	case 0x09: /* print string */
		addr = (char *)GETPTR(sc->sc_ds, sc->sc_edx);
		p = strchr(addr, '$');
		if (p) {
			nbytes = p - addr;
			while (nbytes-- > 0)
				tty_write(*addr++, TTYF_REDIRECT);
		}
		break;

	case 0x0a: /* buffered input */
	func0a:
		addr = (char *)GETPTR(sc->sc_ds, sc->sc_edx);
		/*
		 * Leave room for max, len, '\r' and '\0'
		 */
		avail = ((unsigned char *)addr)[0] - 4;
		nbytes = 0;

		while ((n = tty_read(sc, TTYF_BLOCK|TTYF_CTRL|TTYF_REDIRECT)) >= 0) {
		    switch (n) {
		    case '\r':
			addr[1] = nbytes;
			addr[nbytes+2] = '\r';
			addr[nbytes+3] = '\0';
			return (0);
		    case '\n':
			break;
		    case '\b':
			if (nbytes > 0) {
			    --nbytes;
			    tty_write('\b', TTYF_REDIRECT);
			    if (addr[nbytes+2] < ' ')
				tty_write('\b', TTYF_REDIRECT);
			}
			break;
    	    	    case '\0':
			break;
		    default:
			if (nbytes >= avail) {
			    tty_write('\007', TTYF_REDIRECT);
			} else {
			    addr[nbytes++ +2] = n;
    	    	    	    if (n != '\t' && n < ' ') {
				tty_write('^', TTYF_REDIRECT);
				tty_write(n + '@', TTYF_REDIRECT);
			    } else
				tty_write(n, TTYF_REDIRECT);
			}
			break;
		    }
		}
		break;
		
	case 0x0b: /* check keyboard status */
    	    {
		extern poll_cnt;

		if (!xmode) {
		    SET8L(sc->sc_eax, 255);
		    break;
		}
		n = tty_peek(sc, poll_cnt ? 0 : TTYF_POLL) ? 255 : 0;
		if (n < 0)
		    return (0);
		SET8L(sc->sc_eax, n);
    	    	if (poll_cnt)
		    --poll_cnt;
		break;
    	    }

	case 0x0c: /* flush buffer, read keyboard */
		if (xmode)
		    tty_flush();
		switch (GET8L(sc->sc_eax)) {
		case 0x01: goto func01;
		case 0x06: goto func06;
		case 0x07: goto func07;
		case 0x08: goto func08;
		case 0x0a: goto func0a;
		}
		SET8L(sc->sc_eax, 0);
		break;

        case 0x0d: /* reset drive */
                break;                  /* don't worry, be happy */

	case 0x0e: /* select drive */
		diskdrive = GET8L(sc->sc_edx);
		SET8L(sc->sc_eax, ndisks + 2);
		break;

	case 0x0f: /* open file with FCB */
		fcbp = (struct fcb *)GETPTR(sc->sc_ds, sc->sc_edx);
		fcb_to_string(fcbp, buf);
		error = translate_filename(
		    buf, filename1, &drive); 
		if (error)
			return (error);

		debug(D_FILE_OPS, "open FCB(%s)\n", filename1);

		if (ustat(filename1, &sb) < 0) 
			sb.st_ino = 0;

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (sb.st_ino == 0 || S_ISDIR(sb.st_mode))
			return (FILE_NOT_FOUND);

		if ((fd = open(filename1, O_RDWR)) < 0) {
			if (errno == ENOENT)
				return (FILE_NOT_FOUND);
			else
				return (ACCESS_DENIED);
		}

		fcbp->fcb_fd = fd;
		openfcb(fcbp);
		SET8L(sc->sc_eax, 0);
		break;

	case 0x10: /* close file with FCB */
		fcbp = (struct fcb *)GETPTR(sc->sc_ds, sc->sc_edx);

		debug(D_FILE_OPS, "close FCB(%d)\n", fcbp->fcb_fd);

		if (close(fcbp->fcb_fd) < 0)
			return (HANDLE_INVALID);

		fcbp->fcb_fd = -1;
		SET8L(sc->sc_eax, 0);
		break;

    	case 0x11: /* find_first with FCB */
		dta = (find_block_t *)VECPTR(disk_transfer_addr);

		fcb_to_string(
		    (struct fcb *)GETPTR(sc->sc_ds, sc->sc_edx),
		    buf);
    		error = find_first(
		    buf,
		    fcbp->fcbAttribute,
		    &dosdir, dta);
    		if (error)
			return (error);

		dosdir_to_dta(&dosdir, dta);
		SET8L(sc->sc_eax, 0);
		break;

    	case 0x12: /* find_next with FCB */
		dta = (find_block_t *)VECPTR(disk_transfer_addr);

	    	error = find_next(&dosdir, dta);
    		if (error)
			return (error);

		dosdir_to_dta(&dosdir, dta);
		SET8L(sc->sc_eax, 0);
		break;

	case 0x16: /* create file with FCB */
		fcbp = (struct fcb *)GETPTR(sc->sc_ds, sc->sc_edx);
		fcb_to_string(fcbp, buf);
		error = translate_filename(
		    buf, filename1, &drive); 
		if (error)
			return (error);

		debug(D_FILE_OPS, "creat FCB(%s)\n", filename1);

		if (ustat(filename1, &sb) < 0)
			sb.st_ino = 0;

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (sb.st_ino && !S_ISREG(sb.st_mode))
			return (ACCESS_DENIED);

		if ((fd = open(filename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) < 0)
			return (ACCESS_DENIED);

		fcbp->fcb_fd = fd;
		openfcb(fcbp);
		SET8L(sc->sc_eax, 0);
		break;

	case 0x19: /* get default disk drive */
		SET8L(sc->sc_eax, diskdrive);
		break;

	case 0x1a: /* set disk transfer address */
		debug(D_TRAPS3, "set dta to %x:%x\n", GET16(sc->sc_ds), GET16(sc->sc_edx));
		disk_transfer_addr = GETVEC(sc->sc_ds, sc->sc_edx);
		break;

	case 0x25: /* set interrupt vector */
		debug(D_TRAPS3, "%02x -> %04x:%04x\n", GET8L(sc->sc_eax), GET16(sc->sc_ds), GET16(sc->sc_edx));
		ivec[GET8L(sc->sc_eax)] = GETVEC(sc->sc_ds, sc->sc_edx);
		break;

	case 0x26: /* create psp */
		addr = (char *)MAKEPTR(GET16(sc->sc_edx), 0);
		memcpy (addr, dosmem, 256);
		/* XXX needs a few changes */
		break;

	case 0x27: /* random block read */
		fcbp = (struct fcb *)GETPTR(sc->sc_ds, sc->sc_edx);
		addr = (u_char *)VECPTR(disk_transfer_addr);
		nbytes = getfcb_rec(fcbp, GET16(sc->sc_ecx));

		if (nbytes < 0)
			return (READ_FAULT);
		n = read(fcbp->fcb_fd, addr, nbytes);
		if (n < 0)
			return (READ_FAULT);
		SET16(sc->sc_ecx, setfcb_rec(fcbp, n));
		if (n < nbytes) {
			nbytes = n % fcbp->fcbRecSize;
			if (0 == nbytes) {
				SET8L(sc->sc_eax, 0x01);
			} else {
				bzero(addr + n, fcbp->fcbRecSize - nbytes);
				SET8L(sc->sc_eax, 0x03);
			}
		} else {
			SET8L(sc->sc_eax, 0);
		}
		break;

	case 0x28: /* random block write */
		fcbp = (struct fcb *)GETPTR(sc->sc_ds, sc->sc_edx);
		addr = (u_char *)VECPTR(disk_transfer_addr);
		nbytes = getfcb_rec(fcbp, GET16(sc->sc_ecx));

		if (nbytes < 0)
			return (WRITE_FAULT);
		n = write(fcbp->fcb_fd, addr, nbytes);
		if (n < 0)
			return (WRITE_FAULT);
		SET16(sc->sc_ecx, setfcb_rec(fcbp, n));
		if (n < nbytes) {
			SET8L(sc->sc_eax, 0x01);
		} else {
			SET8L(sc->sc_eax, 0);
		}
		break;

	case 0x29: /* parse filename */
		debug(D_FILE_OPS,
		       "parse filename: flag=%d, ", GET8L(sc->sc_eax));

		SET16(sc->sc_eax, parse_filename(GET8L(sc->sc_eax),
		    GETPTR(sc->sc_ds, sc->sc_esi),
		    GETPTR(sc->sc_es, sc->sc_edi),
		    &nbytes));
		debug(D_FILE_OPS, "%d %s, FCB: %d, %.11s\n",
		       nbytes,
		       GETPTR(sc->sc_ds, sc->sc_esi),
		       *(int *)GETPTR(sc->sc_es, sc->sc_edi),
		       GETPTR(sc->sc_es, sc->sc_edi) + 1);
		       
		SET16(sc->sc_esi, GET16(sc->sc_esi) + nbytes);
		break;

	case 0x2a: /* get date */
		gettimeofday(&tv, &tz);
		now = tv.tv_sec + delta_clock;
		tm = *localtime(&now);

		SET16(sc->sc_ecx, tm.tm_year + 1900);
		SET8H(sc->sc_edx, tm.tm_mon + 1);
		SET8L(sc->sc_edx, tm.tm_mday);
		SET8L(sc->sc_eax, tm.tm_wday);
		break;

	case 0x2b: /* set date */
		gettimeofday(&tv, &tz);
		now = tv.tv_sec + delta_clock;
		tm = *localtime(&now);

		tm.tm_year = GET16(sc->sc_ecx) - 1900;
		tm.tm_mon = GET8H(sc->sc_edx) - 1;
		tm.tm_mday = GET8L(sc->sc_edx);
		tm.tm_wday = GET8L(sc->sc_eax);

		now = mktime(&tm);
		if (now == -1)
			return (DATA_INVALID);

		delta_clock = now - tv.tv_sec;
		SET8L(sc->sc_eax, 0);
		break;

	case 0x2c: /* get time */
		gettimeofday(&tv, &tz);
		now = tv.tv_sec + delta_clock;
		tm = *localtime(&now);

		SET8H(sc->sc_ecx, tm.tm_hour);
		SET8L(sc->sc_ecx, tm.tm_min);
		SET8H(sc->sc_edx, tm.tm_sec);
		SET8L(sc->sc_edx, tv.tv_usec / 10000);
		break;

	case 0x2d: /* set time */
		gettimeofday(&tv, &tz);
		now = tv.tv_sec + delta_clock;
		tm = *localtime(&now);

		tm.tm_hour = GET8H(sc->sc_ecx);
		tm.tm_min = GET8L(sc->sc_ecx);
		tm.tm_sec = GET8H(sc->sc_edx);
		tv.tv_usec = GET8L(sc->sc_edx) * 10000;

		now = mktime(&tm);
		if (now == -1)
			return (DATA_INVALID);

		delta_clock = now - tv.tv_sec;
		SET8L(sc->sc_eax, 0);
		break;

	case 0x2f: /* get dta address */
		PUTVEC(sc->sc_es, sc->sc_ebx, disk_transfer_addr);
		debug(D_TRAPS3, "get dta at %x:%x\n", GET16(sc->sc_es), GET16(sc->sc_ebx));
		break;

	case 0x30: /* get MS-DOS version number */
	    {
		char *cmd = (char *)MAKEPTR(get_env(), 0);

		do {
		    while (*cmd)
			++cmd;
		} while (*++cmd);
		++cmd;
		cmd += *(short *)cmd + 1;
		while (cmd[-1] && cmd[-1] != '\\' && cmd[-1] != ':')
		    --cmd;

    	    	v = getver(cmd);
		SET8L(sc->sc_eax, (v / 100) & 0xff);
		SET8H(sc->sc_eax, v % 100);
		break;
	    }

	case 0x33: /* get or set break flag */
		switch (GET8L(sc->sc_eax)) {
		case 0x00:
			SET8L(sc->sc_edx, ctrl_c_flag);
			break;
		case 0x01:
			ctrl_c_flag = GET8L(sc->sc_edx);
			break;
		case 0x05:
			SET8L(sc->sc_edx, 1);		/* Boot Drive */
			break;
    	    	case 0x06:			/* Dos version */
	    	    	v = getver(NULL);
			SET8L(sc->sc_ebx, (v / 100) & 0xff);
			SET8H(sc->sc_ebx, v % 100);
			SET8H(sc->sc_edx, 0);
			SET8L(sc->sc_edx, 0);
			break;
		default:
			unknown_int3(0x21, 0x33, GET8L(sc->sc_eax), sc);
			return (FUNC_NUM_IVALID);
		}
		break;

	case 0x34: /* Get InDOS Pointer */
		PUTVEC(sc->sc_es, sc->sc_ebx, (u_long)InDOS);
		break;

	case 0x35: /* get interrupt vector ... fake it */
		PUTVEC(sc->sc_es, sc->sc_ebx, ivec[GET8L(sc->sc_eax)]);
		debug(D_TRAPS3, "%02x <- %04x:%04x\n", GET8L(sc->sc_eax), GET16(sc->sc_es), GET16(sc->sc_ebx));
		break;

    	case 0x36: /* get disk space */
		SET16(sc->sc_eax, 8); /* number of sectors per cluster */
		SET16(sc->sc_ebx, 10000); /* number of available clusters */
		SET16(sc->sc_ecx, 512); /* number of bytes per sector */
		SET16(sc->sc_edx, 20000); /* total number of clusters */
	    	break;

	case 0x37:
		switch (GET8L(sc->sc_eax)) {
		case 0: /* get switch character */
			SET8L(sc->sc_edx, '/');
			break;

		case 1: /* set switch character (normally /) */
			break;
		default:
			unknown_int3(0x21, 0x37, GET8L(sc->sc_eax), sc);
			return (FUNC_NUM_IVALID);
		}
		break;

	case 0x38:
                if (GET16(sc->sc_edx) == 0xffff) {
                        debug(D_HALF, "warning: set country code ignored");
                        break;
                }
                addr = (char *)GETPTR(sc->sc_ds, sc->sc_edx);
		PUTVEC(countryinfo.ciCaseMapSegment, countryinfo.ciCaseMapOffset,
		    upcase_vector);
                memcpy(addr, &countryinfo, sizeof(countryinfo));
                break;

	case 0x39:
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "mkdir(%s)\n", filename1);

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (mkdir(filename1, 0777) < 0) {
			if (errno == ENOTDIR || errno == ENOENT)
				return (PATH_NOT_FOUND);
			else
				return (ACCESS_DENIED);
		}
		break;

	case 0x3a:
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "rmdir(%s)\n", filename1);

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (rmdir(filename1) < 0) {
			if (errno == ENOTDIR || errno == ENOENT)
				return (PATH_NOT_FOUND);
			else
				return (ACCESS_DENIED);
		}
		break;

	case 0x3b: /* set current directory */
		debug(D_FILE_OPS, "chdir(%s)\n",
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx));
		error = dos_setcwd(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx));
		if (error)
			return (error);
		break;

	case 0x3c: /* create */
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);
		attr = GET16(sc->sc_ecx);

		debug(D_FILE_OPS, "creat(%s)\n", filename1);

		if (ustat(filename1, &sb) < 0)
			sb.st_ino = 0;

	do_creat:
    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (sb.st_ino && !S_ISREG(sb.st_mode))
			return (ACCESS_DENIED);

		if ((fd = open(filename1, O_CREAT|O_TRUNC|O_RDWR,
		    from_dos_attr(attr))) < 0)
			return (ACCESS_DENIED);

		SET16(sc->sc_eax, fd);
		break;

	case 0x3d: /* open */
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);
		switch (GET8L(sc->sc_eax) & 3) {
		case 0:
			mode = O_RDONLY;
			break;
		case 1:
			mode = O_WRONLY;
			break;
		case 2:
			mode = O_RDWR;
			break;
		default:
			return (FUNC_NUM_IVALID);
		}

		debug(D_FILE_OPS, "open(%s, %d)\n", filename1, mode);

		if (ustat(filename1, &sb) < 0) 
			sb.st_ino = 0;

	do_open:
    	    	if (mode != O_RDONLY && dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (sb.st_ino == 0 || S_ISDIR(sb.st_mode))
			return (FILE_NOT_FOUND);

		if ((fd = open(filename1, mode)) < 0) {
			if (errno == ENOENT)
				return (FILE_NOT_FOUND);
			else
				return (ACCESS_DENIED);
		}

		SET16(sc->sc_eax, fd);
		debug(D_FILE_OPS, "open returns %d\n", fd);
		break;

	case 0x3e: /* close */
		fd = GET16(sc->sc_ebx);

		debug(D_FILE_OPS, "close(%d)\n", fd);

#if 1 /*XXXXX*/
		if (fd == fileno(debugf)) {
			printf("attempt to close debugging fd\n");
			return (HANDLE_INVALID);
		}
#endif
		if (close(fd) < 0)
			return (HANDLE_INVALID);

		break;
		
	case 0x3f: /* read */
		fd = GET16(sc->sc_ebx);
		addr = (char *)GETPTR(sc->sc_ds, sc->sc_edx);
		nbytes = GET16(sc->sc_ecx);

		debug(D_FILE_OPS, "read(%d, %d)\n", fd, nbytes);

		if (fd == 0) {
			if (redirect0) {
			    n = read (fd, addr, nbytes);
			} else {
			    n = 0;
			    while (n < nbytes) {
				avail = tty_read(sc, TTYF_BLOCK|TTYF_CTRL|TTYF_ECHONL);
				if (avail < 0)
				    return (0);
				if ((addr[n++] = avail) == '\r')
				    break;
			    }
			}
				
		} else {
			n = read (fd, addr, nbytes);
		}
		if (n < 0)
			return (READ_FAULT);

		SET16(sc->sc_eax, n);
		debug(D_FILE_OPS, " -> %d\n", n);
		break;

	case 0x40: /* write */
		fd = GET16(sc->sc_ebx);
		addr = (char *)GETPTR(sc->sc_ds, sc->sc_edx);
		nbytes = GET16(sc->sc_ecx);

		debug(D_FILE_OPS, "write(%d, %d)\n", fd, nbytes);

    	    	switch (fd) {
		case 0:
			if (redirect0) {
				n = write (fd, addr, nbytes);
				break;
			}
			n = nbytes;
			while (nbytes-- > 0)
				tty_write(*addr++, -1);
			break;
		case 1:
			if (redirect1) {
				n = write (fd, addr, nbytes);
				break;
			}
			n = nbytes;
			while (nbytes-- > 0)
				tty_write(*addr++, -1);
			break;
		case 2:
			if (redirect2) {
				n = write (fd, addr, nbytes);
				break;
			}
			n = nbytes;
			while (nbytes-- > 0)
				tty_write(*addr++, -1);
			break;
		default:
			n = write (fd, addr, nbytes);
			break;
		}
		if (n < 0)
			return (WRITE_FAULT);

		SET16(sc->sc_eax, n);
		debug(D_FILE_OPS, " -> %d\n", n);
		break;

	case 0x41: /* delete */
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "unlink(%s)\n", filename1);

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (unlink(filename1) < 0) {
			if (errno == ENOENT)
				return (FILE_NOT_FOUND);
			else
				return (ACCESS_DENIED);
		}
		break;

	case 0x42: /* seek */
		fd = GET16(sc->sc_ebx);
		offset = (GET16(sc->sc_ecx) << 16) | GET16(sc->sc_edx);
		switch (GET8L(sc->sc_eax)) {
		case 0:
			whence = SEEK_SET;
			break;
		case 1:
			whence = SEEK_CUR;
			break;
		case 2:
			whence = SEEK_END;
			break;
		default:
			return (FUNC_NUM_IVALID);
		}

		debug(D_FILE_OPS, "seek(%d, 0x%qx, %d)\n",
		    fd, offset, whence);

		if ((offset = lseek(fd, offset, whence)) < 0) {
			if (errno == EBADF)
				return (HANDLE_INVALID);
			else
				return (SEEK_ERROR);
		}

		debug(D_FILE_OPS, " -> 0x%qx\n", offset);

		SET16(sc->sc_edx, offset >> 16);
		SET16(sc->sc_eax, offset);
		break;
			
	case 0x43: /* get/set attributes */
	    {
		struct stat statb;

		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "get/set attributes: %s, cx=%x, al=%d",
				   filename1, GET16(sc->sc_ecx), GET8L(sc->sc_eax));

		if (stat(filename1, &statb) < 0) {
		    debug(D_FILE_OPS, "stat failed for %s", filename1);
		    return (FILE_NOT_FOUND);
		}

		switch (GET8L(sc->sc_eax)) {
		case 0:
			mode = 0;
			if (dos_readonly(drive) || access(filename1, W_OK))
			    mode |= 0x01;
			if (S_ISDIR(statb.st_mode))
			    mode |= 0x10;
			SET16(sc->sc_ecx, mode);
			break;

		case 1:
			if (GET16(sc->sc_ecx) & 0x18)
			    return (ACCESS_DENIED);
			break;

		default:
			return (FUNC_NUM_IVALID);
		}
		debug(D_FILE_OPS, "\n");
		break;
	    }
	case 0x44: /* ioctl */
		error = msdos_ioctl(sc);
		if (error)
			return (error);
		break;

	case 0x45: /* dup handle */
		fd = GET16(sc->sc_ebx);

		debug(D_FILE_OPS, "dup(%d)\n", fd);

		if ((fd2 = dup(fd)) < 0) {
			if (errno == EBADF)
				return (HANDLE_INVALID);
			else
				return (TOO_MANY_OPEN_FILES);
		}

		SET16(sc->sc_eax, fd2);
		break;

	case 0x46: /* dup2 handle */
		fd = GET16(sc->sc_ebx);
		fd2 = GET16(sc->sc_ecx);

		debug(D_FILE_OPS, "dup2(%d, %d)\n", fd, fd2);

		if (dup2(fd, fd2) < 0) {
			if (errno == EMFILE)
				return (TOO_MANY_OPEN_FILES);
			else
				return (HANDLE_INVALID);
		}

		break;
		
	case 0x47: /* get current directory */
		n = GET8L(sc->sc_edx);
		if (!n--)
		    n  = diskdrive;

		p = (char *)dos_getcwd(n) + 1;
		addr = (char *)GETPTR(sc->sc_ds, sc->sc_esi);

		nbytes = strlen(p);
		if (nbytes > 63)
			nbytes = 63;

		memcpy(addr, p, nbytes);
		addr[nbytes] = 0;
		break;
		
	case 0x48: /* allocate memory */
		memseg = mem_alloc(GET16(sc->sc_ebx), pspseg, &avail);

		if (memseg == 0L) {
			SET16(sc->sc_ebx, avail);
			return (INSUF_MEM);
		}

		SET16(sc->sc_eax, memseg);
		break;

	case 0x49: /* free memory */
		if ((n = mem_adjust(GET16(sc->sc_es), 0, NULL)) < 0)
			return (MEM_BLK_ADDR_IVALID);

		break;

	case 0x4a: /* modify memory allocation */
		if ((n = mem_adjust(GET16(sc->sc_es), GET16(sc->sc_ebx), &avail)) < 0) {
			SET16(sc->sc_ebx, avail);
			if (n == -1)
				return (INSUF_MEM);
			else
				return (MEM_BLK_ADDR_IVALID);
		}

		break;

	case 0x4b: /* exec */
		debug(D_EXEC, "exec(%s)\n",
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx));

		if ((fd = open_prog(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx))) < 0) {
			debug(D_EXEC, "%s: command not found\n", filename1);
			return (FILE_NOT_FOUND);
		}

		/* child */
		param = (u_short *)GETPTR(sc->sc_es, sc->sc_ebx);

		switch (GET8L(sc->sc_eax)) {
		case 0x00: /* load and execute */
			exec_command(sc, 1, fd, cmdname, param);
			close(fd);
			break;

		case 0x01: /* just load */
			exec_command(sc, 0, fd, cmdname, param);
			close(fd);
			break;

		case 0x03: /* load overlay */
			load_overlay(fd, param[0], param[1]);
			close(fd);
			break;

		default:
			unknown_int3(0x21, 0x4b, GET8L(sc->sc_eax), sc);
			return (FUNC_NUM_IVALID);
		}

		break;

	case 0x4c: /* exit */
		return_status = GET8L(sc->sc_eax);
		done(sc, GET8L(sc->sc_eax));

	case 0x4d: /* get return code */
		SET16(sc->sc_eax, return_status);
		break;

	case 0x4e: /* find first */
		dta = (find_block_t *)VECPTR(disk_transfer_addr);

	    	error = find_first(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    GET16(sc->sc_ecx),
		    &dosdir, dta);
    		if (error)
			return (error);

		dosdir_to_dta(&dosdir, dta);
		SET16(sc->sc_eax, 0);
		break;

	case 0x4f: /* find next */
		dta = (find_block_t *)VECPTR(disk_transfer_addr);

	    	error = find_next(&dosdir, dta);
    		if (error)
			return (error);

		dosdir_to_dta(&dosdir, dta);
		SET16(sc->sc_eax, 0);
		break;

	case 0x50: /* set psp */
		pspseg = GET16(sc->sc_ebx);
		break;

	case 0x51: /* get psp (documented in dosref) */
		SET16(sc->sc_ebx, pspseg);
		break;

	case 0x54: /* get verify flag */
		SET8L(sc->sc_eax, 0);
		break;

	case 0x56: /* rename file */
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);

		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_es, sc->sc_edi),
		    filename2, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "rename(%s, %s)\n", filename1, filename2);

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (rename(filename1, filename2) < 0) {
			if (errno == ENOENT)
				return (PATH_NOT_FOUND);
			else if (errno == EXDEV)
				return (NOT_SAME_DEV);
			else
				return (ACCESS_DENIED);
		}

		SET16(sc->sc_eax, 0);
		break;

	case 0x57: /* get or set file time */
		error = do_times(sc);
		if (error)
			return (error);
		break;

	case 0x58: /* get/set memory allocation strategy */
		switch (GET8L(sc->sc_eax)) {
		default:
			unknown_int3(0x21, 0x58, GET8L(sc->sc_eax), sc);
			return (FUNC_NUM_IVALID);
		}
		break;

	case 0x59: /* get error code */
		SET16(sc->sc_eax, doserrno);
		switch (doserrno) {
		case 1:
		case 6:
		case 9:
		case 10:
		case 11:
		case 12:
		case 13:
		case 15:
		    SET8H(sc->sc_ebx, 7);
		    break;

		case 2:
		case 3:
		case 4:
		case 5:
		    SET8H(sc->sc_ebx, 8);
		    break;

		case 7:
		case 8:
		    SET8H(sc->sc_ebx, 1);
		    break;
		  
		default:
		    SET8H(sc->sc_ebx, 12);
		    break;
		}
		SET8L(sc->sc_ebx, 6);
		SET8H(sc->sc_ecx, 0);
		break;

	case 0x5a: /* create temporary file */
		name = (char *)GETPTR(sc->sc_ds, sc->sc_edx);
		error = translate_filename(
		    name, filename1, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "tempname(%s)\n", filename1);

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		n = strlen(filename1);
		strcpy(&filename1[n], "$$dostmp.XXX");
		if (mkstemp(filename1) < 0 ||
		    (fd = open(filename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) < 0) {
			return (ACCESS_DENIED);
		}

		strcat(name, &filename1[n]);
		SET16(sc->sc_eax, fd);
		break;

	case 0x5b:
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_edx),
		    filename1, &drive);
		if (error)
			return (error);

		debug(D_FILE_OPS, "creat new(%s)\n", filename1);

		if (ustat(filename1, &sb) < 0)
			sb.st_ino = 0;

    	    	if (dos_readonly(drive))
			return (WRITE_PROT_DISK);

		if (sb.st_ino && !S_ISREG(sb.st_mode))
			return (ACCESS_DENIED);

		if ((fd = open(filename1, O_CREAT|O_RDWR, 0666)) < 0) {
			if (errno == EEXIST)
				return (FILE_ALREADY_EXISTS);
			else
				return (ACCESS_DENIED);
		}

		SET16(sc->sc_eax, fd);
		break;

	case 0x5d:
		switch (GET8L(sc->sc_eax)) {
		case 0x06:
			debug(D_HALF, "Get Swapable Area");
			return (ACCESS_DENIED);
		case 0x08: /* Set redirected printer mode */
			debug(D_HALF, "Redirection is %s\n",
					GET8L(sc->sc_edx) ? "seperate jobs"
						 : "combined");
			break;
		case 0x09: /* Flush redirected printer output */
			break;
		default:
			unknown_int3(0x21, 0x5d, GET8L(sc->sc_eax), sc);
			return (FUNC_NUM_IVALID);
		}
		break;

	case 0x60: /* canonicalize name */
		error = dos_makepath(
		    (char *)GETPTR(sc->sc_ds, sc->sc_esi),
		    (char *)GETPTR(sc->sc_es, sc->sc_edi));
		if (error)
			return (error);
		break;

	case 0x62: /* get PSP */
		SET16(sc->sc_ebx, pspseg);
		break;

	case 0x63: /* get lead byte table (something about inter. charset) */
		debug(D_HALF, "get lead byte table");
		SET16(sc->sc_ds, 0xF800);
		SET16(sc->sc_esi, 0x0000);
		break;

    	case 0x67: /* Set handle count */
		debug(D_HALF, "Wants %d files\n", GET16(sc->sc_ebx));
		/* GET16(sc->sc_ebx);		/* Number of handles */
		break;

	case 0x68: /* fsync */
	case 0x6a:
		fd = GET16(sc->sc_ebx);

		debug(D_FILE_OPS, "fsync(%d)\n", fd);

		if (fsync(fd) < 0)
			return (HANDLE_INVALID);

		break;

	case 0x6c: /* multipurpose open */
		error = translate_filename(
		    (u_char *)GETPTR(sc->sc_ds, sc->sc_esi),
		    filename1, &drive);
		if (error)
			return (error);
		switch (GET8L(sc->sc_ebx) & 3) {
		case 0:
			mode = O_RDONLY;
			break;
		case 1:
			mode = O_WRONLY;
			break;
		case 2:
			mode = O_RDWR;
			break;
		default:
			return (FUNC_NUM_IVALID);
		}
		attr = GET16(sc->sc_ecx);
		action = GET16(sc->sc_edx);
		
		debug(D_FILE_OPS, "mopen(%s, %d)\n", filename1, action);

		if (ustat(filename1, &sb) < 0)
			sb.st_ino = 0;

		if (sb.st_ino == 0) {
			switch (action & 0xf0) {
			case 0x00:
				debug(D_FILE_OPS, " %s: File not found\n", filename1);
				return (FILE_NOT_FOUND);
			case 0x10:
				debug(D_FILE_OPS, " %s: Do Create 2\n", filename1);
				SET16(sc->sc_ecx, 2);
				goto do_creat;
			default:
				return (FUNC_NUM_IVALID);
			}
		} else {
			switch (action & 0x0f) {
			case 0x00:
				debug(D_FILE_OPS, " %s: File already exists\n", filename1);
				return (FILE_ALREADY_EXISTS);
			case 0x01:
				debug(D_FILE_OPS, " %s: Do Open\n", filename1);
				SET16(sc->sc_ecx, 1);
				goto do_open;
			case 0x02:
				debug(D_FILE_OPS, " %s: Do Create 3\n", filename1);
				SET16(sc->sc_ecx, 3);
				goto do_creat;
			default:
				return (FUNC_NUM_IVALID);
			}
		}
		return (FUNC_NUM_IVALID);

	case 0x18: /* CP/M compatibility */
	case 0x1d: /* CP/M compatibility */
	case 0x1e: /* CP/M compatibility */
	case 0x20: /* CP/M compatibility */
	case 0x61: /* unused (reserved for network use) */
	case 0x6b: /* null function */
		SET8L(sc->sc_eax, 0x00);
		break;

	case 0x03: /* read character from stdaux */
	case 0x04: /* write character to stdaux */
	case 0x05: /* write character to stdprn */
	case 0x13: /* delete file using FCB */
	case 0x14: /* sequential read from FCB file */
	case 0x15: /* sequential write to FCB file */
	case 0x17: /* rename file using FCB */
	case 0x1b: /* get allocation information for default drive */
	case 0x1c: /* get allocation information for specific drive */
	case 0x1f: /* get drive parameter block for default drive */
	case 0x21: /* read random record from FCB file */
	case 0x22: /* write random record to FCB file */
	case 0x23: /* get file size for FCB */
	case 0x24: /* set random record number for FCB */
	case 0x2e: /* set verify flag */
	case 0x31: /* terminate and stay resident */
	case 0x32: /* get drive parameter block for specific drive */
    	case 0x52: /* get list of lists */
	case 0x53: /* translate BIOS parameter block to drive paramter block */
	case 0x55: /* create child PSP */
	case 0x5c: /* record locking */
    	case 0x5e: /* Network functions */
	case 0x5f:
	case 0x64: /* set device driver lookahead flag */
    	case 0x65: /* get extended country information */
	case 0x66: /* get/set global code page table */
	case 0x69: /* get/set disk serial number */
		unknown_int2(0x21, GET8H(sc->sc_eax), sc);
		return (FUNC_NUM_IVALID);

	default:
		SET8L(sc->sc_eax, 0x00);
		break;
	}

	return (0);
}

int
msdos_ioctl(struct sigcontext *sc)
{
	int fd;
	
	switch (GET8L(sc->sc_eax)) {
	case 0x00: /* get device info */
		fd = GET16(sc->sc_ebx);

		debug(D_FILE_OPS, "ioctl get %d", fd);

		switch (fd) {
		case 0:
			SET16(sc->sc_edx, 0x80 | 0x01);
			break;
		case 1:
			SET16(sc->sc_edx, 0x80 | 0x02);
			break;
		case 2:
			SET16(sc->sc_edx, 0x80);
			break;
		default:
			if (isatty (fd))
				SET16(sc->sc_edx, 0x80);
			else
				SET16(sc->sc_edx, 0);
			break;
		}
		break;

	case 0x01: /* set device information */
		fd = GET16(sc->sc_ebx);
		debug(D_FILE_OPS, "ioctl set device info %d flags %x",
			fd, GET16(sc->sc_edx));
		break;

	case 0x08: /* test for removable block device */
		SET16(sc->sc_eax, 1); /* fixed */
		break;

    	case 0x0b:
    	case 0x0d:
	default:
		unknown_int3(0x21, 0x44, GET8L(sc->sc_eax), sc);
		return (FUNC_NUM_IVALID);
	}

	return (0);
}

static void
openfcb(struct fcb *fcbp)
{
	struct stat statb;

	fcbp->fcbDriveID = 3;		/* drive C */
	fcbp->fcbCurBlockNo = 0;
	fcbp->fcbRecSize = 128;
	if (fstat(fcbp->fcb_fd, &statb) < 0) {
		debug(D_FILE_OPS, "open not complete with errno %d", errno);
		return;
	}
	encode_dos_file_time(statb.st_mtime,
				&fcbp->fcbFileDate, &fcbp->fcbFileTime);
	fcbp->fcbFileSize = statb.st_size;
}

static int
getfcb_rec(struct fcb *fcbp, int nrec)
{
	int n;

	n = fcbp->fcbRandomRecNo;
	if (fcbp->fcbRecSize >= 64)
		n &= 0xffffff;
	fcbp->fcbCurRecNo = n % 128;
	fcbp->fcbCurBlockNo = n / 128;
	if (lseek(fcbp->fcb_fd, n * fcbp->fcbRecSize, SEEK_SET) < 0)
		return (-1);
	return (nrec * fcbp->fcbRecSize);
}


static int
setfcb_rec(struct fcb *fcbp, int n)
{
	int recs, total;

	total = fcbp->fcbRandomRecNo;
	if (fcbp->fcbRecSize >= 64)
		total &= 0xffffff;
	recs = (n+fcbp->fcbRecSize-1) / fcbp->fcbRecSize;
	total += recs;

	fcbp->fcbRandomRecNo = total;
	fcbp->fcbCurRecNo = total % 128;
	fcbp->fcbCurBlockNo = total / 128;
}

int
translate_filename(u_char *dname, u_char *uname, int *drivep)
{
	u_char newpath[1024];
	int error;

    	if (!strcasecmp(dname, "con")) {
		*drivep = -1;
		strcpy(uname, "/dev/tty");
		return (0);
	}

	error = dos_makepath(dname, newpath);
	if (error)
		return (error);

	error = dos_to_real_path(newpath, uname, drivep);
	if (error)
		return (error);

	return (0);
}

char *
convert_filename (uname)
char *uname;
{
	static char dname[1000];
	char *p, *q;

	p = uname;

	for (q = dname; *p; p++, q++) {
		if (islower (*p))
			*q = toupper (*p);
		else
			*q = *p;

		if (*q == '/')
			*q = '\\';
	}

	*q = 0;

	return (dname);
}

u_char magic[0x7e] = {
	0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
	0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
	0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
	0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,

	0x08, 0x0f, 0x06, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x04, 0x04, 0x0f, 0x0e, 0x06,
	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0f,

	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x06, 0x06, 0x06, 0x0f, 0x0f,

	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
	0x0f, 0x0f, 0x0f, 0x0f, 0x04, 0x0f,
};

#define	isvalid(x)	((magic[x] & 0x01) != 0)
#define	issep(x)	((magic[x] & 0x02) == 0)
#define	iswhite(x)	((magic[x] & 0x04) == 0)

char *
skipwhite(char *p)
{
	while (iswhite(*p))
		++p;
	return (p);
}

#define	get_drive_letter(x)	((x) - 0x40)

int
parse_filename(int flag, char *str, char *fcb, int *nb)
{
	char *p;
	int ret = 0;
	int i;

	p = str;

	p = skipwhite(p);
	if (flag & 1) {
		if (issep(*p)) {
			++p;
			p = skipwhite(p);
		}
	}

	if (isvalid(*p) && p[1] == ':') {
		*fcb++ = get_drive_letter(upcase(*p));
		p += 2;
	} else if (flag & 2) {
		fcb++;
	} else {
		*fcb++ = 0;	/* default drive */
	}

	i = 8;
	if (isvalid(*p)) {
		for (;;) {
			if (!isvalid(*p)) {
				for (; i > 0; i--)
					*fcb++ = ' ';
				break;
			}
			if (i > 0) {
				switch (*p) {
				case '*':
					ret = 1;
					for (; i > 0; i--)
						*fcb++ = '?';
					break;
				case '?':
					ret = 1;
				default:
					*fcb++ = upcase(*p);
					i--;
					break;
				}
			}
			++p;
		}
	} else if (flag & 4) {
		fcb += i;
	} else {
		for (; i > 0; i--)
			*fcb++ = ' ';
	}

	i = 3;
	if (*p == '.') {
		++p;
		for (;;) {
			if (!isvalid(*p)) {
				for (; i > 0; i--)
					*fcb++ = ' ';
				break;
			}
			if (i > 0) {
				switch (*p) {
				case '*':
					ret = 1;
					for (; i > 0; i--)
						*fcb++ = '?';
					break;
				case '?':
					ret = 1;
				default:
					*fcb++ = upcase(*p);
					i--;
					break;
				}
			}
			++p;
		}
	} else if (flag & 8) {
		fcb += i;
	} else {
		for (; i > 0; i--)
			*fcb++ = ' ';
	}

	for (i = 4; i > 0; i--)
		*fcb++ = 0;	/* filler */

	*nb = p - str;
	return (ret);
}

void
encode_dos_file_time(t, dosdatep, dostimep)
	time_t t;
	u_short *dosdatep;
	u_short *dostimep;
{
	struct tm tm;

	tm = *localtime(&t);
	*dostimep = (tm.tm_hour << 11) |
		    (tm.tm_min << 5) |
		    ((tm.tm_sec / 2) << 0);
	*dosdatep = ((tm.tm_year - 80) << 9) |
		    ((tm.tm_mon + 1) << 5) |
		    (tm.tm_mday << 0);
}

time_t
decode_dos_file_time(dosdate, dostime)
	u_short dosdate;
	u_short dostime;
{
	struct tm tm;
	time_t then;

	tm.tm_hour = (dostime >> 11) & 0x1f;
	tm.tm_min  = (dostime >> 5) & 0x3f;
	tm.tm_sec  = ((dostime >> 0) & 0x1f) * 2;
	tm.tm_year = ((dosdate >> 9) & 0x7f) + 80;
	tm.tm_mon  = ((dosdate >> 5) & 0x0f) - 1;
	tm.tm_mday = (dosdate >> 0) & 0x1f;
	/* tm_wday and tm_yday are ignored. */
	tm.tm_isdst = 0;
	/* tm_gmtoff is ignored. */
	then = mktime(&tm);
	return (then);
}

void
pack_name(p, q)
	u_char *p, *q;
{
	int i;

    	for (i = 8; i > 0 && *p != ' '; i--)
		*q++ = *p++;
	p += i;
	if (*p != ' ') {
	    	*q++ = '.';
    		for (i = 3; i > 0 && *p != ' '; i--)
			*q++ = *p++;
		p += i;
	}
	*q = '\0';
}

void
fcb_to_string(fcbp, buf)
	struct fcb *fcbp;
	u_char *buf;
{

	if (fcbp->fcbDriveID != 0x00) {
		*buf++ = fcbp->fcbDriveID - 1 + 'A';
		*buf++ = ':';
	}
	pack_name(fcbp->fcbFileName, buf);
}

void
dosdir_to_dta(dosdir, dta)
	dosdir_t *dosdir;
    	find_block_t *dta;
{
    	u_char *p, *q;

    	dta->attr = dosdir->attr;
    	dta->time = dosdir->time;
    	dta->date = dosdir->date;
    	dta->size = dosdir->size;
	pack_name(dosdir->name, dta->name);
}

int
do_times(struct sigcontext *sc)
{
	struct stat statb;
	struct timeval tv[2];
	int fd;
	u_short date, time;

	fd = GET16(sc->sc_ebx);

	switch (GET8L(sc->sc_eax)) {
	case 0x00: /* get time */
		if (fstat(fd, &statb) < 0)
			return (HANDLE_INVALID);
		encode_dos_file_time(statb.st_mtime, &date, &time);
		SET16(sc->sc_ecx, time);
		SET16(sc->sc_edx, date);
		break;
	case 0x01: /* set time */
		time = GET16(sc->sc_ecx);
		date = GET16(sc->sc_edx);
		tv[0].tv_sec = tv[1].tv_sec =
		    decode_dos_file_time(date, time);
		tv[0].tv_usec = tv[1].tv_usec = 0;
		if (futimes(fd, tv) < 0)
			return (HANDLE_INVALID);
		break;
	default:
		return (FUNC_NUM_IVALID);
	}

	return (0);
}

u_char upcase_trampoline[] = {
	0xf4,	/* HLT */
	0xcb,	/* RETF */
};

void
dos_init(void)
{
	u_long vec;

	vec = insert_softint_trampoline();
	ivec[0x20] = vec;
	register_callback(vec, int20, "int 20");

	vec = insert_softint_trampoline();
	ivec[0x21] = vec;
	register_callback(vec, int21, "int 21");

	vec = insert_softint_trampoline();
	ivec[0x29] = vec;
	register_callback(vec, int29, "int 29");

	vec = insert_null_trampoline();
	ivec[0x28] = vec;	/* dos idle */
	ivec[0x2b] = vec;	/* reserved */
	ivec[0x2c] = vec;	/* reserved */
	ivec[0x2d] = vec;	/* reserved */

	upcase_vector = insert_generic_trampoline(
	    sizeof(upcase_trampoline), upcase_trampoline);
	register_callback(upcase_vector, upcase_entry, "upcase");
}
