afs: Fix fetchInit for negative/large lengths 29/11829/4
authorAndrew Deason <adeason@sinenomine.net>
Wed, 8 Apr 2015 03:10:53 +0000 (22:10 -0500)
committerBenjamin Kaduk <kaduk@mit.edu>
Mon, 25 Jan 2016 02:28:07 +0000 (21:28 -0500)
commitc0f52c3a3d76059c9d8b2df3374df844d8d6861b
tree3c2585ad63bc9579fb01bb701d25ea190f06ab1b
parent3caee7575491c33920b9c84f5dc4098d99373cf6
afs: Fix fetchInit for negative/large lengths

Currently, the 'length64' variable in rxfs_fetchInit is almost
completely unused (it just goes into an icl logging function). For the
length that we actually use ('*alength'), we just take the lower 32
bits of the length that the fileserver told us. This method is
incorrect in at least the following cases:

 - If the fileserver returns a length that is larger than 2^32-1,
   we'll just take the lower 32 bits of the 64-bit length the
   fileserver told us about. The client currently never requests a
   fetch larger than 2^32-1, so this would be an error, but if this
   occurred, we would not detect it until much later in the fetch.

 - If the fileserver returns a length that is larger than 2^31-1, but
   smaller than 2^32, we'll interpret the length as negative (which we
   assume is just 0, due to bugs in older fileservers). This is also
   incorrect.

 - If the fileserver returns a negative length smaller than -2^31+1,
   we may interpret the give length as a positive value instead of a
   negative one. Older fileservers can do this if we fetch data beyond
   the file's EOF (this was fixed in the fileserver in commit
   529d487d65d8561f5d0a43a4dc71f72b86efd975). This positive length
   will cause an error (usually), instead of proceeding without error
   (which is what would happen if we correctly interpreted the length
   as negative).

On Solaris, this can manifest as a failed write, when writing to a
location far beyond the file's EOF from the fileserver's point of
view, because Solaris writes can trigger a fetch for the same area.
Seeking to a location far beyond the file's EOF and writing can
trigger this, as can a normal copy into AFS, if the file is large
enough and the cache is large enough. To explain in more detail:

When copying a file into AFS, the cache manager will buffer the dirty
data in the disk cache until the file is synced/closed, or we run out
of cache space. While this data is buffering, the application will
write into an offset, say, 3GiB into the file. On Solaris, this can
trigger a read for the same region, which will trigger a fetch from
the fileserver at the offset 3GiB into the file. If the fileserver
does not contain the fix in commit
529d487d65d8561f5d0a43a4dc71f72b86efd975, it will respond with a large
negative number, which we interpret as a large positive number; much
larger than the requested length. This will cause the fetch to fail,
which then causes the whole write() call to fail. Specifically this
will fail with EINVAL on Solaris, since that is the error code we
return from afs_GetOnePage when we fail to acquire a dcache. If the
cache is small enough, this will not happen, since we will flush data
to the fileserver before we have a large amount of dirty data,
e.g., 3GiB. (The actual error occurs closer to 2GiB, but this is just
for illustrative purposes.)

To fix this, detect the various ranges of values mentioned above, and
handle them specially. Lengths that are too large will yield an error,
since we cannot handle values over 2^31-1 in the rxfs_* framework
currently.

For lengths that are negative, just act as if we received a length of
0. Do this for both the 64-bit codepath and the non-64-bit codepath,
just so they remain identical.

[mmeffie@sinenomine.net: directly use 64 bit comparisons, don't mask
end call error code, commit nits.]

Change-Id: I7e8f2132d52747b7f0ce4a6a5ba81f6641a298a8
Reviewed-on: http://gerrit.openafs.org/11829
Reviewed-by: Chas Williams <3chas3@gmail.com>
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: BuildBot <buildbot@rampaginggeek.com>
src/afs/afs_fetchstore.c