vserver 1.9.3
[linux-2.6.git] / fs / compat.c
index 689fdd0..7257be9 100644 (file)
 #include <linux/syscalls.h>
 #include <linux/ctype.h>
 #include <linux/module.h>
+#include <linux/dirent.h>
 #include <linux/dnotify.h>
 #include <linux/highuid.h>
 #include <linux/sunrpc/svc.h>
 #include <linux/nfsd/nfsd.h>
 #include <linux/nfsd/syscall.h>
 #include <linux/personality.h>
+#include <linux/rwsem.h>
 
 #include <net/sock.h>          /* siocdevprivate_ioctl */
 
@@ -246,7 +248,8 @@ out:
 /* ioctl32 stuff, used by sparc64, parisc, s390x, ppc64, x86_64, MIPS */
 
 #define IOCTL_HASHSIZE 256
-struct ioctl_trans *ioctl32_hash_table[IOCTL_HASHSIZE];
+static struct ioctl_trans *ioctl32_hash_table[IOCTL_HASHSIZE];
+static DECLARE_RWSEM(ioctl32_sem);
 
 extern struct ioctl_trans ioctl_start[];
 extern int ioctl_table_size;
@@ -268,7 +271,7 @@ static void ioctl32_insert_translation(struct ioctl_trans *trans)
                t = ioctl32_hash_table[hash];
                while (t->next)
                        t = t->next;
-               trans->next = 0;
+               trans->next = NULL;
                t->next = trans;
        }
 }
@@ -290,8 +293,8 @@ static int __init init_sys32_ioctl(void)
 
 __initcall(init_sys32_ioctl);
 
-int register_ioctl32_conversion(unsigned int cmd, int (*handler)(unsigned int,
-                               unsigned int, unsigned long, struct file *))
+int register_ioctl32_conversion(unsigned int cmd,
+                               ioctl_trans_handler_t handler)
 {
        struct ioctl_trans *t;
        struct ioctl_trans *new_t;
@@ -301,12 +304,12 @@ int register_ioctl32_conversion(unsigned int cmd, int (*handler)(unsigned int,
        if (!new_t)
                return -ENOMEM;
 
-       lock_kernel(); 
+       down_write(&ioctl32_sem);
        for (t = ioctl32_hash_table[hash]; t; t = t->next) {
                if (t->cmd == cmd) {
                        printk(KERN_ERR "Trying to register duplicated ioctl32 "
                                        "handler %x\n", cmd);
-                       unlock_kernel();
+                       up_write(&ioctl32_sem);
                        kfree(new_t);
                        return -EINVAL; 
                }
@@ -316,7 +319,7 @@ int register_ioctl32_conversion(unsigned int cmd, int (*handler)(unsigned int,
        new_t->handler = handler;
        ioctl32_insert_translation(new_t);
 
-       unlock_kernel();
+       up_write(&ioctl32_sem);
        return 0;
 }
 EXPORT_SYMBOL(register_ioctl32_conversion);
@@ -336,11 +339,11 @@ int unregister_ioctl32_conversion(unsigned int cmd)
        unsigned long hash = ioctl32_hash(cmd);
        struct ioctl_trans *t, *t1;
 
-       lock_kernel(); 
+       down_write(&ioctl32_sem);
 
        t = ioctl32_hash_table[hash];
        if (!t) { 
-               unlock_kernel();
+               up_write(&ioctl32_sem);
                return -EINVAL;
        } 
 
@@ -350,7 +353,7 @@ int unregister_ioctl32_conversion(unsigned int cmd)
                               __builtin_return_address(0), cmd);
                } else { 
                        ioctl32_hash_table[hash] = t->next;
-                       unlock_kernel();
+                       up_write(&ioctl32_sem);
                        kfree(t);
                        return 0;
                }
@@ -365,7 +368,7 @@ int unregister_ioctl32_conversion(unsigned int cmd)
                                goto out;
                        } else { 
                                t->next = t1->next;
-                               unlock_kernel();
+                               up_write(&ioctl32_sem);
                                kfree(t1);
                                return 0;
                        }
@@ -375,7 +378,7 @@ int unregister_ioctl32_conversion(unsigned int cmd)
        printk(KERN_ERR "Trying to free unknown 32bit ioctl handler %x\n",
                                cmd);
 out:
-       unlock_kernel();
+       up_write(&ioctl32_sem);
        return -EINVAL;
 }
 EXPORT_SYMBOL(unregister_ioctl32_conversion); 
@@ -396,7 +399,7 @@ asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd,
                goto out;
        }
 
-       lock_kernel();
+       down_read(&ioctl32_sem);
 
        t = ioctl32_hash_table[ioctl32_hash (cmd)];
 
@@ -404,14 +407,16 @@ asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd,
                t = t->next;
        if (t) {
                if (t->handler) { 
+                       lock_kernel();
                        error = t->handler(fd, cmd, arg, filp);
                        unlock_kernel();
+                       up_read(&ioctl32_sem);
                } else {
-                       unlock_kernel();
+                       up_read(&ioctl32_sem);
                        error = sys_ioctl(fd, cmd, arg);
                }
        } else {
-               unlock_kernel();
+               up_read(&ioctl32_sem);
                if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {
                        error = siocdevprivate_ioctl(fd, cmd, arg);
                } else {
@@ -428,6 +433,8 @@ asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd,
                                        fn = d_path(filp->f_dentry,
                                                filp->f_vfsmnt, path,
                                                PAGE_SIZE);
+                                       if (IS_ERR(fn))
+                                               fn = "?";
                                }
 
                                sprintf(buf,"'%c'", (cmd>>24) & 0x3f);
@@ -521,8 +528,15 @@ asmlinkage long compat_sys_fcntl64(unsigned int fd, unsigned int cmd,
                ret = sys_fcntl(fd, cmd, (unsigned long)&f);
                set_fs(old_fs);
                if ((cmd == F_GETLK) && (ret == 0)) {
+                       /* POSIX-2001 now defines negative l_len */
+                       if (f.l_len < 0) {
+                               f.l_start += f.l_len;
+                               f.l_len = -f.l_len;
+                       }
+                       if (f.l_start < 0)
+                               return -EINVAL;
                        if ((f.l_start >= COMPAT_OFF_T_MAX) ||
-                           ((f.l_start + f.l_len) >= 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));
@@ -542,8 +556,15 @@ asmlinkage long compat_sys_fcntl64(unsigned int fd, unsigned int cmd,
                                (unsigned long)&f);
                set_fs(old_fs);
                if ((cmd == F_GETLK64) && (ret == 0)) {
+                       /* POSIX-2001 now defines negative l_len */
+                       if (f.l_len < 0) {
+                               f.l_start += f.l_len;
+                               f.l_len = -f.l_len;
+                       }
+                       if (f.l_start < 0)
+                               return -EINVAL;
                        if ((f.l_start >= COMPAT_LOFF_T_MAX) ||
-                           ((f.l_start + f.l_len) >= 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));
@@ -797,6 +818,254 @@ asmlinkage long compat_sys_mount(char __user * dev_name, char __user * dir_name,
        return retval;
 }
 
+#define NAME_OFFSET(de) ((int) ((de)->d_name - (char __user *) (de)))
+#define COMPAT_ROUND_UP(x) (((x)+sizeof(compat_long_t)-1) & \
+                               ~(sizeof(compat_long_t)-1))
+
+struct compat_old_linux_dirent {
+       compat_ulong_t  d_ino;
+       compat_ulong_t  d_offset;
+       unsigned short  d_namlen;
+       char            d_name[1];
+};
+
+struct compat_readdir_callback {
+       struct compat_old_linux_dirent __user *dirent;
+       int result;
+};
+
+static int compat_fillonedir(void *__buf, const char *name, int namlen,
+                       loff_t offset, ino_t ino, unsigned int d_type)
+{
+       struct compat_readdir_callback *buf = __buf;
+       struct compat_old_linux_dirent __user *dirent;
+
+       if (buf->result)
+               return -EINVAL;
+       buf->result++;
+       dirent = buf->dirent;
+       if (!access_ok(VERIFY_WRITE, dirent,
+                       (unsigned long)(dirent->d_name + namlen + 1) -
+                               (unsigned long)dirent))
+               goto efault;
+       if (    __put_user(ino, &dirent->d_ino) ||
+               __put_user(offset, &dirent->d_offset) ||
+               __put_user(namlen, &dirent->d_namlen) ||
+               __copy_to_user(dirent->d_name, name, namlen) ||
+               __put_user(0, dirent->d_name + namlen))
+               goto efault;
+       return 0;
+efault:
+       buf->result = -EFAULT;
+       return -EFAULT;
+}
+
+asmlinkage long compat_old_readdir(unsigned int fd,
+       struct compat_old_linux_dirent __user *dirent, unsigned int count)
+{
+       int error;
+       struct file *file;
+       struct compat_readdir_callback buf;
+
+       error = -EBADF;
+       file = fget(fd);
+       if (!file)
+               goto out;
+
+       buf.result = 0;
+       buf.dirent = dirent;
+
+       error = vfs_readdir(file, compat_fillonedir, &buf);
+       if (error >= 0)
+               error = buf.result;
+
+       fput(file);
+out:
+       return error;
+}
+
+struct compat_linux_dirent {
+       compat_ulong_t  d_ino;
+       compat_ulong_t  d_off;
+       unsigned short  d_reclen;
+       char            d_name[1];
+};
+
+struct compat_getdents_callback {
+       struct compat_linux_dirent __user *current_dir;
+       struct compat_linux_dirent __user *previous;
+       int count;
+       int error;
+};
+
+static int compat_filldir(void *__buf, const char *name, int namlen,
+               loff_t offset, ino_t ino, unsigned int d_type)
+{
+       struct compat_linux_dirent __user * dirent;
+       struct compat_getdents_callback *buf = __buf;
+       int reclen = COMPAT_ROUND_UP(NAME_OFFSET(dirent) + namlen + 2);
+
+       buf->error = -EINVAL;   /* only used if we fail.. */
+       if (reclen > buf->count)
+               return -EINVAL;
+       dirent = buf->previous;
+       if (dirent) {
+               if (__put_user(offset, &dirent->d_off))
+                       goto efault;
+       }
+       dirent = buf->current_dir;
+       if (__put_user(ino, &dirent->d_ino))
+               goto efault;
+       if (__put_user(reclen, &dirent->d_reclen))
+               goto efault;
+       if (copy_to_user(dirent->d_name, name, namlen))
+               goto efault;
+       if (__put_user(0, dirent->d_name + namlen))
+               goto efault;
+       if (__put_user(d_type, (char  __user *) dirent + reclen - 1))
+               goto efault;
+       buf->previous = dirent;
+       dirent = (void __user *)dirent + reclen;
+       buf->current_dir = dirent;
+       buf->count -= reclen;
+       return 0;
+efault:
+       buf->error = -EFAULT;
+       return -EFAULT;
+}
+
+asmlinkage long compat_sys_getdents(unsigned int fd,
+               struct compat_linux_dirent __user *dirent, unsigned int count)
+{
+       struct file * file;
+       struct compat_linux_dirent __user * lastdirent;
+       struct compat_getdents_callback buf;
+       int error;
+
+       error = -EFAULT;
+       if (!access_ok(VERIFY_WRITE, dirent, count))
+               goto out;
+
+       error = -EBADF;
+       file = fget(fd);
+       if (!file)
+               goto out;
+
+       buf.current_dir = dirent;
+       buf.previous = NULL;
+       buf.count = count;
+       buf.error = 0;
+
+       error = vfs_readdir(file, compat_filldir, &buf);
+       if (error < 0)
+               goto out_putf;
+       error = buf.error;
+       lastdirent = buf.previous;
+       if (lastdirent) {
+               if (put_user(file->f_pos, &lastdirent->d_off))
+                       error = -EFAULT;
+               else
+                       error = count - buf.count;
+       }
+
+out_putf:
+       fput(file);
+out:
+       return error;
+}
+
+#ifndef __ARCH_OMIT_COMPAT_SYS_GETDENTS64
+#define COMPAT_ROUND_UP64(x) (((x)+sizeof(u64)-1) & ~(sizeof(u64)-1))
+
+struct compat_getdents_callback64 {
+       struct linux_dirent64 __user *current_dir;
+       struct linux_dirent64 __user *previous;
+       int count;
+       int error;
+};
+
+static int compat_filldir64(void * __buf, const char * name, int namlen, loff_t offset,
+                    ino_t ino, unsigned int d_type)
+{
+       struct linux_dirent64 __user *dirent;
+       struct compat_getdents_callback64 *buf = __buf;
+       int jj = NAME_OFFSET(dirent);
+       int reclen = COMPAT_ROUND_UP64(jj + namlen + 1);
+       u64 off;
+
+       buf->error = -EINVAL;   /* only used if we fail.. */
+       if (reclen > buf->count)
+               return -EINVAL;
+       dirent = buf->previous;
+
+       if (dirent) {
+               if (__put_user_unaligned(offset, &dirent->d_off))
+                       goto efault;
+       }
+       dirent = buf->current_dir;
+       if (__put_user_unaligned(ino, &dirent->d_ino))
+               goto efault;
+       off = 0;
+       if (__put_user_unaligned(off, &dirent->d_off))
+               goto efault;
+       if (__put_user(reclen, &dirent->d_reclen))
+               goto efault;
+       if (__put_user(d_type, &dirent->d_type))
+               goto efault;
+       if (copy_to_user(dirent->d_name, name, namlen))
+               goto efault;
+       if (__put_user(0, dirent->d_name + namlen))
+               goto efault;
+       buf->previous = dirent;
+       dirent = (void __user *)dirent + reclen;
+       buf->current_dir = dirent;
+       buf->count -= reclen;
+       return 0;
+efault:
+       buf->error = -EFAULT;
+       return -EFAULT;
+}
+
+asmlinkage long compat_sys_getdents64(unsigned int fd,
+               struct linux_dirent64 __user * dirent, unsigned int count)
+{
+       struct file * file;
+       struct linux_dirent64 __user * lastdirent;
+       struct compat_getdents_callback64 buf;
+       int error;
+
+       error = -EFAULT;
+       if (!access_ok(VERIFY_WRITE, dirent, count))
+               goto out;
+
+       error = -EBADF;
+       file = fget(fd);
+       if (!file)
+               goto out;
+
+       buf.current_dir = dirent;
+       buf.previous = NULL;
+       buf.count = count;
+       buf.error = 0;
+
+       error = vfs_readdir(file, compat_filldir64, &buf);
+       if (error < 0)
+               goto out_putf;
+       error = buf.error;
+       lastdirent = buf.previous;
+       if (lastdirent) {
+               typeof(lastdirent->d_off) d_off = file->f_pos;
+               __put_user_unaligned(d_off, &lastdirent->d_off);
+               error = count - buf.count;
+       }
+
+out_putf:
+       fput(file);
+out:
+       return error;
+}
+#endif /* ! __ARCH_OMIT_COMPAT_SYS_GETDENTS64 */
+
 static ssize_t compat_do_readv_writev(int type, struct file *file,
                               const struct compat_iovec __user *uvector,
                               unsigned long nr_segs, loff_t *pos)
@@ -1113,97 +1382,99 @@ int compat_do_execve(char * filename,
        compat_uptr_t __user *envp,
        struct pt_regs * regs)
 {
-       struct linux_binprm bprm;
+       struct linux_binprm *bprm;
        struct file *file;
        int retval;
        int i;
 
-       sched_balance_exec();
-
        file = open_exec(filename);
 
        retval = PTR_ERR(file);
        if (IS_ERR(file))
                return retval;
 
-       bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
-       memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0]));
-
-       bprm.file = file;
-       bprm.filename = filename;
-       bprm.interp = filename;
-       bprm.sh_bang = 0;
-       bprm.loader = 0;
-       bprm.exec = 0;
-       bprm.security = NULL;
-       bprm.mm = mm_alloc();
+       sched_exec();
+
        retval = -ENOMEM;
-       if (!bprm.mm)
+       bprm = kmalloc(sizeof(*bprm), GFP_KERNEL);
+       if (!bprm)
+               goto out_ret;
+       memset(bprm, 0, sizeof(*bprm));
+
+       bprm->p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
+       bprm->file = file;
+       bprm->filename = filename;
+       bprm->interp = filename;
+       bprm->mm = mm_alloc();
+       if (!bprm->mm)
                goto out_file;
 
-       retval = init_new_context(current, bprm.mm);
+       retval = init_new_context(current, bprm->mm);
        if (retval < 0)
                goto out_mm;
 
-       bprm.argc = compat_count(argv, bprm.p / sizeof(compat_uptr_t));
-       if ((retval = bprm.argc) < 0)
+       bprm->argc = compat_count(argv, bprm->p / sizeof(compat_uptr_t));
+       if ((retval = bprm->argc) < 0)
                goto out_mm;
 
-       bprm.envc = compat_count(envp, bprm.p / sizeof(compat_uptr_t));
-       if ((retval = bprm.envc) < 0)
+       bprm->envc = compat_count(envp, bprm->p / sizeof(compat_uptr_t));
+       if ((retval = bprm->envc) < 0)
                goto out_mm;
 
-       retval = security_bprm_alloc(&bprm);
+       retval = security_bprm_alloc(bprm);
        if (retval)
                goto out;
 
-       retval = prepare_binprm(&bprm);
+       retval = prepare_binprm(bprm);
        if (retval < 0)
                goto out;
 
-       retval = copy_strings_kernel(1, &bprm.filename, &bprm);
+       retval = copy_strings_kernel(1, &bprm->filename, bprm);
        if (retval < 0)
                goto out;
 
-       bprm.exec = bprm.p;
-       retval = compat_copy_strings(bprm.envc, envp, &bprm);
+       bprm->exec = bprm->p;
+       retval = compat_copy_strings(bprm->envc, envp, bprm);
        if (retval < 0)
                goto out;
 
-       retval = compat_copy_strings(bprm.argc, argv, &bprm);
+       retval = compat_copy_strings(bprm->argc, argv, bprm);
        if (retval < 0)
                goto out;
 
-       retval = search_binary_handler(&bprm,regs);
+       retval = search_binary_handler(bprm, regs);
        if (retval >= 0) {
-               free_arg_pages(&bprm);
+               free_arg_pages(bprm);
 
                /* execve success */
-               security_bprm_free(&bprm);
+               security_bprm_free(bprm);
+               kfree(bprm);
                return retval;
        }
 
 out:
        /* Something went wrong, return the inode and free the argument pages*/
        for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
-               struct page * page = bprm.page[i];
+               struct page * page = bprm->page[i];
                if (page)
                        __free_page(page);
        }
 
-       if (bprm.security)
-               security_bprm_free(&bprm);
+       if (bprm->security)
+               security_bprm_free(bprm);
 
 out_mm:
-       if (bprm.mm)
-               mmdrop(bprm.mm);
+       if (bprm->mm)
+               mmdrop(bprm->mm);
 
 out_file:
-       if (bprm.file) {
-               allow_write_access(bprm.file);
-               fput(bprm.file);
+       if (bprm->file) {
+               allow_write_access(bprm->file);
+               fput(bprm->file);
        }
+       kfree(bprm);
 
+out_ret:
        return retval;
 }