diff options
Diffstat (limited to 'fs/ext4/inode.c')
| -rw-r--r-- | fs/ext4/inode.c | 116 | 
1 files changed, 90 insertions, 26 deletions
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index f2fa5e8a582c..a5763e3505ba 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -639,8 +639,8 @@ static int ext4_alloc_blocks(handle_t *handle, struct inode *inode,  	while (target > 0) {  		count = target;  		/* allocating blocks for indirect blocks and direct blocks */ -		current_block = ext4_new_meta_blocks(handle, inode, -							goal, &count, err); +		current_block = ext4_new_meta_blocks(handle, inode, goal, +						     0, &count, err);  		if (*err)  			goto failed_out; @@ -1930,7 +1930,7 @@ repeat:  	 * We do still charge estimated metadata to the sb though;  	 * we cannot afford to run out of free blocks.  	 */ -	if (ext4_claim_free_blocks(sbi, md_needed + 1)) { +	if (ext4_claim_free_blocks(sbi, md_needed + 1, 0)) {  		dquot_release_reservation_block(inode, 1);  		if (ext4_should_retry_alloc(inode->i_sb, &retries)) {  			yield(); @@ -2796,9 +2796,7 @@ static int write_cache_pages_da(struct address_space *mapping,  				continue;  			} -			if (PageWriteback(page)) -				wait_on_page_writeback(page); - +			wait_on_page_writeback(page);  			BUG_ON(PageWriteback(page));  			if (mpd->next_page != page->index) @@ -3513,7 +3511,7 @@ retry:  			loff_t end = offset + iov_length(iov, nr_segs);  			if (end > isize) -				vmtruncate(inode, isize); +				ext4_truncate_failed_write(inode);  		}  	}  	if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries)) @@ -3916,9 +3914,30 @@ void ext4_set_aops(struct inode *inode)  int ext4_block_truncate_page(handle_t *handle,  		struct address_space *mapping, loff_t from)  { +	unsigned offset = from & (PAGE_CACHE_SIZE-1); +	unsigned length; +	unsigned blocksize; +	struct inode *inode = mapping->host; + +	blocksize = inode->i_sb->s_blocksize; +	length = blocksize - (offset & (blocksize - 1)); + +	return ext4_block_zero_page_range(handle, mapping, from, length); +} + +/* + * ext4_block_zero_page_range() zeros out a mapping of length 'length' + * starting from file offset 'from'.  The range to be zero'd must + * be contained with in one block.  If the specified range exceeds + * the end of the block it will be shortened to end of the block + * that cooresponds to 'from' + */ +int ext4_block_zero_page_range(handle_t *handle, +		struct address_space *mapping, loff_t from, loff_t length) +{  	ext4_fsblk_t index = from >> PAGE_CACHE_SHIFT;  	unsigned offset = from & (PAGE_CACHE_SIZE-1); -	unsigned blocksize, length, pos; +	unsigned blocksize, max, pos;  	ext4_lblk_t iblock;  	struct inode *inode = mapping->host;  	struct buffer_head *bh; @@ -3931,7 +3950,15 @@ int ext4_block_truncate_page(handle_t *handle,  		return -EINVAL;  	blocksize = inode->i_sb->s_blocksize; -	length = blocksize - (offset & (blocksize - 1)); +	max = blocksize - (offset & (blocksize - 1)); + +	/* +	 * correct length if it does not fall between +	 * 'from' and the end of the block +	 */ +	if (length > max || length < 0) +		length = max; +  	iblock = index << (PAGE_CACHE_SHIFT - inode->i_sb->s_blocksize_bits);  	if (!page_has_buffers(page)) @@ -4380,8 +4407,6 @@ static void ext4_free_branches(handle_t *handle, struct inode *inode,  int ext4_can_truncate(struct inode *inode)  { -	if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) -		return 0;  	if (S_ISREG(inode->i_mode))  		return 1;  	if (S_ISDIR(inode->i_mode)) @@ -4392,6 +4417,31 @@ int ext4_can_truncate(struct inode *inode)  }  /* + * ext4_punch_hole: punches a hole in a file by releaseing the blocks + * associated with the given offset and length + * + * @inode:  File inode + * @offset: The offset where the hole will begin + * @len:    The length of the hole + * + * Returns: 0 on sucess or negative on failure + */ + +int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) +{ +	struct inode *inode = file->f_path.dentry->d_inode; +	if (!S_ISREG(inode->i_mode)) +		return -ENOTSUPP; + +	if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) { +		/* TODO: Add support for non extent hole punching */ +		return -ENOTSUPP; +	} + +	return ext4_ext_punch_hole(file, offset, length); +} + +/*   * ext4_truncate()   *   * We block out ext4_get_block() block instantiations across the entire @@ -4617,7 +4667,7 @@ static int __ext4_get_inode_loc(struct inode *inode,  	/*  	 * Figure out the offset within the block group inode table  	 */ -	inodes_per_block = (EXT4_BLOCK_SIZE(sb) / EXT4_INODE_SIZE(sb)); +	inodes_per_block = EXT4_SB(sb)->s_inodes_per_block;  	inode_offset = ((inode->i_ino - 1) %  			EXT4_INODES_PER_GROUP(sb));  	block = ext4_inode_table(sb, gdp) + (inode_offset / inodes_per_block); @@ -5311,8 +5361,7 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)  	if (S_ISREG(inode->i_mode) &&  	    attr->ia_valid & ATTR_SIZE && -	    (attr->ia_size < inode->i_size || -	     (ext4_test_inode_flag(inode, EXT4_INODE_EOFBLOCKS)))) { +	    (attr->ia_size < inode->i_size)) {  		handle_t *handle;  		handle = ext4_journal_start(inode, 3); @@ -5346,14 +5395,15 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)  				goto err_out;  			}  		} -		/* ext4_truncate will clear the flag */ -		if ((ext4_test_inode_flag(inode, EXT4_INODE_EOFBLOCKS))) -			ext4_truncate(inode);  	} -	if ((attr->ia_valid & ATTR_SIZE) && -	    attr->ia_size != i_size_read(inode)) -		rc = vmtruncate(inode, attr->ia_size); +	if (attr->ia_valid & ATTR_SIZE) { +		if (attr->ia_size != i_size_read(inode)) { +			truncate_setsize(inode, attr->ia_size); +			ext4_truncate(inode); +		} else if (ext4_test_inode_flag(inode, EXT4_INODE_EOFBLOCKS)) +			ext4_truncate(inode); +	}  	if (!rc) {  		setattr_copy(inode, attr); @@ -5683,7 +5733,7 @@ int ext4_mark_inode_dirty(handle_t *handle, struct inode *inode)   * so would cause a commit on atime updates, which we don't bother doing.   * We handle synchronous inodes at the highest possible level.   */ -void ext4_dirty_inode(struct inode *inode) +void ext4_dirty_inode(struct inode *inode, int flags)  {  	handle_t *handle; @@ -5811,15 +5861,19 @@ int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)  		goto out_unlock;  	}  	ret = 0; -	if (PageMappedToDisk(page)) -		goto out_unlock; + +	lock_page(page); +	wait_on_page_writeback(page); +	if (PageMappedToDisk(page)) { +		up_read(&inode->i_alloc_sem); +		return VM_FAULT_LOCKED; +	}  	if (page->index == size >> PAGE_CACHE_SHIFT)  		len = size & ~PAGE_CACHE_MASK;  	else  		len = PAGE_CACHE_SIZE; -	lock_page(page);  	/*  	 * return if we have all the buffers mapped. This avoid  	 * the need to call write_begin/write_end which does a @@ -5829,8 +5883,8 @@ int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)  	if (page_has_buffers(page)) {  		if (!walk_page_buffers(NULL, page_buffers(page), 0, len, NULL,  					ext4_bh_unmapped)) { -			unlock_page(page); -			goto out_unlock; +			up_read(&inode->i_alloc_sem); +			return VM_FAULT_LOCKED;  		}  	}  	unlock_page(page); @@ -5850,6 +5904,16 @@ int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)  	if (ret < 0)  		goto out_unlock;  	ret = 0; + +	/* +	 * write_begin/end might have created a dirty page and someone +	 * could wander in and start the IO.  Make sure that hasn't +	 * happened. +	 */ +	lock_page(page); +	wait_on_page_writeback(page); +	up_read(&inode->i_alloc_sem); +	return VM_FAULT_LOCKED;  out_unlock:  	if (ret)  		ret = VM_FAULT_SIGBUS;  | 
