Commit 6db3ac3c authored by David Howells's avatar David Howells

afs: Handle better the server returning excess or short data

When an AFS server is given an FS.FetchData{,64} request to read data from
a file, it is permitted by the protocol to return more or less than was
requested.  kafs currently relies on the latter behaviour in readpage{,s}
to handle a partial page at the end of the file (we just ask for a whole
page and clear space beyond the short read).

However, we don't handle all cases.  Add:

 (1) Handle excess data by discarding it rather than aborting.  Note that
     we use a common static buffer to discard into so that the decryption
     algorithm advances the PCBC state.

 (2) Handle a short read that affects more than just the last page.

Note that if a read comes up unexpectedly short of long, it's possible that
the server's copy of the file changed - in which case the data version
number will have been incremented and the callback will have been broken -
in which case all the pages currently attached to the inode will be zapped
anyway at some point.
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
parent bcd89270
...@@ -184,10 +184,13 @@ int afs_page_filler(void *data, struct page *page) ...@@ -184,10 +184,13 @@ int afs_page_filler(void *data, struct page *page)
if (!req) if (!req)
goto enomem; goto enomem;
/* We request a full page. If the page is a partial one at the
* end of the file, the server will return a short read and the
* unmarshalling code will clear the unfilled space.
*/
atomic_set(&req->usage, 1); atomic_set(&req->usage, 1);
req->pos = (loff_t)page->index << PAGE_SHIFT; req->pos = (loff_t)page->index << PAGE_SHIFT;
req->len = min_t(size_t, i_size_read(inode) - req->pos, req->len = PAGE_SIZE;
PAGE_SIZE);
req->nr_pages = 1; req->nr_pages = 1;
req->pages[0] = page; req->pages[0] = page;
get_page(page); get_page(page);
......
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
#include "internal.h" #include "internal.h"
#include "afs_fs.h" #include "afs_fs.h"
/*
* We need somewhere to discard into in case the server helpfully returns more
* than we asked for in FS.FetchData{,64}.
*/
static u8 afs_discard_buffer[64];
/* /*
* decode an AFSFid block * decode an AFSFid block
*/ */
...@@ -353,12 +359,6 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call) ...@@ -353,12 +359,6 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
req->actual_len |= ntohl(call->tmp); req->actual_len |= ntohl(call->tmp);
_debug("DATA length: %llu", req->actual_len); _debug("DATA length: %llu", req->actual_len);
/* Check that the server didn't want to send us extra. We
* might want to just discard instead, but that requires
* cooperation from AF_RXRPC.
*/
if (req->actual_len > req->len)
return -EBADMSG;
req->remain = req->actual_len; req->remain = req->actual_len;
call->offset = req->pos & (PAGE_SIZE - 1); call->offset = req->pos & (PAGE_SIZE - 1);
...@@ -368,6 +368,7 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call) ...@@ -368,6 +368,7 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
call->unmarshall++; call->unmarshall++;
begin_page: begin_page:
ASSERTCMP(req->index, <, req->nr_pages);
if (req->remain > PAGE_SIZE - call->offset) if (req->remain > PAGE_SIZE - call->offset)
size = PAGE_SIZE - call->offset; size = PAGE_SIZE - call->offset;
else else
...@@ -390,18 +391,37 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call) ...@@ -390,18 +391,37 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
if (req->page_done) if (req->page_done)
req->page_done(call, req); req->page_done(call, req);
if (req->remain > 0) { if (req->remain > 0) {
req->index++;
call->offset = 0; call->offset = 0;
req->index++;
if (req->index >= req->nr_pages)
goto begin_discard;
goto begin_page; goto begin_page;
} }
} }
goto no_more_data;
/* Discard any excess data the server gave us */
begin_discard:
case 4:
size = min_t(size_t, sizeof(afs_discard_buffer), req->remain);
call->count = size;
_debug("extract discard %u/%llu %zu/%u",
req->remain, req->actual_len, call->offset, call->count);
call->offset = 0;
ret = afs_extract_data(call, afs_discard_buffer, call->count, true);
req->remain -= call->offset;
if (ret < 0)
return ret;
if (req->remain > 0)
goto begin_discard;
no_more_data: no_more_data:
call->offset = 0; call->offset = 0;
call->unmarshall++; call->unmarshall = 5;
/* extract the metadata */ /* extract the metadata */
case 4: case 5:
ret = afs_extract_data(call, call->buffer, ret = afs_extract_data(call, call->buffer,
(21 + 3 + 6) * 4, false); (21 + 3 + 6) * 4, false);
if (ret < 0) if (ret < 0)
...@@ -416,16 +436,17 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call) ...@@ -416,16 +436,17 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
call->offset = 0; call->offset = 0;
call->unmarshall++; call->unmarshall++;
case 5: case 6:
break; break;
} }
if (call->count < PAGE_SIZE) { for (; req->index < req->nr_pages; req->index++) {
buffer = kmap(req->pages[req->index]); if (call->count < PAGE_SIZE)
memset(buffer + call->count, 0, PAGE_SIZE - call->count); zero_user_segment(req->pages[req->index],
kunmap(req->pages[req->index]); call->count, PAGE_SIZE);
if (req->page_done) if (req->page_done)
req->page_done(call, req); req->page_done(call, req);
call->count = 0;
} }
_leave(" = 0 [done]"); _leave(" = 0 [done]");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment