about summary refs log tree commit diff
path: root/support
diff options
context:
space:
mode:
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.  */