| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2005 Silicon Graphics, Inc. |
| * Copyright (c) 2013 Red Hat, Inc. |
| * All Rights Reserved. |
| */ |
| #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_da_format.h" |
| #include "xfs_inode.h" |
| #include "xfs_trans.h" |
| #include "xfs_bmap.h" |
| #include "xfs_da_btree.h" |
| #include "xfs_attr.h" |
| #include "xfs_attr_sf.h" |
| #include "xfs_attr_leaf.h" |
| #include "xfs_error.h" |
| #include "xfs_trace.h" |
| #include "xfs_dir2.h" |
| #include "xfs_health.h" |
| |
| STATIC int |
| xfs_attr_shortform_compare(const void *a, const void *b) |
| { |
| xfs_attr_sf_sort_t *sa, *sb; |
| |
| sa = (xfs_attr_sf_sort_t *)a; |
| sb = (xfs_attr_sf_sort_t *)b; |
| if (sa->hash < sb->hash) { |
| return -1; |
| } else if (sa->hash > sb->hash) { |
| return 1; |
| } else { |
| return sa->entno - sb->entno; |
| } |
| } |
| |
| #define XFS_ISRESET_CURSOR(cursor) \ |
| (!((cursor)->initted) && !((cursor)->hashval) && \ |
| !((cursor)->blkno) && !((cursor)->offset)) |
| /* |
| * Copy out entries of shortform attribute lists for attr_list(). |
| * Shortform attribute lists are not stored in hashval sorted order. |
| * If the output buffer is not large enough to hold them all, then |
| * we have to calculate each entries' hashvalue and sort them before |
| * we can begin returning them to the user. |
| */ |
| static int |
| xfs_attr_shortform_list( |
| struct xfs_attr_list_context *context) |
| { |
| struct xfs_attrlist_cursor_kern *cursor = &context->cursor; |
| struct xfs_inode *dp = context->dp; |
| struct xfs_attr_sf_sort *sbuf, *sbp; |
| struct xfs_attr_sf_hdr *sf = dp->i_af.if_data; |
| struct xfs_attr_sf_entry *sfe; |
| int sbsize, nsbuf, count, i; |
| int error = 0; |
| |
| ASSERT(sf != NULL); |
| if (!sf->count) |
| return 0; |
| |
| trace_xfs_attr_list_sf(context); |
| |
| /* |
| * If the buffer is large enough and the cursor is at the start, |
| * do not bother with sorting since we will return everything in |
| * one buffer and another call using the cursor won't need to be |
| * made. |
| * Note the generous fudge factor of 16 overhead bytes per entry. |
| * If bufsize is zero then put_listent must be a search function |
| * and can just scan through what we have. |
| */ |
| if (context->bufsize == 0 || |
| (XFS_ISRESET_CURSOR(cursor) && |
| (dp->i_af.if_bytes + sf->count * 16) < context->bufsize)) { |
| for (i = 0, sfe = xfs_attr_sf_firstentry(sf); i < sf->count; i++) { |
| if (XFS_IS_CORRUPT(context->dp->i_mount, |
| !xfs_attr_namecheck(sfe->flags, |
| sfe->nameval, |
| sfe->namelen))) { |
| xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK); |
| return -EFSCORRUPTED; |
| } |
| context->put_listent(context, |
| sfe->flags, |
| sfe->nameval, |
| (int)sfe->namelen, |
| &sfe->nameval[sfe->namelen], |
| (int)sfe->valuelen); |
| /* |
| * Either search callback finished early or |
| * didn't fit it all in the buffer after all. |
| */ |
| if (context->seen_enough) |
| break; |
| sfe = xfs_attr_sf_nextentry(sfe); |
| } |
| trace_xfs_attr_list_sf_all(context); |
| return 0; |
| } |
| |
| /* do no more for a search callback */ |
| if (context->bufsize == 0) |
| return 0; |
| |
| /* |
| * It didn't all fit, so we have to sort everything on hashval. |
| */ |
| sbsize = sf->count * sizeof(*sbuf); |
| sbp = sbuf = kmalloc(sbsize, GFP_KERNEL | __GFP_NOFAIL); |
| |
| /* |
| * Scan the attribute list for the rest of the entries, storing |
| * the relevant info from only those that match into a buffer. |
| */ |
| nsbuf = 0; |
| for (i = 0, sfe = xfs_attr_sf_firstentry(sf); i < sf->count; i++) { |
| if (unlikely( |
| ((char *)sfe < (char *)sf) || |
| ((char *)sfe >= ((char *)sf + dp->i_af.if_bytes)) || |
| !xfs_attr_check_namespace(sfe->flags))) { |
| XFS_CORRUPTION_ERROR("xfs_attr_shortform_list", |
| XFS_ERRLEVEL_LOW, |
| context->dp->i_mount, sfe, |
| sizeof(*sfe)); |
| kfree(sbuf); |
| xfs_dirattr_mark_sick(dp, XFS_ATTR_FORK); |
| return -EFSCORRUPTED; |
| } |
| |
| sbp->entno = i; |
| sbp->name = sfe->nameval; |
| sbp->namelen = sfe->namelen; |
| /* These are bytes, and both on-disk, don't endian-flip */ |
| sbp->value = &sfe->nameval[sfe->namelen]; |
| sbp->valuelen = sfe->valuelen; |
| sbp->flags = sfe->flags; |
| sbp->hash = xfs_attr_hashval(dp->i_mount, sfe->flags, |
| sfe->nameval, sfe->namelen, |
| sfe->nameval + sfe->namelen, |
| sfe->valuelen); |
| sfe = xfs_attr_sf_nextentry(sfe); |
| sbp++; |
| nsbuf++; |
| } |
| |
| /* |
| * Sort the entries on hash then entno. |
| */ |
| xfs_sort(sbuf, nsbuf, sizeof(*sbuf), xfs_attr_shortform_compare); |
| |
| /* |
| * Re-find our place IN THE SORTED LIST. |
| */ |
| count = 0; |
| cursor->initted = 1; |
| cursor->blkno = 0; |
| for (sbp = sbuf, i = 0; i < nsbuf; i++, sbp++) { |
| if (sbp->hash == cursor->hashval) { |
| if (cursor->offset == count) { |
| break; |
| } |
| count++; |
| } else if (sbp->hash > cursor->hashval) { |
| break; |
| } |
| } |
| if (i == nsbuf) |
| goto out; |
| |
| /* |
| * Loop putting entries into the user buffer. |
| */ |
| for ( ; i < nsbuf; i++, sbp++) { |
| if (cursor->hashval != sbp->hash) { |
| cursor->hashval = sbp->hash; |
| cursor->offset = 0; |
| } |
| if (XFS_IS_CORRUPT(context->dp->i_mount, |
| !xfs_attr_namecheck(sbp->flags, sbp->name, |
| sbp->namelen))) { |
| xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK); |
| error = -EFSCORRUPTED; |
| goto out; |
| } |
| context->put_listent(context, |
| sbp->flags, |
| sbp->name, |
| sbp->namelen, |
| sbp->value, |
| sbp->valuelen); |
| if (context->seen_enough) |
| break; |
| cursor->offset++; |
| } |
| out: |
| kfree(sbuf); |
| return error; |
| } |
| |
| /* |
| * We didn't find the block & hash mentioned in the cursor state, so |
| * walk down the attr btree looking for the hash. |
| */ |
| STATIC int |
| xfs_attr_node_list_lookup( |
| struct xfs_attr_list_context *context, |
| struct xfs_attrlist_cursor_kern *cursor, |
| struct xfs_buf **pbp) |
| { |
| struct xfs_da3_icnode_hdr nodehdr; |
| struct xfs_da_intnode *node; |
| struct xfs_da_node_entry *btree; |
| struct xfs_inode *dp = context->dp; |
| struct xfs_mount *mp = dp->i_mount; |
| struct xfs_trans *tp = context->tp; |
| struct xfs_buf *bp; |
| xfs_failaddr_t fa; |
| int i; |
| int error = 0; |
| unsigned int expected_level = 0; |
| uint16_t magic; |
| |
| ASSERT(*pbp == NULL); |
| cursor->blkno = 0; |
| for (;;) { |
| error = xfs_da3_node_read(tp, dp, cursor->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; |
| if (magic != XFS_DA_NODE_MAGIC && |
| magic != XFS_DA3_NODE_MAGIC) { |
| XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, |
| node, sizeof(*node)); |
| goto out_corruptbuf; |
| } |
| |
| fa = xfs_da3_node_header_check(bp, dp->i_ino); |
| if (fa) |
| goto out_corruptbuf; |
| |
| xfs_da3_node_hdr_from_disk(mp, &nodehdr, node); |
| |
| /* Tree taller than we can handle; bail out! */ |
| if (nodehdr.level >= XFS_DA_NODE_MAXDEPTH) |
| goto out_corruptbuf; |
| |
| /* Check the level from the root node. */ |
| if (cursor->blkno == 0) |
| expected_level = nodehdr.level - 1; |
| else if (expected_level != nodehdr.level) |
| goto out_corruptbuf; |
| else |
| expected_level--; |
| |
| btree = nodehdr.btree; |
| for (i = 0; i < nodehdr.count; btree++, i++) { |
| if (cursor->hashval <= be32_to_cpu(btree->hashval)) { |
| cursor->blkno = be32_to_cpu(btree->before); |
| trace_xfs_attr_list_node_descend(context, |
| btree); |
| break; |
| } |
| } |
| xfs_trans_brelse(tp, bp); |
| |
| if (i == nodehdr.count) |
| return 0; |
| |
| /* We can't point back to the root. */ |
| if (XFS_IS_CORRUPT(mp, cursor->blkno == 0)) { |
| xfs_dirattr_mark_sick(dp, XFS_ATTR_FORK); |
| return -EFSCORRUPTED; |
| } |
| } |
| |
| fa = xfs_attr3_leaf_header_check(bp, dp->i_ino); |
| if (fa) { |
| __xfs_buf_mark_corrupt(bp, fa); |
| goto out_releasebuf; |
| } |
| |
| if (expected_level != 0) |
| goto out_corruptbuf; |
| |
| *pbp = bp; |
| return 0; |
| |
| out_corruptbuf: |
| xfs_buf_mark_corrupt(bp); |
| out_releasebuf: |
| xfs_trans_brelse(tp, bp); |
| xfs_dirattr_mark_sick(dp, XFS_ATTR_FORK); |
| return -EFSCORRUPTED; |
| } |
| |
| STATIC int |
| xfs_attr_node_list( |
| struct xfs_attr_list_context *context) |
| { |
| struct xfs_attrlist_cursor_kern *cursor = &context->cursor; |
| struct xfs_attr3_icleaf_hdr leafhdr; |
| struct xfs_attr_leafblock *leaf; |
| struct xfs_da_intnode *node; |
| struct xfs_buf *bp; |
| struct xfs_inode *dp = context->dp; |
| struct xfs_mount *mp = dp->i_mount; |
| xfs_failaddr_t fa; |
| int error = 0; |
| |
| trace_xfs_attr_node_list(context); |
| |
| cursor->initted = 1; |
| |
| /* |
| * Do all sorts of validation on the passed-in cursor structure. |
| * If anything is amiss, ignore the cursor and look up the hashval |
| * starting from the btree root. |
| */ |
| bp = NULL; |
| if (cursor->blkno > 0) { |
| struct xfs_attr_leaf_entry *entries; |
| |
| error = xfs_da3_node_read(context->tp, dp, cursor->blkno, &bp, |
| XFS_ATTR_FORK); |
| if (xfs_metadata_is_sick(error)) |
| xfs_dirattr_mark_sick(dp, XFS_ATTR_FORK); |
| if (error != 0 && error != -EFSCORRUPTED) |
| return error; |
| if (!bp) |
| goto need_lookup; |
| |
| node = bp->b_addr; |
| switch (be16_to_cpu(node->hdr.info.magic)) { |
| case XFS_DA_NODE_MAGIC: |
| case XFS_DA3_NODE_MAGIC: |
| trace_xfs_attr_list_wrong_blk(context); |
| fa = xfs_da3_node_header_check(bp, dp->i_ino); |
| if (fa) { |
| __xfs_buf_mark_corrupt(bp, fa); |
| xfs_dirattr_mark_sick(dp, XFS_ATTR_FORK); |
| } |
| xfs_trans_brelse(context->tp, bp); |
| bp = NULL; |
| break; |
| case XFS_ATTR_LEAF_MAGIC: |
| case XFS_ATTR3_LEAF_MAGIC: |
| leaf = bp->b_addr; |
| fa = xfs_attr3_leaf_header_check(bp, dp->i_ino); |
| if (fa) { |
| __xfs_buf_mark_corrupt(bp, fa); |
| xfs_trans_brelse(context->tp, bp); |
| xfs_dirattr_mark_sick(dp, XFS_ATTR_FORK); |
| bp = NULL; |
| break; |
| } |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, |
| &leafhdr, leaf); |
| entries = xfs_attr3_leaf_entryp(leaf); |
| if (cursor->hashval > be32_to_cpu( |
| entries[leafhdr.count - 1].hashval)) { |
| trace_xfs_attr_list_wrong_blk(context); |
| xfs_trans_brelse(context->tp, bp); |
| bp = NULL; |
| } else if (cursor->hashval <= be32_to_cpu( |
| entries[0].hashval)) { |
| trace_xfs_attr_list_wrong_blk(context); |
| xfs_trans_brelse(context->tp, bp); |
| bp = NULL; |
| } |
| break; |
| default: |
| trace_xfs_attr_list_wrong_blk(context); |
| xfs_trans_brelse(context->tp, bp); |
| bp = NULL; |
| } |
| } |
| |
| /* |
| * We did not find what we expected given the cursor's contents, |
| * so we start from the top and work down based on the hash value. |
| * Note that start of node block is same as start of leaf block. |
| */ |
| if (bp == NULL) { |
| need_lookup: |
| error = xfs_attr_node_list_lookup(context, cursor, &bp); |
| if (error || !bp) |
| return error; |
| } |
| ASSERT(bp != NULL); |
| |
| /* |
| * Roll upward through the blocks, processing each leaf block in |
| * order. As long as there is space in the result buffer, keep |
| * adding the information. |
| */ |
| for (;;) { |
| leaf = bp->b_addr; |
| error = xfs_attr3_leaf_list_int(bp, context); |
| if (error) |
| break; |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf); |
| if (context->seen_enough || leafhdr.forw == 0) |
| break; |
| cursor->blkno = leafhdr.forw; |
| xfs_trans_brelse(context->tp, bp); |
| error = xfs_attr3_leaf_read(context->tp, dp, dp->i_ino, |
| cursor->blkno, &bp); |
| if (error) |
| return error; |
| } |
| xfs_trans_brelse(context->tp, bp); |
| return error; |
| } |
| |
| /* |
| * Copy out attribute list entries for attr_list(), for leaf attribute lists. |
| */ |
| int |
| xfs_attr3_leaf_list_int( |
| struct xfs_buf *bp, |
| struct xfs_attr_list_context *context) |
| { |
| struct xfs_attrlist_cursor_kern *cursor = &context->cursor; |
| struct xfs_attr_leafblock *leaf; |
| struct xfs_attr3_icleaf_hdr ichdr; |
| struct xfs_attr_leaf_entry *entries; |
| struct xfs_attr_leaf_entry *entry; |
| int i; |
| struct xfs_mount *mp = context->dp->i_mount; |
| |
| trace_xfs_attr_list_leaf(context); |
| |
| leaf = bp->b_addr; |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &ichdr, leaf); |
| entries = xfs_attr3_leaf_entryp(leaf); |
| |
| cursor->initted = 1; |
| |
| /* |
| * Re-find our place in the leaf block if this is a new syscall. |
| */ |
| if (context->resynch) { |
| entry = &entries[0]; |
| for (i = 0; i < ichdr.count; entry++, i++) { |
| if (be32_to_cpu(entry->hashval) == cursor->hashval) { |
| if (cursor->offset == context->dupcnt) { |
| context->dupcnt = 0; |
| break; |
| } |
| context->dupcnt++; |
| } else if (be32_to_cpu(entry->hashval) > |
| cursor->hashval) { |
| context->dupcnt = 0; |
| break; |
| } |
| } |
| if (i == ichdr.count) { |
| trace_xfs_attr_list_notfound(context); |
| return 0; |
| } |
| } else { |
| entry = &entries[0]; |
| i = 0; |
| } |
| context->resynch = 0; |
| |
| /* |
| * We have found our place, start copying out the new attributes. |
| */ |
| for (; i < ichdr.count; entry++, i++) { |
| char *name; |
| void *value; |
| int namelen, valuelen; |
| |
| if (be32_to_cpu(entry->hashval) != cursor->hashval) { |
| cursor->hashval = be32_to_cpu(entry->hashval); |
| cursor->offset = 0; |
| } |
| |
| if ((entry->flags & XFS_ATTR_INCOMPLETE) && |
| !context->allow_incomplete) |
| continue; |
| |
| if (entry->flags & XFS_ATTR_LOCAL) { |
| xfs_attr_leaf_name_local_t *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 { |
| xfs_attr_leaf_name_remote_t *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); |
| } |
| |
| if (XFS_IS_CORRUPT(context->dp->i_mount, |
| !xfs_attr_namecheck(entry->flags, name, |
| namelen))) { |
| xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK); |
| return -EFSCORRUPTED; |
| } |
| context->put_listent(context, entry->flags, |
| name, namelen, value, valuelen); |
| if (context->seen_enough) |
| break; |
| cursor->offset++; |
| } |
| trace_xfs_attr_list_leaf_end(context); |
| return 0; |
| } |
| |
| /* |
| * Copy out attribute entries for attr_list(), for leaf attribute lists. |
| */ |
| STATIC int |
| xfs_attr_leaf_list( |
| struct xfs_attr_list_context *context) |
| { |
| struct xfs_buf *bp; |
| int error; |
| |
| trace_xfs_attr_leaf_list(context); |
| |
| context->cursor.blkno = 0; |
| error = xfs_attr3_leaf_read(context->tp, context->dp, |
| context->dp->i_ino, 0, &bp); |
| if (error) |
| return error; |
| |
| error = xfs_attr3_leaf_list_int(bp, context); |
| xfs_trans_brelse(context->tp, bp); |
| return error; |
| } |
| |
| int |
| xfs_attr_list_ilocked( |
| struct xfs_attr_list_context *context) |
| { |
| struct xfs_inode *dp = context->dp; |
| int error; |
| |
| xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL); |
| |
| /* |
| * Decide on what work routines to call based on the inode size. |
| */ |
| if (!xfs_inode_hasattr(dp)) |
| return 0; |
| if (dp->i_af.if_format == XFS_DINODE_FMT_LOCAL) |
| return xfs_attr_shortform_list(context); |
| |
| /* Prerequisite for xfs_attr_is_leaf */ |
| error = xfs_iread_extents(NULL, dp, XFS_ATTR_FORK); |
| if (error) |
| return error; |
| |
| if (xfs_attr_is_leaf(dp)) |
| return xfs_attr_leaf_list(context); |
| return xfs_attr_node_list(context); |
| } |
| |
| int |
| xfs_attr_list( |
| struct xfs_attr_list_context *context) |
| { |
| struct xfs_inode *dp = context->dp; |
| uint lock_mode; |
| int error; |
| |
| XFS_STATS_INC(dp->i_mount, xs_attr_list); |
| |
| if (xfs_is_shutdown(dp->i_mount)) |
| return -EIO; |
| |
| lock_mode = xfs_ilock_attr_map_shared(dp); |
| error = xfs_attr_list_ilocked(context); |
| xfs_iunlock(dp, lock_mode); |
| return error; |
| } |