diff options
| author | John Baldwin <jhb@FreeBSD.org> | 2026-04-23 15:46:54 +0000 |
|---|---|---|
| committer | John Baldwin <jhb@FreeBSD.org> | 2026-04-23 15:46:54 +0000 |
| commit | f4418cf954c299fa0934f110d6f5e9d50f2d24c5 (patch) | |
| tree | eda1866af0e83fe4a02b3a5ce8647aa723c0edca | |
| parent | 9b95cab0a2927dfe07dbe6dc0056a80d5c730414 (diff) | |
LinuxKPI: Update seq_file to properly implement the iterator interface
The seq_file.rst documentation in the Linux kernel documents the
iterator interface for the seq_file structure. In particular, the
ppos passed to seq_read is a logical offset into a seq_file managed by
the iterator interface, not an offset into the generated data. For
example, if a seq_file outputs state for each node in a linked-list or
array, *ppos might be used as the index of the node to output, not a
byte offset.
Rewrite seq_read to honor this contract which fixes a few bugs:
- Treat *ppos as a logical iterator offset that is only updated by the
next callback after outputting a single item via the show method.
- Use a loop to permit outputting descriptions of multiple items if
the user buffer is large enough.
- Always invoke the stop method after terminating the loop to cleanup
any state setup by start (e.g. if start allocated a buffer or
obtained a lock, the stop method is called to cleanup).
While here, implement support for SEQ_SKIP as documented in the Linux
documentation even though it is not currently used in the tree.
Reviewed by: bz
Sponsored by: AFRL, DARPA
Differential Revision: https://reviews.freebsd.org/D55899
| -rw-r--r-- | sys/compat/linuxkpi/common/include/linux/seq_file.h | 2 | ||||
| -rw-r--r-- | sys/compat/linuxkpi/common/src/linux_seq_file.c | 64 |
2 files changed, 59 insertions, 7 deletions
diff --git a/sys/compat/linuxkpi/common/include/linux/seq_file.h b/sys/compat/linuxkpi/common/include/linux/seq_file.h index 786c09bd6a20..be03c4768b07 100644 --- a/sys/compat/linuxkpi/common/include/linux/seq_file.h +++ b/sys/compat/linuxkpi/common/include/linux/seq_file.h @@ -39,6 +39,8 @@ #undef file #define inode vnode +#define SEQ_SKIP 1 + MALLOC_DECLARE(M_LSEQ); #define DEFINE_SHOW_ATTRIBUTE(__name) \ diff --git a/sys/compat/linuxkpi/common/src/linux_seq_file.c b/sys/compat/linuxkpi/common/src/linux_seq_file.c index eae414ea696e..efa41b28c6b1 100644 --- a/sys/compat/linuxkpi/common/src/linux_seq_file.c +++ b/sys/compat/linuxkpi/common/src/linux_seq_file.c @@ -45,23 +45,73 @@ seq_read(struct linux_file *f, char __user *ubuf, size_t size, off_t *ppos) struct seq_file *m; struct sbuf *sbuf; void *p; - ssize_t rc; + ssize_t rc, oldlen; + size_t todo; m = f->private_data; sbuf = m->buf; sbuf_clear(sbuf); p = m->op->start(m, ppos); - rc = m->op->show(m, p); - if (rc) + if (p == NULL) + return (0); + + rc = 0; + while (size > sbuf_len(sbuf)) { + oldlen = sbuf_len(sbuf); + rc = m->op->show(m, p); + if (rc < 0) + break; + + if (rc == SEQ_SKIP) { + /* Discard any data written in show callback. */ + sbuf_setpos(sbuf, oldlen); + rc = 0; + } + + /* + * If the sbuf has overflowed, bail. Discard any + * partial output from the show callback for this item + * preserving output from any earlier items. Since we + * break before calling the next callback to update + * *ppos, a subsequent read() will start by displaying + * the current item. However, if the current item + * could not be displayed by itself, still fail with + * ENOMEM rather than returning EOF. + */ + if (sbuf_error(sbuf)) { + if (oldlen != 0) + sbuf_setpos(sbuf, oldlen); + break; + } + + /* + * XXX: The seq_file documentation claims that Linux + * warns if this callback doesn't update the value in + * *ppos. We don't bother warning here. + */ + p = m->op->next(m, p, ppos); + if (p == NULL) + break; + } + m->op->stop(m, p); + + if (rc < 0) return (rc); rc = sbuf_finish(sbuf); - if (rc) - return (rc); + if (rc != 0) + return (-rc); + + todo = sbuf_len(sbuf); + if (todo > size) + todo = size; + + rc = copy_to_user(ubuf, sbuf_data(sbuf), todo); + if (rc != 0) + return (-EFAULT); - return (simple_read_from_buffer(ubuf, size, ppos, sbuf_data(sbuf), - sbuf_len(sbuf))); + return (todo); } int |
