/*
 *  linux/ibcs/open.c
 *
 *  Copyright (C) 1993  Joe Portman (baron@hebron.connected.com)
 *  Copyright (C) 1993, 1994  Drew Sullivan (re-worked for iBCS2)
 *
 * $Id: open.c,v 1.7 1994/05/05 10:45:38 mike Exp $
 * $Source: /var/CVS/ibcs/open.c,v $
 */

/* Keep track of which struct definition we really want */
#include <linux/vfs.h>
#include <linux/types.h>
#include <linux/utime.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/tty.h>
#include <linux/time.h>
#include <linux/malloc.h>

#include <asm/segment.h>

#include <ibcs/ibcs.h>

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


#ifdef __cplusplus
extern "C" 
#endif

/* ISC (at least) assumes O_CREAT if O_TRUNC is given. This is emulated
 * here but is it correct for iBCS in general? Do we care?
 */
static unsigned short fl_ibcs_to_linux[] = {
	0x0000, 0x0001, 0x0002, 0x0800, 0x0400, 0x1000, 0x0000, 0x0000,
	0x0800, 0x0040, 0x0240, 0x0080, 0x0100, 0x0000, 0x0000, 0x0000
};

static unsigned short fl_linux_to_ibcs[] = {
	0x0000, 0x0001, 0x0002, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100,
	0x0400, 0x0800, 0x0200, 0x0008, 0x0004, 0x0010, 0x0000, 0x0000
};

static inline unsigned short map_flags(unsigned short f, unsigned short map[])
{
	int i;
	unsigned short m, r;

	r = 0;
	for (i=1,m=1; i < 16; i++,m<<=1)
		if (f & m)
			r |= map[i];

	return r;
}


int ibcs_statfs(const char * path, struct ibcs_statfs * buf)
{
	struct inode * inode;
	struct statfs lxstat;
	struct ibcs_statfs ibcsstat;
	int error;
	int old_fs;

	error = verify_area(VERIFY_WRITE, buf, sizeof(struct ibcs_statfs));
	if (error)
		return error;
	error = namei(path,&inode);
	if (error)
		return error;
	if (!inode->i_sb->s_op->statfs) {
		iput(inode);
		return -ENOSYS;
	}
	old_fs = get_fs();
	set_fs(get_ds());

	inode->i_sb->s_op->statfs(inode->i_sb, &lxstat);

	set_fs(old_fs);
	iput(inode);

	ibcsstat.f_type = lxstat.f_type;
	ibcsstat.f_bsize = lxstat.f_bsize;
	ibcsstat.f_frsize = lxstat.f_bsize;
	ibcsstat.f_blocks = lxstat.f_blocks;
	ibcsstat.f_bfree = lxstat.f_bfree;
	ibcsstat.f_files = lxstat.f_files;
	ibcsstat.f_ffree = lxstat.f_ffree;
	memset(ibcsstat.f_fname, 0, sizeof(ibcsstat.f_fname));
	memset(ibcsstat.f_fpack, 0, sizeof(ibcsstat.f_fpack));

	/* Finally, copy it to the user's buffer */
	memcpy_tofs(buf, &ibcsstat, sizeof(ibcsstat));
	return 0;
}

#ifdef __cplusplus
extern "C" 
#endif
int ibcs_fstatfs(unsigned int fd, struct ibcs_statfs * buf)
{
	struct inode * inode;
	struct file * file;
	struct statfs lxstat;
	struct ibcs_statfs ibcsstat;
	int error;
	int old_fs;

	error = verify_area(VERIFY_WRITE, buf, sizeof(struct ibcs_statfs));
	if (error)
		return error;
	if (fd >= NR_OPEN || !(file = current->FD[fd]))
		return -EBADF;
	if (!(inode = file->f_inode))
		return -ENOENT;
	if (!inode->i_sb->s_op->statfs)
		return -ENOSYS;

	old_fs = get_fs();
	set_fs(get_ds());

	inode->i_sb->s_op->statfs(inode->i_sb, &lxstat);

	set_fs(old_fs);


	ibcsstat.f_type = lxstat.f_type;
	ibcsstat.f_bsize = lxstat.f_bsize;
	ibcsstat.f_frsize = lxstat.f_bsize;
	ibcsstat.f_blocks = lxstat.f_blocks;
	ibcsstat.f_bfree = lxstat.f_bfree;
	ibcsstat.f_files = lxstat.f_files;
	ibcsstat.f_ffree = lxstat.f_ffree;
	memset(ibcsstat.f_fname, 0, sizeof(ibcsstat.f_fname));
	memset(ibcsstat.f_fpack, 0, sizeof(ibcsstat.f_fpack));

	/* Finally, copy it to the user's buffer */
	memcpy_tofs(buf, &ibcsstat, sizeof(ibcsstat));
	return 0;
}


int ibcs_open(const char * fname, int flag, int mode)
{
	return SYS(open)(fname, map_flags(flag, fl_ibcs_to_linux), mode);
}


