]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - fs/ocfs2/ioctl.c
Merge branch 'x86-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[net-next-2.6.git] / fs / ocfs2 / ioctl.c
index 7d9d9c132cef3a5d59a412aa4d0d3c26c10a5fb0..7a48681961521a957e1947ba9abdbb8d290f9cde 100644 (file)
 
 #include <linux/ext2_fs.h>
 
+#define o2info_from_user(a, b) \
+               copy_from_user(&(a), (b), sizeof(a))
+#define o2info_to_user(a, b)   \
+               copy_to_user((typeof(a) __user *)b, &(a), sizeof(a))
+
+/*
+ * This call is void because we are already reporting an error that may
+ * be -EFAULT.  The error will be returned from the ioctl(2) call.  It's
+ * just a best-effort to tell userspace that this request caused the error.
+ */
+static inline void __o2info_set_request_error(struct ocfs2_info_request *kreq,
+                                       struct ocfs2_info_request __user *req)
+{
+       kreq->ir_flags |= OCFS2_INFO_FL_ERROR;
+       (void)put_user(kreq->ir_flags, (__u32 __user *)&(req->ir_flags));
+}
+
+#define o2info_set_request_error(a, b) \
+               __o2info_set_request_error((struct ocfs2_info_request *)&(a), b)
+
 static int ocfs2_get_inode_attr(struct inode *inode, unsigned *flags)
 {
        int status;
@@ -109,6 +129,328 @@ bail:
        return status;
 }
 
