@@ -303,6 +303,13 @@ tests-static += \
$(tests-clone-internal) \
# tests-static
+tests-internal += \
+ tst-procutils \
+ # tests-internal
+tests-static += \
+ tst-procutils \
+ # tests-static
+
CFLAGS-tst-sigcontext-get_pc.c = -fasynchronous-unwind-tables
# Generate the list of SYS_* macros for the system calls (__NR_*
@@ -33,6 +33,8 @@
#define FDINFO_FILENAME_LEN \
(sizeof (FDINFO_TO_FILENAME_PREFIX) + INT_STRLEN_BOUND (int))
+#define FDINFO_MAX_LINE_LEN 256
+
struct parse_fdinfo_t
{
bool found;
@@ -41,12 +43,12 @@ struct parse_fdinfo_t
/* Parse the PID field in the fdinfo entry, if existent. Avoid strtol or
similar to not be locale dependent. */
-static int
+static bool
parse_fdinfo (const char *l, void *arg)
{
enum { fieldlen = sizeof ("Pid:") - 1 };
if (strncmp (l, "Pid:", fieldlen) != 0)
- return 0;
+ return false;
l += fieldlen;
@@ -62,36 +64,36 @@ parse_fdinfo (const char *l, void *arg)
l++;
break;
case '+':
- return -1;
+ return true;
}
if (*l == '\0')
- return 0;
+ return false;
int n = 0;
while (*l != '\0')
{
/* Check if '*l' is a digit. */
if ('0' > *l || *l > '9')
- return -1;
+ return true;
/* Ignore invalid large values. */
if (INT_MULTIPLY_WRAPV (10, n, &n)
|| INT_ADD_WRAPV (n, *l - '0', &n))
- return -1;
+ return true;
l++;
}
/* -1 indicates that the process is terminated. */
if (neg && n != 1)
- return -1;
+ return true;
struct parse_fdinfo_t *fdinfo = arg;
fdinfo->pid = neg ? -n : n;
fdinfo->found = true;
- return 1;
+ return true;
}
static pid_t
@@ -104,7 +106,10 @@ getpid_fdinfo (int fd)
*_fitoa_word (fd, p, 10, 0) = '\0';
struct parse_fdinfo_t fdinfo = { .found = false, .pid = -1 };
- if (!__libc_procutils_read_file (fdinfoname, parse_fdinfo, &fdinfo))
+ char buffer[FDINFO_MAX_LINE_LEN];
+ if (__libc_procutils_read_file (fdinfoname, buffer, sizeof buffer,
+ parse_fdinfo, &fdinfo)
+ == procutils_read_error)
/* The fdinfo contains an invalid 'Pid:' value. */
return INLINE_SYSCALL_ERROR_RETURN_VALUE (EBADF);
@@ -16,83 +16,126 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <assert.h>
#include <not-cancel.h>
#include <procutils.h>
+#include <stdbool.h>
#include <string.h>
-static int
-next_line (char **r, int fd, char *const buffer, char **cp, char **re,
- char *const buffer_end)
+struct line_reader
{
- char *res = *cp;
- char *nl = memchr (*cp, '\n', *re - *cp);
- if (nl == NULL)
+ int fd;
+ char *buffer;
+ /* The last byte of the buffer is reserved for the null terminator. */
+ char *buffer_end;
+ char *cp; /* Start of the unprocessed data. */
+ char *re; /* End of the unprocessed data. */
+ bool eof; /* No more data to read. */
+ bool skip; /* Discard the input up to the next '\n', the initial part of
+ the line has already been returned truncated. */
+};
+
+enum next_line_result_t
+{
+ next_line_ok,
+ next_line_eof,
+ next_line_error
+};
+
+/* Read the next line into *R. Return next_line_ok on success,
+ next_line_eof on EOF, or next_line_error on read error. */
+static enum next_line_result_t
+next_line (struct line_reader *lr, char **r)
+{
+ while (true)
{
- if (*cp != buffer)
- {
- if (*re == buffer_end)
- {
- memmove (buffer, *cp, *re - *cp);
- *re = buffer + (*re - *cp);
- *cp = buffer;
+ char *nl = memchr (lr->cp, '\n', lr->re - lr->cp);
+ if (nl != NULL)
+ {
+ char *line = lr->cp;
+ *nl = '\0';
+ lr->cp = nl + 1;
+ if (lr->skip)
+ {
+ /* End of a line longer than the buffer, start over. */
+ lr->skip = false;
+ continue;
+ }
+ *r = line;
+ return next_line_ok;
+ }
- ssize_t n = TEMP_FAILURE_RETRY (
- __read_nocancel (fd, *re, buffer_end - *re));
- if (n < 0)
- return -1;
+ if (lr->eof)
+ {
+ if (lr->cp == lr->re || lr->skip)
+ return next_line_eof;
+ /* Last line without a trailing newline. */
+ *lr->re = '\0';
+ *r = lr->cp;
+ lr->cp = lr->re;
+ return next_line_ok;
+ }
- *re += n;
+ /* Move the partial line to the start of the buffer to maximize
+ the read size. */
+ if (lr->cp != lr->buffer)
+ {
+ memmove (lr->buffer, lr->cp, lr->re - lr->cp);
+ lr->re = lr->buffer + (lr->re - lr->cp);
+ lr->cp = lr->buffer;
+ }
- nl = memchr (*cp, '\n', *re - *cp);
- if (nl == NULL)
- /* Line too long. */
- return 0;
- }
- else
- nl = memchr (*cp, '\n', *re - *cp);
+ if (lr->re == lr->buffer_end)
+ {
+ /* A line longer than the buffer. Consume the buffered data
+ and, for the initial part of the line, also return it
+ truncated. */
+ lr->cp = lr->re = lr->buffer;
+ if (!lr->skip)
+ {
+ *lr->buffer_end = '\0';
+ *r = lr->buffer;
+ lr->skip = true;
+ return next_line_ok;
+ }
+ }
- res = *cp;
- }
-
- if (nl == NULL)
- nl = *re - 1;
+ ssize_t n = TEMP_FAILURE_RETRY (
+ __read_nocancel (lr->fd, lr->re, lr->buffer_end - lr->re));
+ if (n < 0)
+ return next_line_error;
+ if (n == 0)
+ lr->eof = true;
+ lr->re += n;
}
-
- *nl = '\0';
- *cp = nl + 1;
- assert (*cp <= *re);
-
- if (res == *re)
- return 0;
-
- *r = res;
- return 1;
}
-bool
-__libc_procutils_read_file (const char *filename,
- procutils_closure_t closure,
+enum procutils_read_result_t
+__libc_procutils_read_file (const char *filename, char *buffer,
+ size_t buffer_size, procutils_closure_t closure,
void *arg)
{
- enum { buffer_size = PROCUTILS_MAX_LINE_LEN };
- char buffer[buffer_size];
- char *buffer_end = buffer + buffer_size;
- char *cp = buffer_end;
- char *re = buffer_end;
+ struct line_reader lr =
+ {
+ .buffer = buffer,
+ .buffer_end = buffer + buffer_size - 1,
+ .cp = buffer,
+ .re = buffer,
+ };
- int fd = TEMP_FAILURE_RETRY (
+ lr.fd = TEMP_FAILURE_RETRY (
__open64_nocancel (filename, O_RDONLY | O_CLOEXEC));
- if (fd == -1)
- return false;
+ if (lr.fd == -1)
+ return procutils_read_error;
- char *l;
- int r;
- while ((r = next_line (&l, fd, buffer, &cp, &re, buffer_end)) > 0)
- if (closure (l, arg) != 0)
+ enum next_line_result_t r;
+ char *line;
+ while ((r = next_line (&lr, &line)) == next_line_ok)
+ if (closure (line, arg))
break;
- __close_nocancel_nostatus (fd);
+ __close_nocancel_nostatus (lr.fd);
- return r == 1;
+ if (r == next_line_error)
+ return procutils_read_error;
+ return r == next_line_ok ? procutils_read_stop : procutils_read_eof;
}
@@ -20,25 +20,37 @@
#define _PROCUTILS_H
#include <stdbool.h>
+#include <stddef.h>
-typedef int (*procutils_closure_t) (const char *line, void *arg);
+typedef bool (*procutils_closure_t) (const char *line, void *arg);
-#define PROCUTILS_MAX_LINE_LEN 256
+enum procutils_read_result_t
+{
+ procutils_read_stop,
+ procutils_read_eof,
+ procutils_read_error
+};
/* Open and read the path FILENAME, line per line, and call CLOSURE with
- argument ARG on each line. The read is done with a static buffer,
- with non-cancellable calls, and the line is null terminated.
+ argument ARG on each line. The read is done with non-cancellable
+ calls using BUFFER of BUFFER_SIZE bytes as scratch area, and the line
+ is null terminated (the '\n' is not included).
- The CLOSURE should return 0 if the read should continue, otherwise the
- the function should stop and return early.
+ A line longer than BUFFER_SIZE - 1 characters is passed to CLOSURE
+ truncated to BUFFER_SIZE - 1 characters, and the rest of the line up
+ to the next '\n' is discarded.
- The '\n' is not included in the CLOSURE input argument and lines longer
- than PROCUTILS_MAX_LINE_LEN characteres are ignored.
+ The CLOSURE should return true to stop the read, in which case the
+ function returns early.
- It returns true in case the file is fully read or false if CLOSURE
- returns a value diferent than 0. */
-bool __libc_procutils_read_file (const char *filename,
- procutils_closure_t closure,
- void *arg) attribute_hidden;
+ It returns procutils_read_stop if CLOSURE returned true,
+ procutils_read_eof if the file was fully read, or
+ procutils_read_error if the file could not be opened or a read error
+ occurred (with errno set). */
+enum procutils_read_result_t
+__libc_procutils_read_file (const char *filename,
+ char *buffer, size_t buffer_size,
+ procutils_closure_t closure,
+ void *arg) attribute_hidden;
#endif
new file mode 100644
@@ -0,0 +1,130 @@
+/* Tests for the procutils line reader.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <procutils.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+
+enum
+{
+ max_lines = 8,
+ max_line_len = 63,
+};
+
+struct capture
+{
+ int n;
+ /* Make the closure stop the read after this number of lines, or 0 to
+ always read the whole file. */
+ int stop_at;
+ char lines[max_lines][max_line_len + 1];
+};
+
+static bool
+capture_line (const char *line, void *arg)
+{
+ struct capture *c = arg;
+ TEST_VERIFY_EXIT (c->n < max_lines);
+ TEST_VERIFY_EXIT (strlen (line) <= max_line_len);
+ strcpy (c->lines[c->n++], line);
+ return c->n == c->stop_at;
+}
+
+static char *tempfile;
+
+static void
+run_test (const char *content, char *buffer, size_t buffer_size, int stop_at,
+ enum procutils_read_result_t expected_ret,
+ const char *const expected_lines[],
+ int expected_n)
+{
+ int fd = xopen (tempfile, O_WRONLY | O_TRUNC, 0600);
+ xwrite (fd, content, strlen (content));
+ xclose (fd);
+
+ struct capture c = { .stop_at = stop_at };
+ TEST_COMPARE (__libc_procutils_read_file (tempfile, buffer, buffer_size,
+ capture_line, &c),
+ expected_ret);
+ TEST_COMPARE (c.n, expected_n);
+ for (int i = 0; i < expected_n && i < c.n; i++)
+ TEST_COMPARE_STRING (c.lines[i], expected_lines[i]);
+}
+
+static int
+do_test (void)
+{
+ xclose (create_temp_file ("tst-procutils", &tempfile));
+
+ char buf64[64];
+ /* A small buffer, so lines longer than 7 characters are truncated. */
+ char buf8[8];
+
+ /* Regular lines with a trailing newline, including an empty line. */
+ run_test ("line1\n\nline3\n", buf64, sizeof buf64, 0, procutils_read_eof,
+ (const char *const []) { "line1", "", "line3" }, 3);
+
+ /* Last line without a trailing newline. */
+ run_test ("foo\nbar", buf64, sizeof buf64, 0, procutils_read_eof,
+ (const char *const []) { "foo", "bar" }, 2);
+
+ /* Empty file. */
+ run_test ("", buf64, sizeof buf64, 0, procutils_read_eof, NULL, 0);
+
+ /* A line longer than the buffer is passed truncated to
+ buffer_size - 1 characters and the remainder is discarded. */
+ run_test ("abcdefghijklm\nxy\n", buf8, sizeof buf8, 0, procutils_read_eof,
+ (const char *const []) { "abcdefg", "xy" }, 2);
+
+ /* A long line spanning multiple buffer refills. */
+ run_test ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nend\n", buf8, sizeof buf8, 0,
+ procutils_read_eof,
+ (const char *const []) { "aaaaaaa", "end" }, 2);
+
+ /* A line of exactly buffer_size - 1 characters is not truncated. */
+ run_test ("1234567\n89\n", buf8, sizeof buf8, 0, procutils_read_eof,
+ (const char *const []) { "1234567", "89" }, 2);
+
+ /* A truncated line without a trailing newline at the end of file. */
+ run_test ("abcdefghijklm", buf8, sizeof buf8, 0, procutils_read_eof,
+ (const char *const []) { "abcdefg" }, 1);
+
+ /* The closure stopping the read early. */
+ run_test ("one\ntwo\nthree\n", buf64, sizeof buf64, 2, procutils_read_stop,
+ (const char *const []) { "one", "two" }, 2);
+
+ /* Nonexistent file. */
+ {
+ struct capture c = { 0 };
+ errno = 0;
+ TEST_COMPARE (__libc_procutils_read_file ("/tst-procutils-nonexistent",
+ buf64, sizeof buf64,
+ capture_line, &c),
+ procutils_read_error);
+ TEST_COMPARE (errno, ENOENT);
+ TEST_COMPARE (c.n, 0);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>