From: Hugh Dickins 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 --- 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; } _