+int ocfs2_info_handle_blocksize(struct inode *inode,
+                               struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_blocksize oib;
+
+       if (o2info_from_user(oib, req))
+               goto bail;
+
+       oib.ib_blocksize = inode->i_sb->s_blocksize;
+       oib.ib_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oib, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oib, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_clustersize(struct inode *inode,
+                                 struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_clustersize oic;
+       struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
+       if (o2info_from_user(oic, req))
+               goto bail;
+
+       oic.ic_clustersize = osb->s_clustersize;
+       oic.ic_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oic, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oic, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_maxslots(struct inode *inode,
+                              struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_maxslots oim;
+       struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
+       if (o2info_from_user(oim, req))
+               goto bail;
+
+       oim.im_max_slots = osb->max_slots;
+       oim.im_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oim, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oim, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_label(struct inode *inode,
+                           struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_label oil;
+       struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
+       if (o2info_from_user(oil, req))
+               goto bail;
+
+       memcpy(oil.il_label, osb->vol_label, OCFS2_MAX_VOL_LABEL_LEN);
+       oil.il_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oil, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oil, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_uuid(struct inode *inode,
+                          struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_uuid oiu;
+       struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
+       if (o2info_from_user(oiu, req))
+               goto bail;
+
+       memcpy(oiu.iu_uuid_str, osb->uuid_str, OCFS2_TEXT_UUID_LEN + 1);
+       oiu.iu_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oiu, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oiu, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_fs_features(struct inode *inode,
+                                 struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_fs_features oif;
+       struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
+       if (o2info_from_user(oif, req))
+               goto bail;
+
+       oif.if_compat_features = osb->s_feature_compat;
+       oif.if_incompat_features = osb->s_feature_incompat;
+       oif.if_ro_compat_features = osb->s_feature_ro_compat;
+       oif.if_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oif, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oif, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_journal_size(struct inode *inode,
+                                  struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_journal_size oij;
+       struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
+       if (o2info_from_user(oij, req))
+               goto bail;
+
+       oij.ij_journal_size = osb->journal->j_inode->i_size;
+
+       oij.ij_req.ir_flags |= OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oij, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oij, req);
+
+       return status;
+}
+
+int ocfs2_info_handle_unknown(struct inode *inode,
+                             struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_request oir;
+
+       if (o2info_from_user(oir, req))
+               goto bail;
+
+       oir.ir_flags &= ~OCFS2_INFO_FL_FILLED;
+
+       if (o2info_to_user(oir, req))
+               goto bail;
+
+       status = 0;
+bail:
+       if (status)
+               o2info_set_request_error(oir, req);
+
+       return status;
+}
+
+/*
+ * Validate and distinguish OCFS2_IOC_INFO requests.
+ *
+ * - validate the magic number.
+ * - distinguish different requests.
+ * - validate size of different requests.
+ */
+int ocfs2_info_handle_request(struct inode *inode,
+                             struct ocfs2_info_request __user *req)
+{
+       int status = -EFAULT;
+       struct ocfs2_info_request oir;
+
+       if (o2info_from_user(oir, req))
+               goto bail;
+
+       status = -EINVAL;
+       if (oir.ir_magic != OCFS2_INFO_MAGIC)
+               goto bail;
+
+       switch (oir.ir_code) {
+       case OCFS2_INFO_BLOCKSIZE:
+               if (oir.ir_size == sizeof(struct ocfs2_info_blocksize))
+                       status = ocfs2_info_handle_blocksize(inode, req);
+               break;
+       case OCFS2_INFO_CLUSTERSIZE:
+               if (oir.ir_size == sizeof(struct ocfs2_info_clustersize))
+                       status = ocfs2_info_handle_clustersize(inode, req);
+               break;
+       case OCFS2_INFO_MAXSLOTS:
+               if (oir.ir_size == sizeof(struct ocfs2_info_maxslots))
+                       status = ocfs2_info_handle_maxslots(inode, req);
+               break;
+       case OCFS2_INFO_LABEL:
+               if (oir.ir_size == sizeof(struct ocfs2_info_label))
+                       status = ocfs2_info_handle_label(inode, req);
+               break;
+       case OCFS2_INFO_UUID:
+               if (oir.ir_size == sizeof(struct ocfs2_info_uuid))
+                       status = ocfs2_info_handle_uuid(inode, req);
+               break;
+       case OCFS2_INFO_FS_FEATURES:
+               if (oir.ir_size == sizeof(struct ocfs2_info_fs_features))
+                       status = ocfs2_info_handle_fs_features(inode, req);
+               break;
+       case OCFS2_INFO_JOURNAL_SIZE:
+               if (oir.ir_size == sizeof(struct ocfs2_info_journal_size))
+                       status = ocfs2_info_handle_journal_size(inode, req);
+               break;
+       default:
+               status = ocfs2_info_handle_unknown(inode, req);
+               break;
+       }
+
+bail:
+       return status;
+}
+
+int ocfs2_get_request_ptr(struct ocfs2_info *info, int idx,
+                         u64 *req_addr, int compat_flag)
+{
+       int status = -EFAULT;
+       u64 __user *bp = NULL;
+
+       if (compat_flag) {
+#ifdef CONFIG_COMPAT
+               /*
+                * pointer bp stores the base address of a pointers array,
+                * which collects all addresses of separate request.
+                */
+               bp = (u64 __user *)(unsigned long)compat_ptr(info->oi_requests);
+#else
+               BUG();
+#endif
+       } else
+               bp = (u64 __user *)(unsigned long)(info->oi_requests);
+
+       if (o2info_from_user(*req_addr, bp + idx))
+               goto bail;
+
+       status = 0;
+bail:
+       return status;
+}
+
+/*
+ * OCFS2_IOC_INFO handles an array of requests passed from userspace.
+ *
+ * ocfs2_info_handle() recevies a large info aggregation, grab and
+ * validate the request count from header, then break it into small
+ * pieces, later specific handlers can handle them one by one.
+ *
+ * Idea here is to make each separate request small enough to ensure
+ * a better backward&forward compatibility, since a small piece of
+ * request will be less likely to be broken if disk layout get changed.
+ */
+int ocfs2_info_handle(struct inode *inode, struct ocfs2_info *info,
+                     int compat_flag)
+{
+       int i, status = 0;
+       u64 req_addr;
+       struct ocfs2_info_request __user *reqp;
+
+       if ((info->oi_count > OCFS2_INFO_MAX_REQUEST) ||
+           (!info->oi_requests)) {
+               status = -EINVAL;
+               goto bail;
+       }
+
+       for (i = 0; i < info->oi_count; i++) {
+
+               status = ocfs2_get_request_ptr(info, i, &req_addr, compat_flag);
+               if (status)
+                       break;
+
+               reqp = (struct ocfs2_info_request *)(unsigned long)req_addr;
+               if (!reqp) {
+                       status = -EINVAL;
+                       goto bail;
+               }
+
+               status = ocfs2_info_handle_request(inode, reqp);
+               if (status)
+                       break;
+       }
+
+bail:
+       return status;
+}
+
 long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        struct inode *inode = filp->f_path.dentry->d_inode;
@@ -120,6 +462,7 @@ long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
        struct reflink_arguments args;
        const char *old_path, *new_path;
        bool preserve;
+       struct ocfs2_info info;
 
        switch (cmd) {
        case OCFS2_IOC_GETFLAGS:
@@ -174,6 +517,12 @@ long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                preserve = (args.preserve != 0);
 
                return ocfs2_reflink_ioctl(inode, old_path, new_path, preserve);
+       case OCFS2_IOC_INFO:
+               if (copy_from_user(&info, (struct ocfs2_info __user *)arg,
+                                  sizeof(struct ocfs2_info)))
+                       return -EFAULT;
+
+               return ocfs2_info_handle(inode, &info, 0);
        default:
                return -ENOTTY;
        }
@@ -185,6 +534,7 @@ long ocfs2_compat_ioctl(struct file *file, unsigned cmd, unsigned long arg)
        bool preserve;
        struct reflink_arguments args;
        struct inode *inode = file->f_path.dentry->d_inode;
+       struct ocfs2_info info;
 
        switch (cmd) {
        case OCFS2_IOC32_GETFLAGS:
@@ -209,6 +559,12 @@ long ocfs2_compat_ioctl(struct file *file, unsigned cmd, unsigned long arg)
 
                return ocfs2_reflink_ioctl(inode, compat_ptr(args.old_path),
                                           compat_ptr(args.new_path), preserve);
+       case OCFS2_IOC_INFO:
+               if (copy_from_user(&info, (struct ocfs2_info __user *)arg,
+                                  sizeof(struct ocfs2_info)))
+                       return -EFAULT;
+
+               return ocfs2_info_handle(inode, &info, 1);
        default:
                return -ENOIOCTLCMD;
        }