diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index d6d07116ce97..2c069029cef7 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -275,6 +275,7 @@ static int ocfs2_xattr_tree_list_index_block(struct inode *inode, size_t buffer_size); static int ocfs2_xattr_create_index_block(struct inode *inode, + struct ocfs2_xattr_info *xi, struct ocfs2_xattr_search *xs, struct ocfs2_xattr_set_ctxt *ctxt); @@ -3375,7 +3376,7 @@ static int ocfs2_xattr_block_set(struct inode *inode, else if ((ret != -ENOSPC) || ctxt->set_abort) goto end; else { - ret = ocfs2_xattr_create_index_block(inode, xs, ctxt); + ret = ocfs2_xattr_create_index_block(inode, xi, xs, ctxt); if (ret) goto end; } @@ -4749,13 +4750,10 @@ static void ocfs2_cp_xattr_block_to_bucket(struct inode *inode, * real place of the xattr. */ static void ocfs2_xattr_update_xattr_search(struct inode *inode, - struct ocfs2_xattr_search *xs, - struct buffer_head *old_bh) + struct ocfs2_xattr_info *xi, + struct ocfs2_xattr_search *xs) { - char *buf = old_bh->b_data; - struct ocfs2_xattr_block *old_xb = (struct ocfs2_xattr_block *)buf; - struct ocfs2_xattr_header *old_xh = &old_xb->xb_attrs.xb_header; - int i; + int i, count; xs->header = bucket_xh(xs->bucket); xs->base = bucket_block(xs->bucket, 0); @@ -4764,8 +4762,43 @@ static void ocfs2_xattr_update_xattr_search(struct inode *inode, if (xs->not_found) return; - i = xs->here - old_xh->xh_entries; - xs->here = &xs->header->xh_entries[i]; + /* + * A block holds its entries in insertion order while a bucket holds + * them by name hash, so ocfs2_cp_xattr_block_to_bucket() sorted the + * copies and the index this entry had in the block no longer says + * where it is. It copied every entry and every name unchanged, and + * (type, name) pairs are unique within a block, so find it again by + * name. + * + * Search for the name being set, not for the name xs->here carries. + * A failed set may have removed that entry, and ocfs2_xa_remove_entry() + * then memmoves its successor into the slot. Comparing every entry + * rather than narrowing on the hash also finds an entry stored under + * the legacy signed hash, which ocfs2_find_xe_in_bucket() would skip. + */ + count = le16_to_cpu(xs->header->xh_count); + for (i = 0; i < count; i++) { + struct ocfs2_xattr_entry *xe = &xs->header->xh_entries[i]; + + if (ocfs2_xattr_get_type(xe) != xi->xi_name_index) + continue; + if (xe->xe_name_len != xi->xi_name_len) + continue; + if (!memcmp(xs->base + le16_to_cpu(xe->xe_name_offset), + xi->xi_name, xi->xi_name_len)) { + xs->here = xe; + return; + } + } + + /* + * The entry did not survive the failed set, so this has become an + * insert. Point xs->here back inside the bucket: it still refers to + * the block being converted, which is about to be rewritten as a tree + * root. + */ + xs->not_found = -ENODATA; + xs->here = xs->header->xh_entries; } /* @@ -4773,6 +4806,7 @@ static void ocfs2_xattr_update_xattr_search(struct inode *inode, * is allocated and the xattr block header is rewritten. */ static int ocfs2_xattr_create_index_block(struct inode *inode, + struct ocfs2_xattr_info *xi, struct ocfs2_xattr_search *xs, struct ocfs2_xattr_set_ctxt *ctxt) { @@ -4834,7 +4868,7 @@ static int ocfs2_xattr_create_index_block(struct inode *inode, ocfs2_cp_xattr_block_to_bucket(inode, xb_bh, xs->bucket); ocfs2_xattr_bucket_journal_dirty(handle, xs->bucket); - ocfs2_xattr_update_xattr_search(inode, xs, xb_bh); + ocfs2_xattr_update_xattr_search(inode, xi, xs); /* Change from ocfs2_xattr_header to ocfs2_xattr_tree_root */ memset(&xb->xb_attrs, 0, inode->i_sb->s_blocksize -