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:
@@ -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 @@
|
|||||||
#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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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]");
|
||||||
|
|||||||
Reference in New Issue
Block a user