mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-04 04:04:19 +00:00
fix return values of seq_read_iter()
Unlike ->read(), ->read_iter() instances *must* return the amount
of data they'd left in iterator. For ->read() returning less than
it has actually copied is a QoI issue; read(fd, unmapped_page - 5, 8)
is allowed to fill all 5 bytes of destination and return 4; it's
not nice to caller, but POSIX allows pretty much anything in such
situation, up to and including a SIGSEGV.
generic_file_splice_read() uses pipe-backed iterator as destination;
there a short copy comes from pipe being full, not from running into
an un{mapped,writable} page in the middle of destination as we
have for iovec-backed iterators read(2) uses. And there we rely
upon the ->read_iter() reporting the actual amount it has left
in destination.
Conversion of a ->read() instance into ->read_iter() has to watch
out for that. If you really need an "all or nothing" kind of
behaviour somewhere, you need to do iov_iter_revert() to prune
the partial copy.
In case of seq_read_iter() we can handle short copy just fine;
the data is in m->buf and next call will fetch it from there.
Fixes: d4d50710a8
(seq_file: add seq_read_iter)
Tested-by: Nathan Chancellor <natechancellor@gmail.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
parent
d4d50710a8
commit
4bbf439b09
@ -168,12 +168,14 @@ EXPORT_SYMBOL(seq_read);
|
|||||||
ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
||||||
{
|
{
|
||||||
struct seq_file *m = iocb->ki_filp->private_data;
|
struct seq_file *m = iocb->ki_filp->private_data;
|
||||||
size_t size = iov_iter_count(iter);
|
|
||||||
size_t copied = 0;
|
size_t copied = 0;
|
||||||
size_t n;
|
size_t n;
|
||||||
void *p;
|
void *p;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
|
if (!iov_iter_count(iter))
|
||||||
|
return 0;
|
||||||
|
|
||||||
mutex_lock(&m->lock);
|
mutex_lock(&m->lock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -206,36 +208,34 @@ ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
|||||||
if (!m->buf)
|
if (!m->buf)
|
||||||
goto Enomem;
|
goto Enomem;
|
||||||
}
|
}
|
||||||
/* if not empty - flush it first */
|
// something left in the buffer - copy it out first
|
||||||
if (m->count) {
|
if (m->count) {
|
||||||
n = min(m->count, size);
|
n = copy_to_iter(m->buf + m->from, m->count, iter);
|
||||||
if (copy_to_iter(m->buf + m->from, n, iter) != n)
|
|
||||||
goto Efault;
|
|
||||||
m->count -= n;
|
m->count -= n;
|
||||||
m->from += n;
|
m->from += n;
|
||||||
size -= n;
|
|
||||||
copied += n;
|
copied += n;
|
||||||
if (!size)
|
if (m->count) // hadn't managed to copy everything
|
||||||
goto Done;
|
goto Done;
|
||||||
}
|
}
|
||||||
/* we need at least one record in buffer */
|
// get a non-empty record in the buffer
|
||||||
m->from = 0;
|
m->from = 0;
|
||||||
p = m->op->start(m, &m->index);
|
p = m->op->start(m, &m->index);
|
||||||
while (1) {
|
while (1) {
|
||||||
err = PTR_ERR(p);
|
err = PTR_ERR(p);
|
||||||
if (!p || IS_ERR(p))
|
if (!p || IS_ERR(p)) // EOF or an error
|
||||||
break;
|
break;
|
||||||
err = m->op->show(m, p);
|
err = m->op->show(m, p);
|
||||||
if (err < 0)
|
if (err < 0) // hard error
|
||||||
break;
|
break;
|
||||||
if (unlikely(err))
|
if (unlikely(err)) // ->show() says "skip it"
|
||||||
m->count = 0;
|
m->count = 0;
|
||||||
if (unlikely(!m->count)) {
|
if (unlikely(!m->count)) { // empty record
|
||||||
p = m->op->next(m, p, &m->index);
|
p = m->op->next(m, p, &m->index);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (m->count < m->size)
|
if (!seq_has_overflowed(m)) // got it
|
||||||
goto Fill;
|
goto Fill;
|
||||||
|
// need a bigger buffer
|
||||||
m->op->stop(m, p);
|
m->op->stop(m, p);
|
||||||
kvfree(m->buf);
|
kvfree(m->buf);
|
||||||
m->count = 0;
|
m->count = 0;
|
||||||
@ -244,11 +244,14 @@ ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
|||||||
goto Enomem;
|
goto Enomem;
|
||||||
p = m->op->start(m, &m->index);
|
p = m->op->start(m, &m->index);
|
||||||
}
|
}
|
||||||
|
// EOF or an error
|
||||||
m->op->stop(m, p);
|
m->op->stop(m, p);
|
||||||
m->count = 0;
|
m->count = 0;
|
||||||
goto Done;
|
goto Done;
|
||||||
Fill:
|
Fill:
|
||||||
/* they want more? let's try to get some more */
|
// one non-empty record is in the buffer; if they want more,
|
||||||
|
// try to fit more in, but in any case we need to advance
|
||||||
|
// the iterator once for every record shown.
|
||||||
while (1) {
|
while (1) {
|
||||||
size_t offs = m->count;
|
size_t offs = m->count;
|
||||||
loff_t pos = m->index;
|
loff_t pos = m->index;
|
||||||
@ -259,30 +262,27 @@ ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
|||||||
m->op->next);
|
m->op->next);
|
||||||
m->index++;
|
m->index++;
|
||||||
}
|
}
|
||||||
if (!p || IS_ERR(p)) {
|
if (!p || IS_ERR(p)) // no next record for us
|
||||||
err = PTR_ERR(p);
|
|
||||||
break;
|
break;
|
||||||
}
|
if (m->count >= iov_iter_count(iter))
|
||||||
if (m->count >= size)
|
|
||||||
break;
|
break;
|
||||||
err = m->op->show(m, p);
|
err = m->op->show(m, p);
|
||||||
if (seq_has_overflowed(m) || err) {
|
if (err > 0) { // ->show() says "skip it"
|
||||||
|
m->count = offs;
|
||||||
|
} else if (err || seq_has_overflowed(m)) {
|
||||||
m->count = offs;
|
m->count = offs;
|
||||||
if (likely(err <= 0))
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m->op->stop(m, p);
|
m->op->stop(m, p);
|
||||||
n = min(m->count, size);
|
n = copy_to_iter(m->buf, m->count, iter);
|
||||||
if (copy_to_iter(m->buf, n, iter) != n)
|
|
||||||
goto Efault;
|
|
||||||
copied += n;
|
copied += n;
|
||||||
m->count -= n;
|
m->count -= n;
|
||||||
m->from = n;
|
m->from = n;
|
||||||
Done:
|
Done:
|
||||||
if (!copied)
|
if (unlikely(!copied)) {
|
||||||
copied = err;
|
copied = m->count ? -EFAULT : err;
|
||||||
else {
|
} else {
|
||||||
iocb->ki_pos += copied;
|
iocb->ki_pos += copied;
|
||||||
m->read_pos += copied;
|
m->read_pos += copied;
|
||||||
}
|
}
|
||||||
@ -291,9 +291,6 @@ ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
|||||||
Enomem:
|
Enomem:
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto Done;
|
goto Done;
|
||||||
Efault:
|
|
||||||
err = -EFAULT;
|
|
||||||
goto Done;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(seq_read_iter);
|
EXPORT_SYMBOL(seq_read_iter);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user