fat: fix VFAT compat ioctls on 64-bit systems
[pandora-kernel.git] / fs / fat / dir.c
index 698b85b..ccf161d 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/dirent.h>
 #include <linux/smp_lock.h>
 #include <linux/buffer_head.h>
+#include <linux/compat.h>
 #include <asm/uaccess.h>
 
 static inline loff_t fat_make_i_pos(struct super_block *sb,
@@ -421,7 +422,7 @@ EODir:
 EXPORT_SYMBOL_GPL(fat_search_long);
 
 struct fat_ioctl_filldir_callback {
-       struct dirent __user *dirent;
+       void __user *dirent;
        int result;
        /* for dir ioctl */
        const char *longname;
@@ -578,7 +579,7 @@ parse_record:
        if (!memcmp(de->name, MSDOS_DOT, MSDOS_NAME))
                inum = inode->i_ino;
        else if (!memcmp(de->name, MSDOS_DOTDOT, MSDOS_NAME)) {
-               inum = parent_ino(filp->f_dentry);
+               inum = parent_ino(filp->f_path.dentry);
        } else {
                loff_t i_pos = fat_make_i_pos(sb, bh, de);
                struct inode *tmp = fat_iget(sb, i_pos);
@@ -642,66 +643,89 @@ out:
 
 static int fat_readdir(struct file *filp, void *dirent, filldir_t filldir)
 {
-       struct inode *inode = filp->f_dentry->d_inode;
+       struct inode *inode = filp->f_path.dentry->d_inode;
        return __fat_readdir(inode, filp, dirent, filldir, 0, 0);
 }
 
-static int fat_ioctl_filldir(void *__buf, const char *name, int name_len,
-                            loff_t offset, ino_t ino, unsigned int d_type)
+#define FAT_IOCTL_FILLDIR_FUNC(func, dirent_type)                         \
+static int func(void *__buf, const char *name, int name_len,              \
+                            loff_t offset, u64 ino, unsigned int d_type)  \
+{                                                                         \
+       struct fat_ioctl_filldir_callback *buf = __buf;                    \
+       struct dirent_type __user *d1 = buf->dirent;                       \
+       struct dirent_type __user *d2 = d1 + 1;                            \
+                                                                          \
+       if (buf->result)                                                   \
+               return -EINVAL;                                            \
+       buf->result++;                                                     \
+                                                                          \
+       if (name != NULL) {                                                \
+               /* dirent has only short name */                           \
+               if (name_len >= sizeof(d1->d_name))                        \
+                       name_len = sizeof(d1->d_name) - 1;                 \
+                                                                          \
+               if (put_user(0, d2->d_name)                     ||         \
+                   put_user(0, &d2->d_reclen)                  ||         \
+                   copy_to_user(d1->d_name, name, name_len)    ||         \
+                   put_user(0, d1->d_name + name_len)          ||         \
+                   put_user(name_len, &d1->d_reclen))                     \
+                       goto efault;                                       \
+       } else {                                                           \
+               /* dirent has short and long name */                       \
+               const char *longname = buf->longname;                      \
+               int long_len = buf->long_len;                              \
+               const char *shortname = buf->shortname;                    \
+               int short_len = buf->short_len;                            \
+                                                                          \
+               if (long_len >= sizeof(d1->d_name))                        \
+                       long_len = sizeof(d1->d_name) - 1;                 \
+               if (short_len >= sizeof(d1->d_name))                       \
+                       short_len = sizeof(d1->d_name) - 1;                \
+                                                                          \
+               if (copy_to_user(d2->d_name, longname, long_len)        || \
+                   put_user(0, d2->d_name + long_len)                  || \
+                   put_user(long_len, &d2->d_reclen)                   || \
+                   put_user(ino, &d2->d_ino)                           || \
+                   put_user(offset, &d2->d_off)                        || \
+                   copy_to_user(d1->d_name, shortname, short_len)      || \
+                   put_user(0, d1->d_name + short_len)                 || \
+                   put_user(short_len, &d1->d_reclen))                    \
+                       goto efault;                                       \
+       }                                                                  \
+       return 0;                                                          \
+efault:                                                                           \
+       buf->result = -EFAULT;                                             \
+       return -EFAULT;                                                    \
+}
+
+FAT_IOCTL_FILLDIR_FUNC(fat_ioctl_filldir, dirent)
+
+static int fat_ioctl_readdir(struct inode *inode, struct file *filp,
+                            void __user *dirent, filldir_t filldir,
+                            int short_only, int both)
 {
-       struct fat_ioctl_filldir_callback *buf = __buf;
-       struct dirent __user *d1 = buf->dirent;
-       struct dirent __user *d2 = d1 + 1;
-
-       if (buf->result)
-               return -EINVAL;
-       buf->result++;
-
-       if (name != NULL) {
-               /* dirent has only short name */
-               if (name_len >= sizeof(d1->d_name))
-                       name_len = sizeof(d1->d_name) - 1;
-
-               if (put_user(0, d2->d_name)                     ||
-                   put_user(0, &d2->d_reclen)                  ||
-                   copy_to_user(d1->d_name, name, name_len)    ||
-                   put_user(0, d1->d_name + name_len)          ||
-                   put_user(name_len, &d1->d_reclen))
-                       goto efault;
-       } else {
-               /* dirent has short and long name */
-               const char *longname = buf->longname;
-               int long_len = buf->long_len;
-               const char *shortname = buf->shortname;
-               int short_len = buf->short_len;
-
-               if (long_len >= sizeof(d1->d_name))
-                       long_len = sizeof(d1->d_name) - 1;
-               if (short_len >= sizeof(d1->d_name))
-                       short_len = sizeof(d1->d_name) - 1;
-
-               if (copy_to_user(d2->d_name, longname, long_len)        ||
-                   put_user(0, d2->d_name + long_len)                  ||
-                   put_user(long_len, &d2->d_reclen)                   ||
-                   put_user(ino, &d2->d_ino)                           ||
-                   put_user(offset, &d2->d_off)                        ||
-                   copy_to_user(d1->d_name, shortname, short_len)      ||
-                   put_user(0, d1->d_name + short_len)                 ||
-                   put_user(short_len, &d1->d_reclen))
-                       goto efault;
+       struct fat_ioctl_filldir_callback buf;
+       int ret;
+
+       buf.dirent = dirent;
+       buf.result = 0;
+       mutex_lock(&inode->i_mutex);
+       ret = -ENOENT;
+       if (!IS_DEADDIR(inode)) {
+               ret = __fat_readdir(inode, filp, &buf, filldir,
+                                   short_only, both);
        }
-       return 0;
-efault:
-       buf->result = -EFAULT;
-       return -EFAULT;
+       mutex_unlock(&inode->i_mutex);
+       if (ret >= 0)
+               ret = buf.result;
+       return ret;
 }
 
-static int fat_dir_ioctl(struct inode * inode, struct file * filp,
-                 unsigned int cmd, unsigned long arg)
+static int fat_dir_ioctl(struct inode *inode, struct file *filp,
+                        unsigned int cmd, unsigned long arg)
 {
-       struct fat_ioctl_filldir_callback buf;
-       struct dirent __user *d1;
-       int ret, short_only, both;
+       struct dirent __user *d1 = (struct dirent __user *)arg;
+       int short_only, both;
 
        switch (cmd) {
        case VFAT_IOCTL_READDIR_SHORT:
@@ -716,7 +740,6 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp,
                return fat_generic_ioctl(inode, filp, cmd, arg);
        }
 
-       d1 = (struct dirent __user *)arg;
        if (!access_ok(VERIFY_WRITE, d1, sizeof(struct dirent[2])))
                return -EFAULT;
        /*
@@ -727,24 +750,58 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp,
        if (put_user(0, &d1->d_reclen))
                return -EFAULT;
 
-       buf.dirent = d1;
-       buf.result = 0;
-       mutex_lock(&inode->i_mutex);
-       ret = -ENOENT;
-       if (!IS_DEADDIR(inode)) {
-               ret = __fat_readdir(inode, filp, &buf, fat_ioctl_filldir,
-                                   short_only, both);
+       return fat_ioctl_readdir(inode, filp, d1, fat_ioctl_filldir,
+                                short_only, both);
+}
+
+#ifdef CONFIG_COMPAT
+#define        VFAT_IOCTL_READDIR_BOTH32       _IOR('r', 1, struct compat_dirent[2])
+#define        VFAT_IOCTL_READDIR_SHORT32      _IOR('r', 2, struct compat_dirent[2])
+
+FAT_IOCTL_FILLDIR_FUNC(fat_compat_ioctl_filldir, compat_dirent)
+
+static long fat_compat_dir_ioctl(struct file *filp, unsigned cmd,
+                                unsigned long arg)
+{
+       struct inode *inode = filp->f_path.dentry->d_inode;
+       struct compat_dirent __user *d1 = compat_ptr(arg);
+       int short_only, both;
+
+       switch (cmd) {
+       case VFAT_IOCTL_READDIR_SHORT32:
+               short_only = 1;
+               both = 0;
+               break;
+       case VFAT_IOCTL_READDIR_BOTH32:
+               short_only = 0;
+               both = 1;
+               break;
+       default:
+               return -ENOIOCTLCMD;
        }
-       mutex_unlock(&inode->i_mutex);
-       if (ret >= 0)
-               ret = buf.result;
-       return ret;
+
+       if (!access_ok(VERIFY_WRITE, d1, sizeof(struct compat_dirent[2])))
+               return -EFAULT;
+       /*
+        * Yes, we don't need this put_user() absolutely. However old
+        * code didn't return the right value. So, app use this value,
+        * in order to check whether it is EOF.
+        */
+       if (put_user(0, &d1->d_reclen))
+               return -EFAULT;
+
+       return fat_ioctl_readdir(inode, filp, d1, fat_compat_ioctl_filldir,
+                                short_only, both);
 }
+#endif /* CONFIG_COMPAT */
 
 const struct file_operations fat_dir_operations = {
        .read           = generic_read_dir,
        .readdir        = fat_readdir,
        .ioctl          = fat_dir_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl   = fat_compat_dir_ioctl,
+#endif
        .fsync          = file_fsync,
 };