mm: trim more holes
If prepare_write fails with AOP_TRUNCATED_PAGE, or if commit_write fails, then we may have failed the write operation despite prepare_write having instantiated blocks past i_size. Fix this, and consolidate the trimming into one place. Signed-off-by: Nick Piggin <npiggin@suse.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
5fe1723706
commit
64649a5891
1 changed files with 40 additions and 40 deletions
80
mm/filemap.c
80
mm/filemap.c
|
@ -1895,22 +1895,9 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
|
|||
}
|
||||
|
||||
status = a_ops->prepare_write(file, page, offset, offset+bytes);
|
||||
if (unlikely(status)) {
|
||||
loff_t isize = i_size_read(inode);
|
||||
if (unlikely(status))
|
||||
goto fs_write_aop_error;
|
||||
|
||||
if (status != AOP_TRUNCATED_PAGE)
|
||||
unlock_page(page);
|
||||
page_cache_release(page);
|
||||
if (status == AOP_TRUNCATED_PAGE)
|
||||
continue;
|
||||
/*
|
||||
* prepare_write() may have instantiated a few blocks
|
||||
* outside i_size. Trim these off again.
|
||||
*/
|
||||
if (pos + bytes > isize)
|
||||
vmtruncate(inode, isize);
|
||||
break;
|
||||
}
|
||||
if (likely(nr_segs == 1))
|
||||
copied = filemap_copy_from_user(page, offset,
|
||||
buf, bytes);
|
||||
|
@ -1919,40 +1906,53 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
|
|||
cur_iov, iov_offset, bytes);
|
||||
flush_dcache_page(page);
|
||||
status = a_ops->commit_write(file, page, offset, offset+bytes);
|
||||
if (status == AOP_TRUNCATED_PAGE) {
|
||||
page_cache_release(page);
|
||||
continue;
|
||||
if (unlikely(status < 0 || status == AOP_TRUNCATED_PAGE))
|
||||
goto fs_write_aop_error;
|
||||
if (unlikely(copied != bytes)) {
|
||||
status = -EFAULT;
|
||||
goto fs_write_aop_error;
|
||||
}
|
||||
if (likely(copied > 0)) {
|
||||
if (!status)
|
||||
status = copied;
|
||||
if (unlikely(status > 0)) /* filesystem did partial write */
|
||||
copied = status;
|
||||
|
||||
if (status >= 0) {
|
||||
written += status;
|
||||
count -= status;
|
||||
pos += status;
|
||||
buf += status;
|
||||
if (unlikely(nr_segs > 1)) {
|
||||
filemap_set_next_iovec(&cur_iov,
|
||||
&iov_offset, status);
|
||||
if (count)
|
||||
buf = cur_iov->iov_base +
|
||||
iov_offset;
|
||||
} else {
|
||||
iov_offset += status;
|
||||
}
|
||||
if (likely(copied > 0)) {
|
||||
written += copied;
|
||||
count -= copied;
|
||||
pos += copied;
|
||||
buf += copied;
|
||||
if (unlikely(nr_segs > 1)) {
|
||||
filemap_set_next_iovec(&cur_iov,
|
||||
&iov_offset, copied);
|
||||
if (count)
|
||||
buf = cur_iov->iov_base + iov_offset;
|
||||
} else {
|
||||
iov_offset += copied;
|
||||
}
|
||||
}
|
||||
if (unlikely(copied != bytes))
|
||||
if (status >= 0)
|
||||
status = -EFAULT;
|
||||
unlock_page(page);
|
||||
mark_page_accessed(page);
|
||||
page_cache_release(page);
|
||||
if (status < 0)
|
||||
break;
|
||||
balance_dirty_pages_ratelimited(mapping);
|
||||
cond_resched();
|
||||
continue;
|
||||
|
||||
fs_write_aop_error:
|
||||
if (status != AOP_TRUNCATED_PAGE)
|
||||
unlock_page(page);
|
||||
page_cache_release(page);
|
||||
|
||||
/*
|
||||
* prepare_write() may have instantiated a few blocks
|
||||
* outside i_size. Trim these off again. Don't need
|
||||
* i_size_read because we hold i_mutex.
|
||||
*/
|
||||
if (pos + bytes > inode->i_size)
|
||||
vmtruncate(inode, inode->i_size);
|
||||
if (status == AOP_TRUNCATED_PAGE)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
|
||||
} while (count);
|
||||
*ppos = pos;
|
||||
|
||||
|
|
Loading…
Reference in a new issue