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

#include "doscmd.h"
#include "errno.h"
#include <ctype.h>

static LOL *lol = 0;
static SDA *sda = 0;

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

void
init_drives()
{
	int drive;
	u_char *path;

	for (drive = 0; drive < 26; ++drive) {
		if (path = dos_getpath(drive))
			install_drive(drive, path);
	}
}

void
intff(struct sigcontext *sc)
{

	if (lol && sda)
		return;
	lol = (LOL *)GETPTR(sc->sc_es, sc->sc_edi);
	sda = (SDA *)GETPTR(sc->sc_ds, sc->sc_esi);
	init_drives();
}

int
install_drive(int drive, u_char *path)
{
    CDS *cds;

    if (drive < 0 || drive >= lol->lastdrive) {
	debug(D_REDIR, "Drive %c beyond limit of %c)\n",
			drive + 'A', lol->lastdrive - 1 + 'A');
	return;
    }

    cds = (CDS *)MAKEPTR(lol->cds_seg, lol->cds_offset);
    cds += drive;

#if 0
    if (cds->flag & (CDS_remote | CDS_ready)) {
	debug(D_REDIR, "Drive %c already installed\n", drive + 'A');
	return;
    }
#endif

    debug(D_REDIR, "Installing %c: as %s\n", drive + 'A', path);

    cds->flag |= CDS_remote | CDS_ready | CDS_notnet;
    cds->path[0] = drive + 'A';
    cds->path[1] = ':';
    cds->path[2] = '\\';
    cds->path[3] = '\0';
    cds->offset = 2;	/* offset of \ in current path field */
}

int
int2f_11(struct sigcontext *sc)
{               
	int error;

	error = ifs_call(sc);
	if (error == -1)
		return (0);
	if (error) {
		SET16(sc->sc_eax, error);
		sc->sc_eflags |= PSL_C;
	} else
		sc->sc_eflags &= ~PSL_C;
	return (1);
}

