/*
 * Copyright (c) 1995, 1996, 1997, 1998 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * 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 the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 THE INSTITUTE OR CONTRIBUTORS 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.
 */

#define __NO_VERSION__

#include <xfs/xfs_locl.h>
#include <xfs/xfs_dev.h>
#include <asm/unistd.h>
#include <kafs.h>
#include <xfs/xfs_syscalls.h>
#include <xfs/xfs_common.h>
#include <linux/file.h>
#include <linux/smp_lock.h>
#include <linux/smp.h>

RCSID("$Id: xfs_syscalls.c,v 1.45 1999/01/24 22:53:56 map Exp $");

/* 
 * Def pag:
 *  33536 <= g0 <= 34560
 *  32512 <= g1 <= 48896
 */

#define XFS_PAG1_LLIM 33536
#define XFS_PAG1_ULIM 34560
#define XFS_PAG2_LLIM 32512
#define XFS_PAG2_ULIM 48896

static gid_t pag_part_one = XFS_PAG1_LLIM ;
static gid_t pag_part_two = XFS_PAG2_LLIM ;

#ifndef LINUX2_1
static int xfs_get_ngroups(void)
{
    int i;

    for (i = 0 ; current->groups[i] != NOGROUP && i < NGROUPS; i++);
    return i;
}
#endif /* LINUX2_1 */

