xfs: scrub extended attributes
authorDarrick J. Wong <darrick.wong@oracle.com>
Wed, 18 Oct 2017 04:37:45 +0000 (21:37 -0700)
committerDarrick J. Wong <darrick.wong@oracle.com>
Thu, 26 Oct 2017 22:38:26 +0000 (15:38 -0700)
Scrub the hash tree, keys, and values in an extended attribute structure.
Refactor the attribute code to use the transaction if the caller supplied
one to avoid buffer deadocks.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
fs/xfs/Makefile
fs/xfs/libxfs/xfs_fs.h
fs/xfs/scrub/attr.c [new file with mode: 0644]
fs/xfs/scrub/common.h
fs/xfs/scrub/scrub.c
fs/xfs/scrub/scrub.h
fs/xfs/xfs_attr.h
fs/xfs/xfs_attr_list.c

index d6522e87ddc603a7cdceac49e3bf21d6908749a8..94c9eaf34c27df74582eeb7249c74a5800ce7404 100644 (file)
@@ -145,6 +145,7 @@ xfs-y                               += $(addprefix scrub/, \
                                   trace.o \
                                   agheader.o \
                                   alloc.o \
+                                  attr.o \
                                   bmap.o \
                                   btree.o \
                                   common.o \
index b16d004cf372cafa6bda65061fdcd665ce4c9cb9..0834ce6335183be92a1ef96c07733defba1aefa9 100644 (file)
@@ -499,9 +499,10 @@ struct xfs_scrub_metadata {
 #define XFS_SCRUB_TYPE_BMBTA   13      /* attr fork block mapping */
 #define XFS_SCRUB_TYPE_BMBTC   14      /* CoW fork block mapping */
 #define XFS_SCRUB_TYPE_DIR     15      /* directory */
+#define XFS_SCRUB_TYPE_XATTR   16      /* extended attribute */
 
 /* Number of scrub subcommands. */
-#define XFS_SCRUB_TYPE_NR      16
+#define XFS_SCRUB_TYPE_NR      17
 
 /* i: Repair this metadata. */
 #define XFS_SCRUB_IFLAG_REPAIR         (1 << 0)
diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c
new file mode 100644 (file)
index 0000000..a70cd9b
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 Oracle.  All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "xfs.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_defer.h"
+#include "xfs_btree.h"
+#include "xfs_bit.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_sb.h"
+#include "xfs_inode.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_dir2.h"
+#include "xfs_attr.h"
+#include "xfs_attr_leaf.h"
+#include "scrub/xfs_scrub.h"
+#include "scrub/scrub.h"
+#include "scrub/common.h"
+#include "scrub/dabtree.h"
+#include "scrub/trace.h"
+
+#include <linux/posix_acl_xattr.h>
+#include <linux/xattr.h>
+
+/* Set us up to scrub an inode's extended attributes. */
+int
+xfs_scrub_setup_xattr(
+       struct xfs_scrub_context        *sc,
+       struct xfs_inode                *ip)
+{
+       /* Allocate the buffer without the inode lock held. */
+       sc->buf = kmem_zalloc_large(XATTR_SIZE_MAX, KM_SLEEP);
+       if (!sc->buf)
+               return -ENOMEM;
+
+       return xfs_scrub_setup_inode_contents(sc, ip, 0);
+}
+
+/* Extended Attributes */
+
+struct xfs_scrub_xattr {
+       struct xfs_attr_list_context    context;
+       struct xfs_scrub_context        *sc;
+};
+
+/*
+ * Check that an extended attribute key can be looked up by hash.
+ *
+ * We use the XFS attribute list iterator (i.e. xfs_attr_list_int_ilocked)
+ * to call this function for every attribute key in an inode.  Once
+ * we're here, we load the attribute value to see if any errors happen,
+ * or if we get more or less data than we expected.
+ */
+static void
+xfs_scrub_xattr_listent(
+       struct xfs_attr_list_context    *context,
+       int                             flags,
+       unsigned char                   *name,
+       int                             namelen,
+       int                             valuelen)
+{
+       struct xfs_scrub_xattr          *sx;
+       struct xfs_da_args              args = {0};
+       int                             error = 0;
+
+       sx = container_of(context, struct xfs_scrub_xattr, context);
+
+       if (flags & XFS_ATTR_INCOMPLETE) {
+               /* Incomplete attr key, just mark the inode for preening. */
+               xfs_scrub_ino_set_preen(sx->sc, NULL);
+               return;
+       }
+
+       args.flags = ATTR_KERNOTIME;
+       if (flags & XFS_ATTR_ROOT)
+               args.flags |= ATTR_ROOT;
+       else if (flags & XFS_ATTR_SECURE)
+               args.flags |= ATTR_SECURE;
+       args.geo = context->dp->i_mount->m_attr_geo;
+       args.whichfork = XFS_ATTR_FORK;
+       args.dp = context->dp;
+       args.name = name;
+       args.namelen = namelen;
+       args.hashval = xfs_da_hashname(args.name, args.namelen);
+       args.trans = context->tp;
+       args.value = sx->sc->buf;
+       args.valuelen = XATTR_SIZE_MAX;
+
+       error = xfs_attr_get_ilocked(context->dp, &args);
+       if (error == -EEXIST)
+               error = 0;
+       if (!xfs_scrub_fblock_process_error(sx->sc, XFS_ATTR_FORK, args.blkno,
+                       &error))
+               goto fail_xref;
+       if (args.valuelen != valuelen)
+               xfs_scrub_fblock_set_corrupt(sx->sc, XFS_ATTR_FORK,
+                                            args.blkno);
+
+fail_xref:
+       return;
+}
+
+/* Scrub a attribute btree record. */
+STATIC int
+xfs_scrub_xattr_rec(
+       struct xfs_scrub_da_btree       *ds,
+       int                             level,
+       void                            *rec)
+{
+       struct xfs_mount                *mp = ds->state->mp;
+       struct xfs_attr_leaf_entry      *ent = rec;
+       struct xfs_da_state_blk         *blk;
+       struct xfs_attr_leaf_name_local *lentry;
+       struct xfs_attr_leaf_name_remote        *rentry;
+       struct xfs_buf                  *bp;
+       xfs_dahash_t                    calc_hash;
+       xfs_dahash_t                    hash;
+       int                             nameidx;
+       int                             hdrsize;
+       unsigned int                    badflags;
+       int                             error;
+
+       blk = &ds->state->path.blk[level];
+
+       /* Check the hash of the entry. */
+       error = xfs_scrub_da_btree_hash(ds, level, &ent->hashval);
+       if (error)
+               goto out;
+
+       /* Find the attr entry's location. */
+       bp = blk->bp;
+       hdrsize = xfs_attr3_leaf_hdr_size(bp->b_addr);
+       nameidx = be16_to_cpu(ent->nameidx);
+       if (nameidx < hdrsize || nameidx >= mp->m_attr_geo->blksize) {
+               xfs_scrub_da_set_corrupt(ds, level);
+               goto out;
+       }
+
+       /* Check all the padding. */
+       if (xfs_sb_version_hascrc(&ds->sc->mp->m_sb)) {
+               struct xfs_attr3_leafblock      *leaf = bp->b_addr;
+
+               if (leaf->hdr.pad1 != 0 ||
+                   leaf->hdr.pad2 != cpu_to_be32(0) ||
+                   leaf->hdr.info.hdr.pad != cpu_to_be16(0))
+                       xfs_scrub_da_set_corrupt(ds, level);
+       } else {
+               struct xfs_attr_leafblock       *leaf = bp->b_addr;
+
+               if (leaf->hdr.pad1 != 0 ||
+                   leaf->hdr.info.pad != cpu_to_be16(0))
+                       xfs_scrub_da_set_corrupt(ds, level);
+       }
+       if (ent->pad2 != 0)
+               xfs_scrub_da_set_corrupt(ds, level);
+
+       /* Retrieve the entry and check it. */
+       hash = be32_to_cpu(ent->hashval);
+       badflags = ~(XFS_ATTR_LOCAL | XFS_ATTR_ROOT | XFS_ATTR_SECURE |
+                       XFS_ATTR_INCOMPLETE);
+       if ((ent->flags & badflags) != 0)
+               xfs_scrub_da_set_corrupt(ds, level);
+       if (ent->flags & XFS_ATTR_LOCAL) {
+               lentry = (struct xfs_attr_leaf_name_local *)
+                               (((char *)bp->b_addr) + nameidx);
+               if (lentry->namelen <= 0) {
+                       xfs_scrub_da_set_corrupt(ds, level);
+                       goto out;
+               }
+               calc_hash = xfs_da_hashname(lentry->nameval, lentry->namelen);
+       } else {
+               rentry = (struct xfs_attr_leaf_name_remote *)
+                               (((char *)bp->b_addr) + nameidx);
+               if (rentry->namelen <= 0) {
+                       xfs_scrub_da_set_corrupt(ds, level);
+                       goto out;
+               }
+               calc_hash = xfs_da_hashname(rentry->name, rentry->namelen);
+       }
+       if (calc_hash != hash)
+               xfs_scrub_da_set_corrupt(ds, level);
+
+out:
+       return error;
+}
+
+/* Scrub the extended attribute metadata. */
+int
+xfs_scrub_xattr(
+       struct xfs_scrub_context        *sc)
+{
+       struct xfs_scrub_xattr          sx = { 0 };
+       struct attrlist_cursor_kern     cursor = { 0 };
+       int                             error = 0;
+
+       if (!xfs_inode_hasattr(sc->ip))
+               return -ENOENT;
+
+       memset(&sx, 0, sizeof(sx));
+       /* Check attribute tree structure */
+       error = xfs_scrub_da_btree(sc, XFS_ATTR_FORK, xfs_scrub_xattr_rec);
+       if (error)
+               goto out;
+
+       if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+               goto out;
+
+       /* Check that every attr key can also be looked up by hash. */
+       sx.context.dp = sc->ip;
+       sx.context.cursor = &cursor;
+       sx.context.resynch = 1;
+       sx.context.put_listent = xfs_scrub_xattr_listent;
+       sx.context.tp = sc->tp;
+       sx.context.flags = ATTR_INCOMPLETE;
+       sx.sc = sc;
+
+       /*
+        * Look up every xattr in this file by name.
+        *
+        * Use the backend implementation of xfs_attr_list to call
+        * xfs_scrub_xattr_listent on every attribute key in this inode.
+        * In other words, we use the same iterator/callback mechanism
+        * that listattr uses to scrub extended attributes, though in our
+        * _listent function, we check the value of the attribute.
+        *
+        * The VFS only locks i_rwsem when modifying attrs, so keep all
+        * three locks held because that's the only way to ensure we're
+        * the only thread poking into the da btree.  We traverse the da
+        * btree while holding a leaf buffer locked for the xattr name
+        * iteration, which doesn't really follow the usual buffer
+        * locking order.
+        */
+       error = xfs_attr_list_int_ilocked(&sx.context);
+       if (!xfs_scrub_fblock_process_error(sc, XFS_ATTR_FORK, 0, &error))
+               goto out;
+out:
+       return error;
+}
index 7cd4a78691e7b0ab1cc5d143f03cb70aa401fbae..b938429658d9968af4be2ddbab77060daf1abdf1 100644 (file)
@@ -95,6 +95,8 @@ int xfs_scrub_setup_inode_bmap_data(struct xfs_scrub_context *sc,
                                    struct xfs_inode *ip);
 int xfs_scrub_setup_directory(struct xfs_scrub_context *sc,
                              struct xfs_inode *ip);
+int xfs_scrub_setup_xattr(struct xfs_scrub_context *sc,
+                         struct xfs_inode *ip);
 
 void xfs_scrub_ag_free(struct xfs_scrub_context *sc, struct xfs_scrub_ag *sa);
 int xfs_scrub_ag_init(struct xfs_scrub_context *sc, xfs_agnumber_t agno,
index 68daedf21918f077d3161c98a5479011cf13bb0e..f3fc429e909bfdc8843abf1cfe95abf10114289d 100644 (file)
@@ -158,6 +158,10 @@ xfs_scrub_teardown(
                        iput(VFS_I(sc->ip));
                sc->ip = NULL;
        }
+       if (sc->buf) {
+               kmem_free(sc->buf);
+               sc->buf = NULL;
+       }
        return error;
 }
 
@@ -231,6 +235,10 @@ static const struct xfs_scrub_meta_ops meta_scrub_ops[] = {
                .setup  = xfs_scrub_setup_directory,
                .scrub  = xfs_scrub_directory,
        },
+       { /* extended attributes */
+               .setup  = xfs_scrub_setup_xattr,
+               .scrub  = xfs_scrub_xattr,
+       },
 };
 
 /* This isn't a stable feature, warn once per day. */
index 844506e28b0d1c96bf9d342c39ff0c9eadab5c4b..d31ff589d27d90c9c85d14cfa816369c23187a5d 100644 (file)
@@ -59,6 +59,7 @@ struct xfs_scrub_context {
        const struct xfs_scrub_meta_ops *ops;
        struct xfs_trans                *tp;
        struct xfs_inode                *ip;
+       void                            *buf;
        uint                            ilock_flags;
        bool                            try_harder;
 
@@ -83,5 +84,6 @@ int xfs_scrub_bmap_data(struct xfs_scrub_context *sc);
 int xfs_scrub_bmap_attr(struct xfs_scrub_context *sc);
 int xfs_scrub_bmap_cow(struct xfs_scrub_context *sc);
 int xfs_scrub_directory(struct xfs_scrub_context *sc);
+int xfs_scrub_xattr(struct xfs_scrub_context *sc);
 
 #endif /* __XFS_SCRUB_SCRUB_H__ */
index 5d5a5e277f35d06785901a5907222219a50e01e6..d07bf27451c9b2e03a034581e418d8aeeab7304b 100644 (file)
@@ -48,6 +48,8 @@ struct xfs_attr_list_context;
 #define ATTR_KERNOTIME 0x1000  /* [kernel] don't update inode timestamps */
 #define ATTR_KERNOVAL  0x2000  /* [kernel] get attr size only, not value */
 
+#define ATTR_INCOMPLETE        0x4000  /* [kernel] return INCOMPLETE attr keys */
+
 #define XFS_ATTR_FLAGS \
        { ATTR_DONTFOLLOW,      "DONTFOLLOW" }, \
        { ATTR_ROOT,            "ROOT" }, \
@@ -56,7 +58,8 @@ struct xfs_attr_list_context;
        { ATTR_CREATE,          "CREATE" }, \
        { ATTR_REPLACE,         "REPLACE" }, \
        { ATTR_KERNOTIME,       "KERNOTIME" }, \
-       { ATTR_KERNOVAL,        "KERNOVAL" }
+       { ATTR_KERNOVAL,        "KERNOVAL" }, \
+       { ATTR_INCOMPLETE,      "INCOMPLETE" }
 
 /*
  * The maximum size (into the kernel or returned from the kernel) of an
index 7740c8a5e7369de93caa93cabed20745ad35d6f5..58167868631578cd20a81c6d8370871ed8a0a922 100644 (file)
@@ -407,7 +407,8 @@ xfs_attr3_leaf_list_int(
                        cursor->offset = 0;
                }
 
-               if (entry->flags & XFS_ATTR_INCOMPLETE)
+               if ((entry->flags & XFS_ATTR_INCOMPLETE) &&
+                   !(context->flags & ATTR_INCOMPLETE))
                        continue;               /* skip incomplete entries */
 
                if (entry->flags & XFS_ATTR_LOCAL) {
@@ -583,6 +584,10 @@ xfs_attr_list(
            (cursor->hashval || cursor->blkno || cursor->offset))
                return -EINVAL;
 
+       /* Only internal consumers can retrieve incomplete attrs. */
+       if (flags & ATTR_INCOMPLETE)
+               return -EINVAL;
+
        /*
         * Check for a properly aligned buffer.
         */