]> 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 3c0be58ba25c29fa595520e0622df141e44f43cc..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;
@@ -59,69 +49,11 @@ xfs_iformat_fork(
        int                     error = 0;
        xfs_fsize_t             di_size;
 
-       if (unlikely(be32_to_cpu(dip->di_nextents) +
-                    be16_to_cpu(dip->di_anextents) >
-                    be64_to_cpu(dip->di_nblocks))) {
-               xfs_warn(ip->i_mount,
-                       "corrupt dinode %Lu, extent total = %d, nblocks = %Lu.",
-                       (unsigned long long)ip->i_ino,
-                       (int)(be32_to_cpu(dip->di_nextents) +
-                             be16_to_cpu(dip->di_anextents)),
-                       (unsigned long long)
-                               be64_to_cpu(dip->di_nblocks));
-               XFS_CORRUPTION_ERROR("xfs_iformat(1)", XFS_ERRLEVEL_LOW,
-                                    ip->i_mount, dip);
-               return -EFSCORRUPTED;
-       }
-
-       if (unlikely(dip->di_forkoff > ip->i_mount->m_sb.sb_inodesize)) {
-               xfs_warn(ip->i_mount, "corrupt dinode %Lu, forkoff = 0x%x.",
-                       (unsigned long long)ip->i_ino,
-                       dip->di_forkoff);
-               XFS_CORRUPTION_ERROR("xfs_iformat(2)", XFS_ERRLEVEL_LOW,
-                                    ip->i_mount, dip);
-               return -EFSCORRUPTED;
-       }
-
-       if (unlikely((ip->i_d.di_flags & XFS_DIFLAG_REALTIME) &&
-                    !ip->i_mount->m_rtdev_targp)) {
-               xfs_warn(ip->i_mount,
-                       "corrupt dinode %Lu, has realtime flag set.",
-                       ip->i_ino);
-               XFS_CORRUPTION_ERROR("xfs_iformat(realtime)",
-                                    XFS_ERRLEVEL_LOW, ip->i_mount, dip);
-               return -EFSCORRUPTED;
-       }
-
-       if (unlikely(xfs_is_reflink_inode(ip) && !S_ISREG(inode->i_mode))) {
-               xfs_warn(ip->i_mount,
-                       "corrupt dinode %llu, wrong file type for reflink.",
-                       ip->i_ino);
-               XFS_CORRUPTION_ERROR("xfs_iformat(reflink)",
-                                    XFS_ERRLEVEL_LOW, ip->i_mount, dip);
-               return -EFSCORRUPTED;
-       }
-
-       if (unlikely(xfs_is_reflink_inode(ip) &&
-           (ip->i_d.di_flags & XFS_DIFLAG_REALTIME))) {
-               xfs_warn(ip->i_mount,
-                       "corrupt dinode %llu, has reflink+realtime flag set.",
-                       ip->i_ino);
-               XFS_CORRUPTION_ERROR("xfs_iformat(reflink)",
-                                    XFS_ERRLEVEL_LOW, ip->i_mount, dip);
-               return -EFSCORRUPTED;
-       }
-
        switch (inode->i_mode & S_IFMT) {
        case S_IFIFO:
        case S_IFCHR:
        case S_IFBLK:
        case S_IFSOCK:
-               if (unlikely(dip->di_format != XFS_DINODE_FMT_DEV)) {
-                       XFS_CORRUPTION_ERROR("xfs_iformat(3)", XFS_ERRLEVEL_LOW,
-                                             ip->i_mount, dip);
-                       return -EFSCORRUPTED;
-               }
                ip->i_d.di_size = 0;
                inode->i_rdev = xfs_to_linux_dev_t(xfs_dinode_get_rdev(dip));
                break;
@@ -131,32 +63,7 @@ xfs_iformat_fork(
        case S_IFDIR:
                switch (dip->di_format) {
                case XFS_DINODE_FMT_LOCAL:
-                       /*
-                        * no local regular files yet
-                        */
-                       if (unlikely(S_ISREG(be16_to_cpu(dip->di_mode)))) {
-                               xfs_warn(ip->i_mount,
-                       "corrupt inode %Lu (local format for regular file).",
-                                       (unsigned long long) ip->i_ino);
-                               XFS_CORRUPTION_ERROR("xfs_iformat(4)",
-                                                    XFS_ERRLEVEL_LOW,
-                                                    ip->i_mount, dip);
-                               return -EFSCORRUPTED;
-                       }
-
                        di_size = be64_to_cpu(dip->di_size);
-                       if (unlikely(di_size < 0 ||
-                                    di_size > XFS_DFORK_DSIZE(dip, ip->i_mount))) {
-                               xfs_warn(ip->i_mount,
-                       "corrupt inode %Lu (bad size %Ld for local inode).",
-                                       (unsigned long long) ip->i_ino,
-                                       (long long) di_size);
-                               XFS_CORRUPTION_ERROR("xfs_iformat(5)",
-                                                    XFS_ERRLEVEL_LOW,
-                                                    ip->i_mount, dip);
-                               return -EFSCORRUPTED;
-                       }
-
                        size = (int)di_size;
                        error = xfs_iformat_local(ip, dip, XFS_DATA_FORK, size);
                        break;
@@ -167,28 +74,16 @@ xfs_iformat_fork(
                        error = xfs_iformat_btree(ip, dip, XFS_DATA_FORK);
                        break;
                default:
-                       XFS_ERROR_REPORT("xfs_iformat(6)", XFS_ERRLEVEL_LOW,
-                                        ip->i_mount);
                        return -EFSCORRUPTED;
                }
                break;
 
        default:
-               XFS_ERROR_REPORT("xfs_iformat(7)", XFS_ERRLEVEL_LOW, ip->i_mount);
                return -EFSCORRUPTED;
        }
        if (error)
                return error;
 
-       /* Check inline dir contents. */
-       if (S_ISDIR(inode->i_mode) && dip->di_format == XFS_DINODE_FMT_LOCAL) {
-               error = xfs_dir2_sf_verify(ip);
-               if (error) {
-                       xfs_idestroy_fork(ip, XFS_DATA_FORK);
-                       return error;
-               }
-       }
-
        if (xfs_is_reflink_inode(ip)) {
                ASSERT(ip->i_cowfp == NULL);
                xfs_ifork_init_cow(ip);
@@ -205,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:
@@ -297,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;
        }
 
@@ -333,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;
        }
 
@@ -347,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;
                        }
 
@@ -400,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;
        }
 
@@ -696,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);
@@ -824,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);
+}