]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blobdiff - libxfs/xfs_inode_fork.c
xfs: fix transaction leak on remote attr set/remove failure
[thirdparty/xfsprogs-dev.git] / libxfs / xfs_inode_fork.c
index ded3f83c0f612f078f685426d83ba819f1bfe8e0..3d64fd46fb87c646f60974aaf29c53c1dd284cd5 100644 (file)
@@ -1,19 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (c) 2000-2006 Silicon Graphics, Inc.
  * All Rights Reserved.
- *
- * 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.
- *
- * 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 "libxfs_priv.h"
 #include "xfs_fs.h"
@@ -31,6 +19,8 @@
 #include "xfs_da_format.h"
 #include "xfs_da_btree.h"
 #include "xfs_dir2_priv.h"
+#include "xfs_attr_leaf.h"
+#include "xfs_shared.h"
 
 
 kmem_zone_t *xfs_ifork_zone;
@@ -94,14 +84,6 @@ xfs_iformat_fork(
        if (error)
                return error;
 
-       /* Check inline dir contents. */
-       if (S_ISDIR(inode->i_mode) && dip->di_format == XFS_DINODE_FMT_LOCAL) {
-               if (xfs_dir2_sf_verify(ip)) {
-                       xfs_idestroy_fork(ip, XFS_DATA_FORK);
-                       return -EFSCORRUPTED;
-               }
-       }
-
        if (xfs_is_reflink_inode(ip)) {
                ASSERT(ip->i_cowfp == NULL);
                xfs_ifork_init_cow(ip);
@@ -118,18 +100,6 @@ xfs_iformat_fork(
                atp = (xfs_attr_shortform_t *)XFS_DFORK_APTR(dip);
                size = be16_to_cpu(atp->hdr.totsize);
 
-               if (unlikely(size < sizeof(struct xfs_attr_sf_hdr))) {
-                       xfs_warn(ip->i_mount,
-                               "corrupt inode %Lu (bad attr fork size %Ld).",
-                               (unsigned long long) ip->i_ino,
-                               (long long) size);
-                       XFS_CORRUPTION_ERROR("xfs_iformat(8)",
-                                            XFS_ERRLEVEL_LOW,
-                                            ip->i_mount, dip);
-                       error = -EFSCORRUPTED;
-                       break;
-               }
-
                error = xfs_iformat_local(ip, dip, XFS_ATTR_FORK, size);
                break;
        case XFS_DINODE_FMT_EXTENTS:
@@ -210,8 +180,9 @@ xfs_iformat_local(
        "corrupt inode %Lu (bad size %d for local fork, size = %d).",
                        (unsigned long long) ip->i_ino, size,
                        XFS_DFORK_SIZE(dip, ip->i_mount, whichfork));
-               XFS_CORRUPTION_ERROR("xfs_iformat_local", XFS_ERRLEVEL_LOW,
-                                    ip->i_mount, dip);
+               xfs_inode_verifier_error(ip, -EFSCORRUPTED,
+                               "xfs_iformat_local", dip, sizeof(*dip),
+                               __this_address);
                return -EFSCORRUPTED;
        }
 
@@ -246,8 +217,9 @@ xfs_iformat_extents(
        if (unlikely(size < 0 || size > XFS_DFORK_SIZE(dip, mp, whichfork))) {
                xfs_warn(ip->i_mount, "corrupt inode %Lu ((a)extents = %d).",
                        (unsigned long long) ip->i_ino, nex);
-               XFS_CORRUPTION_ERROR("xfs_iformat_extents(1)", XFS_ERRLEVEL_LOW,
-                                    mp, dip);
+               xfs_inode_verifier_error(ip, -EFSCORRUPTED,
+                               "xfs_iformat_extents(1)", dip, sizeof(*dip),
+                               __this_address);
                return -EFSCORRUPTED;
        }
 
@@ -260,10 +232,14 @@ xfs_iformat_extents(
 
                xfs_iext_first(ifp, &icur);
                for (i = 0; i < nex; i++, dp++) {
+                       xfs_failaddr_t  fa;
+
                        xfs_bmbt_disk_get_all(dp, &new);
-                       if (!xfs_bmbt_validate_extent(mp, whichfork, &new)) {
-                               XFS_ERROR_REPORT("xfs_iformat_extents(2)",
-                                                XFS_ERRLEVEL_LOW, mp);
+                       fa = xfs_bmap_validate_extent(ip, whichfork, &new);
+                       if (fa) {
+                               xfs_inode_verifier_error(ip, -EFSCORRUPTED,
+                                               "xfs_iformat_extents(2)",
+                                               dp, sizeof(*dp), fa);
                                return -EFSCORRUPTED;
                        }
 
@@ -313,14 +289,16 @@ xfs_iformat_btree(
         */
        if (unlikely(XFS_IFORK_NEXTENTS(ip, whichfork) <=
                                        XFS_IFORK_MAXEXT(ip, whichfork) ||
+                    nrecs == 0 ||
                     XFS_BMDR_SPACE_CALC(nrecs) >
                                        XFS_DFORK_SIZE(dip, mp, whichfork) ||
                     XFS_IFORK_NEXTENTS(ip, whichfork) > ip->i_d.di_nblocks) ||
                     level == 0 || level > XFS_BTREE_MAXLEVELS) {
                xfs_warn(mp, "corrupt inode %Lu (btree).",
                                        (unsigned long long) ip->i_ino);
-               XFS_CORRUPTION_ERROR("xfs_iformat_btree", XFS_ERRLEVEL_LOW,
-                                        mp, dip);
+               xfs_inode_verifier_error(ip, -EFSCORRUPTED,
+                               "xfs_iformat_btree", dfp, size,
+                               __this_address);
                return -EFSCORRUPTED;
        }
 
@@ -609,7 +587,7 @@ xfs_iextents_copy(
        for_each_xfs_iext(ifp, &icur, &rec) {
                if (isnullstartblock(rec.br_startblock))
                        continue;
-               ASSERT(xfs_bmbt_validate_extent(ip->i_mount, whichfork, &rec));
+               ASSERT(xfs_bmap_validate_extent(ip, whichfork, &rec) == NULL);
                xfs_bmbt_disk_set_all(dp, &rec);
                trace_xfs_write_extent(ip, &icur, state, _RET_IP_);
                copied += sizeof(struct xfs_bmbt_rec);
@@ -737,3 +715,45 @@ xfs_ifork_init_cow(
        ip->i_cformat = XFS_DINODE_FMT_EXTENTS;
        ip->i_cnextents = 0;
 }
+
+/* Default fork content verifiers. */
+struct xfs_ifork_ops xfs_default_ifork_ops = {
+       .verify_attr    = xfs_attr_shortform_verify,
+       .verify_dir     = xfs_dir2_sf_verify,
+       .verify_symlink = xfs_symlink_shortform_verify,
+};
+
+/* Verify the inline contents of the data fork of an inode. */
+xfs_failaddr_t
+xfs_ifork_verify_data(
+       struct xfs_inode        *ip,
+       struct xfs_ifork_ops    *ops)
+{
+       /* Non-local data fork, we're done. */
+       if (ip->i_d.di_format != XFS_DINODE_FMT_LOCAL)
+               return NULL;
+
+       /* Check the inline data fork if there is one. */
+       switch (VFS_I(ip)->i_mode & S_IFMT) {
+       case S_IFDIR:
+               return ops->verify_dir(ip);
+       case S_IFLNK:
+               return ops->verify_symlink(ip);
+       default:
+               return NULL;
+       }
+}
+
+/* Verify the inline contents of the attr fork of an inode. */
+xfs_failaddr_t
+xfs_ifork_verify_attr(
+       struct xfs_inode        *ip,
+       struct xfs_ifork_ops    *ops)
+{
+       /* There has to be an attr fork allocated if aformat is local. */
+       if (ip->i_d.di_aformat != XFS_DINODE_FMT_LOCAL)
+               return NULL;
+       if (!XFS_IFORK_PTR(ip, XFS_ATTR_FORK))
+               return __this_address;
+       return ops->verify_attr(ip);
+}