From: Hugh Dickins <hugh@veritas.com>

We permanently hold the i_sem of swapfiles so that nobody can addidentally
ftruncate them, causing subsequent filesystem destruction.

Problem is, it's fairly easy for things like backup applications to get
stuck onthe swapfile, sleeping until someone does a swapoff.

So take all that out again and add a new S_SWAPFILE inode flag.  Test that
in the truncate path and refuse to truncate an in-use swapfile.

Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/include/linux/fs.h |    2 ++
 25-akpm/mm/memory.c        |    8 ++++++--
 25-akpm/mm/swapfile.c      |   19 +++++++++++++++----
 3 files changed, 23 insertions(+), 6 deletions(-)

diff -puN include/linux/fs.h~dont-hold-i_sem-on-swapfiles include/linux/fs.h
--- 25/include/linux/fs.h~dont-hold-i_sem-on-swapfiles	2004-06-26 14:06:19.308784056 -0700
+++ 25-akpm/include/linux/fs.h	2004-06-26 14:06:19.316782840 -0700
@@ -141,6 +141,7 @@ extern int leases_enable, dir_notify_ena
 #define S_NOQUOTA	64	/* Inode is not counted to quota */
 #define S_DIRSYNC	128	/* Directory modifications are synchronous */
 #define S_NOCMTIME	256	/* Do not update file c/mtime */
+#define S_SWAPFILE	512	/* Do not truncate: swapon got its bmaps */
 
 /*
  * Note that nosuid etc flags are inode-specific: setting some file-system
@@ -175,6 +176,7 @@ extern int leases_enable, dir_notify_ena
 
 #define IS_DEADDIR(inode)	((inode)->i_flags & S_DEAD)
 #define IS_NOCMTIME(inode)	((inode)->i_flags & S_NOCMTIME)
+#define IS_SWAPFILE(inode)	((inode)->i_flags & S_SWAPFILE)
 
 /* the read-only stuff doesn't really belong here, but any other place is
    probably as bad and I don't want to create yet another include file. */
diff -puN mm/memory.c~dont-hold-i_sem-on-swapfiles mm/memory.c
--- 25/mm/memory.c~dont-hold-i_sem-on-swapfiles	2004-06-26 14:06:19.309783904 -0700
+++ 25-akpm/mm/memory.c	2004-06-26 14:06:19.317782688 -0700
@@ -1236,6 +1236,8 @@ int vmtruncate(struct inode * inode, lof
 
 	if (inode->i_size < offset)
 		goto do_expand;
+	if (IS_SWAPFILE(inode))
+		goto out_busy;
 	i_size_write(inode, offset);
 	unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1);
 	truncate_inode_pages(mapping, offset);
@@ -1246,7 +1248,7 @@ do_expand:
 	if (limit != RLIM_INFINITY && offset > limit)
 		goto out_sig;
 	if (offset > inode->i_sb->s_maxbytes)
-		goto out;
+		goto out_big;
 
 	/*
 	 * Put a pagecache page at the current i_size and lock it while
@@ -1271,8 +1273,10 @@ out_truncate:
 	return 0;
 out_sig:
 	send_sig(SIGXFSZ, current, 0);
-out:
+out_big:
 	return -EFBIG;
+out_busy:
+	return -ETXTBSY;
 }
 
 EXPORT_SYMBOL(vmtruncate);
diff -puN mm/swapfile.c~dont-hold-i_sem-on-swapfiles mm/swapfile.c
--- 25/mm/swapfile.c~dont-hold-i_sem-on-swapfiles	2004-06-26 14:06:19.311783600 -0700
+++ 25-akpm/mm/swapfile.c	2004-06-26 14:06:19.319782384 -0700
@@ -1072,6 +1072,7 @@ asmlinkage long sys_swapoff(const char _
 	unsigned short *swap_map;
 	struct file *swap_file, *victim;
 	struct address_space *mapping;
+	struct inode *inode;
 	char * pathname;
 	int i, type, prev;
 	int err;
@@ -1165,12 +1166,15 @@ asmlinkage long sys_swapoff(const char _
 	swap_list_unlock();
 	up(&swapon_sem);
 	vfree(swap_map);
-	if (S_ISBLK(mapping->host->i_mode)) {
-		struct block_device *bdev = I_BDEV(mapping->host);
+	inode = mapping->host;
+	if (S_ISBLK(inode->i_mode)) {
+		struct block_device *bdev = I_BDEV(inode);
 		set_blocksize(bdev, p->old_block_size);
 		bd_release(bdev);
 	} else {
-		up(&mapping->host->i_sem);
+		down(&inode->i_sem);
+		inode->i_flags &= ~S_SWAPFILE;
+		up(&inode->i_sem);
 	}
 	filp_close(swap_file, NULL);
 	err = 0;
@@ -1388,6 +1392,10 @@ asmlinkage long sys_swapon(const char __
 		p->bdev = inode->i_sb->s_bdev;
 		down(&inode->i_sem);
 		did_down = 1;
+		if (IS_SWAPFILE(inode)) {
+			error = -EBUSY;
+			goto bad_swap;
+		}
 	} else {
 		goto bad_swap;
 	}
@@ -1560,8 +1568,11 @@ out:
 	}
 	if (name)
 		putname(name);
-	if (error && did_down)
+	if (did_down) {
+		if (!error)
+			inode->i_flags |= S_SWAPFILE;
 		up(&inode->i_sem);
+	}
 	return error;
 }
 
_