Merge tag 'xfs-5.3-merge-12' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux
[linux-2.6-block.git] / fs / xfs / xfs_ioctl.c
index fe29aa61293c562131acc90dc540129484234184..6f7848cd5527bc8d32c840c44d4748a1422357fb 100644 (file)
@@ -11,9 +11,8 @@
 #include "xfs_trans_resv.h"
 #include "xfs_mount.h"
 #include "xfs_inode.h"
-#include "xfs_ioctl.h"
-#include "xfs_alloc.h"
 #include "xfs_rtalloc.h"
+#include "xfs_iwalk.h"
 #include "xfs_itable.h"
 #include "xfs_error.h"
 #include "xfs_attr.h"
@@ -25,7 +24,6 @@
 #include "xfs_export.h"
 #include "xfs_trace.h"
 #include "xfs_icache.h"
-#include "xfs_symlink.h"
 #include "xfs_trans.h"
 #include "xfs_acl.h"
 #include "xfs_btree.h"
 #include "xfs_ag.h"
 #include "xfs_health.h"
 
-#include <linux/capability.h>
-#include <linux/cred.h>
-#include <linux/dcache.h>
 #include <linux/mount.h>
 #include <linux/namei.h>
-#include <linux/pagemap.h>
-#include <linux/slab.h>
-#include <linux/exportfs.h>
 
 /*
  * xfs_find_handle maps from userspace xfs_fsop_handlereq structure to
@@ -721,16 +713,45 @@ out_unlock:
        return error;
 }
 
+/* Return 0 on success or positive error */
+int
+xfs_fsbulkstat_one_fmt(
+       struct xfs_ibulk                *breq,
+       const struct xfs_bulkstat       *bstat)
+{
+       struct xfs_bstat                bs1;
+
+       xfs_bulkstat_to_bstat(breq->mp, &bs1, bstat);
+       if (copy_to_user(breq->ubuffer, &bs1, sizeof(bs1)))
+               return -EFAULT;
+       return xfs_ibulk_advance(breq, sizeof(struct xfs_bstat));
+}
+
+int
+xfs_fsinumbers_fmt(
+       struct xfs_ibulk                *breq,
+       const struct xfs_inumbers       *igrp)
+{
+       struct xfs_inogrp               ig1;
+
+       xfs_inumbers_to_inogrp(&ig1, igrp);
+       if (copy_to_user(breq->ubuffer, &ig1, sizeof(struct xfs_inogrp)))
+               return -EFAULT;
+       return xfs_ibulk_advance(breq, sizeof(struct xfs_inogrp));
+}
+
 STATIC int
