summaryrefslogtreecommitdiff
path: root/support
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-06-02 11:59:28 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-06-02 11:59:28 +0200
commit91b6eb1140eda6bab324821ee3785e5d0ca155b8 (patch)
treec8b630c412611a9b9f5e600e8824661f403bfa7f /support
parent09103e40252454e906a0b8543a142fc96b4c17c1 (diff)
Add internal facility for dynamic array handling
This is intended as a type-safe alternative to obstacks and hand-written realloc constructs. The implementation avoids writing function pointers to the heap.
Diffstat (limited to 'support')
-rw-r--r--support/Makefile4
-rw-r--r--support/capture_subprocess.h42
-rw-r--r--support/support_capture_subprocess.c108
-rw-r--r--support/tst-support_capture_subprocess.c188
-rw-r--r--support/xdup2.c28
-rw-r--r--support/xpipe.c28
-rw-r--r--support/xunistd.h2
7 files changed, 400 insertions, 0 deletions
diff --git a/support/Makefile b/support/Makefile
index 38dbd832e9..3ce73a6c76 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -36,6 +36,7 @@ libsupport-routines = \
resolv_test \
set_fortify_handler \
support_become_root \
+ support_capture_subprocess \
support_enter_network_namespace \
support_format_address_family \
support_format_addrinfo \
@@ -56,6 +57,7 @@ libsupport-routines = \
xcalloc \
xclose \
xconnect \
+ xdup2 \
xfclose \
xfopen \
xfork \
@@ -65,6 +67,7 @@ libsupport-routines = \
xmemstream \
xmmap \
xmunmap \
+ xpipe \
xpoll \
xpthread_attr_destroy \
xpthread_attr_init \
@@ -113,6 +116,7 @@ endif
tests = \
README-testing \
tst-support-namespace \
+ tst-support_capture_subprocess \
tst-support_format_dns_packet \
tst-support_record_failure \
diff --git a/support/capture_subprocess.h b/support/capture_subprocess.h
new file mode 100644
index 0000000000..be5d34fbe2
--- /dev/null
+++ b/support/capture_subprocess.h
@@ -0,0 +1,42 @@
+/* Capture output from a subprocess.
+ Copyright (C) 2017 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef SUPPORT_CAPTURE_SUBPROCESS_H
+#define SUPPORT_CAPTURE_SUBPROCESS_H
+
+#include <support/xmemstream.h>
+
+struct support_capture_subprocess
+{
+ struct xmemstream out;
+ struct xmemstream err;
+ int status;
+};
+
+/* Invoke CALLBACK (CLOSURE) in a subprocess and capture standard
+ output, standard error, and the exit status. The out.buffer and
+ err.buffer members in the result are null-terminated strings which
+ can be examined by the caller (out.out and err.out are NULL). */
+struct support_capture_subprocess support_capture_subprocess
+ (void (*callback) (void *), void *closure);
+
+/* Deallocate the subprocess data captured by
+ support_capture_subprocess. */
+void support_capture_subprocess_free (struct support_capture_subprocess *);
+
+#endif /* SUPPORT_CAPTURE_SUBPROCESS_H */
diff --git a/support/support_capture_subprocess.c b/support/support_capture_subprocess.c
new file mode 100644
index 0000000000..030f124252
--- /dev/null
+++ b/support/support_capture_subprocess.c
@@ -0,0 +1,108 @@
+/* Capture output from a subprocess.
+ Copyright (C) 2017 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <support/capture_subprocess.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/xunistd.h>
+#include <support/xsocket.h>
+
+static void
+transfer (const char *what, struct pollfd *pfd, struct xmemstream *stream)
+{
+ if (pfd->revents != 0)
+ {
+ char buf[1024];
+ ssize_t ret = TEMP_FAILURE_RETRY (read (pfd->fd, buf, sizeof (buf)));
+ if (ret < 0)
+ {
+ support_record_failure ();
+ printf ("error: reading from subprocess %s: %m", what);
+ pfd->events = 0;
+ pfd->revents = 0;
+ }
+ else if (ret == 0)
+ {
+ /* EOF reached. Stop listening. */
+ pfd->events = 0;
+ pfd->revents = 0;
+ }
+ else
+ /* Store the data just read. */
+ TEST_VERIFY (fwrite (buf, ret, 1, stream->out) == 1);
+ }
+}
+
+struct support_capture_subprocess
+support_capture_subprocess (void (*callback) (void *), void *closure)
+{
+ struct support_capture_subprocess result;
+ xopen_memstream (&result.out);
+ xopen_memstream (&result.err);
+
+ int stdout_pipe[2];
+ xpipe (stdout_pipe);
+ int stderr_pipe[2];
+ xpipe (stderr_pipe);
+
+ TEST_VERIFY (fflush (stdout) == 0);
+ TEST_VERIFY (fflush (stderr) == 0);
+
+ pid_t pid = xfork ();
+ if (pid == 0)
+ {
+ xclose (stdout_pipe[0]);
+ xclose (stderr_pipe[0]);
+ xdup2 (stdout_pipe[1], STDOUT_FILENO);
+ xdup2 (stderr_pipe[1], STDERR_FILENO);
+ callback (closure);
+ _exit (0);
+ }
+ xclose (stdout_pipe[1]);
+ xclose (stderr_pipe[1]);
+
+ struct pollfd fds[2] =
+ {
+ { .fd = stdout_pipe[0], .events = POLLIN },
+ { .fd = stderr_pipe[0], .events = POLLIN },
+ };
+
+ do
+ {
+ xpoll (fds, 2, -1);
+ transfer ("stdout", &fds[0], &result.out);
+ transfer ("stderr", &fds[1], &result.err);
+ }
+ while (fds[0].events != 0 || fds[1].events != 0);
+ xclose (stdout_pipe[0]);
+ xclose (stderr_pipe[0]);
+
+ xfclose_memstream (&result.out);
+ xfclose_memstream (&result.err);
+ xwaitpid (pid, &result.status, 0);
+ return result;
+}
+
+void
+support_capture_subprocess_free (struct support_capture_subprocess *p)
+{
+ free (p->out.buffer);
+ free (p->err.buffer);
+}
diff --git a/support/tst-support_capture_subprocess.c b/support/tst-support_capture_subprocess.c
new file mode 100644
index 0000000000..5672fba0f7
--- /dev/null
+++ b/support/tst-support_capture_subprocess.c
@@ -0,0 +1,188 @@
+/* Test capturing output from a subprocess.
+ Copyright (C) 2017 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/* Write one byte at *P to FD and advance *P. Do nothing if *P is
+ '\0'. */
+static void
+transfer (const unsigned char **p, int fd)
+{
+ if (**p != '\0')
+ {
+ TEST_VERIFY (write (fd, *p, 1) == 1);
+ ++*p;
+ }
+}
+
+/* Determine the order in which stdout and stderr are written. */
+enum write_mode { out_first, err_first, interleave,
+ write_mode_last = interleave };
+
+/* Describe what to write in the subprocess. */
+struct test
+{
+ char *out;
+ char *err;
+ enum write_mode write_mode;
+ int signal;
+ int status;
+};
+
+/* For use with support_capture_subprocess. */
+static void
+callback (void *closure)
+{
+ const struct test *test = closure;
+ bool mode_ok = false;
+ switch (test->write_mode)
+ {
+ case out_first:
+ TEST_VERIFY (fputs (test->out, stdout) >= 0);
+ TEST_VERIFY (fflush (stdout) == 0);
+ TEST_VERIFY (fputs (test->err, stderr) >= 0);
+ TEST_VERIFY (fflush (stderr) == 0);
+ mode_ok = true;
+ break;
+ case err_first:
+ TEST_VERIFY (fputs (test->err, stderr) >= 0);
+ TEST_VERIFY (fflush (stderr) == 0);
+ TEST_VERIFY (fputs (test->out, stdout) >= 0);
+ TEST_VERIFY (fflush (stdout) == 0);
+ mode_ok = true;
+ break;
+ case interleave:
+ {
+ const unsigned char *pout = (const unsigned char *) test->out;
+ const unsigned char *perr = (const unsigned char *) test->err;
+ do
+ {
+ transfer (&pout, STDOUT_FILENO);
+ transfer (&perr, STDERR_FILENO);
+ }
+ while (*pout != '\0' || *perr != '\0');
+ }
+ mode_ok = true;
+ break;
+ }
+ TEST_VERIFY (mode_ok);
+
+ if (test->signal != 0)
+ raise (test->signal);
+ exit (test->status);
+}
+
+/* Create a heap-allocated random string of letters. */
+static char *
+random_string (size_t length)
+{
+ char *result = xmalloc (length + 1);
+ for (size_t i = 0; i < length; ++i)
+ result[i] = 'a' + (rand () % 26);
+ result[length] = '\0';
+ return result;
+}
+
+/* Check that the specific stream from the captured subprocess matches
+ expectations. */
+static void
+check_stream (const char *what, const struct xmemstream *stream,
+ const char *expected)
+{
+ if (strcmp (stream->buffer, expected) != 0)
+ {
+ support_record_failure ();
+ printf ("error: captured %s data incorrect\n"
+ " expected: %s\n"
+ " actual: %s\n",
+ what, expected, stream->buffer);
+ }
+ if (stream->length != strlen (expected))
+ {
+ support_record_failure ();
+ printf ("error: captured %s data length incorrect\n"
+ " expected: %zu\n"
+ " actual: %zu\n",
+ what, strlen (expected), stream->length);
+ }
+}
+
+static int
+do_test (void)
+{
+ const int lengths[] = {0, 1, 17, 512, 20000, -1};
+
+ /* Test multiple combinations of support_capture_subprocess.
+
+ length_idx_stdout: Index into the lengths array above,
+ controls how many bytes are written by the subprocess to
+ standard output.
+ length_idx_stderr: Same for standard error.
+ write_mode: How standard output and standard error writes are
+ ordered.
+ signal: Exit with no signal if zero, with SIGTERM if one.
+ status: Process exit status: 0 if zero, 3 if one. */
+ for (int length_idx_stdout = 0; lengths[length_idx_stdout] >= 0;
+ ++length_idx_stdout)
+ for (int length_idx_stderr = 0; lengths[length_idx_stderr] >= 0;
+ ++length_idx_stderr)
+ for (int write_mode = 0; write_mode < write_mode_last; ++write_mode)
+ for (int signal = 0; signal < 2; ++signal)
+ for (int status = 0; status < 2; ++status)
+ {
+ struct test test =
+ {
+ .out = random_string (lengths[length_idx_stdout]),
+ .err = random_string (lengths[length_idx_stderr]),
+ .write_mode = write_mode,
+ .signal = signal * SIGTERM, /* 0 or SIGTERM. */
+ .status = status * 3, /* 0 or 3. */
+ };
+ TEST_VERIFY (strlen (test.out) == lengths[length_idx_stdout]);
+ TEST_VERIFY (strlen (test.err) == lengths[length_idx_stderr]);
+
+ struct support_capture_subprocess result
+ = support_capture_subprocess (callback, &test);
+ check_stream ("stdout", &result.out, test.out);
+ check_stream ("stderr", &result.err, test.err);
+ if (test.signal != 0)
+ {
+ TEST_VERIFY (WIFSIGNALED (result.status));
+ TEST_VERIFY (WTERMSIG (result.status) == test.signal);
+ }
+ else
+ {
+ TEST_VERIFY (WIFEXITED (result.status));
+ TEST_VERIFY (WEXITSTATUS (result.status) == test.status);
+ }
+ support_capture_subprocess_free (&result);
+ free (test.out);
+ free (test.err);
+ }
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/xdup2.c b/support/xdup2.c
new file mode 100644
index 0000000000..dc08c94518
--- /dev/null
+++ b/support/xdup2.c
@@ -0,0 +1,28 @@
+/* dup2 with error checking.
+ Copyright (C) 2017 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <support/xunistd.h>
+
+#include <support/check.h>
+
+void
+xdup2 (int from, int to)
+{
+ if (dup2 (from, to) < 0)
+ FAIL_EXIT1 ("dup2 (%d, %d): %m", from, to);
+}
diff --git a/support/xpipe.c b/support/xpipe.c
new file mode 100644
index 0000000000..89a64a55c1
--- /dev/null
+++ b/support/xpipe.c
@@ -0,0 +1,28 @@
+/* pipe with error checking.
+ Copyright (C) 2017 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <support/xunistd.h>
+
+#include <support/check.h>
+
+void
+xpipe (int fds[2])
+{
+ if (pipe (fds) < 0)
+ FAIL_EXIT1 ("pipe: %m");
+}
diff --git a/support/xunistd.h b/support/xunistd.h
index 258bab5c81..7c14bda7be 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -29,6 +29,8 @@ __BEGIN_DECLS
pid_t xfork (void);
pid_t xwaitpid (pid_t, int *status, int flags);
+void xpipe (int[2]);
+void xdup2 (int, int);
/* Close the file descriptor. Ignore EINTR errors, but terminate the
process on other errors. */