int
ifs_call(struct sigcontext *sc)
{
	u_char filename1[1024], filename2[1024];
	char *addr;
	int nbytes;
	u_char *fname;
	CDS *cds;
	u_short action;
	int mode;
	u_short attr;
	off_t offset;
	int drive;
	struct stat sb;
	int fd;
	SFT *sft;
	u_char *p, *e;
	int i;
	int n;
	int whence;
	int error;

	reset_poll();

	debug(D_REDIR, "INT 2F:11:%02x", GET8L(sc->sc_eax));

	if (!sda)
		return (FUNC_NUM_IVALID);

	cds = (CDS *)MAKEPTR(sda->cds_seg, sda->cds_off);
	sft = (SFT *)GETPTR(sc->sc_es, sc->sc_edi);

	switch (GET8L(sc->sc_eax)) {
	default:
	case 0x24:	/* TURN_OFF_PRINTER */
	case 0x25:	/* PRINTER_MODE			Used in DOS 3.1+ */
	case 0x2d:	/* EXTENDED_ATTRIBUTES	Used in DOS 4.x */
		return (-1);
	case 0x00:	/* INSTALLATION_CHECK */
	case 0x1e:	/* CONTROL_REDIRECT */
	case 0x22:	/* PROCESS_TERMINATED */
	case 0x1d:	/* CLOSE_ALL */
		drive = -1;
	case 0x0c:	/* GET_DISK_SPACE */
		cds = (CDS *)GETPTR(sc->sc_es, sc->sc_edi);
	case 0x01:	/* REMOVE_DIRECTORY */
	case 0x02:	/* REMOVE_DIRECTORY_2 */
	case 0x05:	/* SET_CURRENT_DIRECTORY */
	case 0x11:	/* RENAME_FILE */
	case 0x17:	/* CREATE_TRUNCATE_FILE */
	case 0x2e:	/* MULTIPURPOSE_OPEN	Used in DOS 4.0+ */
	case 0x1c:	/* FIND_NEXT */
	case 0x1f:	/* PRINTER_SETUP */
	case 0x20:	/* FLUSH_ALL_DISK_BUFFERS */
		fname = cds->path;
		goto checkname;
	case 0x23:	/* QUALIFY_FILENAME */
		fname = (u_char *)GETPTR(sc->sc_ds, sc->sc_esi);
		goto checkname;
	case 0x03:	/* MAKE_DIRECTORY */
	case 0x04:	/* MAKE_DIRECTORY_2 */
	case 0x18:	/* CREATE_TRUNCATE_NO_CDS */
	case 0x16:	/* OPEN_EXISTING_FILE */
	case 0x0e:	/* SET_FILE_ATTRIBUTES */
	case 0x0f:	/* GET_FILE_ATTRIBUTES */
	case 0x13:	/* DELETE_FILE */
	case 0x1b:	/* FIND_FIRST */
	case 0x19:	/* FIND_FIRST_NO_CDS */
		fname = sda->filename1;
		goto checkname;
	checkname:
		debug(D_REDIR, " [%s]", fname);
		if (fname[1] != ':')
			return (-1);
		drive = toupper(fname[0]) - 'A';
		break;
	case 0x06:	/* CLOSE_FILE */
	case 0x07:	/* COMMIT_FILE */
	case 0x08:	/* READ_FILE */
	case 0x09:	/* WRITE_FILE */
	case 0x0a:	/* LOCK_FILE_REGION */
	case 0x0b:	/* UNLOCK_FILE_REGION */
	case 0x21:	/* SEEK_FROM_EOF */
		drive = sft->info & 0x1f;
		break;
	}

	debug(D_REDIR, " drive %c", drive + 'A');
	if (drive < 0 || !dos_getcwd(drive))
		return (-1);

	switch (GET8L(sc->sc_eax)) {
	case 0x00:	/* INSTALLATION_CHECK */
		SET8L(sc->sc_eax, 0xff);
		break;

	case 0x01:	/* REMOVE_DIRECTORY */
	case 0x02:	/* REMOVE_DIRECTORY_2 */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);

		debug(D_REDIR, "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);
		}
		SET16(sc->sc_eax, 0);
		break;

	case 0x03:	/* MAKE_DIRECTORY */
	case 0x04:	/* MAKE_DIRECTORY_2 */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);

		debug(D_REDIR, "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);
		}
		SET16(sc->sc_eax, 0);
		break;

	case 0x05:	/* SET_CURRENT_DIRECTORY */
		debug(D_REDIR, "chdir(%s)\n", sda->filename1);
		error = dos_setcwd(sda->filename1);
		if (error)
			return (error);
		break;

	case 0x06:	/* CLOSE_FILE */
		fd = sft->fd;

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

		if ((sft->nfiles -= 1) > 0) {
			debug(D_REDIR, "not last close\n");
			break;
		}

		if (close(fd) < 0)
			return (HANDLE_INVALID);
		SET16(sc->sc_eax, 0);
		break;

	case 0x07:	/* COMMIT_FILE */
		debug(D_HALF, "sync file");
		break;

	case 0x08:	/* READ_FILE */
		fd = sft->fd;
		addr = (char *)MAKEPTR(sda->dta_seg, sda->dta_off);
		nbytes = GET16(sc->sc_ecx);

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

		if (lseek(fd, sft->offset, 0) < 0)
			return (SEEK_ERROR);

		n = read(fd, addr, nbytes);
		if (n < 0)
			return (READ_FAULT);

		SET16(sc->sc_ecx, n);
		sft->offset += n;
		debug(D_REDIR, " -> %d\n", n);
		SET16(sc->sc_eax, 0);	/* XXX Is this correct? */
		break;

	case 0x09:	/* WRITE_FILE */
		fd = sft->fd;
		addr = (char *)MAKEPTR(sda->dta_seg, sda->dta_off);
		nbytes = GET16(sc->sc_ecx);

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

		if (lseek(fd, sft->offset, 0) < 0)
			return (SEEK_ERROR);

		n = write(fd, addr, nbytes);
		if (n < 0)
			return (WRITE_FAULT);

		SET16(sc->sc_ecx, n);
		sft->offset += n;
		if (sft->offset > sft->size)
			sft->size = sft->offset;
		debug(D_REDIR, " -> %d\n", n);
		SET16(sc->sc_eax, 0);		/* XXX is this correct? */
		break;

	case 0x0a:	/* LOCK_FILE_REGION */
	case 0x0b:	/* UNLOCK_FILE_REGION */
		debug(D_HALF, "lock/unlock region");
		break;

	case 0x0c:	/* GET_DISK_SPACE */
	    {
		fsstat_t fs;

		error = get_space(drive, &fs);
		if (error)
			return (error);
		SET16(sc->sc_eax, fs.sectors_cluster);
		SET16(sc->sc_ebx, fs.total_clusters);
		SET16(sc->sc_ecx, fs.bytes_sector);
		SET16(sc->sc_edx, fs.avail_clusters);
		break;
	    }

	case 0x0e:	/* SET_FILE_ATTRIBUTES */
	case 0x0f:	/* GET_FILE_ATTRIBUTES */
		debug(D_HALF, "get/set file attributes");
		break;

	case 0x11:	/* RENAME_FILE */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);

		error = translate_filename(
		    sda->filename2, filename2, &drive);
		if (error)
			return (-1);

		debug(D_REDIR, "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 0x13:	/* DELETE_FILE */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);

		debug(D_REDIR, "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 0x16:	/* OPEN_EXISTING_FILE */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);
		switch (sda->open_mode & 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 = *(u_short *)GETPTR(sc->sc_ss, sc->sc_esp) & 0xff;

		debug(D_REDIR, "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);
		}

		e = p = sda->filename1 + 2;

		while (*p) {
			if (*p++ == '\\')
				e = p;
		}

		for (i = 0; i < 8; ++i) {
			if (*e && *e != '.')
				sft->name[i] = *e++;
			else
				sft->name[i] = ' ';
		}

		if (*e == '.')
			++e;

		for (i = 0; i < 3; ++i) {
			if (*e)
				sft->ext[i] = *e++;
			else
				sft->ext[i] = ' ';
		}

		sft->open_mode = sda->open_mode & 0x7f;
		*(u_long *)sft->ddr_dpb = 0;
		sft->size = sb.st_size;
		sft->fd = fd;
		sft->offset = 0;
		*(u_short *)sft->dir_sector = 0;
		sft->dir_entry = 0;
		sft->attribute = attr;
		sft->info = drive + 0x8040;
		encode_dos_file_time(sb.st_mtime, &sft->date, &sft->time);

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

	case 0x17:	/* CREATE_TRUNCATE_FILE */
	case 0x18:	/* CREATE_TRUNCATE_NO_CDS */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);
		attr = *(u_short *)GETPTR(sc->sc_ss, sc->sc_esp) & 0xff;

		debug(D_REDIR, "create(%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);

		e = p = sda->filename1 + 2;

		while (*p) {
			if (*p++ == '\\')
				e = p;
		}

		for (i = 0; i < 8; ++i) {
			if (*e && *e != '.')
				sft->name[i] = *e++;
			else
				sft->name[i] = ' ';
		}

		if (*e == '.')
			++e;

		for (i = 0; i < 3; ++i) {
			if (*e)
				sft->ext[i] = *e++;
			else
				sft->ext[i] = ' ';
		}

		sft->open_mode = 0x03;
		*(u_long *)sft->ddr_dpb = 0;
		sft->size = 0;
		sft->fd = fd;
		sft->offset = 0;
		*(u_short *)sft->dir_sector = 0;
		sft->dir_entry = 0;
		sft->attribute = attr;
		sft->info = drive + 0x8040;
		encode_dos_file_time(sb.st_mtime, &sft->date, &sft->time);

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

	case 0x19:	/* FIND_FIRST_NO_CDS */
	case 0x1b:	/* FIND_FIRST */
		error = find_first(
		    sda->filename1,
		    sda->attrmask,
		    (dosdir_t *)sda->foundentry,
		    (find_block_t *)sda->findfirst);
		if (error)
			return (error);
		break;

	case 0x1c:	/* FIND_NEXT */
		error = find_next(
		    (dosdir_t *)sda->foundentry,
		    (find_block_t *)sda->findfirst);
		if (error)
			return (error);
		break;

	case 0x21:	/* SEEK_FROM_EOF */
		fd = sft->fd;
		offset = (GET16(sc->sc_ecx) << 16) | GET16(sc->sc_edx);
		whence = SEEK_END;

		debug(D_REDIR, "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);
		}

		sft->offset = offset;
		SET16(sc->sc_edx, offset >> 16);
		SET16(sc->sc_eax, offset);
		break;

	case 0x1d:	/* CLOSE_ALL */
	case 0x1e:	/* CONTROL_REDIRECT */
	case 0x1f:	/* PRINTER_SETUP */
	case 0x20:	/* FLUSH_ALL_DISK_BUFFERS */
	case 0x22:	/* PROCESS_TERMINATED */
	case 0x24:	/* TURN_OFF_PRINTER */
		debug(D_HALF, "random crap");
		break;

	case 0x23:	/* QUALIFY_FILENAME */
		error = dos_makepath(
		    (char *)fname,
		    (char *)GETPTR(sc->sc_es, sc->sc_edi));
		if (error)
			return (error);
		break;

	case 0x25:	/* PRINTER_MODE			Used in DOS 3.1+ */
			/* UNDOCUMENTED_FUNCTION_2	Used in DOS 4.0+ */
		mode = *(u_short *)GETPTR(sc->sc_ss, sc->sc_esp);

		switch (mode) {
		case 0x5D07:
			debug(D_REDIR, " Get printer stream state\n");
			break;
		case 0x5D08:
			debug(D_REDIR, " Set printer stream state to %s\n",
			    (GET16(sc->sc_edx) & 0xff) == 0x00 ? "combined" :
			    (GET16(sc->sc_edx) & 0xff) == 0x01 ? "separate" :
			    "unknwon");
			break;
		case 0x5D09:
			debug(D_REDIR, " Finish print job\n");
			break;
		default:
			debug(D_REDIR, " Unknown printer request %04x\n", mode);
			break;
		}
		SET16(sc->sc_eax, 0);
		break;

	case 0x2d:	/* EXTENDED_ATTRIBUTES	Used in DOS 4.x */
		debug(D_HALF, "extended attributes");
		break;

	case 0x2e:	/* MULTIPURPOSE_OPEN	Used in DOS 4.0+ */
		error = translate_filename(
		    sda->filename1, filename1, &drive);
		if (error)
			return (-1);
		switch (sda->ext_mode & 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 = sda->ext_attr;
		action = sda->ext_action;

		debug(D_REDIR, "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_REDIR, " %s: File not found\n", filename1);
				return (FILE_NOT_FOUND);
			case 0x10:
				debug(D_REDIR, " %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_REDIR, " %s: File already exists\n", filename1);
				return (FILE_ALREADY_EXISTS);
			case 0x01:
				debug(D_REDIR, " %s: Do Open\n", filename1);
				SET16(sc->sc_ecx, 1);
				goto do_open;
			case 0x02:
				debug(D_REDIR, " %s: Do Create 3\n", filename1);
				SET16(sc->sc_ecx, 3);
				goto do_creat;
			default:
				return (FUNC_NUM_IVALID);
			}
		}
		return (FUNC_NUM_IVALID);
	}

	return (0);
}
