/*
 * Copyright (c) 1995 - 2000 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 <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.63 2000/05/24 16:49:51 assar 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 ;

static int xfs_is_pag(void)
{
    
    if (current->ngroups >= 2 &&
	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 ;
}


xfs_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
store_pag (gid_t part1, gid_t part2)
{
    int i;

    if (!xfs_is_pag ()) {
	if (current->ngroups + 2 >= NGROUPS)
	    return -E2BIG;
	for (i = current->ngroups - 1; i >= 0; i--) {
	    current->groups[i + 2] = current->groups[i];
	}
	current->ngroups += 2;
    }
    current->groups[0] = part1;
    current->groups[1] = part2;
    return 0;
}

/*
 * Nuke any existing pag.
 */

static int
unpag (void)
{
    int i;

    while(xfs_is_pag ()) {
	for (i = 0; i < current->ngroups - 2; i++) {
	    current->groups[i] = current->groups[i+2];
	}
	/* Since ngroups >= 2 if xfs_is_pag(), this can't go negative. */
	current->ngroups -= 2;
    }
    return 0;
}

static int
xfs_setpag_call(void)
{
    int i;
    int ret;

#ifdef DEBUG
    XFSDEB(XDEBSYS, ("xfs_setpag_call groups: "));
    for (i = 0 ; i < current->ngroups; i++)
	XFSDEB(XDEBSYS, ("%d ",current->groups[i]));
    XFSDEB(XDEBSYS, ("\n"));
#endif

    ret = store_pag (pag_part_one, pag_part_two++);
    if (ret)
	return ret;

    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;
}

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

typedef int (*sys_setgroups_function)(int, gid_t *);

#ifdef ARLA_NR_setgroups16
typedef int (*sys_setgroups16_function)(int, old_gid_t *);
#endif

typedef int (*sys_call_function)(void);

extern sys_call_function sys_call_table[];
#ifdef NEED_VICEIOCTL32
extern u_int32_t sys_call_table32[];
#endif

static sys_call_function old_afs_syscall=NULL;
#ifdef NEED_VICEIOCTL32
static u_int32_t old_afs_syscall32=0;
#endif

static sys_call_function old_setgroups = NULL;

#ifdef ARLA_NR_setgroups16
static sys_call_function old_setgroups16 = NULL;
#endif

/*
 * A wrapper around sys_setgroups that tries to preserve the pag. If
 * the pag wouldn't fit after setting the group list, we return E2BIG.
 * If the user tried to create a pag and didn't have one before, we use
 * unpag() to clear out the fake pag, and return EINVAL, so as not to
 * allow anyone, even root, from attaching to somebody else's pag.
 */

static asmlinkage int
xfs_setgroups (int gidsetsize, gid_t *grouplist)
{
    sys_setgroups_function setgroups = (sys_setgroups_function)old_setgroups;

    if (xfs_is_pag ()) {
	gid_t part1, part2;
	int ret;
	/* Always save room for the PAG. */
	if(gidsetsize + 2 >= NGROUPS)
	    return -E2BIG;

	part1 = current->groups[0];
	part2 = current->groups[1];
	ret = (*setgroups) (gidsetsize, grouplist);
	if (ret) {
	    /* setgroups may return an error after partially setting the
	       groups, i.e. if the user's group buffer was partially
	       out of its address space. Prevent this trick from being
	       used to store a fake PAG. */
	    if(xfs_is_pag ())
		store_pag (part1, part2);

	    return ret;
	}
	return store_pag (part1, part2);
    } else {
	int ret;
	ret = (*setgroups) (gidsetsize, grouplist);

	/* Did a naughty superuser try to create a PAG? */
	if (xfs_is_pag ()) {
	    unpag();
	    /* Let that be a lesson to ya' */
	    return -EINVAL;
	}
	return ret;
    }
}

#ifdef ARLA_NR_setgroups16

/*
 * Linux 2.3.39 and above has 2 setgroups() system calls on arm, i386,
 * m68k, sh, and sparc32. We call the old one setgroups16() because it
 * uses a 16-bit gid_t (old_gid_t).
 * We need to fix it up too.
 */

static asmlinkage int
xfs_setgroups16 (int gidsetsize, old_gid_t *grouplist)
{
    sys_setgroups16_function setgroups16 = (sys_setgroups16_function)old_setgroups16;

    if (xfs_is_pag ()) {
	gid_t part1, part2;
	int ret;

	if(gidsetsize + 2 >= NGROUPS)
	    return -E2BIG;

	part1 = current->groups[0];
	part2 = current->groups[1];
	ret = (*setgroups16) (gidsetsize, grouplist);
	if (ret) {
	    if(xfs_is_pag ())
		store_pag (part1, part2);

	    return ret;
	}
	return store_pag (part1, part2);
    } else {
	int ret;
	ret = (*setgroups16) (gidsetsize, grouplist);

	/* Prevent attaching to somebody's PAG */
	if (xfs_is_pag ()) {
	    unpag();
	    return -EINVAL;
	}
	return ret;
    }
}
#endif /* ARLA_NR_setgroups16 */

