Skip to content

Commit ee8422d

Browse files
committed
hfsplus: fix potential Allocation File corruption after fsync
The generic/348 test-case has revealed the issue of HFS+ volume corruption after simulated power failure: FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.15.0-rc4+ #8 SMP PREEMPT_DYNAMIC Thu May 1 16:43:22 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/348 _check_generic_filesystem: filesystem on /dev/loop51 is inconsistent (see xfstests-dev/results//generic/348.full for details) The fsck tool complains about Allocation File (block bitmap) corruption as a result of such event. The generic/348 creates a symlink, fsync its parent directory, power fail and mount again the filesystem. Currently, HFS+ logic has several flags HFSPLUS_I_CAT_DIRTY, HFSPLUS_I_EXT_DIRTY, HFSPLUS_I_ATTR_DIRTY, HFSPLUS_I_ALLOC_DIRTY. If inode operation modified the Catalog File, Extents Overflow File, Attributes File, or Allocation File, then inode is marked as dirty and one of the mentioned flags has been set. When hfsplus_file_fsync() has been called, then this set of flags is checked and dirty b-tree or/and block bitmap is flushed. However, block bitmap can be modified during file's content allocation. It means that if we call hfsplus_file_fsync() for directory, then we never flush the modified Allocation File in such case because such inode cannot receive HFSPLUS_I_ALLOC_DIRTY flag. Moreover, this inode-centric model is not good at all because Catalog File, Extents Overflow File, Attributes File, and Allocation File represent the whole state of file system metadata. This inode-centric policy is the main reason of the issue. This patch saves the whole approach of using HFSPLUS_I_CAT_DIRTY, HFSPLUS_I_EXT_DIRTY, HFSPLUS_I_ATTR_DIRTY, and HFSPLUS_I_ALLOC_DIRTY flags. But Catalog File, Extents Overflow File, Attributes File, and Allocation File have associated inodes. And namely these inodes become the mechanism of checking the dirty state of metadata. The hfsplus_file_fsync() method checks the dirtiness of file system metadata by testing HFSPLUS_I_CAT_DIRTY, HFSPLUS_I_EXT_DIRTY, HFSPLUS_I_ATTR_DIRTY, and HFSPLUS_I_ALLOC_DIRTY flags of Catalog File's, Extents Overflow File's, Attributes File's, or Allocation File's inodes. As a result, even if we call hfsplus_file_fsync() for parent folder, then dirty Allocation File will be flushed anyway. Signed-off-by: Viacheslav Dubeyko <[email protected]> cc: John Paul Adrian Glaubitz <[email protected]> cc: Yangtao Li <[email protected]> cc: [email protected] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Viacheslav Dubeyko <[email protected]>
1 parent 6de23f8 commit ee8422d

8 files changed

Lines changed: 65 additions & 9 deletions

File tree

fs/hfsplus/attributes.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ int hfsplus_create_attr_nolock(struct inode *inode, const char *name,
241241
return err;
242242
}
243243

244+
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
244245
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
245246

246247
return 0;
@@ -326,6 +327,8 @@ static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
326327
if (err)
327328
return err;
328329

330+
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(inode->i_sb),
331+
HFSPLUS_I_ATTR_DIRTY);
329332
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
330333
return err;
331334
}

fs/hfsplus/catalog.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir,
313313
if (S_ISDIR(inode->i_mode))
314314
hfsplus_subfolders_inc(dir);
315315
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
316+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
316317
hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY);
317318

318319
hfs_find_exit(&fd);
@@ -418,6 +419,7 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
418419
if (type == HFSPLUS_FOLDER)
419420
hfsplus_subfolders_dec(dir);
420421
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
422+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
421423
hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY);
422424

423425
if (type == HFSPLUS_FILE || type == HFSPLUS_FOLDER) {
@@ -540,6 +542,7 @@ int hfsplus_rename_cat(u32 cnid,
540542
}
541543
err = hfs_brec_insert(&dst_fd, &entry, entry_size);
542544

545+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
543546
hfsplus_mark_inode_dirty(dst_dir, HFSPLUS_I_CAT_DIRTY);
544547
hfsplus_mark_inode_dirty(src_dir, HFSPLUS_I_CAT_DIRTY);
545548
out:

fs/hfsplus/dir.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@ static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir,
478478
if (!inode)
479479
goto out;
480480

481+
hfs_dbg("dir->i_ino %lu, inode->i_ino %lu\n",
482+
dir->i_ino, inode->i_ino);
483+
481484
res = page_symlink(inode, symname, strlen(symname) + 1);
482485
if (res)
483486
goto out_err;
@@ -526,6 +529,9 @@ static int hfsplus_mknod(struct mnt_idmap *idmap, struct inode *dir,
526529
if (!inode)
527530
goto out;
528531

532+
hfs_dbg("dir->i_ino %lu, inode->i_ino %lu\n",
533+
dir->i_ino, inode->i_ino);
534+
529535
if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode))
530536
init_special_inode(inode, mode, rdev);
531537

fs/hfsplus/extents.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ static int __hfsplus_ext_write_extent(struct inode *inode,
121121
* redirty the inode. Instead the callers have to be careful
122122
* to explicily mark the inode dirty, too.
123123
*/
124+
set_bit(HFSPLUS_I_EXT_DIRTY,
125+
&HFSPLUS_I(HFSPLUS_EXT_TREE_I(inode->i_sb))->flags);
124126
set_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
125127

126128
return 0;
@@ -513,6 +515,8 @@ int hfsplus_file_extend(struct inode *inode, bool zeroout)
513515
if (!res) {
514516
hip->alloc_blocks += len;
515517
mutex_unlock(&hip->extents_lock);
518+
hfsplus_mark_inode_dirty(HFSPLUS_SB(sb)->alloc_file,
519+
HFSPLUS_I_ALLOC_DIRTY);
516520
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
517521
return 0;
518522
}
@@ -582,6 +586,7 @@ void hfsplus_file_truncate(struct inode *inode)
582586
/* XXX: We lack error handling of hfsplus_file_truncate() */
583587
return;
584588
}
589+
585590
while (1) {
586591
if (alloc_cnt == hip->first_blocks) {
587592
mutex_unlock(&fd.tree->tree_lock);
@@ -623,5 +628,7 @@ void hfsplus_file_truncate(struct inode *inode)
623628
hip->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >>
624629
sb->s_blocksize_bits;
625630
inode_set_bytes(inode, hip->fs_blocks << sb->s_blocksize_bits);
631+
hfsplus_mark_inode_dirty(HFSPLUS_SB(sb)->alloc_file,
632+
HFSPLUS_I_ALLOC_DIRTY);
626633
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
627634
}

fs/hfsplus/hfsplus_fs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ static inline struct hfsplus_inode_info *HFSPLUS_I(struct inode *inode)
238238
return container_of(inode, struct hfsplus_inode_info, vfs_inode);
239239
}
240240

241+
#define HFSPLUS_CAT_TREE_I(sb) \
242+
HFSPLUS_SB(sb)->cat_tree->inode
243+
#define HFSPLUS_EXT_TREE_I(sb) \
244+
HFSPLUS_SB(sb)->ext_tree->inode
245+
#define HFSPLUS_ATTR_TREE_I(sb) \
246+
HFSPLUS_SB(sb)->attr_tree->inode
247+
241248
/*
242249
* Mark an inode dirty, and also mark the btree in which the
243250
* specific type of metadata is stored.

fs/hfsplus/inode.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
324324
{
325325
struct inode *inode = file->f_mapping->host;
326326
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
327+
struct super_block *sb = inode->i_sb;
327328
struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
328329
struct hfsplus_vh *vhdr = sbi->s_vhdr;
329330
int error = 0, error2;
@@ -344,29 +345,39 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
344345
/*
345346
* And explicitly write out the btrees.
346347
*/
347-
if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags))
348+
if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,
349+
&HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))->flags)) {
350+
clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags);
348351
error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping);
352+
}
349353

350-
if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags)) {
354+
if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,
355+
&HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))->flags)) {
356+
clear_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
351357
error2 =
352358
filemap_write_and_wait(sbi->ext_tree->inode->i_mapping);
353359
if (!error)
354360
error = error2;
355361
}
356362

357-
if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags)) {
358-
if (sbi->attr_tree) {
363+
if (sbi->attr_tree) {
364+
if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,
365+
&HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))->flags)) {
366+
clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags);
359367
error2 =
360368
filemap_write_and_wait(
361369
sbi->attr_tree->inode->i_mapping);
362370
if (!error)
363371
error = error2;
364-
} else {
365-
pr_err("sync non-existent attributes tree\n");
366372
}
373+
} else {
374+
if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags))
375+
pr_err("sync non-existent attributes tree\n");
367376
}
368377

369-
if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags)) {
378+
if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,
379+
&HFSPLUS_I(sbi->alloc_file)->flags)) {
380+
clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags);
370381
error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping);
371382
if (!error)
372383
error = error2;
@@ -709,6 +720,8 @@ int hfsplus_cat_write_inode(struct inode *inode)
709720
sizeof(struct hfsplus_cat_file));
710721
}
711722

723+
set_bit(HFSPLUS_I_CAT_DIRTY,
724+
&HFSPLUS_I(HFSPLUS_CAT_TREE_I(inode->i_sb))->flags);
712725
set_bit(HFSPLUS_I_CAT_DIRTY, &HFSPLUS_I(inode)->flags);
713726
out:
714727
hfs_find_exit(&fd);

fs/hfsplus/super.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
625625
}
626626

627627
mutex_unlock(&sbi->vh_mutex);
628+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb),
629+
HFSPLUS_I_CAT_DIRTY);
628630
hfsplus_mark_inode_dirty(sbi->hidden_dir,
629631
HFSPLUS_I_CAT_DIRTY);
630632
}

fs/hfsplus/xattr.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ static int hfsplus_create_attributes_file(struct super_block *sb)
236236
put_page(page);
237237
}
238238

239+
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
239240
hfsplus_mark_inode_dirty(attr_file, HFSPLUS_I_ATTR_DIRTY);
240241

241242
sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID);
@@ -314,8 +315,11 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
314315
hfs_bnode_write(cat_fd.bnode, &entry,
315316
cat_fd.entryoffset,
316317
sizeof(struct hfsplus_cat_folder));
317-
hfsplus_mark_inode_dirty(inode,
318+
hfsplus_mark_inode_dirty(
319+
HFSPLUS_CAT_TREE_I(inode->i_sb),
318320
HFSPLUS_I_CAT_DIRTY);
321+
hfsplus_mark_inode_dirty(inode,
322+
HFSPLUS_I_CAT_DIRTY);
319323
} else {
320324
err = -ERANGE;
321325
goto end_setxattr;
@@ -327,8 +331,11 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
327331
hfs_bnode_write(cat_fd.bnode, &entry,
328332
cat_fd.entryoffset,
329333
sizeof(struct hfsplus_cat_file));
330-
hfsplus_mark_inode_dirty(inode,
334+
hfsplus_mark_inode_dirty(
335+
HFSPLUS_CAT_TREE_I(inode->i_sb),
331336
HFSPLUS_I_CAT_DIRTY);
337+
hfsplus_mark_inode_dirty(inode,
338+
HFSPLUS_I_CAT_DIRTY);
332339
} else {
333340
err = -ERANGE;
334341
goto end_setxattr;
@@ -381,6 +388,8 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
381388
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
382389
offsetof(struct hfsplus_cat_folder, flags),
383390
cat_entry_flags);
391+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
392+
HFSPLUS_I_CAT_DIRTY);
384393
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
385394
} else if (cat_entry_type == HFSPLUS_FILE) {
386395
cat_entry_flags = hfs_bnode_read_u16(cat_fd.bnode,
@@ -392,6 +401,8 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
392401
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
393402
offsetof(struct hfsplus_cat_file, flags),
394403
cat_entry_flags);
404+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
405+
HFSPLUS_I_CAT_DIRTY);
395406
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
396407
} else {
397408
pr_err("invalid catalog entry type\n");
@@ -862,6 +873,8 @@ static int hfsplus_removexattr(struct inode *inode, const char *name)
862873
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
863874
offsetof(struct hfsplus_cat_folder, flags),
864875
flags);
876+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
877+
HFSPLUS_I_CAT_DIRTY);
865878
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
866879
} else if (cat_entry_type == HFSPLUS_FILE) {
867880
flags = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset +
@@ -873,6 +886,8 @@ static int hfsplus_removexattr(struct inode *inode, const char *name)
873886
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
874887
offsetof(struct hfsplus_cat_file, flags),
875888
flags);
889+
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
890+
HFSPLUS_I_CAT_DIRTY);
876891
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
877892
} else {
878893
pr_err("invalid catalog entry type\n");

0 commit comments

Comments
 (0)