/*
 *  linux/abi/emulate.c
 *
 *  Copyright (C) 1993  Linus Torvalds
 *
 *   Modified by Eric Youngdale to include all ibcs syscalls.
 *   Re-written by Drew Sullivan to handle lots more of the syscalls correctly.
 *
 * $Id: coff.c,v 1.8 1994/05/05 10:45:36 mike Exp $
 * $Source: /var/CVS/ibcs/coff.c,v $
 */

#define __KERNEL__ 1
/*
 * Emulate.c contains the entry point for the 'lcall 7,xxx' handler.
 */
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/segment.h>
#include <linux/ptrace.h>
#include <linux/config.h>
#include <linux/fcntl.h>
#include <linux/time.h>

#include <asm/segment.h>
#include <asm/system.h>
#include <linux/fs.h>
#include <linux/sys.h>
#include <linux/malloc.h>

#include <ibcs/ibcs.h>

#ifdef IBCS_TRACE
#include <ibcs/trace.h>
#endif

/* Here we handle syscalls that need a little more special treatment */
int ibcs_fork(struct pt_regs * regs) {
	int rvalue;

	regs->eflags &= ~1; /* Clear carry flag */
	rvalue = SYS(fork)(regs->ebx, regs->ecx, 1,
		regs->esi, regs->edi, regs->ebp, regs->eax, regs->ds,
		regs->es, regs->fs, regs->gs, regs->orig_eax,
		regs->eip, regs->cs, regs->eflags, regs->esp, regs->ss);
	regs->edx = 0;
	return rvalue;
}

int ibcs_pipe(struct pt_regs * regs) {
	long filedes[2];
	int old_fs = get_fs();
	int rvalue;

	set_fs(get_ds());
	rvalue = SYS(pipe)(&filedes);
	set_fs(old_fs);
	if (rvalue == 0) {
		rvalue = filedes[0];
		regs->edx = filedes[1];
	}
	return rvalue;
}

/* note the double value return in eax and edx */
int ibcs_getpid(struct pt_regs * regs) {
	regs->edx = current->p_pptr->pid;

	return current->pid;
}

/* note the double value return in eax and edx */
int ibcs_getuid(struct pt_regs * regs) {
	regs->edx = current->euid;

	return current->uid;
}

/* note the double value return in eax and edx */
int ibcs_getgid(struct pt_regs * regs) {
	regs->edx = current->egid;

	return current->gid;
}

#define FLAG_ZF 0x0040
#define FLAG_PF 0x0004
#define FLAG_SF 0x0080
#define FLAG_OF 0x0800

#define MAGIC_WAITPID_FLAG (FLAG_ZF | FLAG_PF | FLAG_SF | FLAG_OF)

int ibcs_wait(struct pt_regs * regs) {
	long	result, kopt;
	int	pid, loc, opt;

	/* if ZF,PF,SF,and OF are set then it is waitpid */
	if ((regs->eflags & MAGIC_WAITPID_FLAG) == MAGIC_WAITPID_FLAG) {
		pid = get_fs_long(((unsigned long *) regs->esp) + 1);
		loc = get_fs_long(((unsigned long *) regs->esp) + 2);
		opt = get_fs_long(((unsigned long *) regs->esp) + 3);

		/* Now translate the options from the SVr4 numbers */
		kopt = 0;
		if(opt & 0100) kopt |= WNOHANG;

		result = SYS(wait4)(pid, loc, kopt, NULL);
	} else {
		loc = get_fs_long(((unsigned long *) regs->esp) + 1);
		result = SYS(wait4)(-1, loc, 0L, NULL);
	}
	if(loc)
		regs->edx = get_fs_long((unsigned long *) loc);
	return result;
}

/*
 * ibcs_execv() executes a new program.
 *
 *  int execv (char *program, va_list argp);
 *
 *   Environment strings are taken from the current context. argp is
 *   terminated by a NULL pointer.
 */
int ibcs_execv(struct pt_regs *regs)
{
	/* does this entry point do the searching in the PATH line? */
        /* No. See above. Al. */
	return ibcs_exec(regs);
}

/*
 * ibcs_exec() executes a new program.
 */
int ibcs_exec(struct pt_regs *regs)
{
	int error;
	char *pgm, **argv, **envp;
	char *filename;

	pgm = (char *)get_fs_long(((unsigned long *) regs->esp) + 1);
	argv = (char **)get_fs_long(((unsigned long *) regs->esp) + 2);
	envp = (char **)get_fs_long(((unsigned long *) regs->esp) + 3);

	error = getname(pgm, &filename);
	if (error == 0) {
		/* if you get an error on this undefined, then remove the */
		/* 'static' declaration in /linux/fs/exec.c */
		error = do_execve(filename, argv, envp, regs);
		putname (filename);
        }
	return error;
}

