]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - fs/ext4/super.c
ext4: Avoid races caused by on-line resizing and SMP memory reordering
[mirror_ubuntu-bionic-kernel.git] / fs / ext4 / super.c
index 3f4475daa66d6024ce37cb32c2ae77da37b2d814..fcd7b24c6df37a3f0b67a7936cced18a26b652ff 100644 (file)
@@ -54,8 +54,7 @@ static struct kset *ext4_kset;
 
 static int ext4_load_journal(struct super_block *, struct ext4_super_block *,
                             unsigned long journal_devnum);
-static int ext4_commit_super(struct super_block *sb,
-                             struct ext4_super_block *es, int sync);
+static int ext4_commit_super(struct super_block *sb, int sync);
 static void ext4_mark_recovery_complete(struct super_block *sb,
                                        struct ext4_super_block *es);
 static void ext4_clear_journal_err(struct super_block *sb,
@@ -306,7 +305,7 @@ static void ext4_handle_error(struct super_block *sb)
                printk(KERN_CRIT "Remounting filesystem read-only\n");
                sb->s_flags |= MS_RDONLY;
        }
-       ext4_commit_super(sb, es, 1);
+       ext4_commit_super(sb, 1);
        if (test_opt(sb, ERRORS_PANIC))
                panic("EXT4-fs (device %s): panic forced after error\n",
                        sb->s_id);
@@ -448,7 +447,7 @@ __acquires(bitlock)
        if (test_opt(sb, ERRORS_CONT)) {
                EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
                es->s_state |= cpu_to_le16(EXT4_ERROR_FS);
-               ext4_commit_super(sb, es, 0);
+               ext4_commit_super(sb, 0);
                return;
        }
        ext4_unlock_group(sb, grp);