/* If/when the readdir function is changed to read multiple entries
 * at once this should be updated to take advantage of the fact.
 *
 * N.B. For Linux the reclen in a dirent is the number of characters
 * in the filename, for SCO (at least) reclen is the total size of
 * the particular dirent rounded up to the next multiple of 4. The SCO
 * behaviour is faithfully emulated here.
 *
 * XXXX
 * We don't truncate long filenames at all when copying. If we meet a
 * long filename and the buffer supplied by the application simply isn't
 * big enough to hold it we'll return without filling the buffer (i.e
 * return 0). The application will see this as a (premature) end of
 * directory. Is there a work around for this at all???
 */
int ibcs_getdents(int fd, char *buf, int nbytes)
{
	int error, here, posn, reclen;
	struct file *file;
	struct dirent *d;
	int old_fs;

	error = verify_area(VERIFY_WRITE, buf, nbytes);
	if (error)
		return error;

	/* Check the file handle here. This is so we can access the current
	 * position in the file structure safely without a tedious call
	 * to sys_lseek that does nothing useful.
	 */
	if (fd < 0 || fd >= NR_OPEN
	|| !(file=current->FD[fd])
	|| !(file->f_inode))
		return -EBADF;

	/* 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 = (sizeof(long) + sizeof(off_t)
			+ sizeof(unsigned short) + d->d_reclen + 1
			+ 3) & (~3);
		if (posn + reclen <= nbytes) {
			d->d_reclen = reclen;
			d->d_off = file->f_pos;
			memcpy_tofs(buf+posn, d, reclen);
			posn += reclen;
		} else if (posn) {
			SYS(lseek)(fd, here, 0);
		} /* else posn == 0 */
	}

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

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


struct ibcs_flock {
	short l_type;	/* numbers don't match */
	short l_whence;
	off_t l_start;
	off_t l_len;	/* 0 means to end of file */
	short l_sysid;
	short l_pid;
};


int
ibcs_fcntl(struct pt_regs *regs)
{
	int arg1, arg2, arg3;
	int error, retval;

	error = verify_area(VERIFY_READ,
			((unsigned long *)regs->esp)+1,
			3*sizeof(long));
	if (error)
		return error;

	arg1 = get_fs_long (((unsigned long *) regs->esp) + (1));
	arg2 = get_fs_long (((unsigned long *) regs->esp) + (2));
	arg3 = get_fs_long (((unsigned long *) regs->esp) + (3));

	switch (arg2) {
		/* These match the Linux commands. */
		case 0: /* F_DUPFD */
		case 1: /* F_GETFD */
		case 2: /* F_SETFD */
			return SYS(fcntl)(arg1, arg2, arg3);

		/* The iBCS flags don't match Linux flags. */
		case 3: /* F_GETFL */
			return map_flags(SYS(fcntl)(arg1, arg2, arg3),
					fl_linux_to_ibcs);
		case 4: /* F_SETFL */
			arg3 = map_flags(arg3, fl_ibcs_to_linux);
			return SYS(fcntl)(arg1, arg2, arg3);

		/* The lock stucture is different. */
		case 5: /* F_GETLK */
		case 6: /* F_SETLK */
		case 7: /* F_SETLKW */
			__asm__("decw %%fs:%0"
				: /* no outputs */
				: "m" (((struct ibcs_flock *)arg3)->l_type));
#ifdef IBCS_TRACE
			if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace) {
				struct ibcs_flock fl;

  				memcpy_fromfs(&fl,(void *)arg3,sizeof(fl));
				printk (KERN_DEBUG "iBCS: lock l_type: %d l_whence: %d l_start: %lu l_len: %lu l_sysid: %d l_pid: %d\n",
							fl.l_type,
							fl.l_whence,
							fl.l_start,
							fl.l_len,
							fl.l_sysid,
							fl.l_pid);

				}
#endif
			/* okay do some magic here */
			retval = SYS(fcntl)(arg1, arg2, arg3);
			__asm__("incw %%fs:%0"
				: /* no outputs */
				: "m" (((struct ibcs_flock *)arg3)->l_type));
			return retval;

		/* The following are defined but reserved and unknown. */
		case  8: /* F_CHKFL */
		case 10: /* F_ALLOCSP */
		case 11: /* F_FREESP */

		/* These are intended to support the Xenix chsize() and
		 * rdchk() system calls. I don't know if these may be
		 * generated by applications or not.
		 */
		case 0x6000: /* F_CHSIZE */
		case 0x6001: /* F_RDCHK */

		/* These are made from the Xenix locking() system call.
		 * According to available documentation these would
		 * never be generated by an application - only by the
		 * kernel Xenix support.
		 */
		case 0x6300: /* F_LK_UNLCK */
		case 0x7200: /* F_LK_LOCK */
		case 0x6200: /* F_LK_NBLCK */
		case 0x7100: /* F_LK_RLCK */
		case 0x6100: /* F_LK_NBRLCK */

		default:
#ifdef IBCS_TRACE
			if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace) {
					printk(KERN_ERR "iBCS: unsupported fcntl %d, arg 0x%lx\n",
					arg2, (unsigned long)arg3);
			}
#endif
			return -EINVAL;
			break;
	}
			
			
}
