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

Configure Feed

Select the types of activity you want to include in your feed.

hfsplus: fix volume corruption issue for generic/101

The xfstests' test-case generic/101 leaves HFS+ volume
in corrupted state:

sudo ./check generic/101
FSTYP -- hfsplus
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.17.0-rc1+ #4 SMP PREEMPT_DYNAMIC Wed Oct 1 15:02:44 PDT 2025
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch

generic/101 _check_generic_filesystem: filesystem on /dev/loop51 is inconsistent
(see XFSTESTS-2/xfstests-dev/results//generic/101.full for details)

Ran: generic/101
Failures: generic/101
Failed 1 of 1 tests

sudo fsck.hfsplus -d /dev/loop51
** /dev/loop51
Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K.
Executing fsck_hfs (version 540.1-Linux).
** Checking non-journaled HFS Plus Volume.
The volume name is untitled
** Checking extents overflow file.
** Checking catalog file.
** Checking multi-linked files.
** Checking catalog hierarchy.
** Checking extended attributes file.
** Checking volume bitmap.
** Checking volume information.
Invalid volume free block count
(It should be 2614350 instead of 2614382)
Verify Status: VIStat = 0x8000, ABTStat = 0x0000 EBTStat = 0x0000
CBTStat = 0x0000 CatStat = 0x00000000
** Repairing volume.
** Rechecking volume.
** Checking non-journaled HFS Plus Volume.
The volume name is untitled
** Checking extents overflow file.
** Checking catalog file.
** Checking multi-linked files.
** Checking catalog hierarchy.
** Checking extended attributes file.
** Checking volume bitmap.
** Checking volume information.
** The volume untitled was repaired successfully.

This test executes such steps: "Test that if we truncate a file
to a smaller size, then truncate it to its original size or
a larger size, then fsyncing it and a power failure happens,
the file will have the range [first_truncate_size, last_size[ with
all bytes having a value of 0x00 if we read it the next time
the filesystem is mounted.".

HFS+ keeps volume's free block count in the superblock.
However, hfsplus_file_fsync() doesn't store superblock's
content. As a result, superblock contains not correct
value of free blocks if a power failure happens.

This patch adds functionality of saving superblock's
content during hfsplus_file_fsync() call.

sudo ./check generic/101
FSTYP -- hfsplus
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.18.0-rc3+ #96 SMP PREEMPT_DYNAMIC Wed Nov 19 12:47:37 PST 2025
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch

generic/101 32s ... 30s
Ran: generic/101
Passed all 1 tests

sudo fsck.hfsplus -d /dev/loop51
** /dev/loop51
Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K.
Executing fsck_hfs (version 540.1-Linux).
** Checking non-journaled HFS Plus Volume.
The volume name is untitled
** Checking extents overflow file.
** Checking catalog file.
** Checking multi-linked files.
** Checking catalog hierarchy.
** Checking extended attributes file.
** Checking volume bitmap.
** Checking volume information.
** The volume untitled appears to be OK.

Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
Link: https://lore.kernel.org/r/20251119223219.1824434-1-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>

+65 -33
+2
fs/hfsplus/hfsplus_fs.h
··· 477 477 /* super.c */ 478 478 struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino); 479 479 void hfsplus_mark_mdb_dirty(struct super_block *sb); 480 + void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr); 481 + int hfsplus_commit_superblock(struct super_block *sb); 480 482 481 483 /* tables.c */ 482 484 extern u16 hfsplus_case_fold_table[];
+9
fs/hfsplus/inode.c
··· 325 325 struct inode *inode = file->f_mapping->host; 326 326 struct hfsplus_inode_info *hip = HFSPLUS_I(inode); 327 327 struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); 328 + struct hfsplus_vh *vhdr = sbi->s_vhdr; 328 329 int error = 0, error2; 329 330 330 331 error = file_write_and_wait_range(file, start, end); ··· 368 367 if (!error) 369 368 error = error2; 370 369 } 370 + 371 + mutex_lock(&sbi->vh_mutex); 372 + hfsplus_prepare_volume_header_for_commit(vhdr); 373 + mutex_unlock(&sbi->vh_mutex); 374 + 375 + error2 = hfsplus_commit_superblock(inode->i_sb); 376 + if (!error) 377 + error = error2; 371 378 372 379 if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) 373 380 blkdev_issue_flush(inode->i_sb->s_bdev);
+54 -33
fs/hfsplus/super.c
··· 187 187 } 188 188 } 189 189 190 - static int hfsplus_sync_fs(struct super_block *sb, int wait) 190 + int hfsplus_commit_superblock(struct super_block *sb) 191 191 { 192 192 struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); 193 193 struct hfsplus_vh *vhdr = sbi->s_vhdr; 194 194 int write_backup = 0; 195 - int error, error2; 196 - 197 - if (!wait) 198 - return 0; 195 + int error = 0, error2; 199 196 200 197 hfs_dbg("starting...\n"); 201 - 202 - /* 203 - * Explicitly write out the special metadata inodes. 204 - * 205 - * While these special inodes are marked as hashed and written 206 - * out peridocically by the flusher threads we redirty them 207 - * during writeout of normal inodes, and thus the life lock 208 - * prevents us from getting the latest state to disk. 209 - */ 210 - error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping); 211 - error2 = filemap_write_and_wait(sbi->ext_tree->inode->i_mapping); 212 - if (!error) 213 - error = error2; 214 - if (sbi->attr_tree) { 215 - error2 = 216 - filemap_write_and_wait(sbi->attr_tree->inode->i_mapping); 217 - if (!error) 218 - error = error2; 219 - } 220 - error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping); 221 - if (!error) 222 - error = error2; 223 198 224 199 mutex_lock(&sbi->vh_mutex); 225 200 mutex_lock(&sbi->alloc_mutex); ··· 224 249 sbi->part_start + sbi->sect_count - 2, 225 250 sbi->s_backup_vhdr_buf, NULL, REQ_OP_WRITE); 226 251 if (!error) 227 - error2 = error; 252 + error = error2; 228 253 out: 229 254 mutex_unlock(&sbi->alloc_mutex); 230 255 mutex_unlock(&sbi->vh_mutex); 256 + 257 + hfs_dbg("finished: err %d\n", error); 258 + 259 + return error; 260 + } 261 + 262 + static int hfsplus_sync_fs(struct super_block *sb, int wait) 263 + { 264 + struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); 265 + int error, error2; 266 + 267 + if (!wait) 268 + return 0; 269 + 270 + hfs_dbg("starting...\n"); 271 + 272 + /* 273 + * Explicitly write out the special metadata inodes. 274 + * 275 + * While these special inodes are marked as hashed and written 276 + * out peridocically by the flusher threads we redirty them 277 + * during writeout of normal inodes, and thus the life lock 278 + * prevents us from getting the latest state to disk. 279 + */ 280 + error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping); 281 + error2 = filemap_write_and_wait(sbi->ext_tree->inode->i_mapping); 282 + if (!error) 283 + error = error2; 284 + if (sbi->attr_tree) { 285 + error2 = 286 + filemap_write_and_wait(sbi->attr_tree->inode->i_mapping); 287 + if (!error) 288 + error = error2; 289 + } 290 + error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping); 291 + if (!error) 292 + error = error2; 293 + 294 + error2 = hfsplus_commit_superblock(sb); 295 + if (!error) 296 + error = error2; 231 297 232 298 if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) 233 299 blkdev_issue_flush(sb->s_bdev); ··· 410 394 .statfs = hfsplus_statfs, 411 395 .show_options = hfsplus_show_options, 412 396 }; 397 + 398 + void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr) 399 + { 400 + vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); 401 + vhdr->modify_date = hfsp_now2mt(); 402 + be32_add_cpu(&vhdr->write_count, 1); 403 + vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); 404 + vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); 405 + } 413 406 414 407 static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc) 415 408 { ··· 587 562 * H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused 588 563 * all three are registered with Apple for our use 589 564 */ 590 - vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); 591 - vhdr->modify_date = hfsp_now2mt(); 592 - be32_add_cpu(&vhdr->write_count, 1); 593 - vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); 594 - vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); 565 + hfsplus_prepare_volume_header_for_commit(vhdr); 595 566 hfsplus_sync_fs(sb, 1); 596 567 597 568 if (!sbi->hidden_dir) {