/*
 *  linux/fs/compat.c
 *
 *  Kernel compatibililty routines for e.g. 32 bit syscall support
 *  on 64 bit kernels.
 *
 *  Copyright (C) 2002       Stephen Rothwell, IBM Corporation
 *  Copyright (C) 1997-2000  Jakub Jelinek  (jakub@redhat.com)
 *  Copyright (C) 1998       Eddie C. Dost  (ecd@skynet.be)
 *  Copyright (C) 2001,2002  Andi Kleen, SuSE Labs 
 *  Copyright (C) 2003       Pavel Machek (pavel@suse.cz)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 */

#include <linux/linkage.h>
#include <linux/compat.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/namei.h>
#include <linux/file.h>
#include <linux/vfs.h>
#include <linux/ioctl32.h>
#include <linux/init.h>
#include <linux/sockios.h>	/* for SIOCDEVPRIVATE */
#include <linux/fs.h>
#include <linux/smp_lock.h>
#include <linux/ctype.h>
#include <linux/module.h>

#include <asm/uaccess.h>

/*
 * Not all architectures have sys_utime, so implement this in terms
 * of sys_utimes.
 */
asmlinkage long compat_sys_utime(char *filename, struct compat_utimbuf *t)
{
	struct timeval tv[2];

	if (t) {
		if (get_user(tv[0].tv_sec, &t->actime) ||
		    get_user(tv[1].tv_sec, &t->modtime))
			return -EFAULT;
		tv[0].tv_usec = 0;
		tv[1].tv_usec = 0;
	}
	return do_utimes(filename, t ? tv : NULL);
}


asmlinkage long compat_sys_newstat(char * filename,
		struct compat_stat *statbuf)
{
	struct kstat stat;
	int error = vfs_stat(filename, &stat);

	if (!error)
		error = cp_compat_stat(&stat, statbuf);
	return error;
}

asmlinkage long compat_sys_newlstat(char * filename,
		struct compat_stat *statbuf)
{
	struct kstat stat;
	int error = vfs_lstat(filename, &stat);

	if (!error)
		error = cp_compat_stat(&stat, statbuf);
	return error;
}

asmlinkage long compat_sys_newfstat(unsigned int fd,
		struct compat_stat * statbuf)
{
	struct kstat stat;
	int error = vfs_fstat(fd, &stat);

	if (!error)
		error = cp_compat_stat(&stat, statbuf);
	return error;
}

static int put_compat_statfs(struct compat_statfs *ubuf, struct statfs *kbuf)
{
	if (verify_area(VERIFY_WRITE, ubuf, sizeof(*ubuf)) ||
	    __put_user(kbuf->f_type, &ubuf->f_type) ||
	    __put_user(kbuf->f_bsize, &ubuf->f_bsize) ||
	    __put_user(kbuf->f_blocks, &ubuf->f_blocks) ||
	    __put_user(kbuf->f_bfree, &ubuf->f_bfree) ||
	    __put_user(kbuf->f_bavail, &ubuf->f_bavail) ||
	    __put_user(kbuf->f_files, &ubuf->f_files) ||
	    __put_user(kbuf->f_ffree, &ubuf->f_ffree) ||
	    __put_user(kbuf->f_namelen, &ubuf->f_namelen) ||
	    __put_user(kbuf->f_fsid.val[0], &ubuf->f_fsid.val[0]) ||
	    __put_user(kbuf->f_fsid.val[1], &ubuf->f_fsid.val[1]))
		return -EFAULT;
	return 0;
}

/*
 * The following statfs calls are copies of code from fs/open.c and
 * should be checked against those from time to time
 */
asmlinkage long compat_sys_statfs(const char *path, struct compat_statfs *buf)
{
	struct nameidata nd;
	int error;

	error = user_path_walk(path, &nd);
	if (!error) {
		struct statfs tmp;
		error = vfs_statfs(nd.dentry->d_inode->i_sb, &tmp);
		if (!error && put_compat_statfs(buf, &tmp))
			error = -EFAULT;
		path_release(&nd);
	}
	return error;
}