int ibcs_read(int fd, char *buf, int nbytes)
{
	int error, here, posn, reclen;
	struct file *file;
	struct dirent *d;
	int old_fs;

	error = SYS(read)(fd, buf, nbytes);
	if (error != -EISDIR)
		return error;

	/* Stupid bloody thing is trying to read a directory. Some old
	 * programs expect this to work. It works on SCO. To emulate it
	 * we have to map a dirent to a direct. This involves shrinking
	 * a long inode to a short. Fortunately nothing this archaic is
	 * likely to care about anything but the filenames of entries
	 * with non-zero inodes. We also assume that anything doing
	 * this doesn't understand names longer than 14 characters
	 * and quietly ignore anything longer.
	 */

	/* sys_read has already done a verify_area and checked the
	 * decriptor number.
	 */
	file = current->FD[fd];

	/* XXXXXX
	 * The size of dirent must be explicit and not rely on the name
	 * array quietly extending past the end of the structure!
	 * It may be safer just to use get_free_page for this?
	 */
	d = (struct dirent *)kmalloc(sizeof(struct dirent), GFP_KERNEL);
	if (!d)
		return -ENOMEM;

	error = posn = reclen = 0;

	while (posn + reclen < nbytes) {
		/* Save the current position and get another dirent */
		here = file->f_pos;
		old_fs = get_fs();
		set_fs (get_ds());
		error = SYS(readdir)(fd, d, 1);
		set_fs(old_fs);
		if (error <= 0)
			break;

		/* If it'll fit in the buffer save it otherwise back up
		 * so it is read next time around.
		 * Oh, if we're at the beginning of the buffer there's
		 * no chance that this entry will ever fit so don't
		 * copy it and don't back off - we'll just pretend it
		 * isn't here...
		 */
		reclen = 16 * ((d->d_reclen + 13) / 14);
		if (posn + reclen <= nbytes) {
			/* SCO (at least) handles long filenames by breaking
			 * them up in to 14 character chunks of which all
			 * but the last have the inode set to 0xffff.
			 */
			char *p = d->d_name;

			/* Put all but the last chunk. */
			while (d->d_reclen > 14) {
				put_fs_word(0xffff, buf+posn);
				posn += 2;
				memcpy_tofs(buf+posn, p, 14);
				posn += 14;
				p += 14;
				d->d_reclen -= 14;
			}
			/* Put the last chunk. Note the we have to fold a
			 * long inode number down to a short. Hopefully
			 * nothing uses the inode number!
			 */
			if ((unsigned long)d->d_ino > 0xfffe)
				put_fs_word(0xfffe, buf+posn);
			else
				put_fs_word((short)d->d_ino, buf+posn);
			posn += 2;
			memcpy_tofs(buf+posn, p, d->d_reclen);

			/* Ensure that filenames that don't fill the array
			 * completely are null filled.
			 */
			for (; d->d_reclen < 14; d->d_reclen++)
				put_fs_byte('\0', buf+posn+d->d_reclen);

			posn += 14;
		} else if (posn) {
			SYS(lseek)(fd, here, 0);
		} /* else posn == 0 */
	}

	/* Lose the intermediate buffer. */
	kfree(d);

	/* If we've put something in the buffer return the byte count
	 * otherwise return the error status.
	 */
	return (posn ? posn : error);
}

int ibcs_procids(struct pt_regs * regs)
{
	int op = get_fs_long(((unsigned long *)regs->esp)+1);

	switch (op) {
		case 0: /* getpgrp */
			return current->pgrp;

		case 1: /* setpgrp */
			SYS(setpgid)(0, 0);
			return current->pgrp;

		case 2: { /* setpgid */
			int pid, pgid;

			/* N.B. These arguments are further up the stack
			 * than you might think because there is the
			 * return address for a call in between. Try
			 * tracing the assembler of a program!
			 */
			pid = get_fs_long(((unsigned long *)regs->esp)+3);
			pgid = get_fs_long(((unsigned long *)regs->esp)+4);
			return SYS(setpgid)(pid, pgid);
		}

		case 3: /* setsid */
			return SYS(setsid)();
	}

	return -EINVAL;
}

int ibcs_select(struct pt_regs *regs)
{
#ifdef IBCS_TRACE
	int error;
	struct timeval *t;

	if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace) {
		error = verify_area(VERIFY_READ,
				((unsigned long *)regs->esp)+1,
				5*sizeof(long));
		if (error)
			return error;
		t = (struct timeval *)get_fs_long(((unsigned long *)regs->esp)+5);
		if (t) {
			error = verify_area(VERIFY_READ, t, sizeof(struct timeval));
			if (error)
				return error;
			printk(KERN_DEBUG "iBCS: select timeout in %lus, %luus\n",
				get_fs_long(&(t->tv_sec)),
				get_fs_long(&(t->tv_usec)));
		}
	}
#endif
	return SYS(select)(((unsigned long *)regs->esp) + 1);
}


int
ibcs_time(void)
{
	return SYS(time)(0);
}
