| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) 2022-2024 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <djwong@kernel.org> |
| */ |
| #include "xfs.h" |
| #include "xfs_fs.h" |
| #include "xfs_shared.h" |
| #include "xfs_format.h" |
| #include "xfs_log_format.h" |
| #include "xfs_trans_resv.h" |
| #include "xfs_mount.h" |
| #include "xfs_inode.h" |
| #include "xfs_da_format.h" |
| #include "xfs_da_btree.h" |
| #include "xfs_attr.h" |
| #include "xfs_attr_leaf.h" |
| #include "xfs_attr_sf.h" |
| #include "xfs_trans.h" |
| #include "scrub/scrub.h" |
| #include "scrub/bitmap.h" |
| #include "scrub/dab_bitmap.h" |
| #include "scrub/listxattr.h" |
| |
| /* Call a function for every entry in a shortform xattr structure. */ |
| STATIC int |
| xchk_xattr_walk_sf( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| xchk_xattr_fn attr_fn, |
| void *priv) |
| { |
| struct xfs_attr_sf_hdr *hdr = ip->i_af.if_data; |
| struct xfs_attr_sf_entry *sfe; |
| unsigned int i; |
| int error; |
| |
| sfe = xfs_attr_sf_firstentry(hdr); |
| for (i = 0; i < hdr->count; i++) { |
| error = attr_fn(sc, ip, sfe->flags, sfe->nameval, sfe->namelen, |
| &sfe->nameval[sfe->namelen], sfe->valuelen, |
| priv); |
| if (error) |
| return error; |
| |
| sfe = xfs_attr_sf_nextentry(sfe); |
| } |
| |
| return 0; |
| } |
| |
| /* Call a function for every entry in this xattr leaf block. */ |
| STATIC int |
| xchk_xattr_walk_leaf_entries( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| xchk_xattr_fn attr_fn, |
| struct xfs_buf *bp, |
| void *priv) |
| { |
| struct xfs_attr3_icleaf_hdr ichdr; |
| struct xfs_mount *mp = sc->mp; |
| struct xfs_attr_leafblock *leaf = bp->b_addr; |
| struct xfs_attr_leaf_entry *entry; |
| unsigned int i; |
| int error; |
| |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &ichdr, leaf); |
| entry = xfs_attr3_leaf_entryp(leaf); |
| |
| for (i = 0; i < ichdr.count; entry++, i++) { |
| void *value; |
| unsigned char *name; |
| unsigned int namelen, valuelen; |
| |
| if (entry->flags & XFS_ATTR_LOCAL) { |
| struct xfs_attr_leaf_name_local *name_loc; |
| |
| name_loc = xfs_attr3_leaf_name_local(leaf, i); |
| name = name_loc->nameval; |
| namelen = name_loc->namelen; |
| value = &name_loc->nameval[name_loc->namelen]; |
| valuelen = be16_to_cpu(name_loc->valuelen); |
| } else { |
| struct xfs_attr_leaf_name_remote *name_rmt; |
| |
| name_rmt = xfs_attr3_leaf_name_remote(leaf, i); |
| name = name_rmt->name; |
| namelen = name_rmt->namelen; |
| value = NULL; |
| valuelen = be32_to_cpu(name_rmt->valuelen); |
| } |
| |
| error = attr_fn(sc, ip, entry->flags, name, namelen, value, |
| valuelen, priv); |
| if (error) |
| return error; |
| |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Call a function for every entry in a leaf-format xattr structure. Avoid |
| * memory allocations for the loop detector since there's only one block. |
| */ |
| STATIC int |
| xchk_xattr_walk_leaf( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| xchk_xattr_fn attr_fn, |
| void *priv) |
| { |
| struct xfs_buf *leaf_bp; |
| int error; |
| |
| error = xfs_attr3_leaf_read(sc->tp, ip, ip->i_ino, 0, &leaf_bp); |
| if (error) |
| return error; |
| |
| error = xchk_xattr_walk_leaf_entries(sc, ip, attr_fn, leaf_bp, priv); |
| xfs_trans_brelse(sc->tp, leaf_bp); |
| return error; |
| } |
| |
| /* Find the leftmost leaf in the xattr dabtree. */ |
| STATIC int |
| xchk_xattr_find_leftmost_leaf( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| struct xdab_bitmap *seen_dablks, |
| struct xfs_buf **leaf_bpp) |
| { |
| struct xfs_da3_icnode_hdr nodehdr; |
| struct xfs_mount *mp = sc->mp; |
| struct xfs_trans *tp = sc->tp; |
| struct xfs_da_intnode *node; |
| struct xfs_da_node_entry *btree; |
| struct xfs_buf *bp; |
| xfs_failaddr_t fa; |
| xfs_dablk_t blkno = 0; |
| unsigned int expected_level = 0; |
| int error; |
| |
| for (;;) { |
| xfs_extlen_t len = 1; |
| uint16_t magic; |
| |
| /* Make sure we haven't seen this new block already. */ |
| if (xdab_bitmap_test(seen_dablks, blkno, &len)) |
| return -EFSCORRUPTED; |
| |
| error = xfs_da3_node_read(tp, ip, blkno, &bp, XFS_ATTR_FORK); |
| if (error) |
| return error; |
| |
| node = bp->b_addr; |
| magic = be16_to_cpu(node->hdr.info.magic); |
| if (magic == XFS_ATTR_LEAF_MAGIC || |
| magic == XFS_ATTR3_LEAF_MAGIC) |
| break; |
| |
| error = -EFSCORRUPTED; |
| if (magic != XFS_DA_NODE_MAGIC && |
| magic != XFS_DA3_NODE_MAGIC) |
| goto out_buf; |
| |
| fa = xfs_da3_node_header_check(bp, ip->i_ino); |
| if (fa) |
| goto out_buf; |
| |
| xfs_da3_node_hdr_from_disk(mp, &nodehdr, node); |
| |
| if (nodehdr.count == 0 || nodehdr.level >= XFS_DA_NODE_MAXDEPTH) |
| goto out_buf; |
| |
| /* Check the level from the root node. */ |
| if (blkno == 0) |
| expected_level = nodehdr.level - 1; |
| else if (expected_level != nodehdr.level) |
| goto out_buf; |
| else |
| expected_level--; |
| |
| /* Remember that we've seen this node. */ |
| error = xdab_bitmap_set(seen_dablks, blkno, 1); |
| if (error) |
| goto out_buf; |
| |
| /* Find the next level towards the leaves of the dabtree. */ |
| btree = nodehdr.btree; |
| blkno = be32_to_cpu(btree->before); |
| xfs_trans_brelse(tp, bp); |
| } |
| |
| error = -EFSCORRUPTED; |
| fa = xfs_attr3_leaf_header_check(bp, ip->i_ino); |
| if (fa) |
| goto out_buf; |
| |
| if (expected_level != 0) |
| goto out_buf; |
| |
| /* Remember that we've seen this leaf. */ |
| error = xdab_bitmap_set(seen_dablks, blkno, 1); |
| if (error) |
| goto out_buf; |
| |
| *leaf_bpp = bp; |
| return 0; |
| |
| out_buf: |
| xfs_trans_brelse(tp, bp); |
| return error; |
| } |
| |
| /* Call a function for every entry in a node-format xattr structure. */ |
| STATIC int |
| xchk_xattr_walk_node( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| xchk_xattr_fn attr_fn, |
| xchk_xattrleaf_fn leaf_fn, |
| void *priv) |
| { |
| struct xfs_attr3_icleaf_hdr leafhdr; |
| struct xdab_bitmap seen_dablks; |
| struct xfs_mount *mp = sc->mp; |
| struct xfs_attr_leafblock *leaf; |
| struct xfs_buf *leaf_bp; |
| int error; |
| |
| xdab_bitmap_init(&seen_dablks); |
| |
| error = xchk_xattr_find_leftmost_leaf(sc, ip, &seen_dablks, &leaf_bp); |
| if (error) |
| goto out_bitmap; |
| |
| for (;;) { |
| xfs_extlen_t len; |
| |
| error = xchk_xattr_walk_leaf_entries(sc, ip, attr_fn, leaf_bp, |
| priv); |
| if (error) |
| goto out_leaf; |
| |
| /* Find the right sibling of this leaf block. */ |
| leaf = leaf_bp->b_addr; |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf); |
| if (leafhdr.forw == 0) |
| goto out_leaf; |
| |
| xfs_trans_brelse(sc->tp, leaf_bp); |
| |
| if (leaf_fn) { |
| error = leaf_fn(sc, priv); |
| if (error) |
| goto out_bitmap; |
| } |
| |
| /* Make sure we haven't seen this new leaf already. */ |
| len = 1; |
| if (xdab_bitmap_test(&seen_dablks, leafhdr.forw, &len)) { |
| error = -EFSCORRUPTED; |
| goto out_bitmap; |
| } |
| |
| error = xfs_attr3_leaf_read(sc->tp, ip, ip->i_ino, |
| leafhdr.forw, &leaf_bp); |
| if (error) |
| goto out_bitmap; |
| |
| /* Remember that we've seen this new leaf. */ |
| error = xdab_bitmap_set(&seen_dablks, leafhdr.forw, 1); |
| if (error) |
| goto out_leaf; |
| } |
| |
| out_leaf: |
| xfs_trans_brelse(sc->tp, leaf_bp); |
| out_bitmap: |
| xdab_bitmap_destroy(&seen_dablks); |
| return error; |
| } |
| |
| /* |
| * Call a function for every extended attribute in a file. |
| * |
| * Callers must hold the ILOCK. No validation or cursor restarts allowed. |
| * Returns -EFSCORRUPTED on any problem, including loops in the dabtree. |
| */ |
| int |
| xchk_xattr_walk( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| xchk_xattr_fn attr_fn, |
| xchk_xattrleaf_fn leaf_fn, |
| void *priv) |
| { |
| int error; |
| |
| xfs_assert_ilocked(ip, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL); |
| |
| if (!xfs_inode_hasattr(ip)) |
| return 0; |
| |
| if (ip->i_af.if_format == XFS_DINODE_FMT_LOCAL) |
| return xchk_xattr_walk_sf(sc, ip, attr_fn, priv); |
| |
| /* attr functions require that the attr fork is loaded */ |
| error = xfs_iread_extents(sc->tp, ip, XFS_ATTR_FORK); |
| if (error) |
| return error; |
| |
| if (xfs_attr_is_leaf(ip)) |
| return xchk_xattr_walk_leaf(sc, ip, attr_fn, priv); |
| |
| return xchk_xattr_walk_node(sc, ip, attr_fn, leaf_fn, priv); |
| } |