@@ -577,7 +576,7 @@ static void ext4_put_super(struct super_block *sb)
        if (!(sb->s_flags & MS_RDONLY)) {
                EXT4_CLEAR_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
                es->s_state = cpu_to_le16(sbi->s_mount_state);
-               ext4_commit_super(sb, es, 1);
+               ext4_commit_super(sb, 1);
        }
        if (sbi->s_proc) {
                remove_proc_entry(sb->s_id, ext4_proc_root);
@@ -996,7 +995,6 @@ static const struct super_operations ext4_sops = {
        .dirty_inode    = ext4_dirty_inode,
        .delete_inode   = ext4_delete_inode,
        .put_super      = ext4_put_super,
-       .write_super    = ext4_write_super,
        .sync_fs        = ext4_sync_fs,
        .freeze_fs      = ext4_freeze,
        .unfreeze_fs    = ext4_unfreeze,
@@ -1011,6 +1009,25 @@ static const struct super_operations ext4_sops = {
        .bdev_try_to_free_page = bdev_try_to_free_page,
 };
 
+static const struct super_operations ext4_nojournal_sops = {
+       .alloc_inode    = ext4_alloc_inode,
+       .destroy_inode  = ext4_destroy_inode,
+       .write_inode    = ext4_write_inode,
+       .dirty_inode    = ext4_dirty_inode,
+       .delete_inode   = ext4_delete_inode,
+       .write_super    = ext4_write_super,
+       .put_super      = ext4_put_super,
+       .statfs         = ext4_statfs,
+       .remount_fs     = ext4_remount,
+       .clear_inode    = ext4_clear_inode,
+       .show_options   = ext4_show_options,
+#ifdef CONFIG_QUOTA
+       .quota_read     = ext4_quota_read,
+       .quota_write    = ext4_quota_write,
+#endif
+       .bdev_try_to_free_page = bdev_try_to_free_page,
+};
+
 static const struct export_operations ext4_export_ops = {
        .fh_to_dentry = ext4_fh_to_dentry,
        .fh_to_parent = ext4_fh_to_parent,
@@ -1483,7 +1500,7 @@ set_qf_format:
                                return 0;
                        if (option < 0 || option > (1 << 30))
                                return 0;
-                       if (option & (option - 1)) {
+                       if (!is_power_of_2(option)) {
                                printk(KERN_ERR "EXT4-fs: inode_readahead_blks"
                                       " must be a power of 2\n");
                                return 0;
@@ -1596,7 +1613,7 @@ static int ext4_setup_super(struct super_block *sb, struct ext4_super_block *es,
        if (sbi->s_journal)
                EXT4_SET_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
 
-       ext4_commit_super(sb, es, 1);
+       ext4_commit_super(sb, 1);
        if (test_opt(sb, DEBUG))
                printk(KERN_INFO "[EXT4 FS bs=%lu, gc=%u, "
                                "bpg=%lu, ipg=%lu, mo=%04lx]\n",
@@ -2101,8 +2118,7 @@ static ssize_t inode_readahead_blks_store(struct ext4_attr *a,
        if (parse_strtoul(buf, 0x40000000, &t))
                return -EINVAL;
 
-       /* inode_readahead_blks must be a power of 2 */
-       if (t & (t-1))
+       if (!is_power_of_2(t))
                return -EINVAL;
 
        sbi->s_inode_readahead_blks = t;
@@ -2617,7 +2633,11 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
        /*
         * set up enough so that it can read an inode
         */
-       sb->s_op = &ext4_sops;
+       if (!test_opt(sb, NOLOAD) &&
+           EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_HAS_JOURNAL))
+               sb->s_op = &ext4_sops;
+       else
+               sb->s_op = &ext4_nojournal_sops;
        sb->s_export_op = &ext4_export_ops;
        sb->s_xattr = ext4_xattr_handlers;
 #ifdef CONFIG_QUOTA
@@ -2656,7 +2676,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
                        if (test_opt(sb, ERRORS_PANIC)) {
                                EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
                                es->s_state |= cpu_to_le16(EXT4_ERROR_FS);
-                               ext4_commit_super(sb, es, 1);
+                               ext4_commit_super(sb, 1);
                                goto failed_mount4;
                        }
                }
@@ -3130,18 +3150,17 @@ static int ext4_load_journal(struct super_block *sb,
        if (journal_devnum &&
            journal_devnum != le32_to_cpu(es->s_journal_dev)) {
                es->s_journal_dev = cpu_to_le32(journal_devnum);
-               sb->s_dirt = 1;
 
                /* Make sure we flush the recovery flag to disk. */
-               ext4_commit_super(sb, es, 1);
+               ext4_commit_super(sb, 1);
        }
 
        return 0;
 }
 
-static int ext4_commit_super(struct super_block *sb,
-                             struct ext4_super_block *es, int sync)
+static int ext4_commit_super(struct super_block *sb, int sync)
 {
+       struct ext4_super_block *es = EXT4_SB(sb)->s_es;
        struct buffer_head *sbh = EXT4_SB(sb)->s_sbh;
        int error = 0;
 
@@ -3170,7 +3189,7 @@ static int ext4_commit_super(struct super_block *sb,
                                        &EXT4_SB(sb)->s_freeblocks_counter));
        es->s_free_inodes_count = cpu_to_le32(percpu_counter_sum_positive(
                                        &EXT4_SB(sb)->s_freeinodes_counter));
-
+       sb->s_dirt = 0;
        BUFFER_TRACE(sbh, "marking dirty");
        mark_buffer_dirty(sbh);
        if (sync) {
@@ -3212,8 +3231,7 @@ static void ext4_mark_recovery_complete(struct super_block *sb,
        if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER) &&
            sb->s_flags & MS_RDONLY) {
                EXT4_CLEAR_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
-               sb->s_dirt = 0;
-               ext4_commit_super(sb, es, 1);
+               ext4_commit_super(sb, 1);
        }
        unlock_super(sb);
 
@@ -3254,7 +3272,7 @@ static void ext4_clear_journal_err(struct super_block *sb,
 
                EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
                es->s_state |= cpu_to_le16(EXT4_ERROR_FS);
-               ext4_commit_super(sb, es, 1);
+               ext4_commit_super(sb, 1);
 
                jbd2_journal_clear_err(journal);
        }
@@ -3273,29 +3291,15 @@ int ext4_force_commit(struct super_block *sb)
                return 0;
 
        journal = EXT4_SB(sb)->s_journal;
-       if (journal) {
-               sb->s_dirt = 0;
+       if (journal)
                ret = ext4_journal_force_commit(journal);
-       }
 
        return ret;
 }
 
-/*
- * Ext4 always journals updates to the superblock itself, so we don't
- * have to propagate any other updates to the superblock on disk at this
- * point.  (We can probably nuke this function altogether, and remove
- * any mention to sb->s_dirt in all of fs/ext4; eventual cleanup...)
- */
 static void ext4_write_super(struct super_block *sb)
 {
-       if (EXT4_SB(sb)->s_journal) {
-               if (mutex_trylock(&sb->s_lock) != 0)
-                       BUG();
-               sb->s_dirt = 0;
-       } else {
-               ext4_commit_super(sb, EXT4_SB(sb)->s_es, 1);
-       }
+       ext4_commit_super(sb, 1);
 }
 
 static int ext4_sync_fs(struct super_block *sb, int wait)
@@ -3304,16 +3308,9 @@ static int ext4_sync_fs(struct super_block *sb, int wait)
        tid_t target;
 
        trace_mark(ext4_sync_fs, "dev %s wait %d", sb->s_id, wait);
-       sb->s_dirt = 0;
-       if (EXT4_SB(sb)->s_journal) {
-               if (jbd2_journal_start_commit(EXT4_SB(sb)->s_journal,
-                                             &target)) {
-                       if (wait)
-                               jbd2_log_wait_commit(EXT4_SB(sb)->s_journal,
-                                                    target);
-               }
-       } else {
-               ext4_commit_super(sb, EXT4_SB(sb)->s_es, wait);
+       if (jbd2_journal_start_commit(EXT4_SB(sb)->s_journal, &target)) {
+               if (wait)
+                       jbd2_log_wait_commit(EXT4_SB(sb)->s_journal, target);
        }
        return ret;
 }
@@ -3326,34 +3323,32 @@ static int ext4_freeze(struct super_block *sb)
 {
        int error = 0;
        journal_t *journal;
-       sb->s_dirt = 0;
 
-       if (!(sb->s_flags & MS_RDONLY)) {
-               journal = EXT4_SB(sb)->s_journal;
+       if (sb->s_flags & MS_RDONLY)
+               return 0;
 
-               if (journal) {
-                       /* Now we set up the journal barrier. */
-                       jbd2_journal_lock_updates(journal);
+       journal = EXT4_SB(sb)->s_journal;
 
-                       /*
-                        * We don't want to clear needs_recovery flag when we
-                        * failed to flush the journal.
-                        */
-                       error = jbd2_journal_flush(journal);
-                       if (error < 0)
-                               goto out;
-               }
+       /* Now we set up the journal barrier. */
+       jbd2_journal_lock_updates(journal);
 
-               /* Journal blocked and flushed, clear needs_recovery flag. */
-               EXT4_CLEAR_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
-               error = ext4_commit_super(sb, EXT4_SB(sb)->s_es, 1);
-               if (error)
-                       goto out;
+       /*
+        * Don't clear the needs_recovery flag if we failed to flush
+        * the journal.
+        */
+       error = jbd2_journal_flush(journal);
+       if (error < 0) {
+       out:
+               jbd2_journal_unlock_updates(journal);
+               return error;
        }
+
+       /* Journal blocked and flushed, clear needs_recovery flag. */
+       EXT4_CLEAR_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
+       error = ext4_commit_super(sb, 1);
+       if (error)
+               goto out;
        return 0;
-out:
-       jbd2_journal_unlock_updates(journal);
-       return error;
 }
 
 /*
@@ -3362,14 +3357,15 @@ out:
  */
 static int ext4_unfreeze(struct super_block *sb)
 {
-       if (EXT4_SB(sb)->s_journal && !(sb->s_flags & MS_RDONLY)) {
-               lock_super(sb);
-               /* Reser the needs_recovery flag before the fs is unlocked. */
-               EXT4_SET_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
-               ext4_commit_super(sb, EXT4_SB(sb)->s_es, 1);
-               unlock_super(sb);
-               jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal);
-       }
+       if (sb->s_flags & MS_RDONLY)
+               return 0;
+
+       lock_super(sb);
+       /* Reset the needs_recovery flag before the fs is unlocked. */
+       EXT4_SET_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER);
+       ext4_commit_super(sb, 1);
+       unlock_super(sb);
+       jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal);
        return 0;
 }
 
@@ -3521,7 +3517,7 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
                }
        }
        if (sbi->s_journal == NULL)
-               ext4_commit_super(sb, es, 1);
+               ext4_commit_super(sb, 1);
 
 #ifdef CONFIG_QUOTA
        /* Release old quota file names */
@@ -3561,9 +3557,8 @@ static int ext4_statfs(struct dentry *dentry, struct kstatfs *buf)
        if (test_opt(sb, MINIX_DF)) {
                sbi->s_overhead_last = 0;
        } else if (sbi->s_blocks_last != ext4_blocks_count(es)) {
-               ext4_group_t ngroups = sbi->s_groups_count, i;
+               ext4_group_t i, ngroups = ext4_get_groups_count(sb);
                ext4_fsblk_t overhead = 0;
-               smp_rmb();
 
                /*
                 * Compute the overhead (FS structures).  This is constant