mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-06 05:06:29 +00:00
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: David Howells <dhowells@redhat.com>
This commit is contained in:
parent
bcd89270d9
commit
6db3ac3c4b
@ -184,10 +184,13 @@ int afs_page_filler(void *data, struct page *page)
|
||||
if (!req)
|
||||
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);
|
||||
req->pos = (loff_t)page->index << PAGE_SHIFT;
|
||||
req->len = min_t(size_t, i_size_read(inode) - req->pos,
|
||||
PAGE_SIZE);
|
||||
req->len = PAGE_SIZE;
|
||||
req->nr_pages = 1;
|
||||
req->pages[0] = page;
|
||||
get_page(page);
|
||||
|
@ -16,6 +16,12 @@
|
||||
#include "internal.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
|
||||
*/
|
||||
@ -353,12 +359,6 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
|
||||
|
||||
req->actual_len |= ntohl(call->tmp);
|
||||
_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;
|
||||
call->offset = req->pos & (PAGE_SIZE - 1);
|
||||
@ -368,6 +368,7 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
|
||||
call->unmarshall++;
|
||||
|
||||
begin_page:
|
||||
ASSERTCMP(req->index, <, req->nr_pages);
|
||||
if (req->remain > PAGE_SIZE - call->offset)
|
||||
size = PAGE_SIZE - call->offset;
|
||||
else
|
||||
@ -390,18 +391,37 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
|
||||
if (req->page_done)
|
||||
req->page_done(call, req);
|
||||
if (req->remain > 0) {
|
||||
req->index++;
|
||||
call->offset = 0;
|
||||
req->index++;
|
||||
if (req->index >= req->nr_pages)
|
||||
goto begin_discard;
|
||||
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:
|
||||
call->offset = 0;
|
||||
call->unmarshall++;
|
||||
call->unmarshall = 5;
|
||||
|
||||
/* extract the metadata */
|
||||
case 4:
|
||||
case 5:
|
||||
ret = afs_extract_data(call, call->buffer,
|
||||
(21 + 3 + 6) * 4, false);
|
||||
if (ret < 0)
|
||||
@ -416,16 +436,17 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
|
||||
call->offset = 0;
|
||||
call->unmarshall++;
|
||||
|
||||
case 5:
|
||||
case 6:
|
||||
break;
|
||||
}
|
||||
|
||||
if (call->count < PAGE_SIZE) {
|
||||
buffer = kmap(req->pages[req->index]);
|
||||
memset(buffer + call->count, 0, PAGE_SIZE - call->count);
|
||||
kunmap(req->pages[req->index]);
|
||||
for (; req->index < req->nr_pages; req->index++) {
|
||||
if (call->count < PAGE_SIZE)
|
||||
zero_user_segment(req->pages[req->index],
|
||||
call->count, PAGE_SIZE);
|
||||
if (req->page_done)
|
||||
req->page_done(call, req);
|
||||
call->count = 0;
|
||||
}
|
||||
|
||||
_leave(" = 0 [done]");
|
||||
|
Loading…
Reference in New Issue
Block a user