
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            |   12 ++++++++--
 fs/ext3/super.c       |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/quota.h |    2 +
 3 files changed, 70 insertions(+), 2 deletions(-)

diff -puN fs/dquot.c~ext3-quota-deadlock-fix fs/dquot.c
--- 25/fs/dquot.c~ext3-quota-deadlock-fix	2003-04-12 00:30:08.000000000 -0700
+++ 25-akpm/fs/dquot.c	2003-04-12 00:30:08.000000000 -0700
@@ -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);
diff -puN fs/ext3/super.c~ext3-quota-deadlock-fix fs/ext3/super.c
--- 25/fs/ext3/super.c~ext3-quota-deadlock-fix	2003-04-12 00:30:08.000000000 -0700
+++ 25-akpm/fs/ext3/super.c	2003-04-12 00:56:43.000000000 -0700
@@ -566,6 +566,8 @@ static void ext3_clear_inode(struct inod
 # 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
 	 */
 	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
 	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;
diff -puN include/linux/quota.h~ext3-quota-deadlock-fix include/linux/quota.h
--- 25/include/linux/quota.h~ext3-quota-deadlock-fix	2003-04-12 00:30:08.000000000 -0700
+++ 25-akpm/include/linux/quota.h	2003-04-12 00:30:08.000000000 -0700
@@ -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
 

_