asmlinkage long compat_sys_fstatfs(unsigned int fd, struct compat_statfs *buf)
{
	struct file * file;
	struct statfs tmp;
	int error;

	error = -EBADF;
	file = fget(fd);
	if (!file)
		goto out;
	error = vfs_statfs(file->f_dentry->d_inode->i_sb, &tmp);
	if (!error && put_compat_statfs(buf, &tmp))
		error = -EFAULT;
	fput(file);
out:
	return error;
}


/* ioctl32 stuff, used by sparc64, parisc, s390x, ppc64, x86_64 */

#define IOCTL_HASHSIZE 256
struct ioctl_trans *ioctl32_hash_table[IOCTL_HASHSIZE];

extern struct ioctl_trans ioctl_start[], ioctl_end[]; 

static inline unsigned long ioctl32_hash(unsigned long cmd)
{
	return (((cmd >> 6) ^ (cmd >> 4) ^ cmd)) % IOCTL_HASHSIZE;
}

static void ioctl32_insert_translation(struct ioctl_trans *trans)
{
	unsigned long hash;
	struct ioctl_trans *t;

	hash = ioctl32_hash (trans->cmd);
	if (!ioctl32_hash_table[hash])
		ioctl32_hash_table[hash] = trans;
	else {
		t = ioctl32_hash_table[hash];
		while (t->next)
			t = t->next;
		trans->next = 0;
		t->next = trans;
	}
}

static int __init init_sys32_ioctl(void)
{
	int i;

	for (i = 0; &ioctl_start[i] < &ioctl_end[0]; i++) {
		if (ioctl_start[i].next != 0) { 
			printk("ioctl translation %d bad\n",i); 
			return -1;
		}

		ioctl32_insert_translation(&ioctl_start[i]);
	}
	return 0;
}

__initcall(init_sys32_ioctl);

static struct ioctl_trans *ioctl_free_list;

/* Never free them really. This avoids SMP races. With a Read-Copy-Update
   enabled kernel we could just use the RCU infrastructure for this. */
static void free_ioctl(struct ioctl_trans *t) 
{ 
	t->cmd = 0; 
	mb();
	t->next = ioctl_free_list;
	ioctl_free_list = t;
} 

int register_ioctl32_conversion(unsigned int cmd, int (*handler)(unsigned int, unsigned int, unsigned long, struct file *))
{
	struct ioctl_trans *t;
	unsigned long hash = ioctl32_hash(cmd);

	lock_kernel(); 
	for (t = (struct ioctl_trans *)ioctl32_hash_table[hash];
	     t;
	     t = t->next) { 
		if (t->cmd == cmd) {
			printk("Trying to register duplicated ioctl32 handler %x\n", cmd);
			unlock_kernel();
			return -EINVAL; 
		}
	} 

	if (ioctl_free_list) { 
		t = ioctl_free_list; 
		ioctl_free_list = t->next; 
	} else { 
		t = kmalloc(sizeof(struct ioctl_trans), GFP_KERNEL); 
		if (!t) { 
			unlock_kernel();
			return -ENOMEM;
		}
	}
	
	t->next = NULL;
	t->cmd = cmd;
	t->handler = handler; 
	ioctl32_insert_translation(t);

	unlock_kernel();
	return 0;
}

static inline int builtin_ioctl(struct ioctl_trans *t)
{ 
	return t >= (struct ioctl_trans *)ioctl_start &&
	       t < (struct ioctl_trans *)ioctl_end; 
} 

/* Problem: 
   This function cannot unregister duplicate ioctls, because they are not
   unique.
   When they happen we need to extend the prototype to pass the handler too. */

