]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Fix deadlock with ext3+quota
authorAndrew Morton <akpm@digeo.com>
Sat, 12 Apr 2003 19:55:21 +0000 (12:55 -0700)
committerJames Bottomley <jejb@raven.il.steeleye.com>
Sat, 12 Apr 2003 19:55:21 +0000 (12:55 -0700)
From: Jan Kara <jack@ucw.cz>

Fixes a deadlock-causing lock-ranking bug between dqio_sem and
journal_start().

It sets up the needed infrastructure so that the quota code's sync_dquot()
operation can call into ext3 and arrange for the transaction start to be
nested outside the taking of dqio_sem.

fs/dquot.c
fs/ext3/super.c
include/linux/quota.h

index b92f9a26c5299faff27e0397d67c35475dabd041..737b9f1d54ab4b2855c8154db07b99e92e2778fe 100644 (file)
@@ -326,7 +326,7 @@ restart:
                if (!dquot_dirty(dquot))
                        continue;
                spin_unlock(&dq_list_lock);
-               commit_dqblk(dquot);
+               sb->dq_op->sync_dquot(dquot);
                goto restart;
        }
        spin_unlock(&dq_list_lock);
@@ -1072,9 +1072,16 @@ struct dquot_operations dquot_operations = {
        .alloc_inode    = dquot_alloc_inode,
        .free_space     = dquot_free_space,
        .free_inode     = dquot_free_inode,
-       .transfer       = dquot_transfer
+       .transfer       = dquot_transfer,
+       .sync_dquot     = commit_dqblk
 };
 
+/* Function used by filesystems for initializing the dquot_operations structure */
+void init_dquot_operations(struct dquot_operations *fsdqops)
+{
+       memcpy(fsdqops, &dquot_operations, sizeof(dquot_operations));
+}
+
 static inline void set_enable_flags(struct quota_info *dqopt, int type)
 {
        switch (type) {
@@ -1432,3 +1439,4 @@ EXPORT_SYMBOL(unregister_quota_format);
 EXPORT_SYMBOL(dqstats);
 EXPORT_SYMBOL(dq_list_lock);
 EXPORT_SYMBOL(dq_data_lock);
+EXPORT_SYMBOL(init_dquot_operations);
index 1213b61c9a13908ec65a8dd870938626384dbf57..da7f57d10e871f93a1cf0e0563cc1fa1f61b7e4c 100644 (file)
@@ -566,6 +566,8 @@ static void ext3_clear_inode(struct inode *inode)
 # define ext3_clear_inode NULL
 #endif
 
+static struct dquot_operations ext3_qops;
+
 static struct super_operations ext3_sops = {
        .alloc_inode    = ext3_alloc_inode,
        .destroy_inode  = ext3_destroy_inode,
@@ -1337,6 +1339,7 @@ static int ext3_fill_super (struct super_block *sb, void *data, int silent)
         */
        sb->s_op = &ext3_sops;
        sb->s_export_op = &ext3_export_ops;
+       sb->dq_op = &ext3_qops;
        INIT_LIST_HEAD(&sbi->s_orphan); /* unlinked but open files */
 
        sb->s_root = 0;
@@ -1977,6 +1980,56 @@ int ext3_statfs (struct super_block * sb, struct statfs * buf)
        return 0;
 }
 
+/* Helper function for writing quotas on sync - we need to start transaction before quota file
+ * is locked for write. Otherwise the are possible deadlocks:
+ * Process 1                         Process 2
+ * ext3_create()                     quota_sync()
+ *   journal_start()                   write_dquot()
+ *   DQUOT_INIT()                        down(dqio_sem)
+ *     down(dqio_sem)                    journal_start()
+ *
+ */
+
+#ifdef CONFIG_QUOTA
+
+#define EXT3_OLD_QFMT_BLOCKS 2
+#define EXT3_V0_QFMT_BLOCKS 6
+
+static int (*old_sync_dquot)(struct dquot *dquot);
+
+static int ext3_sync_dquot(struct dquot *dquot)
+{
+       int nblocks, ret;
+       handle_t *handle;
+       struct quota_info *dqops = sb_dqopt(dquot->dq_sb);
+       struct inode *qinode;
+
+       switch (dqops->info[dquot->dq_type].dqi_format->qf_fmt_id) {
+               case QFMT_VFS_OLD:
+                       nblocks = EXT3_OLD_QFMT_BLOCKS;
+                       break;
+               case QFMT_VFS_V0:
+                       nblocks = EXT3_V0_QFMT_BLOCKS;
+                       break;
+               default:
+                       nblocks = EXT3_MAX_TRANS_DATA;
+       }
+       lock_kernel();
+       qinode = dqops->files[dquot->dq_type]->f_dentry->d_inode;
+       handle = ext3_journal_start(qinode, nblocks);
+       if (IS_ERR(handle)) {
+               unlock_kernel();
+               return PTR_ERR(handle);
+       }
+       unlock_kernel();
+       ret = old_sync_dquot(dquot);
+       lock_kernel();
+       ret = ext3_journal_stop(handle);
+       unlock_kernel();
+       return ret;
+}
+#endif
+
 static struct super_block *ext3_get_sb(struct file_system_type *fs_type,
        int flags, char *dev_name, void *data)
 {
@@ -1999,6 +2052,11 @@ static int __init init_ext3_fs(void)
        err = init_inodecache();
        if (err)
                goto out1;
+#ifdef CONFIG_QUOTA
+       init_dquot_operations(&ext3_qops);
+       old_sync_dquot = ext3_qops.sync_dquot;
+       ext3_qops.sync_dquot = ext3_sync_dquot;
+#endif
         err = register_filesystem(&ext3_fs_type);
        if (err)
                goto out;
index e1c097e338d9e0652cacd6f9cd5c0c7be1bee649..77d017472dc7efd5d31f750518ed63240f2e48e4 100644 (file)
@@ -250,6 +250,7 @@ struct dquot_operations {
        void (*free_space) (struct inode *, qsize_t);
        void (*free_inode) (const struct inode *, unsigned long);
        int (*transfer) (struct inode *, struct iattr *);
+       int (*sync_dquot) (struct dquot *);
 };
 
 /* Operations handling requests from userspace */
@@ -303,6 +304,7 @@ struct quota_info {
 
 int register_quota_format(struct quota_format_type *fmt);
 void unregister_quota_format(struct quota_format_type *fmt);
+void init_dquot_operations(struct dquot_operations *fsdqops);
 
 #else