splice: don't read more than available pipe space
RFC PATCH 1/1 splice: don't read more than available pipe space
From: Darrick J. Wong <[email protected]>
In commit 4721a601099, we tried to fix a problem wherein directio reads into a splice pipe will bounce EFAULT/EAGAIN all the way out to userspace by simulating a zero-byte short read. This happens because some directio read implementations (xfs) will call bio_iov_iter_get_pages to grab pipe buffer pages and issue asynchronous reads, but as soon as we run out of pipe buffers that _get_pages call returns EFAULT, which the splice code translates to EAGAIN and bounces out to userspace.
In that commit, the iomap code catches the EFAULT and simulates a zero-byte read, but that causes assertion errors on regular splice reads because xfs doesn't allow short directio reads.
The brokenness is compounded by splice_direct_to_actor immediately bailing on do_splice_to returning <= 0 without ever calling ->actor (which empties out the pipe), so if userspace calls back we'll EFAULT again on the full pipe, and nothing ever gets copied.
Therefore, teach splice_direct_to_actor to clamp its requests to the amount of free space in the pipe and remove the simulated short read from the iomap directio code.
--- fs/iomap.c | 9 --------- fs/splice.c | 5 ++++- 2 files changed, 4 insertions(+), 10 deletions(-)
fs/iomap.c +0 -9
@@ -1877,15 +1877,6 @@ iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, dio->wait_for_completion = true; ret =0 ; } - -/* -* Splicing to pipes can fail on a full pipe. We have to -* swallow this to make it look like a short IO -* otherwise the higher splice layers will completely -* mishandle the error and stop moving data. -*/ -if (ret == -EFAULT ) - ret =0 ;break ; } pos += ret;
fs/splice.c +4 -1
@@ -949,7 +949,10 @@ ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd,size_t read_len;loff_t pos = sd->pos, prev_pos = pos; - ret =do_splice_to (in, &pos, pipe, len, flags); +/* Don't try to read more the pipe has space for. */ + read_len =min_t (size_t, len, + (pipe->buffers - pipe->nrbufs) <<PAGE_SHIFT ); + ret =do_splice_to (in, &pos, pipe, read_len, flags);if (unlikely (ret <=0 )) goto out_release;
Quoted 21 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
In commit
4721a601099, we tried to fix a problem wherein directio reads into a splice pipe will bounce EFAULT/EAGAIN all the way out to userspace by simulating a zero-byte short read. This happens because some directio read implementations (xfs) will callbio_iov_iter_get_pagesto grab pipe buffer pages and issue asynchronous reads, but as soon as we run out of pipe buffers that_get_pagescall returns EFAULT, which the splice code translates to EAGAIN and bounces out to userspace.In that commit, the iomap code catches the EFAULT and simulates a zero-byte read, but that causes assertion errors on regular splice reads because xfs doesn't allow short directio reads.
The brokenness is compounded by
splice_direct_to_actorimmediately bailing ondo_splice_toreturning <= 0 without ever calling ->actor (which empties out the pipe), so if userspace calls back we'll EFAULT again on the full pipe, and nothing ever gets copied.
Quoted 7 lines
Therefore, teach
splice_direct_to_actorto clamp its requests to the amount of free space in the pipe and remove the simulated short read from the iomap directio code.Fixes 4721a601099 ("iomap: dio data corruption and spurious errors when pipes fill")Signed-off-by Darrick J. Wong <[email protected]>---
Problem is the regressing commit does two things and you here revert one of them and re-fix the bug.
IMO, it would be nicer to:
- Revert the regression commit
- Attribute Reported-by and specify regression details in revert commit
- Re-apply Dave's zero_tail fix with its own commit instead of a "To make matters worse..." clause.
- Apply your re-fix with description of original problem and without the story about the regression
Your call.
Miklos,
Can you please review the suggested re-fix?
Thanks,
Amir.
Quoted 3 lines · Darrick J. Wong
Therefore, teach
splice_direct_to_actorto clamp its requests to the amount of free space in the pipe and remove the simulated short read from the iomap directio code.
Seems like this should be two different commits? E.g. fix the splice code first, remove the hack from XFS next? (and yes, I know we already have a similar but slightly different suggestion from Amir, but I don't think explicit reverts buy us anything).
Otherwise this looks good to me: