diff options
author | Christoph Hellwig <hch@tuxera.com> | 2011-02-02 11:32:39 -0500 |
---|---|---|
committer | Christoph Hellwig <hch@tuxera.com> | 2011-02-03 18:33:51 -0500 |
commit | c5b8d0bce052949e173b5b32f96bd59bceaa2ab0 (patch) | |
tree | 5b99f48ad948c351ae07650c44009d847460329a /fs/hfsplus | |
parent | ebf53826e105f488f4f628703a108e98940d1dc5 (diff) |
hfsplus: fix failed mount handling
Currently the error handling in hfsplus_fill_super is a mess, and can
lead to accessing fields in the superblock that haven't been even set
up yet. Fix this by making sure we do not set up sb->s_root until we
have the mount fully set up, and before that do proper step by step
unwinding instead of using hfsplus_put_super as a big hammer.
Reported-by: Dan Williams <dcbw@redhat.com>
Signed-off-by: Christoph Hellwig <hch@tuxera.com>
Diffstat (limited to 'fs/hfsplus')
-rw-r--r-- | fs/hfsplus/super.c | 106 |
1 files changed, 61 insertions, 45 deletions
diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index 9a3b4795f43c..b49b55584c84 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c | |||
@@ -338,20 +338,22 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) | |||
338 | struct inode *root, *inode; | 338 | struct inode *root, *inode; |
339 | struct qstr str; | 339 | struct qstr str; |
340 | struct nls_table *nls = NULL; | 340 | struct nls_table *nls = NULL; |
341 | int err = -EINVAL; | 341 | int err; |
342 | 342 | ||
343 | err = -EINVAL; | ||
343 | sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); | 344 | sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); |
344 | if (!sbi) | 345 | if (!sbi) |
345 | return -ENOMEM; | 346 | goto out; |
346 | 347 | ||
347 | sb->s_fs_info = sbi; | 348 | sb->s_fs_info = sbi; |
348 | mutex_init(&sbi->alloc_mutex); | 349 | mutex_init(&sbi->alloc_mutex); |
349 | mutex_init(&sbi->vh_mutex); | 350 | mutex_init(&sbi->vh_mutex); |
350 | hfsplus_fill_defaults(sbi); | 351 | hfsplus_fill_defaults(sbi); |
352 | |||
353 | err = -EINVAL; | ||
351 | if (!hfsplus_parse_options(data, sbi)) { | 354 | if (!hfsplus_parse_options(data, sbi)) { |
352 | printk(KERN_ERR "hfs: unable to parse mount options\n"); | 355 | printk(KERN_ERR "hfs: unable to parse mount options\n"); |
353 | err = -EINVAL; | 356 | goto out_unload_nls; |
354 | goto cleanup; | ||
355 | } | 357 | } |
356 | 358 | ||
357 | /* temporarily use utf8 to correctly find the hidden dir below */ | 359 | /* temporarily use utf8 to correctly find the hidden dir below */ |
@@ -359,16 +361,14 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) | |||
359 | sbi->nls = load_nls("utf8"); | 361 | sbi->nls = load_nls("utf8"); |
360 | if (!sbi->nls) { | 362 | if (!sbi->nls) { |
361 | printk(KERN_ERR "hfs: unable to load nls for utf8\n"); | 363 | printk(KERN_ERR "hfs: unable to load nls for utf8\n"); |
362 | err = -EINVAL; | 364 | goto out_unload_nls; |
363 | goto cleanup; | ||
364 | } | 365 | } |
365 | 366 | ||
366 | /* Grab the volume header */ | 367 | /* Grab the volume header */ |
367 | if (hfsplus_read_wrapper(sb)) { | 368 | if (hfsplus_read_wrapper(sb)) { |
368 | if (!silent) | 369 | if (!silent) |
369 | printk(KERN_WARNING "hfs: unable to find HFS+ superblock\n"); | 370 | printk(KERN_WARNING "hfs: unable to find HFS+ superblock\n"); |
370 | err = -EINVAL; | 371 | goto out_unload_nls; |
371 | goto cleanup; | ||
372 | } | 372 | } |
373 | vhdr = sbi->s_vhdr; | 373 | vhdr = sbi->s_vhdr; |
374 | 374 | ||
@@ -377,7 +377,7 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) | |||
377 | if (be16_to_cpu(vhdr->version) < HFSPLUS_MIN_VERSION || | 377 | if (be16_to_cpu(vhdr->version) < HFSPLUS_MIN_VERSION || |
378 | be16_to_cpu(vhdr->version) > HFSPLUS_CURRENT_VERSION) { | 378 | be16_to_cpu(vhdr->version) > HFSPLUS_CURRENT_VERSION) { |
379 | printk(KERN_ERR "hfs: wrong filesystem version\n"); | 379 | printk(KERN_ERR "hfs: wrong filesystem version\n"); |
380 | goto cleanup; | 380 | goto out_free_vhdr; |
381 | } | 381 | } |
382 | sbi->total_blocks = be32_to_cpu(vhdr->total_blocks); | 382 | sbi->total_blocks = be32_to_cpu(vhdr->total_blocks); |
383 | sbi->free_blocks = be32_to_cpu(vhdr->free_blocks); | 383 | sbi->free_blocks = be32_to_cpu(vhdr->free_blocks); |
@@ -421,19 +421,19 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) | |||
421 | sbi->ext_tree = hfs_btree_open(sb, HFSPLUS_EXT_CNID); | 421 | sbi->ext_tree = hfs_btree_open(sb, HFSPLUS_EXT_CNID); |
422 | if (!sbi->ext_tree) { | 422 | if (!sbi->ext_tree) { |
423 | printk(KERN_ERR "hfs: failed to load extents file\n"); | 423 | printk(KERN_ERR "hfs: failed to load extents file\n"); |
424 | goto cleanup; | 424 | goto out_free_vhdr; |
425 | } | 425 | } |
426 | sbi->cat_tree = hfs_btree_open(sb, HFSPLUS_CAT_CNID); | 426 | sbi->cat_tree = hfs_btree_open(sb, HFSPLUS_CAT_CNID); |
427 | if (!sbi->cat_tree) { | 427 | if (!sbi->cat_tree) { |
428 | printk(KERN_ERR "hfs: failed to load catalog file\n"); | 428 | printk(KERN_ERR "hfs: failed to load catalog file\n"); |
429 | goto cleanup; | 429 | goto out_close_ext_tree; |
430 | } | 430 | } |
431 | 431 | ||
432 | inode = hfsplus_iget(sb, HFSPLUS_ALLOC_CNID); | 432 | inode = hfsplus_iget(sb, HFSPLUS_ALLOC_CNID); |
433 | if (IS_ERR(inode)) { | 433 | if (IS_ERR(inode)) { |
434 | printk(KERN_ERR "hfs: failed to load allocation file\n"); | 434 | printk(KERN_ERR "hfs: failed to load allocation file\n"); |
435 | err = PTR_ERR(inode); | 435 | err = PTR_ERR(inode); |
436 | goto cleanup; | 436 | goto out_close_cat_tree; |
437 | } | 437 | } |
438 | sbi->alloc_file = inode; | 438 | sbi->alloc_file = inode; |
439 | 439 | ||
@@ -442,14 +442,7 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) | |||
442 | if (IS_ERR(root)) { | 442 | if (IS_ERR(root)) { |
443 | printk(KERN_ERR "hfs: failed to load root directory\n"); | 443 | printk(KERN_ERR "hfs: failed to load root directory\n"); |
444 | err = PTR_ERR(root); | 444 | err = PTR_ERR(root); |
445 | goto cleanup; | 445 | goto out_put_alloc_file; |
446 | } | ||
447 | sb->s_d_op = &hfsplus_dentry_operations; | ||
448 | sb->s_root = d_alloc_root(root); | ||
449 | if (!sb->s_root) { | ||
450 | iput(root); | ||
451 | err = -ENOMEM; | ||
452 | goto cleanup; | ||
453 | } | 446 | } |
454 | 447 | ||
455 | str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1; | 448 | str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1; |
@@ -459,46 +452,69 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) | |||
459 | if (!hfs_brec_read(&fd, &entry, sizeof(entry))) { | 452 | if (!hfs_brec_read(&fd, &entry, sizeof(entry))) { |
460 | hfs_find_exit(&fd); | 453 | hfs_find_exit(&fd); |
461 | if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) | 454 | if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) |
462 | goto cleanup; | 455 | goto out_put_root; |
463 | inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id)); | 456 | inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id)); |
464 | if (IS_ERR(inode)) { | 457 | if (IS_ERR(inode)) { |
465 | err = PTR_ERR(inode); | 458 | err = PTR_ERR(inode); |
466 | goto cleanup; | 459 | goto out_put_root; |
467 | } | 460 | } |
468 | sbi->hidden_dir = inode; | 461 | sbi->hidden_dir = inode; |
469 | } else | 462 | } else |
470 | hfs_find_exit(&fd); | 463 | hfs_find_exit(&fd); |
471 | 464 | ||
472 | if (sb->s_flags & MS_RDONLY) | 465 | if (!(sb->s_flags & MS_RDONLY)) { |
473 | goto out; | 466 | /* |
467 | * H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused | ||
468 | * all three are registered with Apple for our use | ||
469 | */ | ||
470 | vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); | ||
471 | vhdr->modify_date = hfsp_now2mt(); | ||
472 | be32_add_cpu(&vhdr->write_count, 1); | ||
473 | vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); | ||
474 | vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); | ||
475 | hfsplus_sync_fs(sb, 1); | ||
474 | 476 | ||
475 | /* H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused | 477 | if (!sbi->hidden_dir) { |
476 | * all three are registered with Apple for our use | 478 | mutex_lock(&sbi->vh_mutex); |
477 | */ | 479 | sbi->hidden_dir = hfsplus_new_inode(sb, S_IFDIR); |
478 | vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); | 480 | hfsplus_create_cat(sbi->hidden_dir->i_ino, root, &str, |
479 | vhdr->modify_date = hfsp_now2mt(); | 481 | sbi->hidden_dir); |
480 | be32_add_cpu(&vhdr->write_count, 1); | 482 | mutex_unlock(&sbi->vh_mutex); |
481 | vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); | 483 | |
482 | vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); | 484 | hfsplus_mark_inode_dirty(sbi->hidden_dir, |
483 | hfsplus_sync_fs(sb, 1); | 485 | HFSPLUS_I_CAT_DIRTY); |
484 | 486 | } | |
485 | if (!sbi->hidden_dir) { | ||
486 | mutex_lock(&sbi->vh_mutex); | ||
487 | sbi->hidden_dir = hfsplus_new_inode(sb, S_IFDIR); | ||
488 | hfsplus_create_cat(sbi->hidden_dir->i_ino, sb->s_root->d_inode, | ||
489 | &str, sbi->hidden_dir); | ||
490 | mutex_unlock(&sbi->vh_mutex); | ||
491 | |||
492 | hfsplus_mark_inode_dirty(sbi->hidden_dir, HFSPLUS_I_CAT_DIRTY); | ||
493 | } | 487 | } |
494 | out: | 488 | |
489 | sb->s_d_op = &hfsplus_dentry_operations; | ||
490 | sb->s_root = d_alloc_root(root); | ||
491 | if (!sb->s_root) { | ||
492 | err = -ENOMEM; | ||
493 | goto out_put_hidden_dir; | ||
494 | } | ||
495 | |||
495 | unload_nls(sbi->nls); | 496 | unload_nls(sbi->nls); |
496 | sbi->nls = nls; | 497 | sbi->nls = nls; |
497 | return 0; | 498 | return 0; |
498 | 499 | ||
499 | cleanup: | 500 | out_put_hidden_dir: |
500 | hfsplus_put_super(sb); | 501 | iput(sbi->hidden_dir); |
502 | out_put_root: | ||
503 | iput(sbi->alloc_file); | ||
504 | out_put_alloc_file: | ||
505 | iput(sbi->alloc_file); | ||
506 | out_close_cat_tree: | ||
507 | hfs_btree_close(sbi->cat_tree); | ||
508 | out_close_ext_tree: | ||
509 | hfs_btree_close(sbi->ext_tree); | ||
510 | out_free_vhdr: | ||
511 | kfree(sbi->s_vhdr); | ||
512 | kfree(sbi->s_backup_vhdr); | ||
513 | out_unload_nls: | ||
514 | unload_nls(sbi->nls); | ||
501 | unload_nls(nls); | 515 | unload_nls(nls); |
516 | kfree(sbi); | ||
517 | out: | ||
502 | return err; | 518 | return err; |
503 | } | 519 | } |
504 | 520 | ||