hfs_btree_open() sets SB_RDONLY when the map record of a b-tree doesn't mark the header node as used. That's fine while mounting. But hfsplus_create_attributes_file() calls it too when the first extended attribute is set on a filesystem that has no attributes file yet. That runs on the live filesystem without s_umount and a filesystem must not change SB_RDONLY behind the VFS' back. hfsplus_create_attributes_file() opens a tree it has just created itself with the header node marked as used, so there's nothing to check there. Move the check into hfsplus_fill_super() and do it for every tree it opens. No functional changes. Suggested-by: Viacheslav Dubeyko Signed-off-by: Christian Brauner (Amutable) --- fs/hfsplus/btree.c | 42 ++++++++++++++++++++++++++++-------------- fs/hfsplus/hfsplus_fs.h | 1 + fs/hfsplus/super.c | 5 +++++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index bd4dbdbc8f98..4a67569050e3 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -270,7 +270,6 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id) struct hfs_btree *tree; struct hfs_btree_header_rec *head; struct address_space *mapping; - struct hfs_bnode *node; struct inode *inode; struct page *page; unsigned int size; @@ -381,19 +380,6 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id) kunmap_local(head); put_page(page); - node = hfs_bnode_find(tree, HFSPLUS_TREE_HEAD); - if (IS_ERR(node)) - goto free_inode; - - if (!hfs_bmap_test_bit(node, 0)) { - pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\n", - sb->s_id, hfs_btree_name(id), id); - pr_warn("Run fsck.hfsplus to repair.\n"); - sb->s_flags |= SB_RDONLY; - } - - hfs_bnode_put(node); - return tree; fail_page: @@ -407,6 +393,34 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id) return NULL; } +/* + * Check the map record of a b-tree at mount. hfs_btree_open() can't do it + * because hfsplus_create_attributes_file() calls it on the live filesystem + * where SB_RDONLY must not be set. + */ +int hfsplus_btree_check_map(struct hfs_btree *tree) +{ + struct super_block *sb = tree->sb; + struct hfs_bnode *node; + + node = hfs_bnode_find(tree, HFSPLUS_TREE_HEAD); + if (IS_ERR(node)) { + pr_err("failed to load %s header node\n", + hfs_btree_name(tree->cnid)); + return PTR_ERR(node); + } + + if (!hfs_bmap_test_bit(node, 0)) { + pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\n", + sb->s_id, hfs_btree_name(tree->cnid), tree->cnid); + pr_warn("Run fsck.hfsplus to repair.\n"); + sb->s_flags |= SB_RDONLY; + } + + hfs_bnode_put(node); + return 0; +} + /* Release resources used by a btree */ void hfs_btree_close(struct hfs_btree *tree) { diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 6f240f87f8ff..58c780b09b6f 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -362,6 +362,7 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count); u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size, u64 sectors, int file_id); struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id); +int hfsplus_btree_check_map(struct hfs_btree *tree); void hfs_btree_close(struct hfs_btree *tree); int hfs_btree_write(struct hfs_btree *tree); int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes); diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index ff7d6b3336a6..532f23502df3 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -564,6 +564,11 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc) } sb->s_xattr = hfsplus_xattr_handlers; + if (hfsplus_btree_check_map(sbi->ext_tree) || + hfsplus_btree_check_map(sbi->cat_tree) || + (sbi->attr_tree && hfsplus_btree_check_map(sbi->attr_tree))) + goto out_close_attr_tree; + inode = hfsplus_iget(sb, HFSPLUS_ALLOC_CNID); if (IS_ERR(inode)) { pr_err("failed to load allocation file\n"); -- 2.53.0