struct file_handle {
    kdev_t dev;
    ino_t inode;
    __u32 gen;
};

static int
fhget_call(struct ViceIoctl *vice_ioctl, struct dentry *dentry)
{
    struct file_handle fh;
    struct xfs_cache_handle temp_fh;

    fh.dev   = dentry->d_inode->i_dev;
    fh.inode = dentry->d_inode->i_ino;
    fh.gen   = dentry->d_inode->i_generation;

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

    if (vice_ioctl->out_size < sizeof(struct 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(struct xfs_cache_handle)) != 0) {
	dput(dentry);
	return -EFAULT;
    }

    dput(dentry);

    return 0;
}

/*
 * lookup file identified by `handle' and return a dentry or an error.
 */

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",
		       (unsigned int) fh.dev, (unsigned int) fh.inode));
    
    sb = get_super (fh.dev);
    if (sb == NULL)
	return ERR_PTR(-ENXIO);

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

    if (inode->i_generation != fh.gen) {
	iput (inode);
	XFSDEB(XDEBVFOPS, ("xfs_fh_to_dentry: bad generation %u != %u\n",
			   (unsigned int)inode->i_generation,
			   (unsigned int)fh.gen));
	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;
}

/*
 * open the file identified by the file handle in `vice_ioctl' with
 * `mode'.
 * Return an fd or an error.
 */

static int
fhopen_call(struct ViceIoctl *vice_ioctl, int mode)
{
    struct dentry *dentry;
    struct xfs_cache_handle handle;
    int fd;
    struct inode *inode;

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

    if (copy_from_user(&handle, vice_ioctl->in, sizeof(handle)) != 0)
	return -EFAULT;

    dentry = xfs_fh_to_dentry(&handle);
    if (IS_ERR(dentry))
	return PTR_ERR(dentry);
    inode = dentry->d_inode;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,99)
    fd = open_dentry(dentry, O_RDWR);
    if (fd >= 0) {
	int error = get_write_access(inode);
	if (error) {
	    dput(dentry);
	    sys_close(fd);
	    return error;
	}
    }
    dput(dentry);
#else
    {
	struct file *f;
	int error;

	error = get_write_access (inode);
	if (error) {
	    dput (dentry);
	    return error;
	}

	f = dentry_open(dentry, NULL, O_RDWR);
	if (IS_ERR(f))
	    return PTR_ERR(f);
	fd = get_unused_fd();
	if (fd < 0) {
	    put_filp(f);
	    dput(dentry);
	    return fd;
	}
	fd_install(fd, f);
    }
#endif
    XFSDEB(XDEBSYS, ("fhopen_call: returns fd: %d\n", fd));

    return fd;
}

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, struct dentry *node)
{
    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;
	case XDEBNODE:
	    if (node) {
		print_aliases(node->d_inode);
		print_childs(node);
	    } else {
		print_nodes(0);
	    }
	    return 0;
	default:
	    return -EINVAL;
	}
    }

    return 0;
}

/*
 * convert the path `user_path' (in user memory) into an dentry.
 * follow symlinks iff `follow'
 */

#ifdef LINUX2_3

static struct dentry *
user_path2dentry (char *user_path, int follow)
{
    char *kname;
    int flags;
    struct nameidata nd;
    int error;

    kname = getname (user_path);
    if (IS_ERR(kname))
	return ERR_PTR(PTR_ERR(kname));
    flags = LOOKUP_POSITIVE;
    if (follow)
	flags |= LOOKUP_FOLLOW;

    error = 0;
    if (path_init (kname, flags, &nd))
	error = path_walk(kname, &nd);
    putname(kname);
    if (error)
	return ERR_PTR(error);
    return nd.dentry;
}

#else /* !LINUX2_3 */

static struct dentry *
user_path2dentry (char *user_path, int follow)
{
    if (follow)
	return namei (user_path);
    else
	return lnamei (user_path);
}

#endif /* !LINUX2_3 */

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

asmlinkage int
sys_afs_int (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"));
	memcpy(&vice_ioctl,a_paramsP,sizeof(*a_paramsP));
	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));
	    dentry = user_path2dentry (a_pathP, a_followSymlinks);
	    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));
	}

	switch (a_opcode) {
	case VIOC_FHGET:
#ifdef VIOC_FHGET_32
	case VIOC_FHGET_32:
#endif
	    error = fhget_call(&vice_ioctl, dentry);
	    dentry = NULL;
	    goto unlock;
	case VIOC_FHOPEN:
#ifdef VIOC_FHOPEN_32
	case VIOC_FHOPEN_32:
#endif
	    if (dentry)
		dput(dentry);
	    dentry = NULL;
	    error = fhopen_call(&vice_ioctl, a_followSymlinks);
	    goto unlock;
	case VIOC_XFSDEBUG:
#ifdef VIOC_XFSDEBUG_32
	case VIOC_XFSDEBUG_32:
#endif
	    error = xfs_debug (&vice_ioctl);
	    if (dentry)
		dput(dentry);
	    dentry = NULL;
	    goto unlock;
	case VIOC_XFSDEBUG_PRINT:
#ifdef VIOC_XFSDEBUG_PRINT_32
	case VIOC_XFSDEBUG_PRINT_32:
#endif
	    error = xfs_debug_print (&vice_ioctl, dentry);
	    if (dentry)
		dput(dentry);
	    dentry = NULL;
	    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));
	    if (xn == NULL) {
		XFSDEB(XDEBMSG, ("xfs_syscall: is an xfs dentry, but has no xnode\n"));
		dput(dentry);
		dentry = NULL;
		error = -EINVAL;
		goto unlock;
	    }
	    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;

	if (error == -ENODEV)
	    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;
}

#ifdef NEED_VICEIOCTL32
asmlinkage int
sys32_afs (int operation,
	   char *a_pathP,
	   int a_opcode,
	   struct ViceIoctl32 *a_paramsP,
	   int a_followSymlinks)
{
    struct ViceIoctl vice_ioctl;
    struct ViceIoctl32 vice_ioctl32;

    if (operation == AFSCALL_PIOCTL) {
	if(copy_from_user(&vice_ioctl32, a_paramsP, sizeof(*a_paramsP)) != 0)
	    return -EFAULT;

	vice_ioctl.in = (caddr_t) (u_int64_t) vice_ioctl32.in;
	vice_ioctl.out = (caddr_t) (u_int64_t) vice_ioctl32.out;
	vice_ioctl.in_size = vice_ioctl32.in_size;
	vice_ioctl.out_size = vice_ioctl32.out_size;
    }

    return sys_afs_int(operation, a_pathP, a_opcode, &vice_ioctl,
		       a_followSymlinks);
}
#endif

asmlinkage int
sys_afs (int operation,
	 char *a_pathP,
	 int a_opcode,
	 struct ViceIoctl *a_paramsP,
	 int a_followSymlinks)
{
    struct ViceIoctl vice_ioctl;

    if (operation == AFSCALL_PIOCTL) {
	if(copy_from_user(&vice_ioctl, a_paramsP, sizeof(*a_paramsP)) != 0)
	    return -EFAULT;
    }

    return sys_afs_int(operation, a_pathP, a_opcode, &vice_ioctl,
		       a_followSymlinks);
}


void
install_afs_syscall (void)
{
    old_afs_syscall = sys_call_table[__NR_afs_syscall];
    sys_call_table[__NR_afs_syscall] = (sys_call_function)&sys_afs;
#ifdef NEED_VICEIOCTL32
    old_afs_syscall32 = sys_call_table32[__NR_afs_syscall];
    sys_call_table32[__NR_afs_syscall] = (u_int32_t)&sys32_afs;
#endif
    old_setgroups = sys_call_table[ARLA_NR_setgroups];
    sys_call_table[ARLA_NR_setgroups] = (sys_call_function)&xfs_setgroups;
#ifdef ARLA_NR_setgroups16
    old_setgroups16 = sys_call_table[ARLA_NR_setgroups16];
    sys_call_table[ARLA_NR_setgroups16] = (sys_call_function)&xfs_setgroups16;
#endif
}

void
restore_afs_syscall (void)
{
    if (old_afs_syscall) {
	sys_call_table[__NR_afs_syscall] = old_afs_syscall;
	old_afs_syscall = NULL;
    }
#ifdef NEED_VICEIOCTL32
    if (old_afs_syscall32) {
	sys_call_table32[__NR_afs_syscall] = old_afs_syscall32;
	old_afs_syscall32 = 0;
    }
#endif
    if (old_setgroups) {
	sys_call_table[ARLA_NR_setgroups] = old_setgroups;
	old_setgroups = NULL;
    }
#ifdef ARLA_NR_setgroups16
    if (old_setgroups16) {
	sys_call_table[ARLA_NR_setgroups16] = old_setgroups16;
	old_setgroups16 = NULL;
    }
#endif
}