-xfs_ioc_bulkstat(
+xfs_ioc_fsbulkstat(
        xfs_mount_t             *mp,
        unsigned int            cmd,
        void                    __user *arg)
 {
-       xfs_fsop_bulkreq_t      bulkreq;
-       int                     count;  /* # of records returned */
-       xfs_ino_t               inlast; /* last inode number */
-       int                     done;
+       struct xfs_fsop_bulkreq bulkreq;
+       struct xfs_ibulk        breq = {
+               .mp             = mp,
+               .ocount         = 0,
+       };
+       xfs_ino_t               lastino;
        int                     error;
 
        /* done = 1 if there are more stats to get and if bulkstat */
@@ -742,41 +763,243 @@ xfs_ioc_bulkstat(
        if (XFS_FORCED_SHUTDOWN(mp))
                return -EIO;
 
-       if (copy_from_user(&bulkreq, arg, sizeof(xfs_fsop_bulkreq_t)))
+       if (copy_from_user(&bulkreq, arg, sizeof(struct xfs_fsop_bulkreq)))
                return -EFAULT;
 
-       if (copy_from_user(&inlast, bulkreq.lastip, sizeof(__s64)))
+       if (copy_from_user(&lastino, bulkreq.lastip, sizeof(__s64)))
                return -EFAULT;
 
-       if ((count = bulkreq.icount) <= 0)
+       if (bulkreq.icount <= 0)
                return -EINVAL;
 
        if (bulkreq.ubuffer == NULL)
                return -EINVAL;
 
-       if (cmd == XFS_IOC_FSINUMBERS)
-               error = xfs_inumbers(mp, &inlast, &count,
-                                       bulkreq.ubuffer, xfs_inumbers_fmt);
-       else if (cmd == XFS_IOC_FSBULKSTAT_SINGLE)
-               error = xfs_bulkstat_one(mp, inlast, bulkreq.ubuffer,
-                                       sizeof(xfs_bstat_t), NULL, &done);
-       else    /* XFS_IOC_FSBULKSTAT */
-               error = xfs_bulkstat(mp, &inlast, &count, xfs_bulkstat_one,
-                                    sizeof(xfs_bstat_t), bulkreq.ubuffer,
-                                    &done);
+       breq.ubuffer = bulkreq.ubuffer;
+       breq.icount = bulkreq.icount;
+
+       /*
+        * FSBULKSTAT_SINGLE expects that *lastip contains the inode number
+        * that we want to stat.  However, FSINUMBERS and FSBULKSTAT expect
+        * that *lastip contains either zero or the number of the last inode to
+        * be examined by the previous call and return results starting with
+        * the next inode after that.  The new bulk request back end functions
+        * take the inode to start with, so we have to compute the startino
+        * parameter from lastino to maintain correct function.  lastino == 0
+        * is a special case because it has traditionally meant "first inode
+        * in filesystem".
+        */
+       if (cmd == XFS_IOC_FSINUMBERS) {
+               breq.startino = lastino ? lastino + 1 : 0;
+               error = xfs_inumbers(&breq, xfs_fsinumbers_fmt);
+               lastino = breq.startino - 1;
+       } else if (cmd == XFS_IOC_FSBULKSTAT_SINGLE) {
+               breq.startino = lastino;
+               breq.icount = 1;
+               error = xfs_bulkstat_one(&breq, xfs_fsbulkstat_one_fmt);
+       } else {        /* XFS_IOC_FSBULKSTAT */
+               breq.startino = lastino ? lastino + 1 : 0;
+               error = xfs_bulkstat(&breq, xfs_fsbulkstat_one_fmt);
+               lastino = breq.startino - 1;
+       }
 
        if (error)
                return error;
 
-       if (bulkreq.ocount != NULL) {
-               if (copy_to_user(bulkreq.lastip, &inlast,
-                                               sizeof(xfs_ino_t)))
-                       return -EFAULT;
+       if (bulkreq.lastip != NULL &&
+           copy_to_user(bulkreq.lastip, &lastino, sizeof(xfs_ino_t)))
+               return -EFAULT;
 
-               if (copy_to_user(bulkreq.ocount, &count, sizeof(count)))
-                       return -EFAULT;
+       if (bulkreq.ocount != NULL &&
+           copy_to_user(bulkreq.ocount, &breq.ocount, sizeof(__s32)))
+               return -EFAULT;
+
+       return 0;
+}
+
+/* Return 0 on success or positive error */
+static int
+xfs_bulkstat_fmt(
+       struct xfs_ibulk                *breq,
+       const struct xfs_bulkstat       *bstat)
+{
+       if (copy_to_user(breq->ubuffer, bstat, sizeof(struct xfs_bulkstat)))
+               return -EFAULT;
+       return xfs_ibulk_advance(breq, sizeof(struct xfs_bulkstat));
+}
+
+/*
+ * Check the incoming bulk request @hdr from userspace and initialize the
+ * internal @breq bulk request appropriately.  Returns 0 if the bulk request
+ * should proceed; XFS_ITER_ABORT if there's nothing to do; or the usual
+ * negative error code.
+ */
+static int
+xfs_bulk_ireq_setup(
+       struct xfs_mount        *mp,
+       struct xfs_bulk_ireq    *hdr,
+       struct xfs_ibulk        *breq,
+       void __user             *ubuffer)
+{
+       if (hdr->icount == 0 ||
+           (hdr->flags & ~XFS_BULK_IREQ_FLAGS_ALL) ||
+           memchr_inv(hdr->reserved, 0, sizeof(hdr->reserved)))
+               return -EINVAL;
+
+       breq->startino = hdr->ino;
+       breq->ubuffer = ubuffer;
+       breq->icount = hdr->icount;
+       breq->ocount = 0;
+       breq->flags = 0;
+
+       /*
+        * The @ino parameter is a special value, so we must look it up here.
+        * We're not allowed to have IREQ_AGNO, and we only return one inode
+        * worth of data.
+        */
+       if (hdr->flags & XFS_BULK_IREQ_SPECIAL) {
+               if (hdr->flags & XFS_BULK_IREQ_AGNO)
+                       return -EINVAL;
+
+               switch (hdr->ino) {
+               case XFS_BULK_IREQ_SPECIAL_ROOT:
+                       hdr->ino = mp->m_sb.sb_rootino;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               breq->icount = 1;
        }
 
+       /*
+        * The IREQ_AGNO flag means that we only want results from a given AG.
+        * If @hdr->ino is zero, we start iterating in that AG.  If @hdr->ino is
+        * beyond the specified AG then we return no results.
+        */
+       if (hdr->flags & XFS_BULK_IREQ_AGNO) {
+               if (hdr->agno >= mp->m_sb.sb_agcount)
+                       return -EINVAL;
+
+               if (breq->startino == 0)
+                       breq->startino = XFS_AGINO_TO_INO(mp, hdr->agno, 0);
+               else if (XFS_INO_TO_AGNO(mp, breq->startino) < hdr->agno)
+                       return -EINVAL;
+
+               breq->flags |= XFS_IBULK_SAME_AG;
+
+               /* Asking for an inode past the end of the AG?  We're done! */
+               if (XFS_INO_TO_AGNO(mp, breq->startino) > hdr->agno)
+                       return XFS_ITER_ABORT;
+       } else if (hdr->agno)
+               return -EINVAL;
+
+       /* Asking for an inode past the end of the FS?  We're done! */
+       if (XFS_INO_TO_AGNO(mp, breq->startino) >= mp->m_sb.sb_agcount)
+               return XFS_ITER_ABORT;
+
+       return 0;
+}
+
+/*
+ * Update the userspace bulk request @hdr to reflect the end state of the
+ * internal bulk request @breq.
+ */
+static void
+xfs_bulk_ireq_teardown(
+       struct xfs_bulk_ireq    *hdr,
+       struct xfs_ibulk        *breq)
+{
+       hdr->ino = breq->startino;
+       hdr->ocount = breq->ocount;
+}
+
+/* Handle the v5 bulkstat ioctl. */
+STATIC int
+xfs_ioc_bulkstat(
+       struct xfs_mount                *mp,
+       unsigned int                    cmd,
+       struct xfs_bulkstat_req __user  *arg)
+{
+       struct xfs_bulk_ireq            hdr;
+       struct xfs_ibulk                breq = {
+               .mp                     = mp,
+       };
+       int                             error;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (XFS_FORCED_SHUTDOWN(mp))
+               return -EIO;
+
+       if (copy_from_user(&hdr, &arg->hdr, sizeof(hdr)))
+               return -EFAULT;
+
+       error = xfs_bulk_ireq_setup(mp, &hdr, &breq, arg->bulkstat);
+       if (error == XFS_ITER_ABORT)
+               goto out_teardown;
+       if (error < 0)
+               return error;
+
+       error = xfs_bulkstat(&breq, xfs_bulkstat_fmt);
+       if (error)
+               return error;
+
+out_teardown:
+       xfs_bulk_ireq_teardown(&hdr, &breq);
+       if (copy_to_user(&arg->hdr, &hdr, sizeof(hdr)))
+               return -EFAULT;
+
+       return 0;
+}
+
+STATIC int
+xfs_inumbers_fmt(
+       struct xfs_ibulk                *breq,
+       const struct xfs_inumbers       *igrp)
+{
+       if (copy_to_user(breq->ubuffer, igrp, sizeof(struct xfs_inumbers)))
+               return -EFAULT;
+       return xfs_ibulk_advance(breq, sizeof(struct xfs_inumbers));
+}
+
+/* Handle the v5 inumbers ioctl. */
+STATIC int
+xfs_ioc_inumbers(
+       struct xfs_mount                *mp,
+       unsigned int                    cmd,
+       struct xfs_inumbers_req __user  *arg)
+{
+       struct xfs_bulk_ireq            hdr;
+       struct xfs_ibulk                breq = {
+               .mp                     = mp,
+       };
+       int                             error;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (XFS_FORCED_SHUTDOWN(mp))
+               return -EIO;
+
+       if (copy_from_user(&hdr, &arg->hdr, sizeof(hdr)))
+               return -EFAULT;
+
+       error = xfs_bulk_ireq_setup(mp, &hdr, &breq, arg->inumbers);
+       if (error == XFS_ITER_ABORT)
+               goto out_teardown;
+       if (error < 0)
+               return error;
+
+       error = xfs_inumbers(&breq, xfs_inumbers_fmt);
+       if (error)
+               return error;
+
+out_teardown:
+       xfs_bulk_ireq_teardown(&hdr, &breq);
+       if (copy_to_user(&arg->hdr, &hdr, sizeof(hdr)))
+               return -EFAULT;
+
        return 0;
 }
 
@@ -1926,7 +2149,12 @@ xfs_file_ioctl(
        case XFS_IOC_FSBULKSTAT_SINGLE:
        case XFS_IOC_FSBULKSTAT:
        case XFS_IOC_FSINUMBERS:
+               return xfs_ioc_fsbulkstat(mp, cmd, arg);
+
+       case XFS_IOC_BULKSTAT:
                return xfs_ioc_bulkstat(mp, cmd, arg);
+       case XFS_IOC_INUMBERS:
+               return xfs_ioc_inumbers(mp, cmd, arg);
 
        case XFS_IOC_FSGEOMETRY_V1:
                return xfs_ioc_fsgeometry(mp, arg, 3);