[PATCH 3/5] fuse: writepages: crop secondary requests on attach

From: Maxim Patlasov
Date: Wed Oct 02 2013 - 07:02:41 EST


If a request was already cropped according to i_size (i.e. went through
fuse_send_writepage()) and then another writeback happens and we decided
to attach it the request, we must crop it according to misc.write.in.size
of the request. Otherwise the following scenario could lead to writing
stale data (where zeros are expected):

1. Shrinking ftruncate(2) is handled by fuse_do_setattr() which eventually
calls fuse_release_nowrite() which properly crops requests sitting in
fi->queued_writes. Since now some range in a page-cache page is invalid
(i.e. contains stale data which shouldn't come to the server). This properly
reflected by misc.write.in.size of a request.
2. The page is re-dirtied, new writeback happens, fuse_writepage_in_flight()
attaches new request to the request (because it's already in-flight).
3. Extending ftruncate(2) increases i_size, so by the time that attached
request comes to fuse_send_writepage(), i_size is already extended
and that stale range of a page will come to the server. The result is that
the user will see stale data where zeros are expected.

Signed-off-by: Maxim Patlasov <MPatlasov@xxxxxxxxxxxxx>
---
fs/fuse/file.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index a5d1f87..036fcf3 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1680,6 +1680,14 @@ static void fuse_writepages_send(struct fuse_fill_wb_data *data)
end_page_writeback(data->orig_pages[i]);
}

+static inline loff_t fuse_req_end_pos(struct fuse_req *req)
+{
+ __u64 data_size = req->misc.write.in.size ? :
+ req->num_pages * PAGE_CACHE_SIZE;
+
+ return req->misc.write.in.offset + data_size;
+}
+
static bool fuse_writepage_in_flight(struct fuse_req *new_req,
struct page *page)
{
@@ -1689,6 +1697,7 @@ static bool fuse_writepage_in_flight(struct fuse_req *new_req,
struct fuse_req *old_req;
bool found = false;
pgoff_t curr_index;
+ bool page_copied = false;

BUG_ON(new_req->num_pages != 0);

@@ -1722,8 +1731,12 @@ static bool fuse_writepage_in_flight(struct fuse_req *new_req,
if (old_req->num_pages == 1 && (old_req->state == FUSE_REQ_INIT ||
old_req->state == FUSE_REQ_PENDING)) {
copy_highpage(old_req->pages[0], page);
- spin_unlock(&fc->lock);
+ page_copied = true;
+ }

+ if (page_copied ||
+ new_req->misc.write.in.offset >= fuse_req_end_pos(old_req)) {
+ spin_unlock(&fc->lock);
dec_bdi_stat(page->mapping->backing_dev_info, BDI_WRITEBACK);
dec_zone_page_state(page, NR_WRITEBACK_TEMP);
fuse_writepage_free(fc, new_req);
@@ -1732,6 +1745,11 @@ static bool fuse_writepage_in_flight(struct fuse_req *new_req,
} else {
new_req->misc.write.next = old_req->misc.write.next;
old_req->misc.write.next = new_req;
+
+ if (fuse_req_end_pos(new_req) > fuse_req_end_pos(old_req))
+ new_req->misc.write.in.size =
+ fuse_req_end_pos(old_req) -
+ new_req->misc.write.in.offset;
}
out_unlock:
spin_unlock(&fc->lock);

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/