hfsplus: prevent btree data loss on ENOSPC
authorErnesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
Tue, 30 Oct 2018 22:06:14 +0000 (15:06 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 28 Nov 2019 17:28:47 +0000 (18:28 +0100)
[ Upstream commit d92915c35bfaf763d78bf1d5ac7f183420e3bd99 ]

Inserting or deleting a record in a btree may require splitting several of
its nodes.  If we hit ENOSPC halfway through, the new nodes will be left
orphaned and their records will be lost.  This could mean lost inodes,
extents or xattrs.

Henceforth, check the available disk space before making any changes.
This still leaves the potential problem of corruption on ENOMEM.

The patch can be tested with xfstests generic/027.

Link: http://lkml.kernel.org/r/4596eef22fbda137b4ffa0272d92f0da15364421.1536269129.git.ernesto.mnd.fernandez@gmail.com
Signed-off-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/hfsplus/attributes.c
fs/hfsplus/btree.c
fs/hfsplus/catalog.c
fs/hfsplus/extents.c
fs/hfsplus/hfsplus_fs.h

index e5b221de7de636e7d7b126f8ce3c7c978e1d56c9..d7455ea702878a86a55b2af3df0e99e0f7551587 100644 (file)
@@ -216,6 +216,11 @@ int hfsplus_create_attr(struct inode *inode,
        if (err)
                goto failed_init_create_attr;
 
+       /* Fail early and avoid ENOSPC during the btree operation */
+       err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
+       if (err)
+               goto failed_create_attr;
+
        if (name) {
                err = hfsplus_attr_build_key(sb, fd.search_key,
                                                inode->i_ino, name);
@@ -312,6 +317,11 @@ int hfsplus_delete_attr(struct inode *inode, const char *name)
        if (err)
                return err;
 
+       /* Fail early and avoid ENOSPC during the btree operation */
+       err = hfs_bmap_reserve(fd.tree, fd.tree->depth);
+       if (err)
+               goto out;
+
        if (name) {
                err = hfsplus_attr_build_key(sb, fd.search_key,
                                                inode->i_ino, name);
index 8d2256454efe6b4f9f737d046c24aa90b5c572d8..7e96b4c294f7ae0e062f9ebb0eee6193a81aac3c 100644 (file)
@@ -341,26 +341,21 @@ static struct hfs_bnode *hfs_bmap_new_bmap(struct hfs_bnode *prev, u32 idx)
        return node;
 }
 
-struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
+/* Make sure @tree has enough space for the @rsvd_nodes */
+int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes)
 {
-       struct hfs_bnode *node, *next_node;
-       struct page **pagep;
-       u32 nidx, idx;
-       unsigned off;
-       u16 off16;
-       u16 len;
-       u8 *data, byte, m;
-       int i;
+       struct inode *inode = tree->inode;
+       struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+       u32 count;
+       int res;
 
-       while (!tree->free_nodes) {
-               struct inode *inode = tree->inode;
-               struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
-               u32 count;
-               int res;
+       if (rsvd_nodes <= 0)
+               return 0;
 
+       while (tree->free_nodes < rsvd_nodes) {
                res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree));
                if (res)
-                       return ERR_PTR(res);
+                       return res;
                hip->phys_size = inode->i_size =
                        (loff_t)hip->alloc_blocks <<
                                HFSPLUS_SB(tree->sb)->alloc_blksz_shift;
@@ -368,9 +363,26 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
                        hip->alloc_blocks << HFSPLUS_SB(tree->sb)->fs_shift;
                inode_set_bytes(inode, inode->i_size);
                count = inode->i_size >> tree->node_size_shift;
-               tree->free_nodes = count - tree->node_count;
+               tree->free_nodes += count - tree->node_count;
                tree->node_count = count;
        }
+       return 0;
+}
+
+struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
+{
+       struct hfs_bnode *node, *next_node;
+       struct page **pagep;
+       u32 nidx, idx;
+       unsigned off;
+       u16 off16;
+       u16 len;
+       u8 *data, byte, m;
+       int i, res;
+
+       res = hfs_bmap_reserve(tree, 1);
+       if (res)
+               return ERR_PTR(res);
 
        nidx = 0;
        node = hfs_bnode_find(tree, nidx);
index a5e00f7a4c1434ba9d5718935c213fe0c2158868..947da72e72a303069cf0a396eff37af2650998f5 100644 (file)
@@ -264,6 +264,14 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir,
        if (err)
                return err;
 
+       /*
+        * Fail early and avoid ENOSPC during the btree operations. We may
+        * have to split the root node at most once.
+        */
+       err = hfs_bmap_reserve(fd.tree, 2 * fd.tree->depth);
+       if (err)
+               goto err2;
+
        hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
        entry_size = hfsplus_fill_cat_thread(sb, &entry,
                S_ISDIR(inode->i_mode) ?
@@ -332,6 +340,14 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
        if (err)
                return err;
 
+       /*
+        * Fail early and avoid ENOSPC during the btree operations. We may
+        * have to split the root node at most once.
+        */
+       err = hfs_bmap_reserve(fd.tree, 2 * (int)fd.tree->depth - 2);
+       if (err)
+               goto out;
+
        if (!str) {
                int len;
 
@@ -432,6 +448,14 @@ int hfsplus_rename_cat(u32 cnid,
                return err;
        dst_fd = src_fd;
 
+       /*
+        * Fail early and avoid ENOSPC during the btree operations. We may
+        * have to split the root node at most twice.
+        */
+       err = hfs_bmap_reserve(src_fd.tree, 4 * (int)src_fd.tree->depth - 1);
+       if (err)
+               goto out;
+
        /* find the old dir entry and read the data */
        err = hfsplus_cat_build_key(sb, src_fd.search_key,
                        src_dir->i_ino, src_name);
index feca524ce2a5c7d6034bf0a528679fe923381507..ce0b8f8374081e08dc0fe98c834c2357e664e20d 100644 (file)
@@ -99,6 +99,10 @@ static int __hfsplus_ext_write_extent(struct inode *inode,
        if (hip->extent_state & HFSPLUS_EXT_NEW) {
                if (res != -ENOENT)
                        return res;
+               /* Fail early and avoid ENOSPC during the btree operation */
+               res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1);
+               if (res)
+                       return res;
                hfs_brec_insert(fd, hip->cached_extents,
                                sizeof(hfsplus_extent_rec));
                hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
index a3f03b24746376ddfcb8bac254613334ae4747b9..35cd703c66045c4f6c114e314f02ce4ac97f1726 100644 (file)
@@ -311,6 +311,7 @@ static inline unsigned short hfsplus_min_io_size(struct super_block *sb)
 #define hfs_btree_open hfsplus_btree_open
 #define hfs_btree_close hfsplus_btree_close
 #define hfs_btree_write hfsplus_btree_write
+#define hfs_bmap_reserve hfsplus_bmap_reserve
 #define hfs_bmap_alloc hfsplus_bmap_alloc
 #define hfs_bmap_free hfsplus_bmap_free
 #define hfs_bnode_read hfsplus_bnode_read
@@ -395,6 +396,7 @@ u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size, u64 sectors,
 struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id);
 void hfs_btree_close(struct hfs_btree *tree);
 int hfs_btree_write(struct hfs_btree *tree);
+int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes);
 struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
 void hfs_bmap_free(struct hfs_bnode *node);