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

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
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -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);
+35 −14
Original line number Diff line number Diff line
@@ -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]");