diff options
Diffstat (limited to 'fs/ext2/inode.c')
| -rw-r--r-- | fs/ext2/inode.c | 160 | 
1 files changed, 122 insertions, 38 deletions
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index fc13cc119aad..19214435b752 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -22,7 +22,6 @@   *  Assorted race fixes, rewrite of ext2_get_block() by Al Viro, 2000   */ -#include <linux/smp_lock.h>  #include <linux/time.h>  #include <linux/highuid.h>  #include <linux/pagemap.h> @@ -55,6 +54,18 @@ static inline int ext2_inode_is_fast_symlink(struct inode *inode)  		inode->i_blocks - ea_blocks == 0);  } +static void ext2_truncate_blocks(struct inode *inode, loff_t offset); + +static void ext2_write_failed(struct address_space *mapping, loff_t to) +{ +	struct inode *inode = mapping->host; + +	if (to > inode->i_size) { +		truncate_pagecache(inode, to, inode->i_size); +		ext2_truncate_blocks(inode, inode->i_size); +	} +} +  /*   * Called at the last iput() if i_nlink is zero.   */ @@ -72,7 +83,7 @@ void ext2_delete_inode (struct inode * inode)  	inode->i_size = 0;  	if (inode->i_blocks) -		ext2_truncate (inode); +		ext2_truncate_blocks(inode, 0);  	ext2_free_inode (inode);  	return; @@ -758,8 +769,8 @@ int __ext2_write_begin(struct file *file, struct address_space *mapping,  		loff_t pos, unsigned len, unsigned flags,  		struct page **pagep, void **fsdata)  { -	return block_write_begin(file, mapping, pos, len, flags, pagep, fsdata, -							ext2_get_block); +	return block_write_begin_newtrunc(file, mapping, pos, len, flags, +					pagep, fsdata, ext2_get_block);  }  static int @@ -767,8 +778,25 @@ ext2_write_begin(struct file *file, struct address_space *mapping,  		loff_t pos, unsigned len, unsigned flags,  		struct page **pagep, void **fsdata)  { +	int ret; +  	*pagep = NULL; -	return __ext2_write_begin(file, mapping, pos, len, flags, pagep,fsdata); +	ret = __ext2_write_begin(file, mapping, pos, len, flags, pagep, fsdata); +	if (ret < 0) +		ext2_write_failed(mapping, pos + len); +	return ret; +} + +static int ext2_write_end(struct file *file, struct address_space *mapping, +			loff_t pos, unsigned len, unsigned copied, +			struct page *page, void *fsdata) +{ +	int ret; + +	ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata); +	if (ret < len) +		ext2_write_failed(mapping, pos + len); +	return ret;  }  static int @@ -776,13 +804,18 @@ ext2_nobh_write_begin(struct file *file, struct address_space *mapping,  		loff_t pos, unsigned len, unsigned flags,  		struct page **pagep, void **fsdata)  { +	int ret; +  	/*  	 * Dir-in-pagecache still uses ext2_write_begin. Would have to rework  	 * directory handling code to pass around offsets rather than struct  	 * pages in order to make this work easily.  	 */ -	return nobh_write_begin(file, mapping, pos, len, flags, pagep, fsdata, -							ext2_get_block); +	ret = nobh_write_begin_newtrunc(file, mapping, pos, len, flags, pagep, +						fsdata, ext2_get_block); +	if (ret < 0) +		ext2_write_failed(mapping, pos + len); +	return ret;  }  static int ext2_nobh_writepage(struct page *page, @@ -801,10 +834,15 @@ ext2_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov,  			loff_t offset, unsigned long nr_segs)  {  	struct file *file = iocb->ki_filp; -	struct inode *inode = file->f_mapping->host; - -	return blockdev_direct_IO(rw, iocb, inode, inode->i_sb->s_bdev, iov, -				offset, nr_segs, ext2_get_block, NULL); +	struct address_space *mapping = file->f_mapping; +	struct inode *inode = mapping->host; +	ssize_t ret; + +	ret = blockdev_direct_IO_newtrunc(rw, iocb, inode, inode->i_sb->s_bdev, +				iov, offset, nr_segs, ext2_get_block, NULL); +	if (ret < 0 && (rw & WRITE)) +		ext2_write_failed(mapping, offset + iov_length(iov, nr_segs)); +	return ret;  }  static int @@ -819,7 +857,7 @@ const struct address_space_operations ext2_aops = {  	.writepage		= ext2_writepage,  	.sync_page		= block_sync_page,  	.write_begin		= ext2_write_begin, -	.write_end		= generic_write_end, +	.write_end		= ext2_write_end,  	.bmap			= ext2_bmap,  	.direct_IO		= ext2_direct_IO,  	.writepages		= ext2_writepages, @@ -1028,7 +1066,7 @@ static void ext2_free_branches(struct inode *inode, __le32 *p, __le32 *q, int de  		ext2_free_data(inode, p, q);  } -void ext2_truncate(struct inode *inode) +static void __ext2_truncate_blocks(struct inode *inode, loff_t offset)  {  	__le32 *i_data = EXT2_I(inode)->i_data;  	struct ext2_inode_info *ei = EXT2_I(inode); @@ -1040,27 +1078,8 @@ void ext2_truncate(struct inode *inode)  	int n;  	long iblock;  	unsigned blocksize; - -	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || -	    S_ISLNK(inode->i_mode))) -		return; -	if (ext2_inode_is_fast_symlink(inode)) -		return; -	if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) -		return; -  	blocksize = inode->i_sb->s_blocksize; -	iblock = (inode->i_size + blocksize-1) -					>> EXT2_BLOCK_SIZE_BITS(inode->i_sb); - -	if (mapping_is_xip(inode->i_mapping)) -		xip_truncate_page(inode->i_mapping, inode->i_size); -	else if (test_opt(inode->i_sb, NOBH)) -		nobh_truncate_page(inode->i_mapping, -				inode->i_size, ext2_get_block); -	else -		block_truncate_page(inode->i_mapping, -				inode->i_size, ext2_get_block); +	iblock = (offset + blocksize-1) >> EXT2_BLOCK_SIZE_BITS(inode->i_sb);  	n = ext2_block_to_path(inode, iblock, offsets, NULL);  	if (n == 0) @@ -1128,6 +1147,62 @@ do_indirects:  	ext2_discard_reservation(inode);  	mutex_unlock(&ei->truncate_mutex); +} + +static void ext2_truncate_blocks(struct inode *inode, loff_t offset) +{ +	/* +	 * XXX: it seems like a bug here that we don't allow +	 * IS_APPEND inode to have blocks-past-i_size trimmed off. +	 * review and fix this. +	 * +	 * Also would be nice to be able to handle IO errors and such, +	 * but that's probably too much to ask. +	 */ +	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || +	    S_ISLNK(inode->i_mode))) +		return; +	if (ext2_inode_is_fast_symlink(inode)) +		return; +	if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) +		return; +	__ext2_truncate_blocks(inode, offset); +} + +int ext2_setsize(struct inode *inode, loff_t newsize) +{ +	loff_t oldsize; +	int error; + +	error = inode_newsize_ok(inode, newsize); +	if (error) +		return error; + +	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || +	    S_ISLNK(inode->i_mode))) +		return -EINVAL; +	if (ext2_inode_is_fast_symlink(inode)) +		return -EINVAL; +	if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) +		return -EPERM; + +	if (mapping_is_xip(inode->i_mapping)) +		error = xip_truncate_page(inode->i_mapping, newsize); +	else if (test_opt(inode->i_sb, NOBH)) +		error = nobh_truncate_page(inode->i_mapping, +				newsize, ext2_get_block); +	else +		error = block_truncate_page(inode->i_mapping, +				newsize, ext2_get_block); +	if (error) +		return error; + +	oldsize = inode->i_size; +	i_size_write(inode, newsize); +	truncate_pagecache(inode, oldsize, newsize); + +	__ext2_truncate_blocks(inode, newsize); +  	inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;  	if (inode_needs_sync(inode)) {  		sync_mapping_buffers(inode->i_mapping); @@ -1135,6 +1210,8 @@ do_indirects:  	} else {  		mark_inode_dirty(inode);  	} + +	return 0;  }  static struct ext2_inode *ext2_get_inode(struct super_block *sb, ino_t ino, @@ -1406,11 +1483,11 @@ static int __ext2_write_inode(struct inode *inode, int do_sync)  			       /* If this is the first large file  				* created, add a flag to the superblock.  				*/ -				lock_kernel(); +				spin_lock(&EXT2_SB(sb)->s_lock);  				ext2_update_dynamic_rev(sb);  				EXT2_SET_RO_COMPAT_FEATURE(sb,  					EXT2_FEATURE_RO_COMPAT_LARGE_FILE); -				unlock_kernel(); +				spin_unlock(&EXT2_SB(sb)->s_lock);  				ext2_write_super(sb);  			}  		} @@ -1467,7 +1544,7 @@ int ext2_setattr(struct dentry *dentry, struct iattr *iattr)  	if (error)  		return error; -	if (iattr->ia_valid & ATTR_SIZE) +	if (is_quota_modification(inode, iattr))  		dquot_initialize(inode);  	if ((iattr->ia_valid & ATTR_UID && iattr->ia_uid != inode->i_uid) ||  	    (iattr->ia_valid & ATTR_GID && iattr->ia_gid != inode->i_gid)) { @@ -1475,8 +1552,15 @@ int ext2_setattr(struct dentry *dentry, struct iattr *iattr)  		if (error)  			return error;  	} -	error = inode_setattr(inode, iattr); -	if (!error && (iattr->ia_valid & ATTR_MODE)) +	if (iattr->ia_valid & ATTR_SIZE) { +		error = ext2_setsize(inode, iattr->ia_size); +		if (error) +			return error; +	} +	generic_setattr(inode, iattr); +	if (iattr->ia_valid & ATTR_MODE)  		error = ext2_acl_chmod(inode); +	mark_inode_dirty(inode); +  	return error;  }  |