int unregister_ioctl32_conversion(unsigned int cmd)
{
	unsigned long hash = ioctl32_hash(cmd);
	struct ioctl_trans *t, *t1;

	lock_kernel(); 

	t = (struct ioctl_trans *)ioctl32_hash_table[hash];
	if (!t) { 
		unlock_kernel();
		return -EINVAL;
	} 

	if (t->cmd == cmd) { 
		if (builtin_ioctl(t)) {
			printk("%p tried to unregister builtin ioctl %x\n",
			       __builtin_return_address(0), cmd);
		} else { 
		ioctl32_hash_table[hash] = t->next;
			free_ioctl(t); 
			unlock_kernel();
		return 0;
		}
	} 
	while (t->next) {
		t1 = (struct ioctl_trans *)(long)t->next;
		if (t1->cmd == cmd) { 
			if (builtin_ioctl(t1)) {
				printk("%p tried to unregister builtin ioctl %x\n",
				       __builtin_return_address(0), cmd);
				goto out;
			} else { 
			t->next = t1->next;
				free_ioctl(t1); 
				unlock_kernel();
			return 0;
			}
		}
		t = t1;
	}
	printk(KERN_ERR "Trying to free unknown 32bit ioctl handler %x\n", cmd);
 out:
	unlock_kernel();
	return -EINVAL;
}

EXPORT_SYMBOL(register_ioctl32_conversion); 
EXPORT_SYMBOL(unregister_ioctl32_conversion); 

asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
	struct file * filp;
	int error = -EBADF;
	struct ioctl_trans *t;

	filp = fget(fd);
	if(!filp)
		goto out2;

	if (!filp->f_op || !filp->f_op->ioctl) {
		error = sys_ioctl (fd, cmd, arg);
		goto out;
	}

	t = (struct ioctl_trans *)ioctl32_hash_table [ioctl32_hash (cmd)];

	while (t && t->cmd != cmd)
		t = (struct ioctl_trans *)t->next;
	if (t) {
		if (t->handler)
			error = t->handler(fd, cmd, arg, filp);
		else
			error = sys_ioctl(fd, cmd, arg);
	} else if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {
		error = siocdevprivate_ioctl(fd, cmd, arg);
	} else {
		static int count;
		if (++count <= 50) { 
			char buf[10];
			char *path = (char *)__get_free_page(GFP_KERNEL), *fn = "?"; 

			/* find the name of the device. */
			if (path) {
		       		fn = d_path(filp->f_dentry, filp->f_vfsmnt, 
					    path, PAGE_SIZE);
			}

			sprintf(buf,"'%c'", (cmd>>24) & 0x3f); 
			if (!isprint(buf[1]))
			    sprintf(buf, "%02x", buf[1]);
			printk("ioctl32(%s:%d): Unknown cmd fd(%d) "
			       "cmd(%08x){%s} arg(%08x) on %s\n",
			       current->comm, current->pid,
			       (int)fd, (unsigned int)cmd, buf, (unsigned int)arg,
			       fn);
			if (path) 
				free_page((unsigned long)path); 
		}
		error = -EINVAL;
	}
out:
	fput(filp);
out2:
	return error;
}

static int get_compat_flock(struct flock *kfl, struct compat_flock *ufl)
{
	if (!access_ok(VERIFY_READ, ufl, sizeof(*ufl)) ||
	    __get_user(kfl->l_type, &ufl->l_type) ||
	    __get_user(kfl->l_whence, &ufl->l_whence) ||
	    __get_user(kfl->l_start, &ufl->l_start) ||
	    __get_user(kfl->l_len, &ufl->l_len) ||
	    __get_user(kfl->l_pid, &ufl->l_pid))
		return -EFAULT;
	return 0;
}

