From: Jens Axboe <axboe@kernel.dk>
Subject: Fix superblock locking when doing writeback
Patch-mainline: Never (fixed differently)
References: bnc#617764 bnc#617437

In some cases superblock is already locked even for WB_SYNC_NONE writeback.
The first writeback pass during umount is one such prominent case. Currently
writeback code failed to lock the superblock in such case and bailed out
thus leaving all the writeback for the synchronous mode which can take much
longer.

Also when bdi_start_fn called wb_do_writeback, we did WB_SYNC_ALL writeback
without having superblock locked possibly leading to Oops if umount happened
at the right moment.

Fix both these issues by introducing new 'locked' bit expressing whether the
caller took care of the superblock locking or not.

Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
Signed-off-by: Jan Kara <jack@suse.cz>

---
 fs/fs-writeback.c           |   31 +++++++++++++++++++++++--------
 fs/sync.c                   |    2 +-
 fs/ubifs/budget.c           |    2 +-
 include/linux/backing-dev.h |    2 +-
 include/linux/writeback.h   |    4 +++-
 mm/page-writeback.c         |    2 +-
 6 files changed, 30 insertions(+), 13 deletions(-)

Index: linux-2.6.34-openSUSE-11.3/fs/fs-writeback.c
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/fs/fs-writeback.c
+++ linux-2.6.34-openSUSE-11.3/fs/fs-writeback.c
@@ -45,6 +45,7 @@ struct wb_writeback_args {
 	int for_kupdate:1;
 	int range_cyclic:1;
 	int for_background:1;
+	int locked:1;
 };
 
 /*
@@ -230,6 +231,7 @@ static void bdi_sync_writeback(struct ba
 		.sync_mode	= WB_SYNC_ALL,
 		.nr_pages	= LONG_MAX,
 		.range_cyclic	= 0,
+		.locked		= 1,
 	};
 	struct bdi_work work;
 
@@ -253,13 +255,14 @@ static void bdi_sync_writeback(struct ba
  *
  */
 void bdi_start_writeback(struct backing_dev_info *bdi, struct super_block *sb,
-			 long nr_pages)
+			 long nr_pages, int locked)
 {
 	struct wb_writeback_args args = {
 		.sb		= sb,
 		.sync_mode	= WB_SYNC_NONE,
 		.nr_pages	= nr_pages,
 		.range_cyclic	= 1,
+		.locked		= locked,
 	};
 
 	/*
@@ -271,7 +274,17 @@ void bdi_start_writeback(struct backing_
 		args.for_background = 1;
 	}
 
-	bdi_alloc_queue_work(bdi, &args);
+	if (locked) {
+		struct bdi_work work;
+
+		bdi_work_init(&work, &args);
+		work.state |= WS_ONSTACK;
+
+		bdi_queue_work(bdi, &work);
+		bdi_wait_on_work_clear(&work);
+	} else {
+		bdi_alloc_queue_work(bdi, &args);
+	}
 }
 
 /*
@@ -577,7 +590,7 @@ static enum sb_pin_state pin_sb_for_writ
 	/*
 	 * Caller must already hold the ref for this
 	 */
-	if (wbc->sync_mode == WB_SYNC_ALL) {
+	if (wbc->locked) {
 		WARN_ON(!rwsem_is_locked(&sb->s_umount));
 		return SB_NOT_PINNED;
 	}
@@ -751,6 +764,7 @@ static long wb_writeback(struct bdi_writ
 		.for_kupdate		= args->for_kupdate,
 		.for_background		= args->for_background,
 		.range_cyclic		= args->range_cyclic,
+		.locked			= args->locked,
 	};
 	unsigned long oldest_jif;
 	long wrote = 0;
@@ -904,7 +918,7 @@ long wb_do_writeback(struct bdi_writebac
 		 * If this isn't a data integrity operation, just notify
 		 * that we have seen this work and we are now starting it.
 		 */
-		if (args.sync_mode == WB_SYNC_NONE)
+		if (args.sync_mode == WB_SYNC_NONE && !args.locked)
 			wb_clear_pending(wb, work);
 
 		wrote += wb_writeback(wb, &args);
@@ -913,7 +927,7 @@ long wb_do_writeback(struct bdi_writebac
 		 * This is a data integrity writeback, so only do the
 		 * notification when we have completed the work.
 		 */
-		if (args.sync_mode == WB_SYNC_ALL)
+		if (args.sync_mode == WB_SYNC_ALL || args.locked)
 			wb_clear_pending(wb, work);
 	}
 
@@ -1196,13 +1210,14 @@ static void wait_sb_inodes(struct super_
 /**
  * writeback_inodes_sb	-	writeback dirty inodes from given super_block
  * @sb: the superblock
+ * @locked: sb already pinned
  *
  * Start writeback on some inodes on this super_block. No guarantees are made
  * on how many (if any) will be written, and this function does not wait
  * for IO completion of submitted IO. The number of pages submitted is
  * returned.
  */
-void writeback_inodes_sb(struct super_block *sb)
+void writeback_inodes_sb(struct super_block *sb, int locked)
 {
 	unsigned long nr_dirty = global_page_state(NR_FILE_DIRTY);
 	unsigned long nr_unstable = global_page_state(NR_UNSTABLE_NFS);
@@ -1211,7 +1226,7 @@ void writeback_inodes_sb(struct super_bl
 	nr_to_write = nr_dirty + nr_unstable +
 			(inodes_stat.nr_inodes - inodes_stat.nr_unused);
 
-	bdi_start_writeback(sb->s_bdi, sb, nr_to_write);
+	bdi_start_writeback(sb->s_bdi, sb, nr_to_write, locked);
 }
 EXPORT_SYMBOL(writeback_inodes_sb);
 
@@ -1225,7 +1240,7 @@ EXPORT_SYMBOL(writeback_inodes_sb);
 int writeback_inodes_sb_if_idle(struct super_block *sb)
 {
 	if (!writeback_in_progress(sb->s_bdi)) {
-		writeback_inodes_sb(sb);
+		writeback_inodes_sb(sb, 0);
 		return 1;
 	} else
 		return 0;
Index: linux-2.6.34-openSUSE-11.3/fs/sync.c
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/fs/sync.c
+++ linux-2.6.34-openSUSE-11.3/fs/sync.c
@@ -42,7 +42,7 @@ static int __sync_filesystem(struct supe
 	if (wait)
 		sync_inodes_sb(sb);
 	else
-		writeback_inodes_sb(sb);
+		writeback_inodes_sb(sb, 1);
 
 	if (sb->s_op->sync_fs)
 		sb->s_op->sync_fs(sb, wait);
Index: linux-2.6.34-openSUSE-11.3/fs/ubifs/budget.c
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/fs/ubifs/budget.c
+++ linux-2.6.34-openSUSE-11.3/fs/ubifs/budget.c
@@ -62,7 +62,7 @@
  */
 static void shrink_liability(struct ubifs_info *c, int nr_to_write)
 {
-	writeback_inodes_sb(c->vfs_sb);
+	writeback_inodes_sb(c->vfs_sb, 0);
 }
 
 /**
Index: linux-2.6.34-openSUSE-11.3/include/linux/backing-dev.h
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/include/linux/backing-dev.h
+++ linux-2.6.34-openSUSE-11.3/include/linux/backing-dev.h
@@ -103,7 +103,7 @@ int bdi_register_dev(struct backing_dev_
 void bdi_unregister(struct backing_dev_info *bdi);
 int bdi_setup_and_register(struct backing_dev_info *, char *, unsigned int);
 void bdi_start_writeback(struct backing_dev_info *bdi, struct super_block *sb,
-				long nr_pages);
+				long nr_pages, int locked);
 int bdi_writeback_task(struct bdi_writeback *wb);
 int bdi_has_dirty_io(struct backing_dev_info *bdi);
 
Index: linux-2.6.34-openSUSE-11.3/include/linux/writeback.h
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/include/linux/writeback.h
+++ linux-2.6.34-openSUSE-11.3/include/linux/writeback.h
@@ -56,6 +56,8 @@ struct writeback_control {
 	unsigned for_reclaim:1;		/* Invoked from the page allocator */
 	unsigned range_cyclic:1;	/* range_start is cyclic */
 	unsigned more_io:1;		/* more io to be dispatched */
+	unsigned locked:1;		/* sb pinned for WB_SYNC_NONE */
+
 	/*
 	 * write_cache_pages() won't update wbc->nr_to_write and
 	 * mapping->writeback_index if no_nrwrite_index_update
@@ -72,7 +74,7 @@ struct writeback_control {
  */	
 struct bdi_writeback;
 int inode_wait(void *);
-void writeback_inodes_sb(struct super_block *);
+void writeback_inodes_sb(struct super_block *, int);
 int writeback_inodes_sb_if_idle(struct super_block *);
 void sync_inodes_sb(struct super_block *);
 void writeback_inodes_wbc(struct writeback_control *wbc);
Index: linux-2.6.34-openSUSE-11.3/mm/page-writeback.c
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/mm/page-writeback.c
+++ linux-2.6.34-openSUSE-11.3/mm/page-writeback.c
@@ -597,7 +597,7 @@ static void balance_dirty_pages(struct a
 	    (!laptop_mode && ((global_page_state(NR_FILE_DIRTY)
 			       + global_page_state(NR_UNSTABLE_NFS))
 					  > background_thresh)))
-		bdi_start_writeback(bdi, NULL, 0);
+		bdi_start_writeback(bdi, NULL, 0, 0);
 }
 
 void set_page_dirty_balance(struct page *page, int page_mkwrite)
Index: linux-2.6.34-openSUSE-11.3/fs/quota/quota.c
===================================================================
--- linux-2.6.34-openSUSE-11.3.orig/fs/quota/quota.c
+++ linux-2.6.34-openSUSE-11.3/fs/quota/quota.c
@@ -268,7 +268,7 @@ static int do_quotactl(struct super_bloc
 		/* caller already holds s_umount */
 		if (sb->s_flags & MS_RDONLY)
 			return -EROFS;
-		writeback_inodes_sb(sb);
+		writeback_inodes_sb(sb, 1);
 		return 0;
 	default:
 		return -EINVAL;
