From: "Milton D. Miller II" The code at present has a small problem: when a fault is encountered we will run commit_write() to cover the amount of data which was successfully copied in from userspace. But filemap_copy_from_user() may have zeroed out some more of the page. So pagecache now has zeroes and the buffer_head which represents those zeroes is not dirtied. So a subsequent eviction and re-read of the file in the window beyond the faulting offset will return the file's old contents and not the zeroes. So we change filemap_copy_from_user_iovec() to have the same behaviour as the non-iovec filemap_copy_from_user(), and ensure that the commit_write() covers the parts of the page which copy_from_user() zeroed out. 25-akpm/mm/filemap.c | 30 +++++++++++++++++++++++------- 1 files changed, 23 insertions(+), 7 deletions(-) diff -puN mm/filemap.c~generic_file_write-fix-2 mm/filemap.c --- 25/mm/filemap.c~generic_file_write-fix-2 Tue Jun 3 11:00:00 2003 +++ 25-akpm/mm/filemap.c Tue Jun 3 11:00:00 2003 @@ -1408,6 +1408,11 @@ void remove_suid(struct dentry *dentry) } } +/* + * Copy as much as we can into the page and return the number of bytes which + * were sucessfully copied. If a fault is encountered then clear the page + * out to (offset+bytes) and return the number of bytes which were copied. + */ static inline size_t filemap_copy_from_user(struct page *page, unsigned long offset, const char __user *buf, unsigned bytes) @@ -1425,30 +1430,42 @@ filemap_copy_from_user(struct page *page left = __copy_from_user(kaddr + offset, buf, bytes); kunmap(page); } - return left ? 0 : bytes; + return bytes - left; } static size_t __filemap_copy_from_user_iovec(char *vaddr, const struct iovec *iov, size_t base, size_t bytes) { - size_t copied = 0; + size_t copied = 0, left = 0; while (bytes) { char __user *buf = iov->iov_base + base; int copy = min(bytes, iov->iov_len - base); base = 0; - if (__copy_from_user(vaddr, buf, copy)) - break; + left = __copy_from_user(vaddr, buf, copy); copied += copy; bytes -= copy; vaddr += copy; iov++; + + if (unlikely(left)) { + /* zero the rest of the target like __copy_from_user */ + if (bytes) + memset(vaddr, 0, bytes); + break; + } } - return copied; + return copied - left; } +/* + * This has the same sideeffects and return value as filemap_copy_from_user(). + * The difference is that on a fault we need to memset the remainder of the + * page (out to offset+bytes), to emulate filemap_copy_from_user()'s + * single-segment behaviour. + */ static inline size_t filemap_copy_from_user_iovec(struct page *page, unsigned long offset, const struct iovec *iov, size_t base, size_t bytes) @@ -1716,8 +1733,7 @@ generic_file_aio_write_nolock(struct kio copied = filemap_copy_from_user_iovec(page, offset, cur_iov, iov_base, bytes); flush_dcache_page(page); - status = a_ops->commit_write(file, page, offset, - offset + copied); + status = a_ops->commit_write(file, page, offset, offset+bytes); if (likely(copied > 0)) { if (!status) status = copied; _