git: f4418cf954c2 - main - LinuxKPI: Update seq_file to properly implement the iterator interface
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 23 Apr 2026 15:48:11 UTC
The branch main has been updated by jhb:
URL: https://cgit.FreeBSD.org/src/commit/?id=f4418cf954c299fa0934f110d6f5e9d50f2d24c5
commit f4418cf954c299fa0934f110d6f5e9d50f2d24c5
Author: John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2026-04-23 15:46:54 +0000
Commit: John Baldwin <jhb@FreeBSD.org>
CommitDate: 2026-04-23 15:46:54 +0000
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
---
.../linuxkpi/common/include/linux/seq_file.h | 2 +
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