summaryrefslogtreecommitdiffstats
path: root/fs/hfsplus
diff options
context:
space:
mode:
authorErnesto A. Fernández <ernesto.mnd.fernandez@gmail.com>2018-10-30 18:06:14 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-10-31 11:54:13 -0400
commitd92915c35bfaf763d78bf1d5ac7f183420e3bd99 (patch)
tree6c55bdd6cdbff28215f7a4df2c61ed31716ca09a /fs/hfsplus
parentef75bcc5763d130451a99825f247d301088b790b (diff)
hfsplus: prevent btree data loss on ENOSPC
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>
Diffstat (limited to 'fs/hfsplus')
-rw-r--r--fs/hfsplus/attributes.c10
-rw-r--r--fs/hfsplus/btree.c44
-rw-r--r--fs/hfsplus/catalog.c24
-rw-r--r--fs/hfsplus/extents.c4
-rw-r--r--fs/hfsplus/hfsplus_fs.h2
5 files changed, 68 insertions, 16 deletions
diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c
index 2bab6b3cdba4..e6d554476db4 100644
--- a/fs/hfsplus/attributes.c
+++ b/fs/hfsplus/attributes.c
@@ -217,6 +217,11 @@ int hfsplus_create_attr(struct inode *inode,
217 if (err) 217 if (err)
218 goto failed_init_create_attr; 218 goto failed_init_create_attr;
219 219
220 /* Fail early and avoid ENOSPC during the btree operation */
221 err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
222 if (err)
223 goto failed_create_attr;
224
220 if (name) { 225 if (name) {
221 err = hfsplus_attr_build_key(sb, fd.search_key, 226 err = hfsplus_attr_build_key(sb, fd.search_key,
222 inode->i_ino, name); 227 inode->i_ino, name);
@@ -313,6 +318,11 @@ int hfsplus_delete_attr(struct inode *inode, const char *name)
313 if (err) 318 if (err)
314 return err; 319 return err;
315 320
321 /* Fail early and avoid ENOSPC during the btree operation */
322 err = hfs_bmap_reserve(fd.tree, fd.tree->depth);
323 if (err)
324 goto out;
325
316 if (name) { 326 if (name) {
317 err = hfsplus_attr_build_key(sb, fd.search_key, 327 err = hfsplus_attr_build_key(sb, fd.search_key,
318 inode->i_ino, name); 328 inode->i_ino, name);
diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c
index de14b2b6881b..236efe51eca6 100644
--- a/fs/hfsplus/btree.c
+++ b/fs/hfsplus/btree.c
@@ -342,26 +342,21 @@ static struct hfs_bnode *hfs_bmap_new_bmap(struct hfs_bnode *prev, u32 idx)
342 return node; 342 return node;
343} 343}
344 344
345struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) 345/* Make sure @tree has enough space for the @rsvd_nodes */
346int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes)
346{ 347{
347 struct hfs_bnode *node, *next_node; 348 struct inode *inode = tree->inode;
348 struct page **pagep; 349 struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
349 u32 nidx, idx; 350 u32 count;
350 unsigned off; 351 int res;
351 u16 off16;
352 u16 len;
353 u8 *data, byte, m;
354 int i;
355 352
356 while (!tree->free_nodes) { 353 if (rsvd_nodes <= 0)
357 struct inode *inode = tree->inode; 354 return 0;
358 struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
359 u32 count;
360 int res;
361 355
356 while (tree->free_nodes < rsvd_nodes) {
362 res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree)); 357 res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree));
363 if (res) 358 if (res)
364 return ERR_PTR(res); 359 return res;
365 hip->phys_size = inode->i_size = 360 hip->phys_size = inode->i_size =
366 (loff_t)hip->alloc_blocks << 361 (loff_t)hip->alloc_blocks <<
367 HFSPLUS_SB(tree->sb)->alloc_blksz_shift; 362 HFSPLUS_SB(tree->sb)->alloc_blksz_shift;
@@ -369,9 +364,26 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
369 hip->alloc_blocks << HFSPLUS_SB(tree->sb)->fs_shift; 364 hip->alloc_blocks << HFSPLUS_SB(tree->sb)->fs_shift;
370 inode_set_bytes(inode, inode->i_size); 365 inode_set_bytes(inode, inode->i_size);
371 count = inode->i_size >> tree->node_size_shift; 366 count = inode->i_size >> tree->node_size_shift;
372 tree->free_nodes = count - tree->node_count; 367 tree->free_nodes += count - tree->node_count;
373 tree->node_count = count; 368 tree->node_count = count;
374 } 369 }
370 return 0;
371}
372
373struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
374{
375 struct hfs_bnode *node, *next_node;
376 struct page **pagep;
377 u32 nidx, idx;
378 unsigned off;
379 u16 off16;
380 u16 len;
381 u8 *data, byte, m;
382 int i, res;
383
384 res = hfs_bmap_reserve(tree, 1);
385 if (res)
386 return ERR_PTR(res);
375 387
376 nidx = 0; 388 nidx = 0;
377 node = hfs_bnode_find(tree, nidx); 389 node = hfs_bnode_find(tree, nidx);
diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c
index a196369ba779..35472cba750e 100644
--- a/fs/hfsplus/catalog.c
+++ b/fs/hfsplus/catalog.c
@@ -265,6 +265,14 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir,
265 if (err) 265 if (err)
266 return err; 266 return err;
267 267
268 /*
269 * Fail early and avoid ENOSPC during the btree operations. We may
270 * have to split the root node at most once.
271 */
272 err = hfs_bmap_reserve(fd.tree, 2 * fd.tree->depth);
273 if (err)
274 goto err2;
275
268 hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid); 276 hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
269 entry_size = hfsplus_fill_cat_thread(sb, &entry, 277 entry_size = hfsplus_fill_cat_thread(sb, &entry,
270 S_ISDIR(inode->i_mode) ? 278 S_ISDIR(inode->i_mode) ?
@@ -333,6 +341,14 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
333 if (err) 341 if (err)
334 return err; 342 return err;
335 343
344 /*
345 * Fail early and avoid ENOSPC during the btree operations. We may
346 * have to split the root node at most once.
347 */
348 err = hfs_bmap_reserve(fd.tree, 2 * (int)fd.tree->depth - 2);
349 if (err)
350 goto out;
351
336 if (!str) { 352 if (!str) {
337 int len; 353 int len;
338 354
@@ -433,6 +449,14 @@ int hfsplus_rename_cat(u32 cnid,
433 return err; 449 return err;
434 dst_fd = src_fd; 450 dst_fd = src_fd;
435 451
452 /*
453 * Fail early and avoid ENOSPC during the btree operations. We may
454 * have to split the root node at most twice.
455 */
456 err = hfs_bmap_reserve(src_fd.tree, 4 * (int)src_fd.tree->depth - 1);
457 if (err)
458 goto out;
459
436 /* find the old dir entry and read the data */ 460 /* find the old dir entry and read the data */
437 err = hfsplus_cat_build_key(sb, src_fd.search_key, 461 err = hfsplus_cat_build_key(sb, src_fd.search_key,
438 src_dir->i_ino, src_name); 462 src_dir->i_ino, src_name);
diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c
index 8e0f59767694..8a8893d522ef 100644
--- a/fs/hfsplus/extents.c
+++ b/fs/hfsplus/extents.c
@@ -100,6 +100,10 @@ static int __hfsplus_ext_write_extent(struct inode *inode,
100 if (hip->extent_state & HFSPLUS_EXT_NEW) { 100 if (hip->extent_state & HFSPLUS_EXT_NEW) {
101 if (res != -ENOENT) 101 if (res != -ENOENT)
102 return res; 102 return res;
103 /* Fail early and avoid ENOSPC during the btree operation */
104 res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1);
105 if (res)
106 return res;
103 hfs_brec_insert(fd, hip->cached_extents, 107 hfs_brec_insert(fd, hip->cached_extents,
104 sizeof(hfsplus_extent_rec)); 108 sizeof(hfsplus_extent_rec));
105 hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW); 109 hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
index 8e039435958a..dd7ad9f13e3a 100644
--- a/fs/hfsplus/hfsplus_fs.h
+++ b/fs/hfsplus/hfsplus_fs.h
@@ -311,6 +311,7 @@ static inline unsigned short hfsplus_min_io_size(struct super_block *sb)
311#define hfs_btree_open hfsplus_btree_open 311#define hfs_btree_open hfsplus_btree_open
312#define hfs_btree_close hfsplus_btree_close 312#define hfs_btree_close hfsplus_btree_close
313#define hfs_btree_write hfsplus_btree_write 313#define hfs_btree_write hfsplus_btree_write
314#define hfs_bmap_reserve hfsplus_bmap_reserve
314#define hfs_bmap_alloc hfsplus_bmap_alloc 315#define hfs_bmap_alloc hfsplus_bmap_alloc
315#define hfs_bmap_free hfsplus_bmap_free 316#define hfs_bmap_free hfsplus_bmap_free
316#define hfs_bnode_read hfsplus_bnode_read 317#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,
395struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id); 396struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id);
396void hfs_btree_close(struct hfs_btree *tree); 397void hfs_btree_close(struct hfs_btree *tree);
397int hfs_btree_write(struct hfs_btree *tree); 398int hfs_btree_write(struct hfs_btree *tree);
399int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes);
398struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); 400struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
399void hfs_bmap_free(struct hfs_bnode *node); 401void hfs_bmap_free(struct hfs_bnode *node);
400 402