hfs: prevent btree data loss on ENOSPC
authorErnesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
Tue, 30 Oct 2018 22:06:17 +0000 (15:06 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 31 Oct 2018 15:54:13 +0000 (08:54 -0700)
Inserting a new 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 or
extents.

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

There is no need to reserve space before deleting a catalog record, as we
do for hfsplus.  This difference is because hfs index nodes have fixed
length keys.

Link: http://lkml.kernel.org/r/ab5fc8a7d5ffccfd5f27b1cf2cb4ceb6c110da74.1536269131.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>
fs/hfs/btree.c
fs/hfs/btree.h
fs/hfs/catalog.c
fs/hfs/extent.c

index 374b568..98b96ff 100644 (file)
@@ -220,25 +220,17 @@ static struct hfs_bnode *hfs_bmap_new_bmap(struct hfs_bnode *prev, u32 idx)
        return node;
 }
 
        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;
-
-       while (!tree->free_nodes) {
-               struct inode *inode = tree->inode;
-               u32 count;
-               int res;
+       struct inode *inode = tree->inode;
+       u32 count;
+       int res;
 
 
+       while (tree->free_nodes < rsvd_nodes) {
                res = hfs_extend_file(inode);
                if (res)
                res = hfs_extend_file(inode);
                if (res)
-                       return ERR_PTR(res);
+                       return res;
                HFS_I(inode)->phys_size = inode->i_size =
                                (loff_t)HFS_I(inode)->alloc_blocks *
                                HFS_SB(tree->sb)->alloc_blksz;
                HFS_I(inode)->phys_size = inode->i_size =
                                (loff_t)HFS_I(inode)->alloc_blocks *
                                HFS_SB(tree->sb)->alloc_blksz;
@@ -246,9 +238,26 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
                                          tree->sb->s_blocksize_bits;
                inode_set_bytes(inode, inode->i_size);
                count = inode->i_size >> tree->node_size_shift;
                                          tree->sb->s_blocksize_bits;
                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;
        }
                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);
 
        nidx = 0;
        node = hfs_bnode_find(tree, nidx);
index c8b252d..dcc2aab 100644 (file)
@@ -82,6 +82,7 @@ struct hfs_find_data {
 extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp);
 extern void hfs_btree_close(struct hfs_btree *);
 extern void hfs_btree_write(struct hfs_btree *);
 extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp);
 extern void hfs_btree_close(struct hfs_btree *);
 extern void hfs_btree_write(struct hfs_btree *);
+extern int hfs_bmap_reserve(struct hfs_btree *, int);
 extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *);
 extern void hfs_bmap_free(struct hfs_bnode *node);
 
 extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *);
 extern void hfs_bmap_free(struct hfs_bnode *node);
 
index 8a66405..d365bf0 100644 (file)
@@ -97,6 +97,14 @@ int hfs_cat_create(u32 cnid, struct inode *dir, const struct qstr *str, struct i
        if (err)
                return err;
 
        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;
+
        hfs_cat_build_key(sb, fd.search_key, cnid, NULL);
        entry_size = hfs_cat_build_thread(sb, &entry, S_ISDIR(inode->i_mode) ?
                        HFS_CDR_THD : HFS_CDR_FTH,
        hfs_cat_build_key(sb, fd.search_key, cnid, NULL);
        entry_size = hfs_cat_build_thread(sb, &entry, S_ISDIR(inode->i_mode) ?
                        HFS_CDR_THD : HFS_CDR_FTH,
@@ -295,6 +303,14 @@ int hfs_cat_move(u32 cnid, struct inode *src_dir, const struct qstr *src_name,
                return err;
        dst_fd = src_fd;
 
                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 once.
+        */
+       err = hfs_bmap_reserve(src_fd.tree, 2 * src_fd.tree->depth);
+       if (err)
+               goto out;
+
        /* find the old dir entry and read the data */
        hfs_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
        err = hfs_brec_find(&src_fd);
        /* find the old dir entry and read the data */
        hfs_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
        err = hfs_brec_find(&src_fd);
index 5d01826..0c638c6 100644 (file)
@@ -117,6 +117,10 @@ static int __hfs_ext_write_extent(struct inode *inode, struct hfs_find_data *fd)
        if (HFS_I(inode)->flags & HFS_FLG_EXT_NEW) {
                if (res != -ENOENT)
                        return res;
        if (HFS_I(inode)->flags & HFS_FLG_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, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec));
                HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
        } else {
                hfs_brec_insert(fd, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec));
                HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
        } else {