static int put_compat_flock(struct flock *kfl, struct compat_flock *ufl)
{
	if (!access_ok(VERIFY_WRITE, ufl, sizeof(*ufl)) ||
	    __put_user(kfl->l_type, &ufl->l_type) ||
	    __put_user(kfl->l_whence, &ufl->l_whence) ||
	    __put_user(kfl->l_start, &ufl->l_start) ||
	    __put_user(kfl->l_len, &ufl->l_len) ||
	    __put_user(kfl->l_pid, &ufl->l_pid))
		return -EFAULT;
	return 0;
}

static int get_compat_flock64(struct flock *kfl, struct compat_flock64 *ufl)
{
	if (!access_ok(VERIFY_READ, ufl, sizeof(*ufl)) ||
	    __get_user(kfl->l_type, &ufl->l_type) ||
	    __get_user(kfl->l_whence, &ufl->l_whence) ||
	    __get_user(kfl->l_start, &ufl->l_start) ||
	    __get_user(kfl->l_len, &ufl->l_len) ||
	    __get_user(kfl->l_pid, &ufl->l_pid))
		return -EFAULT;
	return 0;
}

static int put_compat_flock64(struct flock *kfl, struct compat_flock64 *ufl)
{
	if (!access_ok(VERIFY_WRITE, ufl, sizeof(*ufl)) ||
	    __put_user(kfl->l_type, &ufl->l_type) ||
	    __put_user(kfl->l_whence, &ufl->l_whence) ||
	    __put_user(kfl->l_start, &ufl->l_start) ||
	    __put_user(kfl->l_len, &ufl->l_len) ||
	    __put_user(kfl->l_pid, &ufl->l_pid))
		return -EFAULT;
	return 0;
}

extern asmlinkage long sys_fcntl(unsigned int, unsigned int, unsigned long);

asmlinkage long compat_sys_fcntl64(unsigned int fd, unsigned int cmd,
		unsigned long arg)
{
	mm_segment_t old_fs;
	struct flock f;
	long ret;

	switch (cmd) {
	case F_GETLK:
	case F_SETLK:
	case F_SETLKW:
		ret = get_compat_flock(&f, compat_ptr(arg));
		if (ret != 0)
			break;
		old_fs = get_fs();
		set_fs(KERNEL_DS);
		ret = sys_fcntl(fd, cmd, (unsigned long)&f);
		set_fs(old_fs);
		if ((cmd == F_GETLK) && (ret == 0)) {
			if ((f.l_start >= COMPAT_OFF_T_MAX) ||
			    ((f.l_start + f.l_len) >= COMPAT_OFF_T_MAX))
				ret = -EOVERFLOW;
			if (ret == 0)
				ret = put_compat_flock(&f, compat_ptr(arg));
		}
		break;

	case F_GETLK64:
	case F_SETLK64:
	case F_SETLKW64:
		ret = get_compat_flock64(&f, compat_ptr(arg));
		if (ret != 0)
			break;
		old_fs = get_fs();
		set_fs(KERNEL_DS);
		ret = sys_fcntl(fd, F_GETLK, (unsigned long)&f);
		ret = sys_fcntl(fd, (cmd == F_GETLK64) ? F_GETLK :
				((cmd == F_SETLK64) ? F_SETLK : F_SETLKW),
				(unsigned long)&f);
		set_fs(old_fs);
		if ((cmd == F_GETLK64) && (ret == 0)) {
			if ((f.l_start >= COMPAT_LOFF_T_MAX) ||
			    ((f.l_start + f.l_len) >= COMPAT_LOFF_T_MAX))
				ret = -EOVERFLOW;
			if (ret == 0)
				ret = put_compat_flock64(&f, compat_ptr(arg));
		}
		break;

	default:
		ret = sys_fcntl(fd, cmd, arg);
		break;
	}
	return ret;
}

asmlinkage long compat_sys_fcntl(unsigned int fd, unsigned int cmd,
		unsigned long arg)
{
	if ((cmd == F_GETLK64) || (cmd == F_SETLK64) || (cmd == F_SETLKW64))
		return -EINVAL;
	return compat_sys_fcntl64(fd, cmd, arg);
}

