[PATCH v2 0/4] New line reading implementation
As discussed on our call, the current line_read() implementation is pretty ugly and has some definite bugs. This series replaces it with a new implementation which should be more solid and more readable. The new implementation is larger than I'd really like, but it turns out its fiddlier to handle the various edge cases than you might think. David Gibson (4): Add cleaner line-by-line reading primitives Parse resolv.conf with new lineread implementation Use new lineread implementation for procfs_scan_listen() Remove unused line_read() Makefile | 8 ++-- conf.c | 22 ++++++---- lineread.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lineread.h | 31 +++++++++++++++ util.c | 64 +++-------------------------- 5 files changed, 170 insertions(+), 70 deletions(-) create mode 100644 lineread.c create mode 100644 lineread.h -- 2.36.1
Two places in passt need to read files line by line (one parsing
resolv.conf, the other parsing /proc/net/*. They can't use fgets()
because in glibc that can allocate memory. Instead they use an
implementation line_read() in util.c. This has some problems:
* It has two completely separate modes of operation, one buffering
and one not, the relation between these and how they're activated
is subtle and confusing
* At least in non-buffered mode, it will mishandle an empty line,
folding them onto the start of the next non-empty line
* In non-buffered mode it will use lseek() which prevents using this
on non-regular files (we don't need that at present, but it's a
surprising limitation)
* It has a lot of difficult to read pointer mangling
Add a new cleaner implementation of allocation-free line-by-line
reading in lineread.c. This one always buffers, using a state
structure to keep track of what we need. This is larger than I'd
like, but it turns out handling all the edge cases of line-by-line
reading in C is surprisingly hard.
This just adds the code, subsequent patches will change the existing
users of line_read() to the new implementation.
Signed-off-by: David Gibson
On Fri, 24 Jun 2022 12:17:29 +1000
David Gibson
[...]
+/** + * lineread_get() - Read a single line from file (no allocation) + * @lr: Line reader state structure + * @line: Place a pointer to the next line in this variable + * + * Return: Length of line read on success, 0 on EOF, negative on error + */ +int lineread_get(struct lineread *lr, char **line) +{ + bool eof = false; + int line_len; + + while ((line_len = peek_line(lr, eof)) < 0) { + int rc; + + if ((lr->next_line + lr->count) == LINEREAD_BUFFER_SIZE) { + /* No space at end */ + if (lr->next_line == 0) { + /* Buffer is full, which means we've + * hit a line too long for us to + * process. FIXME: report error + * better + */ + return -1; + } + memmove(lr->buf, lr->buf + lr->next_line, lr->count); + lr->next_line = 0; + } + + /* Read more data into the end of buffer */ + rc = read(lr->fd, lr->buf + lr->next_line + lr->count, + LINEREAD_BUFFER_SIZE - lr->next_line - lr->count); + if (rc < 0) { + return rc; + } else if (rc == 0) {
clang-tidy still complains about this one, and I think it's reasonable (though not fundamental) to change it, because (rc < 0) is a clear error condition we're returning right away. I'd just change this on merge. -- Stefano
On Mon, Jun 27, 2022 at 12:36:32PM +0200, Stefano Brivio wrote:
On Fri, 24 Jun 2022 12:17:29 +1000 David Gibson
wrote: [...]
+/** + * lineread_get() - Read a single line from file (no allocation) + * @lr: Line reader state structure + * @line: Place a pointer to the next line in this variable + * + * Return: Length of line read on success, 0 on EOF, negative on error + */ +int lineread_get(struct lineread *lr, char **line) +{ + bool eof = false; + int line_len; + + while ((line_len = peek_line(lr, eof)) < 0) { + int rc; + + if ((lr->next_line + lr->count) == LINEREAD_BUFFER_SIZE) { + /* No space at end */ + if (lr->next_line == 0) { + /* Buffer is full, which means we've + * hit a line too long for us to + * process. FIXME: report error + * better + */ + return -1; + } + memmove(lr->buf, lr->buf + lr->next_line, lr->count); + lr->next_line = 0; + } + + /* Read more data into the end of buffer */ + rc = read(lr->fd, lr->buf + lr->next_line + lr->count, + LINEREAD_BUFFER_SIZE - lr->next_line - lr->count); + if (rc < 0) { + return rc; + } else if (rc == 0) {
clang-tidy still complains about this one, and I think it's reasonable (though not fundamental) to change it, because (rc < 0) is a clear error condition we're returning right away. I'd just change this on merge.
Fair enough. -- David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
Switch the resolv.conf parsing in conf.c to use the new lineread
implementation. This means that it can now handle a resolv.conf file which
contains blank lines.
There are quite a few other fragilities with the resolv.conf parsing, but
that's out of scope for this patch.
Signed-off-by: David Gibson
Use the new more solid implementation of line by line reading for
procfs_scan_listen().
Signed-off-by: David Gibson
The old, ugly implementation of line_read() is no longer used. Remove it.
Signed-off-by: David Gibson
On Fri, 24 Jun 2022 12:17:28 +1000
David Gibson
As discussed on our call, the current line_read() implementation is pretty ugly and has some definite bugs. This series replaces it with a new implementation which should be more solid and more readable.
The new implementation is larger than I'd really like, but it turns out its fiddlier to handle the various edge cases than you might think.
David Gibson (4): Add cleaner line-by-line reading primitives Parse resolv.conf with new lineread implementation Use new lineread implementation for procfs_scan_listen() Remove unused line_read()
Makefile | 8 ++-- conf.c | 22 ++++++---- lineread.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lineread.h | 31 +++++++++++++++ util.c | 64 +++-------------------------- 5 files changed, 170 insertions(+), 70 deletions(-) create mode 100644 lineread.c create mode 100644 lineread.h
Applied, finally, sorry for the delay! -- Stefano
participants (2)
-
David Gibson
-
Stefano Brivio