static int xfs_is_pag(void)
{
    
#ifndef LINUX2_1
    if (current->groups[0] != NOGROUP &&
	current->groups[1] != NOGROUP &&
#else
    if (current->ngroups >= 2 &&
#endif /* LINUX2_1 */
	current->groups[0] >= XFS_PAG1_LLIM &&
	current->groups[0] <= XFS_PAG1_ULIM &&
	current->groups[1] >= XFS_PAG2_LLIM &&
	current->groups[1] <= XFS_PAG2_ULIM)
	
	return 1;
    else
	return 0 ;
}


pag_t xfs_get_pag(void)
{
    if (xfs_is_pag()) {
	
	return (((current->groups[0] << 16) & 0xFFFF0000) |
		((current->groups[1] & 0x0000FFFF))) ;

    } else
	return current->uid;
}

static int
xfs_setpag_call(void)
{
    int i ;

#ifdef DEBUG
    XFSDEB(XDEBSYS, ("xfs_setpag_call groups: "));
#ifndef LINUX2_1
    for (i = 0 ; current->groups[i] != NOGROUP && i < NGROUPS; i++)
#else
    for (i = 0 ; i < current->ngroups; i++)
#endif /* LINUX2_1 */
	XFSDEB(XDEBSYS, ("%d ",current->groups[i]));
    XFSDEB(XDEBSYS, ("\n"));
#endif

    if (!xfs_is_pag()) {
#ifndef LINUX2_1
	int ngroups = xfs_get_ngroups();
#endif /* LINUX2_1 */

	/* Check if it fits */
#ifndef LINUX2_1
	if (ngroups + 2 >= NGROUPS) 
#else
	if (current->ngroups + 2 >= NGROUPS) 
#endif /* LINUX2_1 */
	    return -E2BIG;             /* XXX Hmmm, better error ? */

	/* Copy the groups */
#ifndef LINUX2_1
	for (i = ngroups - 1; i >= 0 ; i--) {
#else
	for (i = current->ngroups - 1; i >= 0 ; i--) {
#endif /* LINUX2_1 */
	    current->groups[i+2] = current->groups[i] ; 
	}
#ifndef LINUX2_1
	ngroups += 2 ;
	if (ngroups < NGROUPS)
	    current->groups[ngroups] = NOGROUP;
#else
	current->ngroups += 2 ;
#endif /* LINUX2_1 */

    }

    current->groups[0] = pag_part_one ;
    current->groups[1] = pag_part_two++ ;

    if (pag_part_two > XFS_PAG2_ULIM) {
	pag_part_one++ ;
	pag_part_two = XFS_PAG2_LLIM ;
    }

    /* Opps, we wraped around... do some smarter stuff ? */
    if (pag_part_one > XFS_PAG1_ULIM) 
	pag_part_one = XFS_PAG1_LLIM ;
	
    return 0;
}

struct file_handle {
    unsigned int dev_index;
    ino_t inode;
};

struct super_block *(sb_list[100]);
unsigned int sb_list_counter = 0;

#ifdef LINUX2_1
static int
fhget_call(struct ViceIoctl *vice_ioctl, struct DENTRY *dentry)
{
    struct file_handle fh;
    struct xfs_cache_handle temp_fh;
    int dev_index = -1;
    int i;

    for (i = 0; i < sb_list_counter ; i++) {
	if (sb_list[i]->s_dev == dentry->d_inode->i_dev) {
	    dev_index = i;
	}
    }
    if (dev_index == -1) {
	dev_index = sb_list_counter;
	sb_list[dev_index] = dentry->d_sb;
	sb_list_counter++;
    }
    if (dev_index >= 100) {
	DPUT(dentry);
	return -ENOMEM;
    }

    fh.dev_index = dev_index;
    fh.inode = dentry->d_inode->i_ino;

    XFSDEB(XDEBVFOPS, ("fhget_call: dev: %u inode: %u\n",
		       fh.dev_index, (unsigned int) fh.inode));

    if (vice_ioctl->out_size < sizeof(xfs_cache_handle)) {
	DPUT(dentry);
	return -EINVAL;
    }
    memset(&temp_fh, 0, sizeof(temp_fh));
    
    if (sizeof(temp_fh) < sizeof(fh))
	printk(KERN_EMERG "XFS Panic: Temporary file handle in fhget_call() is too small");

    memcpy(&temp_fh, &fh, sizeof(fh));

    if (copy_to_user (vice_ioctl->out,
		      &temp_fh,
		      sizeof(xfs_cache_handle)) != 0) {
	DPUT(dentry);
	return -EFAULT;
    }

    DPUT(dentry);

    return 0;
}

struct dentry * xfs_fh_to_dentry(struct xfs_cache_handle *handle)
{
    struct file_handle fh;
    struct dentry *dentry;
    struct inode *inode;
    struct super_block *sb;
    
    memcpy(&fh, handle, sizeof(fh));

    XFSDEB(XDEBVFOPS, ("xfs_fh_to_dentry: dev: %u inode: %u\n",
		       fh.dev_index, (unsigned int) fh.inode));

    if (fh.dev_index >= sb_list_counter)
	return ERR_PTR(-EINVAL);
    
    sb = sb_list[fh.dev_index];

    inode = iget(sb, fh.inode);
    if (inode == NULL) {
	XFSDEB(XDEBVFOPS, ("xfs_fh_to_dentry: inode == NULL\n"));
	return ERR_PTR(-ENOENT);
    }

    dentry = d_alloc(NULL, &(const struct qstr) { "xfs cache file", 14, 0});
    dentry->d_sb = sb;
    dentry->d_parent = dentry;
    
    d_instantiate(dentry, inode);

    return dentry;
}

static int
fhopen_call(struct ViceIoctl *vice_ioctl, int mode)
{
    struct dentry *dentry;
    int fd;
    int error;

    if (!suser())
        return -EPERM;
    
    if (vice_ioctl->in_size < sizeof(xfs_cache_handle))
	return -EINVAL;

    dentry = xfs_fh_to_dentry((struct xfs_cache_handle *) vice_ioctl->in);
    if (dentry == NULL)
	return -ENOENT;
    if (IS_ERR(dentry))
	return PTR_ERR(dentry);

    fd = open_dentry(dentry, O_RDWR);
    if (fd >= 0) {
	error = get_write_access(dentry->d_inode);
	if (error) {
	    sys_close(fd);
	    return error;
	}
    }
    
    DPUT(dentry);

    XFSDEB(XDEBSYS, ("fhopen_call: returns fd: %d\n", fd));

    return fd;
}
#else
int
fhget_call(struct ViceIoctl *vice_ioctl, struct DENTRY *dentry)
{
    DPUT(dentry);
    return -EINVAL;
}

int
fhopen_call(struct ViceIoctl *vice_ioctl, int mode)
{
    return -EINVAL;
}
#endif

static int
xfs_debug (struct ViceIoctl *vice_ioctl)
{
    if (!suser())
        return -EPERM;

    if (vice_ioctl->in_size != 0) {
	int32_t tmp;

	if (vice_ioctl->in_size < sizeof(int32_t))
	    return -EINVAL;
	
	if (copy_from_user (&tmp,
			    vice_ioctl->in,
			    sizeof(tmp)) != 0)
	    return -EFAULT;

	xfsdeb = tmp;
    }

    if (vice_ioctl->out_size != 0) {
	int32_t tmp = xfsdeb;

	if (vice_ioctl->out_size < sizeof(int32_t))
	    return -EINVAL;
	
	if (copy_to_user (vice_ioctl->out,
			  &tmp,
			  sizeof(tmp)) != 0)
	    return -EFAULT;
    }

    return 0;
}

static int
xfs_debug_print (struct ViceIoctl *vice_ioctl)
{
    if (!suser())
        return -EPERM;

    if (vice_ioctl->in_size != 0) {
	int32_t tmp;

	if (vice_ioctl->in_size < sizeof(int32_t))
	    return -EINVAL;
	
	if (copy_from_user (&tmp,
			    vice_ioctl->in,
			    sizeof(tmp)) != 0)
	    return -EFAULT;

	switch (tmp) {
	case XDEBMEM:
	    xfs_tell_alloc();
	    return 0;
	case XDEBMSG:
	    xfs_print_sleep_queue();
	    return 0;
	default:
	    return -EINVAL;
	}
    }

    return 0;
}

#define MIN(a,b) ((a<b)?a:b)

asmlinkage int
sys_afs (int operation,
	 char *a_pathP,
	 int a_opcode,
	 struct ViceIoctl *a_paramsP,
	 int a_followSymlinks)
{
    int error = 0;
    struct ViceIoctl vice_ioctl; 
    struct xfs_message_pioctl msg;
    struct xfs_message_wakeup_data *msg2;
    struct DENTRY *dentry = NULL;
    
#ifdef __SMP__
    XFSDEB(XDEBSYS, ("sys_afs locking kernel; cpu: %d\n", smp_processor_id()));

    lock_kernel();

    XFSDEB(XDEBSYS, ("sys_afs kernel locked; cpu: %d\n", smp_processor_id()));
#endif

    XFSDEB(XDEBSYS, ("sys_afs operation: %d a_pathP: %p "
		     "a_opcode: %d a_paramsP: %p "
		     "a_followSymlinks: %d\n",
		     operation, a_pathP, a_opcode,
		     a_paramsP, a_followSymlinks));
    
    switch (operation) {
    case AFSCALL_PIOCTL:
	XFSDEB(XDEBSYS, ("xfs_pioctl\n"));
	if(copy_from_user(&vice_ioctl,a_paramsP,sizeof(*a_paramsP)) != 0) {
	    error = -EFAULT;
	    goto unlock;
	}
	if (vice_ioctl.in_size > 2048) {
	    XFSDEB(XDEBSYS, ("xfs_pioctl_call: got a humongous in packet: "
			     "opcode: %d (size %d)",
		   a_opcode, vice_ioctl.in_size));
	    error = -EINVAL;
	    goto unlock;
	}
	if (vice_ioctl.in_size != 0) {
	    if(copy_from_user(&msg.msg,
			      vice_ioctl.in,
			      vice_ioctl.in_size) != 0) {
		error = -EFAULT;
		goto unlock;
	    }
	}
	if (a_pathP != NULL) {
	    XFSDEB(XDEBMSG, ("xfs_syscall: looking up: %p\n", a_pathP));

#ifndef LINUX2_1
	    if (a_followSymlinks)
		error = namei(a_pathP, &dentry);
	    else
		error = lnamei(a_pathP, &dentry);
	    if (dentry)
		XFSDEB(XDEBMSG,("xfs_syscall: inode: %p inodenum: %lx\n",
				dentry, dentry->i_ino));
	    if (error) {
		XFSDEB(XDEBMSG, ("xfs_syscall: error during namei: %d\n",
				 error));
		goto unlock;
	    }
#else
	    if (a_followSymlinks)
		dentry = namei(a_pathP);
	    else
		dentry = lnamei(a_pathP);
	    if (!dentry) {
		error = -EINVAL;
		goto unlock;
	    }
	    if (IS_ERR(dentry)) {
		XFSDEB(XDEBMSG, ("xfs_syscall: error during namei: %ld\n",
				 PTR_ERR(dentry)));
		error = PTR_ERR(dentry);
		goto unlock;
	    }
	    XFSDEB(XDEBMSG,("xfs_syscall: inode: %p inodenum: %lx\n",
			    dentry->d_inode, dentry->d_inode->i_ino));
#endif /* LINUX2_1 */
	}

	switch (a_opcode) {
	case VIOC_FHGET:
	    error = fhget_call(&vice_ioctl, dentry);
	    goto unlock;
	case VIOC_FHOPEN:
	    DPUT(dentry);
	    error = fhopen_call(&vice_ioctl, a_followSymlinks);
	    goto unlock;
	case VIOC_XFSDEBUG:
	    error = xfs_debug (&vice_ioctl);
	    goto unlock;
	case VIOC_XFSDEBUG_PRINT:
	    error = xfs_debug_print (&vice_ioctl);
	    goto unlock;
	}

	if (dentry != NULL) {
	    struct xfs_node *xn;
	    if (strcmp(DENTRY_TO_INODE(dentry)->i_sb->s_type->name,
		       "xfs") != 0) {
		XFSDEB(XDEBMSG, ("xfs_syscall: not in afs\n"));
		DPUT(dentry);
		dentry = NULL;
		error = -EINVAL;
		goto unlock;
	    }
	    xn = VNODE_TO_XNODE(DENTRY_TO_INODE(dentry));
	    msg.handle = xn->handle;
	    DPUT(dentry);
	    dentry = NULL;
	}

	msg.header.opcode = XFS_MSG_PIOCTL;
	msg.opcode = a_opcode;
	
	msg.insize = vice_ioctl.in_size;
	msg.outsize = vice_ioctl.out_size;
	msg.cred.uid = current->uid;
	msg.cred.pag = xfs_get_pag();
	
	error = xfs_message_rpc(0, &msg.header, sizeof(msg)); /* XXX */
	msg2 = (struct xfs_message_wakeup_data *) &msg;
	if (error == 0)
	    error = msg2->error;
	else
	    error = -EINVAL;

	if (error == 0 && msg2->header.opcode == XFS_MSG_WAKEUP_DATA) {
	    if(copy_to_user(vice_ioctl.out, msg2->msg,
			    MIN(msg2->len, vice_ioctl.out_size)) != 0) {
		XFSDEB(XDEBSYS, ("xfs_syscall copy_to_user "
				 "vice_ioctl.out: %p msg2->msg: %p "
				 "msg2->len: %d vice_ioctl.out_size: %d\n",
				 vice_ioctl.out, msg2->msg,
				 msg2->len, vice_ioctl.out_size));
		error = -EFAULT;
	    }
	}

	break;
    case AFSCALL_SETPAG:
	error = xfs_setpag_call();
	break;
    default:
	XFSDEB(XDEBSYS, ("xfs_syscalls: unimplemented call\n"));
	error = -EINVAL;
	break;
    }
    
 unlock:
    XFSDEB(XDEBSYS, ("xfs_syscall returns error: %d\n", error));
#ifdef __SMP__
    XFSDEB(XDEBSYS, ("sys_afs kernel unlock; cpu: %d\n", smp_processor_id()));
    unlock_kernel();
#endif
    return error;
}

typedef int (*sys_call_routine)(int operation,
				char *a_pathP,
				int a_opcode,
				struct ViceIoctl *a_paramsP,
				int a_followSymlinks);

extern sys_call_routine sys_call_table[];

static sys_call_routine old_afs_syscall=NULL;

void
install_afs_syscall (void)
{
    old_afs_syscall = sys_call_table[__NR_afs_syscall];
    sys_call_table[__NR_afs_syscall] = &sys_afs;
}

void
restore_afs_syscall (void)
{
    if (old_afs_syscall) {
	sys_call_table[__NR_afs_syscall] = old_afs_syscall;
	old_afs_syscall = NULL;
    }
}
