nilfs2: implementation of NILFS_IOCTL_SET_SUINFO ioctl
[pandora-kernel.git] / fs / nilfs2 / ioctl.c
index 2b34021..c19a231 100644 (file)
@@ -1163,6 +1163,95 @@ static int nilfs_ioctl_get_info(struct inode *inode, struct file *filp,
        return ret;
 }
 
+/**
+ * nilfs_ioctl_set_suinfo - set segment usage info
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: Expects an array of nilfs_suinfo_update structures
+ * encapsulated in nilfs_argv and updates the segment usage info
+ * according to the flags in nilfs_suinfo_update.
+ *
+ * Return Value: On success, 0 is returned. On error, one of the
+ * following negative error codes is returned.
+ *
+ * %-EPERM - Not enough permissions
+ *
+ * %-EFAULT - Error copying input data
+ *
+ * %-EIO - I/O error.
+ *
+ * %-ENOMEM - Insufficient amount of memory available.
+ *
+ * %-EINVAL - Invalid values in input (segment number, flags or nblocks)
+ */
+static int nilfs_ioctl_set_suinfo(struct inode *inode, struct file *filp,
+                               unsigned int cmd, void __user *argp)
+{
+       struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+       struct nilfs_transaction_info ti;
+       struct nilfs_argv argv;
+       size_t len;
+       void __user *base;
+       void *kbuf;
+       int ret;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       ret = mnt_want_write_file(filp);
+       if (ret)
+               return ret;
+
+       ret = -EFAULT;
+       if (copy_from_user(&argv, argp, sizeof(argv)))
+               goto out;
+
+       ret = -EINVAL;
+       if (argv.v_size < sizeof(struct nilfs_suinfo_update))
+               goto out;
+
+       if (argv.v_nmembs > nilfs->ns_nsegments)
+               goto out;
+
+       if (argv.v_nmembs >= UINT_MAX / argv.v_size)
+               goto out;
+
+       len = argv.v_size * argv.v_nmembs;
+       if (!len) {
+               ret = 0;
+               goto out;
+       }
+
+       base = (void __user *)(unsigned long)argv.v_base;
+       kbuf = vmalloc(len);
+       if (!kbuf) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       if (copy_from_user(kbuf, base, len)) {
+               ret = -EFAULT;
+               goto out_free;
+       }
+
+       nilfs_transaction_begin(inode->i_sb, &ti, 0);
+       ret = nilfs_sufile_set_suinfo(nilfs->ns_sufile, kbuf, argv.v_size,
+                       argv.v_nmembs);
+       if (unlikely(ret < 0))
+               nilfs_transaction_abort(inode->i_sb);
+       else
+               nilfs_transaction_commit(inode->i_sb); /* never fails */
+
+out_free:
+       vfree(kbuf);
+out:
+       mnt_drop_write_file(filp);
+       return ret;
+}
+
 long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        struct inode *inode = file_inode(filp);
@@ -1189,6 +1278,8 @@ long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return nilfs_ioctl_get_info(inode, filp, cmd, argp,
                                            sizeof(struct nilfs_suinfo),
                                            nilfs_ioctl_do_get_suinfo);
+       case NILFS_IOCTL_SET_SUINFO:
+               return nilfs_ioctl_set_suinfo(inode, filp, cmd, argp);
        case NILFS_IOCTL_GET_SUSTAT:
                return nilfs_ioctl_get_sustat(inode, filp, cmd, argp);
        case NILFS_IOCTL_GET_VINFO:
@@ -1228,6 +1319,7 @@ long nilfs_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
        case NILFS_IOCTL_GET_CPINFO:
        case NILFS_IOCTL_GET_CPSTAT:
        case NILFS_IOCTL_GET_SUINFO:
+       case NILFS_IOCTL_SET_SUINFO:
        case NILFS_IOCTL_GET_SUSTAT:
        case NILFS_IOCTL_GET_VINFO:
        case NILFS_IOCTL_GET_BDESCS: