Commit 7a35e30c authored by Hugh Dickins's avatar Hugh Dickins Committed by Linus Torvalds

[PATCH] Don't hold i_sem on swapfiles

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.

Synchronisation between swapon and truncate is via i_sem.
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 53d4b32b
...@@ -140,6 +140,7 @@ extern int leases_enable, dir_notify_enable, lease_break_time; ...@@ -140,6 +140,7 @@ extern int leases_enable, dir_notify_enable, lease_break_time;
#define S_NOQUOTA 64 /* Inode is not counted to quota */ #define S_NOQUOTA 64 /* Inode is not counted to quota */
#define S_DIRSYNC 128 /* Directory modifications are synchronous */ #define S_DIRSYNC 128 /* Directory modifications are synchronous */
#define S_NOCMTIME 256 /* Do not update file c/mtime */ #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 * Note that nosuid etc flags are inode-specific: setting some file-system
...@@ -174,6 +175,7 @@ extern int leases_enable, dir_notify_enable, lease_break_time; ...@@ -174,6 +175,7 @@ extern int leases_enable, dir_notify_enable, lease_break_time;
#define IS_DEADDIR(inode) ((inode)->i_flags & S_DEAD) #define IS_DEADDIR(inode) ((inode)->i_flags & S_DEAD)
#define IS_NOCMTIME(inode) ((inode)->i_flags & S_NOCMTIME) #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 /* 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. */ probably as bad and I don't want to create yet another include file. */
......
...@@ -1223,6 +1223,12 @@ int vmtruncate(struct inode * inode, loff_t offset) ...@@ -1223,6 +1223,12 @@ int vmtruncate(struct inode * inode, loff_t offset)
if (inode->i_size < offset) if (inode->i_size < offset)
goto do_expand; goto do_expand;
/*
* truncation of in-use swapfiles is disallowed - it would cause
* subsequent swapout to scribble on the now-freed blocks.
*/
if (IS_SWAPFILE(inode))
goto out_busy;
i_size_write(inode, offset); i_size_write(inode, offset);
unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1); unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1);
truncate_inode_pages(mapping, offset); truncate_inode_pages(mapping, offset);
...@@ -1233,7 +1239,7 @@ int vmtruncate(struct inode * inode, loff_t offset) ...@@ -1233,7 +1239,7 @@ int vmtruncate(struct inode * inode, loff_t offset)
if (limit != RLIM_INFINITY && offset > limit) if (limit != RLIM_INFINITY && offset > limit)
goto out_sig; goto out_sig;
if (offset > inode->i_sb->s_maxbytes) if (offset > inode->i_sb->s_maxbytes)
goto out; goto out_big;
i_size_write(inode, offset); i_size_write(inode, offset);
out_truncate: out_truncate:
...@@ -1242,8 +1248,10 @@ int vmtruncate(struct inode * inode, loff_t offset) ...@@ -1242,8 +1248,10 @@ int vmtruncate(struct inode * inode, loff_t offset)
return 0; return 0;
out_sig: out_sig:
send_sig(SIGXFSZ, current, 0); send_sig(SIGXFSZ, current, 0);
out: out_big:
return -EFBIG; return -EFBIG;
out_busy:
return -ETXTBSY;
} }
EXPORT_SYMBOL(vmtruncate); EXPORT_SYMBOL(vmtruncate);
......
...@@ -1072,6 +1072,7 @@ asmlinkage long sys_swapoff(const char __user * specialfile) ...@@ -1072,6 +1072,7 @@ asmlinkage long sys_swapoff(const char __user * specialfile)
unsigned short *swap_map; unsigned short *swap_map;
struct file *swap_file, *victim; struct file *swap_file, *victim;
struct address_space *mapping; struct address_space *mapping;
struct inode *inode;
char * pathname; char * pathname;
int i, type, prev; int i, type, prev;
int err; int err;
...@@ -1165,12 +1166,15 @@ asmlinkage long sys_swapoff(const char __user * specialfile) ...@@ -1165,12 +1166,15 @@ asmlinkage long sys_swapoff(const char __user * specialfile)
swap_list_unlock(); swap_list_unlock();
up(&swapon_sem); up(&swapon_sem);
vfree(swap_map); vfree(swap_map);
if (S_ISBLK(mapping->host->i_mode)) { inode = mapping->host;
struct block_device *bdev = I_BDEV(mapping->host); if (S_ISBLK(inode->i_mode)) {
struct block_device *bdev = I_BDEV(inode);
set_blocksize(bdev, p->old_block_size); set_blocksize(bdev, p->old_block_size);
bd_release(bdev); bd_release(bdev);
} else { } else {
up(&mapping->host->i_sem); down(&inode->i_sem);
inode->i_flags &= ~S_SWAPFILE;
up(&inode->i_sem);
} }
filp_close(swap_file, NULL); filp_close(swap_file, NULL);
err = 0; err = 0;
...@@ -1388,6 +1392,10 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags) ...@@ -1388,6 +1392,10 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags)
p->bdev = inode->i_sb->s_bdev; p->bdev = inode->i_sb->s_bdev;
down(&inode->i_sem); down(&inode->i_sem);
did_down = 1; did_down = 1;
if (IS_SWAPFILE(inode)) {
error = -EBUSY;
goto bad_swap;
}
} else { } else {
goto bad_swap; goto bad_swap;
} }
...@@ -1560,8 +1568,11 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags) ...@@ -1560,8 +1568,11 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags)
} }
if (name) if (name)
putname(name); putname(name);
if (error && did_down) if (did_down) {
if (!error)
inode->i_flags |= S_SWAPFILE;
up(&inode->i_sem); up(&inode->i_sem);
}
return error; return error;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment