Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hfs: prevent btree data loss on ENOSPC

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>

authored by

Ernesto A. Fernández and committed by
Linus Torvalds
54640c75 d92915c3

+49 -19
+28 -19
fs/hfs/btree.c
··· 220 220 return node; 221 221 } 222 222 223 + /* Make sure @tree has enough space for the @rsvd_nodes */ 224 + int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes) 225 + { 226 + struct inode *inode = tree->inode; 227 + u32 count; 228 + int res; 229 + 230 + while (tree->free_nodes < rsvd_nodes) { 231 + res = hfs_extend_file(inode); 232 + if (res) 233 + return res; 234 + HFS_I(inode)->phys_size = inode->i_size = 235 + (loff_t)HFS_I(inode)->alloc_blocks * 236 + HFS_SB(tree->sb)->alloc_blksz; 237 + HFS_I(inode)->fs_blocks = inode->i_size >> 238 + tree->sb->s_blocksize_bits; 239 + inode_set_bytes(inode, inode->i_size); 240 + count = inode->i_size >> tree->node_size_shift; 241 + tree->free_nodes += count - tree->node_count; 242 + tree->node_count = count; 243 + } 244 + return 0; 245 + } 246 + 223 247 struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) 224 248 { 225 249 struct hfs_bnode *node, *next_node; ··· 253 229 u16 off16; 254 230 u16 len; 255 231 u8 *data, byte, m; 256 - int i; 232 + int i, res; 257 233 258 - while (!tree->free_nodes) { 259 - struct inode *inode = tree->inode; 260 - u32 count; 261 - int res; 262 - 263 - res = hfs_extend_file(inode); 264 - if (res) 265 - return ERR_PTR(res); 266 - HFS_I(inode)->phys_size = inode->i_size = 267 - (loff_t)HFS_I(inode)->alloc_blocks * 268 - HFS_SB(tree->sb)->alloc_blksz; 269 - HFS_I(inode)->fs_blocks = inode->i_size >> 270 - tree->sb->s_blocksize_bits; 271 - inode_set_bytes(inode, inode->i_size); 272 - count = inode->i_size >> tree->node_size_shift; 273 - tree->free_nodes = count - tree->node_count; 274 - tree->node_count = count; 275 - } 234 + res = hfs_bmap_reserve(tree, 1); 235 + if (res) 236 + return ERR_PTR(res); 276 237 277 238 nidx = 0; 278 239 node = hfs_bnode_find(tree, nidx);
+1
fs/hfs/btree.h
··· 82 82 extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp); 83 83 extern void hfs_btree_close(struct hfs_btree *); 84 84 extern void hfs_btree_write(struct hfs_btree *); 85 + extern int hfs_bmap_reserve(struct hfs_btree *, int); 85 86 extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *); 86 87 extern void hfs_bmap_free(struct hfs_bnode *node); 87 88
+16
fs/hfs/catalog.c
··· 97 97 if (err) 98 98 return err; 99 99 100 + /* 101 + * Fail early and avoid ENOSPC during the btree operations. We may 102 + * have to split the root node at most once. 103 + */ 104 + err = hfs_bmap_reserve(fd.tree, 2 * fd.tree->depth); 105 + if (err) 106 + goto err2; 107 + 100 108 hfs_cat_build_key(sb, fd.search_key, cnid, NULL); 101 109 entry_size = hfs_cat_build_thread(sb, &entry, S_ISDIR(inode->i_mode) ? 102 110 HFS_CDR_THD : HFS_CDR_FTH, ··· 302 294 if (err) 303 295 return err; 304 296 dst_fd = src_fd; 297 + 298 + /* 299 + * Fail early and avoid ENOSPC during the btree operations. We may 300 + * have to split the root node at most once. 301 + */ 302 + err = hfs_bmap_reserve(src_fd.tree, 2 * src_fd.tree->depth); 303 + if (err) 304 + goto out; 305 305 306 306 /* find the old dir entry and read the data */ 307 307 hfs_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
+4
fs/hfs/extent.c
··· 117 117 if (HFS_I(inode)->flags & HFS_FLG_EXT_NEW) { 118 118 if (res != -ENOENT) 119 119 return res; 120 + /* Fail early and avoid ENOSPC during the btree operation */ 121 + res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1); 122 + if (res) 123 + return res; 120 124 hfs_brec_insert(fd, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec)); 121 125 HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW); 122 126 } else {