The unified diff between revisions [f48a12ad..] and [3ba2af4a..] is displayed below. It can also be downloaded as a raw diff.

#
#
# patch "atf-c/check.c"
#  from [351dd5ace1742377ed11540e62ebd926fb8aa884]
#    to [ef0e40037178101675afe9ba3769897a24bbce19]
#
# patch "atf-c/process.c"
#  from [951565dc23d58942899eb2c68a9e9b9d1b48928e]
#    to [0abd2229aaa6a55aa99fb4ac23fd7f285862e12e]
#
# patch "atf-c/process.h"
#  from [8265e8436a6c94dcf2e843ba31df7d99c3772b82]
#    to [038a4ff06acb2132257bdabc9c3b387ce50ecb4c]
#
# patch "atf-c/tc.c"
#  from [8990a94abfa2417ef6f1f4ec78556cc3f35353af]
#    to [bcb9833d27cdd68cff6f1b58c0018f7f30c79866]
#
# patch "atf-c++/process.cpp"
#  from [1c1397ab2ab18e0b6fcf8d6fbe7f2c3e991b9bb7]
#    to [aaec03f9fdb8cd8769b0c897e7498e07089add79]
#
# patch "tests/atf/atf-c/t_process.c"
#  from [70c2e3a828220c193cf33aee22cb769e09cb4891]
#    to [4e54aa872f22559d6dc2d819ebf3191e2deb6389]
#
# patch "tests/atf/atf-c/t_sanity.c"
#  from [f198b24a03b91d99d034dd4e6c8b629b56e470a8]
#    to [f357d5b3f5ad0742a725b18970a3e60d40549bc5]
#
# patch "tests/atf/atf-c/t_signals.c"
#  from [5db33b1465a5d1185ecf442e6cfe8f5d24a2eb52]
#    to [de2770b573a42267cff064efa928e170bc154dc0]
#
============================================================
--- atf-c/check.c	351dd5ace1742377ed11540e62ebd926fb8aa884
+++ atf-c/check.c	ef0e40037178101675afe9ba3769897a24bbce19
@@ -119,7 +119,7 @@ fork_and_wait(const char *const *argv, i
     int status;
     pid_t pid;

-    err = atf_process_fork(&pid);
+    err = atf_process_oldfork(&pid);
     if (atf_is_error(err))
         goto out;

============================================================
--- atf-c/process.c	951565dc23d58942899eb2c68a9e9b9d1b48928e
+++ atf-c/process.c	0abd2229aaa6a55aa99fb4ac23fd7f285862e12e
@@ -31,14 +31,20 @@
 #include <sys/wait.h>

 #include <errno.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>

+#include "atf-c/defs.h"
 #include "atf-c/error.h"
 #include "atf-c/process.h"
 #include "atf-c/sanity.h"

+/* This prototype is not in the header file because this is a private
+ * function; however, we need to access it during testing. */
+atf_error_t atf_process_status_init(atf_process_status_t *, int);
+
 /* ---------------------------------------------------------------------
  * The "child" error type.
  * --------------------------------------------------------------------- */
@@ -78,24 +84,440 @@ child_error(const char *cmd, int state)
 }

 /* ---------------------------------------------------------------------
+ * The "stream_prepare" auxiliary type.
+ * --------------------------------------------------------------------- */
+
+struct stream_prepare {
+    const atf_process_stream_t *m_sb;
+
+    bool m_pipefds_ok;
+    int m_pipefds[2];
+};
+typedef struct stream_prepare stream_prepare_t;
+
+static
+atf_error_t
+stream_prepare_init(stream_prepare_t *sp, const atf_process_stream_t *sb)
+{
+    atf_error_t err;
+
+    const int type = atf_process_stream_type(sb);
+
+    sp->m_sb = sb;
+    sp->m_pipefds_ok = false;
+
+    if (type == atf_process_stream_type_capture) {
+        if (pipe(sp->m_pipefds) == -1)
+            err = atf_libc_error(errno, "Failed to create pipe");
+        else {
+            err = atf_no_error();
+            sp->m_pipefds_ok = true;
+        }
+    } else
+        err = atf_no_error();
+
+    return err;
+}
+
+static
+void
+stream_prepare_fini(stream_prepare_t *sp)
+{
+    if (sp->m_pipefds_ok) {
+        close(sp->m_pipefds[0]);
+        close(sp->m_pipefds[1]);
+    }
+}
+
+/* ---------------------------------------------------------------------
+ * The "atf_process_stream" type.
+ * --------------------------------------------------------------------- */
+
+const int atf_process_stream_type_capture = 1;
+const int atf_process_stream_type_inherit = 2;
+const int atf_process_stream_type_redirect_fd = 3;
+const int atf_process_stream_type_redirect_path = 4;
+
+static
+bool
+stream_is_valid(const atf_process_stream_t *sb)
+{
+    return (sb->m_type == atf_process_stream_type_capture) ||
+           (sb->m_type == atf_process_stream_type_inherit) ||
+           (sb->m_type == atf_process_stream_type_redirect_fd) ||
+           (sb->m_type == atf_process_stream_type_redirect_path);
+}
+
+atf_error_t
+atf_process_stream_init_capture(atf_process_stream_t *sb)
+{
+    atf_object_init(&sb->m_object);
+
+    sb->m_type = atf_process_stream_type_capture;
+
+    POST(stream_is_valid(sb));
+    return atf_no_error();
+}
+
+atf_error_t
+atf_process_stream_init_inherit(atf_process_stream_t *sb)
+{
+    atf_object_init(&sb->m_object);
+
+    sb->m_type = atf_process_stream_type_inherit;
+
+    POST(stream_is_valid(sb));
+    return atf_no_error();
+}
+
+atf_error_t
+atf_process_stream_init_redirect_fd(atf_process_stream_t *sb,
+                                    const int fd)
+{
+    atf_object_init(&sb->m_object);
+
+    sb->m_type = atf_process_stream_type_redirect_fd;
+    sb->m_fd = fd;
+
+    POST(stream_is_valid(sb));
+    return atf_no_error();
+}
+
+atf_error_t
+atf_process_stream_init_redirect_path(atf_process_stream_t *sb,
+                                      const atf_fs_path_t *path)
+{
+    atf_object_init(&sb->m_object);
+
+    sb->m_type = atf_process_stream_type_redirect_path;
+    sb->m_path = path;
+
+    POST(stream_is_valid(sb));
+    return atf_no_error();
+}
+
+void
+atf_process_stream_fini(atf_process_stream_t *sb)
+{
+    PRE(stream_is_valid(sb));
+
+    atf_object_fini(&sb->m_object);
+}
+
+int
+atf_process_stream_type(const atf_process_stream_t *sb)
+{
+    PRE(stream_is_valid(sb));
+
+    return sb->m_type;
+}
+
+/* ---------------------------------------------------------------------
+ * The "atf_process_status" type.
+ * --------------------------------------------------------------------- */
+
+atf_error_t
+atf_process_status_init(atf_process_status_t *s, int status)
+{
+    atf_object_init(&s->m_object);
+
+    s->m_status = status;
+
+    return atf_no_error();
+}
+
+void
+atf_process_status_fini(atf_process_status_t *s)
+{
+    atf_object_fini(&s->m_object);
+}
+
+bool
+atf_process_status_exited(const atf_process_status_t *s)
+{
+    int mutable_status = s->m_status;
+    return WIFEXITED(mutable_status);
+}
+
+int
+atf_process_status_exitstatus(const atf_process_status_t *s)
+{
+    PRE(atf_process_status_exited(s));
+    int mutable_status = s->m_status;
+    return WEXITSTATUS(mutable_status);
+}
+
+bool
+atf_process_status_signaled(const atf_process_status_t *s)
+{
+    int mutable_status = s->m_status;
+    return WIFSIGNALED(mutable_status);
+}
+
+int
+atf_process_status_termsig(const atf_process_status_t *s)
+{
+    PRE(atf_process_status_signaled(s));
+    int mutable_status = s->m_status;
+    return WTERMSIG(mutable_status);
+}
+
+bool
+atf_process_status_coredump(const atf_process_status_t *s)
+{
+    PRE(atf_process_status_signaled(s));
+#if defined(WCOREDUMP)
+    int mutable_status = s->m_status;
+    return WCOREDUMP(mutable_status);
+#else
+    return false;
+#endif
+}
+
+/* ---------------------------------------------------------------------
+ * The "atf_process_child" type.
+ * --------------------------------------------------------------------- */
+
+static
+atf_error_t
+atf_process_child_init(atf_process_child_t *c)
+{
+    atf_object_init(&c->m_object);
+
+    c->m_pid = 0;
+    c->m_stdout = -1;
+    c->m_stderr = -1;
+
+    return atf_no_error();
+}
+
+static
+void
+atf_process_child_fini(atf_process_child_t *c)
+{
+    atf_object_fini(&c->m_object);
+}
+
+atf_error_t
+atf_process_child_wait(atf_process_child_t *c, atf_process_status_t *s)
+{
+    atf_error_t err;
+    int status;
+
+    if (waitpid(c->m_pid, &status, 0) == -1)
+        err = atf_libc_error(errno, "Failed waiting for process %d",
+                             c->m_pid);
+    else {
+        atf_process_child_fini(c);
+        err = atf_process_status_init(s, status);
+    }
+
+    return err;
+}
+
+int
+atf_process_child_stdout(atf_process_child_t *c)
+{
+    PRE(c->m_stdout != -1);
+    return c->m_stdout;
+}
+
+int
+atf_process_child_stderr(atf_process_child_t *c)
+{
+    PRE(c->m_stderr != -1);
+    return c->m_stderr;
+}
+
+/* ---------------------------------------------------------------------
  * Free functions.
  * --------------------------------------------------------------------- */

+static
 atf_error_t
-atf_process_fork(pid_t *outpid)
+safe_dup(const int oldfd, const int newfd)
 {
     atf_error_t err;
+
+    if (oldfd != newfd) {
+        if (dup2(oldfd, newfd) == -1) {
+            err = atf_libc_error(errno, "Could not allocate file descriptor");
+        } else {
+            close(oldfd);
+            err = atf_no_error();
+        }
+    } else
+        err = atf_no_error();
+
+    return err;
+}
+
+static
+atf_error_t
+child_connect(const stream_prepare_t *sp, int procfd)
+{
+    atf_error_t err;
+    const int type = atf_process_stream_type(sp->m_sb);
+
+    if (type == atf_process_stream_type_capture) {
+        close(sp->m_pipefds[0]);
+        err = safe_dup(sp->m_pipefds[1], procfd);
+    } else if (type == atf_process_stream_type_inherit) {
+        err = atf_no_error();
+    } else if (type == atf_process_stream_type_redirect_fd) {
+        err = safe_dup(sp->m_sb->m_fd, procfd);
+    } else if (type == atf_process_stream_type_redirect_path) {
+        int aux = open(atf_fs_path_cstring(sp->m_sb->m_path),
+                       O_WRONLY | O_CREAT | O_TRUNC, 0644);
+        if (aux == -1)
+            err = atf_libc_error(errno, "Could not create %s",
+                                 atf_fs_path_cstring(sp->m_sb->m_path));
+        else {
+            err = safe_dup(aux, procfd);
+            if (atf_is_error(err))
+                close(aux);
+        }
+    } else {
+        UNREACHABLE;
+        err = atf_no_error();
+    }
+
+    return err;
+}
+
+static
+void
+parent_connect(const stream_prepare_t *sp, int *fd)
+{
+    const int type = atf_process_stream_type(sp->m_sb);
+
+    if (type == atf_process_stream_type_capture) {
+        close(sp->m_pipefds[1]);
+        *fd = sp->m_pipefds[0];
+    } else if (type == atf_process_stream_type_inherit) {
+        /* Do nothing. */
+    } else if (type == atf_process_stream_type_redirect_fd) {
+        /* Do nothing. */
+    } else if (type == atf_process_stream_type_redirect_path) {
+        /* Do nothing. */
+    } else {
+        UNREACHABLE;
+    }
+}
+
+static
+atf_error_t
+do_parent(atf_process_child_t *c,
+          const pid_t pid,
+          const stream_prepare_t *outsp,
+          const stream_prepare_t *errsp)
+{
+    atf_error_t err;
+
+    err = atf_process_child_init(c);
+    if (atf_is_error(err))
+        goto out;
+
+    c->m_pid = pid;
+
+    parent_connect(outsp, &c->m_stdout);
+    parent_connect(errsp, &c->m_stderr);
+
+out:
+    return err;
+}
+
+static
+void
+do_child(atf_error_t (*)(const void *),
+         const void *,
+         const stream_prepare_t *,
+         const stream_prepare_t *) ATF_DEFS_ATTRIBUTE_NORETURN;
+
+static
+void
+do_child(atf_error_t (*start)(const void *),
+         const void *v,
+         const stream_prepare_t *outsp,
+         const stream_prepare_t *errsp)
+{
+    atf_error_t err;
+
+    atf_reset_exit_checks();
+    err = child_connect(outsp, STDOUT_FILENO);
+    if (atf_is_error(err))
+        goto out;
+
+    err = child_connect(errsp, STDERR_FILENO);
+    if (atf_is_error(err))
+        goto out;
+
+    err = start(v);
+
+out:
+    if (atf_is_error(err)) {
+        char buf[1024];
+
+        atf_error_format(err, buf, sizeof(buf));
+        fprintf(stderr, "Unhandled error: %s\n", buf);
+        atf_error_free(err);
+
+        exit(EXIT_FAILURE);
+    } else
+        exit(EXIT_SUCCESS);
+}
+
+atf_error_t
+atf_process_oldfork(pid_t *pid)
+{
+    *pid = fork();
+    return atf_no_error();
+}
+
+atf_error_t
+atf_process_fork(atf_process_child_t *c,
+                 atf_error_t (*start)(const void *),
+                 const atf_process_stream_t *outsb,
+                 const atf_process_stream_t *errsb,
+                 const void *v)
+{
+    atf_error_t err;
+    stream_prepare_t outsp;
+    stream_prepare_t errsp;
     pid_t pid;

+    err = stream_prepare_init(&outsp, outsb);
+    if (atf_is_error(err))
+        goto out;
+
+    err = stream_prepare_init(&errsp, errsb);
+    if (atf_is_error(err))
+        goto err_outpipe;
+
     pid = fork();
     if (pid == -1) {
         err = atf_libc_error(errno, "Failed to fork");
-        goto out;
+        goto err_errpipe;
     }

-    *outpid = pid;
-    err = atf_no_error();
+    if (pid == 0) {
+        do_child(start, v, &outsp, &errsp);
+        UNREACHABLE;
+        abort();
+        err = atf_no_error();
+    } else {
+        err = do_parent(c, pid, &outsp, &errsp);
+        if (atf_is_error(err))
+            goto err_errpipe;
+    }

+    goto out;
+
+err_errpipe:
+    stream_prepare_fini(&errsp);
+err_outpipe:
+    stream_prepare_fini(&outsp);
+
 out:
     return err;
 }
============================================================
--- atf-c/process.h	8265e8436a6c94dcf2e843ba31df7d99c3772b82
+++ atf-c/process.h	038a4ff06acb2132257bdabc9c3b387ce50ecb4c
@@ -32,13 +32,92 @@

 #include <sys/types.h>

+#include <stdbool.h>
+
 #include <atf-c/error_fwd.h>
+#include <atf-c/fs.h>
+#include <atf-c/object.h>

 /* ---------------------------------------------------------------------
+ * The "atf_process_stream" type.
+ * --------------------------------------------------------------------- */
+
+struct atf_process_stream {
+    atf_object_t m_object;
+
+    int m_type;
+
+    // Valid if m_type == redirect_fd.
+    int m_fd;
+
+    // Valid if m_type == redirect_path.
+    const atf_fs_path_t *m_path;
+};
+typedef struct atf_process_stream atf_process_stream_t;
+
+extern const int atf_process_stream_type_capture;
+extern const int atf_process_stream_type_inherit;
+extern const int atf_process_stream_type_redirect_fd;
+extern const int atf_process_stream_type_redirect_path;
+
+atf_error_t atf_process_stream_init_capture(atf_process_stream_t *);
+atf_error_t atf_process_stream_init_inherit(atf_process_stream_t *);
+atf_error_t atf_process_stream_init_redirect_fd(atf_process_stream_t *,
+                                                const int fd);
+atf_error_t atf_process_stream_init_redirect_path(atf_process_stream_t *,
+                                                  const atf_fs_path_t *);
+void atf_process_stream_fini(atf_process_stream_t *);
+
+int atf_process_stream_type(const atf_process_stream_t *);
+
+/* ---------------------------------------------------------------------
+ * The "atf_process_status" type.
+ * --------------------------------------------------------------------- */
+
+struct atf_process_status {
+    atf_object_t m_object;
+
+    int m_status;
+};
+typedef struct atf_process_status atf_process_status_t;
+
+void atf_process_status_fini(atf_process_status_t *);
+
+bool atf_process_status_exited(const atf_process_status_t *);
+int atf_process_status_exitstatus(const atf_process_status_t *);
+bool atf_process_status_signaled(const atf_process_status_t *);
+int atf_process_status_termsig(const atf_process_status_t *);
+bool atf_process_status_coredump(const atf_process_status_t *);
+
+/* ---------------------------------------------------------------------
+ * The "atf_process_child" type.
+ * --------------------------------------------------------------------- */
+
+struct atf_process_child {
+    atf_object_t m_object;
+
+    pid_t m_pid;
+
+    int m_stdout;
+    int m_stderr;
+};
+typedef struct atf_process_child atf_process_child_t;
+
+atf_error_t atf_process_child_wait(atf_process_child_t *,
+                                   atf_process_status_t *);
+int atf_process_child_stdout(atf_process_child_t *);
+int atf_process_child_stderr(atf_process_child_t *);
+
+/* ---------------------------------------------------------------------
  * Free functions.
  * --------------------------------------------------------------------- */

-atf_error_t atf_process_fork(pid_t *);
-atf_error_t atf_process_system(const char *);
+atf_error_t atf_process_oldfork(pid_t *pid); // XXX Kill this.
+atf_error_t atf_process_fork(atf_process_child_t *,
+                             atf_error_t (*)(const void *),
+                             const atf_process_stream_t *,
+                             const atf_process_stream_t *,
+                             const void *);
+atf_error_t atf_process_system(const char *); // XXX Kill this.

 #endif /* !defined(ATF_C_PROCESS_H) */
============================================================
--- atf-c/tc.c	8990a94abfa2417ef6f1f4ec78556cc3f35353af
+++ atf-c/tc.c	bcb9833d27cdd68cff6f1b58c0018f7f30c79866
@@ -453,7 +453,7 @@ fork_body(const atf_tc_t *tc, int fdout,
     atf_error_t err;
     pid_t pid;

-    err = atf_process_fork(&pid);
+    err = atf_process_oldfork(&pid);
     if (atf_is_error(err))
         goto out;

@@ -480,7 +480,7 @@ fork_cleanup(const atf_tc_t *tc, int fdo
     if (tc->m_cleanup == NULL)
         err = atf_no_error();
     else {
-        err = atf_process_fork(&pid);
+        err = atf_process_oldfork(&pid);
         if (atf_is_error(err))
             goto out;

============================================================
--- atf-c++/process.cpp	1c1397ab2ab18e0b6fcf8d6fbe7f2c3e991b9bb7
+++ atf-c++/process.cpp	aaec03f9fdb8cd8769b0c897e7498e07089add79
@@ -48,7 +48,7 @@ impl::fork(void)
     atf_error_t err;
     pid_t pid;

-    err = atf_process_fork(&pid);
+    err = atf_process_oldfork(&pid);
     if (atf_is_error(err))
         throw_atf_error(err);

============================================================
--- tests/atf/atf-c/t_process.c	70c2e3a828220c193cf33aee22cb769e09cb4891
+++ tests/atf/atf-c/t_process.c	4e54aa872f22559d6dc2d819ebf3191e2deb6389
@@ -27,37 +27,668 @@
  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */

+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
 #include <atf-c.h>

+#include "atf-c/defs.h"
+#include "atf-c/io.h"
 #include "atf-c/process.h"
+#include "atf-c/sanity.h"

 #include "h_lib.h"

+atf_error_t atf_process_status_init(atf_process_status_t *, int);
+
 /* ---------------------------------------------------------------------
- * Test cases for the free functions.
+ * Auxiliary functions for testing of 'atf_process_fork'.
  * --------------------------------------------------------------------- */

-ATF_TC(fork);
-ATF_TC_HEAD(fork, tc)
+/*
+ * Testing of atf_process_fork is quite messy.  We want to be able to test
+ * all the possible combinations of stdout and stderr behavior to ensure
+ * that the streams are manipulated correctly.
+ *
+ * To do this, the do_fork function is a wrapper for atf_process_fork that
+ * issues stream-specific hooks before fork, while the child is running and
+ * after the child terminates.  We then provide test cases that just call
+ * do_fork with different hooks.
+ *
+ * The hooks are described by base_stream, and we then have one *_stream
+ * type for ever possible stream behavior.
+ */
+
+enum out_type { stdout_type, stderr_type };
+
+struct base_stream {
+    void (*init)(void *);
+    void (*process)(void *, atf_process_child_t *);
+    void (*fini)(void *);
+
+    atf_process_stream_t m_sb;
+    enum out_type m_type;
+};
+#define BASE_STREAM(ihook, phook, fhook, type) \
+    { .init = ihook, \
+      .process = phook, \
+      .fini = fhook, \
+      .m_type = type }
+
+static
+void
+check_file(const enum out_type type)
 {
-    atf_tc_set_md_var(tc, "descr", "Tests the atf_process_fork function");
+    switch (type) {
+    case stdout_type:
+        ATF_CHECK(grep_file("stdout", "stdout: msg"));
+        ATF_CHECK(!grep_file("stdout", "stderr: msg"));
+        break;
+    case stderr_type:
+        ATF_CHECK(grep_file("stderr", "stderr: msg"));
+        ATF_CHECK(!grep_file("stderr", "stdout: msg"));
+        break;
+    default:
+        UNREACHABLE;
+    }
 }
-ATF_TC_BODY(fork, tc)
+
+struct capture_stream {
+    struct base_stream m_base;
+
+    atf_dynstr_t m_msg;
+};
+#define CAPTURE_STREAM(type) \
+    { .m_base = BASE_STREAM(capture_stream_init, \
+                            capture_stream_process, \
+                            capture_stream_fini, \
+                            type) }
+
+static
+void
+capture_stream_init(void *v)
 {
-    atf_tc_skip("Unimplemented test case; process API not yet decided");
+    struct capture_stream *s = v;
+
+    RE(atf_process_stream_init_capture(&s->m_base.m_sb));
+    RE(atf_dynstr_init(&s->m_msg));
 }

-ATF_TC(system);
-ATF_TC_HEAD(system, tc)
+static
+void
+capture_stream_process(void *v, atf_process_child_t *c)
 {
-    atf_tc_set_md_var(tc, "descr", "Tests the atf_process_system function");
+    struct capture_stream *s = v;
+
+    bool eof;
+    switch (s->m_base.m_type) {
+    case stdout_type:
+        RE(atf_io_readline(atf_process_child_stdout(c),
+                           &s->m_msg, &eof));
+        ATF_CHECK(grep_string(&s->m_msg, "stdout: msg"));
+        ATF_CHECK(!grep_string(&s->m_msg, "stderr: msg"));
+        break;
+    case stderr_type:
+        RE(atf_io_readline(atf_process_child_stderr(c),
+                           &s->m_msg, &eof));
+        ATF_CHECK(!grep_string(&s->m_msg, "stdout: msg"));
+        ATF_CHECK(grep_string(&s->m_msg, "stderr: msg"));
+        break;
+    default:
+        UNREACHABLE;
+    }
 }
-ATF_TC_BODY(system, tc)
+
+static
+void
+capture_stream_fini(void *v)
 {
-    atf_tc_skip("Unimplemented test case; process API not yet decided");
+    struct capture_stream *s = v;
+
+    atf_dynstr_fini(&s->m_msg);
+    atf_process_stream_fini(&s->m_base.m_sb);
 }

+struct inherit_stream {
+    struct base_stream m_base;
+    int m_fd;
+
+    int m_old_fd;
+};
+#define INHERIT_STREAM(type) \
+    { .m_base = BASE_STREAM(inherit_stream_init, \
+                            NULL, \
+                            inherit_stream_fini, \
+                            type) }
+
+static
+void
+inherit_stream_init(void *v)
+{
+    struct inherit_stream *s = v;
+    const char *name;
+
+    RE(atf_process_stream_init_inherit(&s->m_base.m_sb));
+
+    switch (s->m_base.m_type) {
+    case stdout_type:
+        s->m_fd = STDOUT_FILENO;
+        name = "stdout";
+        break;
+    case stderr_type:
+        s->m_fd = STDERR_FILENO;
+        name = "stderr";
+        break;
+    default:
+        UNREACHABLE;
+        name = NULL;
+    }
+
+    s->m_old_fd = dup(s->m_fd);
+    ATF_REQUIRE(s->m_old_fd != -1);
+    ATF_REQUIRE(close(s->m_fd) != -1);
+    ATF_REQUIRE_EQ(open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644),
+                   s->m_fd);
+}
+
+static
+void
+inherit_stream_fini(void *v)
+{
+    struct inherit_stream *s = v;
+
+    ATF_REQUIRE(dup2(s->m_old_fd, s->m_fd) != -1);
+    ATF_REQUIRE(close(s->m_old_fd) != -1);
+
+    atf_process_stream_fini(&s->m_base.m_sb);
+
+    check_file(s->m_base.m_type);
+}
+
+struct redirect_fd_stream {
+    struct base_stream m_base;
+
+    int m_fd;
+};
+#define REDIRECT_FD_STREAM(type) \
+    { .m_base = BASE_STREAM(redirect_fd_stream_init, \
+                            NULL, \
+                            redirect_fd_stream_fini, \
+                            type) }
+
+static
+void
+redirect_fd_stream_init(void *v)
+{
+    struct redirect_fd_stream *s = v;
+
+    switch (s->m_base.m_type) {
+    case stdout_type:
+        s->m_fd = open("stdout", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+        break;
+    case stderr_type:
+        s->m_fd = open("stderr", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+        break;
+    default:
+        UNREACHABLE;
+    }
+    ATF_REQUIRE(s->m_fd != -1);
+
+    RE(atf_process_stream_init_redirect_fd(&s->m_base.m_sb, s->m_fd));
+}
+
+static
+void
+redirect_fd_stream_fini(void *v)
+{
+    struct redirect_fd_stream *s = v;
+
+    ATF_REQUIRE(close(s->m_fd) != -1);
+
+    atf_process_stream_fini(&s->m_base.m_sb);
+
+    check_file(s->m_base.m_type);
+}
+
+struct redirect_path_stream {
+    struct base_stream m_base;
+
+    atf_fs_path_t m_path;
+};
+#define REDIRECT_PATH_STREAM(type) \
+    { .m_base = BASE_STREAM(redirect_path_stream_init, \
+                            NULL, \
+                            redirect_path_stream_fini, \
+                            type) }
+
+static
+void
+redirect_path_stream_init(void *v)
+{
+    struct redirect_path_stream *s = v;
+
+    switch (s->m_base.m_type) {
+    case stdout_type:
+        RE(atf_fs_path_init_fmt(&s->m_path, "stdout"));
+        break;
+    case stderr_type:
+        RE(atf_fs_path_init_fmt(&s->m_path, "stderr"));
+        break;
+    default:
+        UNREACHABLE;
+    }
+
+    RE(atf_process_stream_init_redirect_path(&s->m_base.m_sb, &s->m_path));
+}
+
+static
+void
+redirect_path_stream_fini(void *v)
+{
+    struct redirect_path_stream *s = v;
+
+    atf_process_stream_fini(&s->m_base.m_sb);
+
+    atf_fs_path_fini(&s->m_path);
+
+    check_file(s->m_base.m_type);
+}
+
+static
+atf_error_t
+child_print(const void *v)
+{
+    const char *msg = v;
+
+    fprintf(stdout, "stdout: %s\n", msg);
+    fprintf(stderr, "stderr: %s\n", msg);
+
+    return atf_no_error();
+}
+
+static
+void
+do_fork(const struct base_stream *outfs, void *out,
+        const struct base_stream *errfs, void *err)
+{
+    atf_process_child_t child;
+    atf_process_status_t status;
+
+    outfs->init(out);
+    errfs->init(err);
+
+    RE(atf_process_fork(&child, child_print,
+                        &outfs->m_sb, &errfs->m_sb, "msg"));
+    if (outfs->process != NULL)
+        outfs->process(out, &child);
+    if (errfs->process != NULL)
+        errfs->process(err, &child);
+    RE(atf_process_child_wait(&child, &status));
+
+    outfs->fini(out);
+    errfs->fini(err);
+
+    atf_process_status_fini(&status);
+}
+
 /* ---------------------------------------------------------------------
+ * Test cases for the "stream" type.
+ * --------------------------------------------------------------------- */
+
+ATF_TC(stream_init_capture);
+ATF_TC_HEAD(stream_init_capture, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the "
+                      "atf_process_stream_init_capture function");
+}
+ATF_TC_BODY(stream_init_capture, tc)
+{
+    atf_process_stream_t sb;
+
+    RE(atf_process_stream_init_capture(&sb));
+
+    ATF_CHECK_EQ(atf_process_stream_type(&sb),
+                 atf_process_stream_type_capture);
+
+    atf_process_stream_fini(&sb);
+}
+
+ATF_TC(stream_init_inherit);
+ATF_TC_HEAD(stream_init_inherit, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the "
+                      "atf_process_stream_init_inherit function");
+}
+ATF_TC_BODY(stream_init_inherit, tc)
+{
+    atf_process_stream_t sb;
+
+    RE(atf_process_stream_init_inherit(&sb));
+
+    ATF_CHECK_EQ(atf_process_stream_type(&sb),
+                 atf_process_stream_type_inherit);
+
+    atf_process_stream_fini(&sb);
+}
+
+ATF_TC(stream_init_redirect_fd);
+ATF_TC_HEAD(stream_init_redirect_fd, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the "
+                      "atf_process_stream_init_redirect_fd function");
+}
+ATF_TC_BODY(stream_init_redirect_fd, tc)
+{
+    atf_process_stream_t sb;
+
+    RE(atf_process_stream_init_redirect_fd(&sb, 1));
+
+    ATF_CHECK_EQ(atf_process_stream_type(&sb),
+                 atf_process_stream_type_redirect_fd);
+
+    atf_process_stream_fini(&sb);
+}
+
+ATF_TC(stream_init_redirect_path);
+ATF_TC_HEAD(stream_init_redirect_path, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the "
+                      "atf_process_stream_init_redirect_path function");
+}
+ATF_TC_BODY(stream_init_redirect_path, tc)
+{
+    atf_process_stream_t sb;
+    atf_fs_path_t path;
+
+    RE(atf_fs_path_init_fmt(&path, "foo"));
+    RE(atf_process_stream_init_redirect_path(&sb, &path));
+
+    ATF_CHECK_EQ(atf_process_stream_type(&sb),
+                 atf_process_stream_type_redirect_path);
+
+    atf_process_stream_fini(&sb);
+    atf_fs_path_fini(&path);
+}
+
+/* ---------------------------------------------------------------------
+ * Test cases for the "status" type.
+ * --------------------------------------------------------------------- */
+
+static void child_exit_success(void) ATF_DEFS_ATTRIBUTE_NORETURN;
+static void child_exit_failure(void) ATF_DEFS_ATTRIBUTE_NORETURN;
+static void child_sigkill(void) ATF_DEFS_ATTRIBUTE_NORETURN;
+static void child_sigquit(void) ATF_DEFS_ATTRIBUTE_NORETURN;
+static void child_sigterm(void) ATF_DEFS_ATTRIBUTE_NORETURN;
+
+void
+child_exit_success(void)
+{
+    exit(EXIT_SUCCESS);
+}
+
+void
+child_exit_failure(void)
+{
+    exit(EXIT_FAILURE);
+}
+
+void
+child_sigkill(void)
+{
+    kill(getpid(), SIGKILL);
+    abort();
+}
+
+void
+child_sigquit(void)
+{
+    kill(getpid(), SIGQUIT);
+    abort();
+}
+
+void
+child_sigterm(void)
+{
+    kill(getpid(), SIGTERM);
+    abort();
+}
+
+static
+int
+fork_and_wait_child(void (*child_func)(void))
+{
+    pid_t pid;
+    int status;
+
+    pid = fork();
+    ATF_REQUIRE(pid != -1);
+    if (pid == 0) {
+        status = 0; /* Silence compiler warnings */
+        child_func();
+        UNREACHABLE;
+        abort();
+    } else {
+        ATF_REQUIRE(waitpid(pid, &status, 0) != 0);
+    }
+
+    return status;
+}
+
+ATF_TC(status_exited);
+ATF_TC_HEAD(status_exited, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the status type for processes "
+                      "that exit cleanly");
+}
+ATF_TC_BODY(status_exited, tc)
+{
+    {
+        const int rawstatus = fork_and_wait_child(child_exit_success);
+        atf_process_status_t s;
+        RE(atf_process_status_init(&s, rawstatus));
+        ATF_CHECK(atf_process_status_exited(&s));
+        ATF_CHECK_EQ(atf_process_status_exitstatus(&s), EXIT_SUCCESS);
+        ATF_CHECK(!atf_process_status_signaled(&s));
+        atf_process_status_fini(&s);
+    }
+
+    {
+        const int rawstatus = fork_and_wait_child(child_exit_failure);
+        atf_process_status_t s;
+        RE(atf_process_status_init(&s, rawstatus));
+        ATF_CHECK(atf_process_status_exited(&s));
+        ATF_CHECK_EQ(atf_process_status_exitstatus(&s), EXIT_FAILURE);
+        ATF_CHECK(!atf_process_status_signaled(&s));
+        atf_process_status_fini(&s);
+    }
+}
+
+ATF_TC(status_signaled);
+ATF_TC_HEAD(status_signaled, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the status type for processes "
+                      "that end due to a signal");
+}
+ATF_TC_BODY(status_signaled, tc)
+{
+    {
+        const int rawstatus = fork_and_wait_child(child_sigkill);
+        atf_process_status_t s;
+        RE(atf_process_status_init(&s, rawstatus));
+        ATF_CHECK(!atf_process_status_exited(&s));
+        ATF_CHECK(atf_process_status_signaled(&s));
+        ATF_CHECK_EQ(atf_process_status_termsig(&s), SIGKILL);
+        ATF_CHECK(!atf_process_status_coredump(&s));
+        atf_process_status_fini(&s);
+    }
+
+    {
+        const int rawstatus = fork_and_wait_child(child_sigterm);
+        atf_process_status_t s;
+        RE(atf_process_status_init(&s, rawstatus));
+        ATF_CHECK(!atf_process_status_exited(&s));
+        ATF_CHECK(atf_process_status_signaled(&s));
+        ATF_CHECK_EQ(atf_process_status_termsig(&s), SIGTERM);
+        ATF_CHECK(!atf_process_status_coredump(&s));
+        atf_process_status_fini(&s);
+    }
+}
+
+ATF_TC(status_coredump);
+ATF_TC_HEAD(status_coredump, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests the status type for processes "
+                      "that crash");
+}
+ATF_TC_BODY(status_coredump, tc)
+{
+    struct rlimit rl;
+    rl.rlim_cur = RLIM_INFINITY;
+    rl.rlim_max = RLIM_INFINITY;
+    if (setrlimit(RLIMIT_CORE, &rl) == -1)
+        atf_tc_skip("Cannot unlimit the core file size; check limits "
+                    "manually");
+
+    const int rawstatus = fork_and_wait_child(child_sigquit);
+    atf_process_status_t s;
+    RE(atf_process_status_init(&s, rawstatus));
+    ATF_CHECK(!atf_process_status_exited(&s));
+    ATF_CHECK(atf_process_status_signaled(&s));
+    ATF_CHECK_EQ(atf_process_status_termsig(&s), SIGQUIT);
+    ATF_CHECK(atf_process_status_coredump(&s));
+    atf_process_status_fini(&s);
+}
+
+/* ---------------------------------------------------------------------
+ * Tests cases for the free functions.
+ * --------------------------------------------------------------------- */
+
+static const int exit_v_null = 1;
+static const int exit_v_notnull = 2;
+
+static
+atf_error_t
+child_cookie(const void *v)
+{
+    if (v == NULL)
+        exit(exit_v_null);
+    else
+        exit(exit_v_notnull);
+
+    UNREACHABLE;
+    return atf_no_error();
+}
+
+ATF_TC(fork_cookie);
+ATF_TC_HEAD(fork_cookie, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests forking a child, with "
+                      "a null and non-null data cookie");
+}
+ATF_TC_BODY(fork_cookie, tc)
+{
+    atf_process_stream_t outsb, errsb;
+
+    RE(atf_process_stream_init_inherit(&outsb));
+    RE(atf_process_stream_init_inherit(&errsb));
+
+    {
+        atf_process_child_t child;
+        atf_process_status_t status;
+
+        RE(atf_process_fork(&child, child_cookie, &outsb, &errsb, NULL));
+        RE(atf_process_child_wait(&child, &status));
+
+        ATF_CHECK(atf_process_status_exited(&status));
+        ATF_CHECK_EQ(atf_process_status_exitstatus(&status), exit_v_null);
+
+        atf_process_status_fini(&status);
+    }
+
+    {
+        atf_process_child_t child;
+        atf_process_status_t status;
+
+        RE(atf_process_fork(&child, child_cookie, &outsb, &errsb, "cookie"));
+        RE(atf_process_child_wait(&child, &status));
+
+        ATF_CHECK(atf_process_status_exited(&status));
+        ATF_CHECK_EQ(atf_process_status_exitstatus(&status), exit_v_notnull);
+
+        atf_process_status_fini(&status);
+    }
+
+    atf_process_stream_fini(&errsb);
+    atf_process_stream_fini(&outsb);
+}
+
+#define TC_FORK_STREAMS(outlc, outuc, errlc, erruc) \
+    ATF_TC(fork_out ## outlc ## _err ## errlc); \
+    ATF_TC_HEAD(fork_out ## outlc ## _err ## errlc, tc) \
+    { \
+        atf_tc_set_md_var(tc, "descr", "Tests forking a child, with " \
+                          "stdout " #outuc " and stderr " #erruc); \
+    } \
+    ATF_TC_BODY(fork_out ## outlc ## _err ## errlc, tc) \
+    { \
+        struct outlc ## _stream out = outuc ## _STREAM(stdout_type); \
+        struct errlc ## _stream err = erruc ## _STREAM(stderr_type); \
+        do_fork(&out.m_base, &out, &err.m_base, &err); \
+    }
+
+TC_FORK_STREAMS(capture, CAPTURE, inherit, INHERIT);
+
+ATF_TC(fork_outinherit_errinherit);
+ATF_TC_HEAD(fork_outinherit_errinherit, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests forking a child, with "
+                      "inherited stdout and inherited stderr");
+}
+ATF_TC_BODY(fork_outinherit_errinherit, tc)
+{
+    struct inherit_stream out = INHERIT_STREAM(stdout_type);
+    struct inherit_stream err = INHERIT_STREAM(stderr_type);
+
+    do_fork(&out.m_base, &out, &err.m_base, &err);
+}
+
+ATF_TC(fork_outredirectfd_errinherit);
+ATF_TC_HEAD(fork_outredirectfd_errinherit, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests forking a child, with "
+                      "inherited stdout and inherited stderr");
+}
+ATF_TC_BODY(fork_outredirectfd_errinherit, tc)
+{
+    struct redirect_fd_stream out = REDIRECT_FD_STREAM(stdout_type);
+    struct inherit_stream err = INHERIT_STREAM(stderr_type);
+
+    do_fork(&out.m_base, &out, &err.m_base, &err);
+}
+
+ATF_TC(fork_outredirectpath_errinherit);
+ATF_TC_HEAD(fork_outredirectpath_errinherit, tc)
+{
+    atf_tc_set_md_var(tc, "descr", "Tests forking a child, with "
+                      "inherited stdout and inherited stderr");
+}
+ATF_TC_BODY(fork_outredirectpath_errinherit, tc)
+{
+    struct redirect_path_stream out = REDIRECT_PATH_STREAM(stdout_type);
+    struct inherit_stream err = INHERIT_STREAM(stderr_type);
+
+    do_fork(&out.m_base, &out, &err.m_base, &err);
+}
+
+/* ---------------------------------------------------------------------
  * Tests cases for the header file.
  * --------------------------------------------------------------------- */

@@ -69,9 +700,23 @@ ATF_TP_ADD_TCS(tp)

 ATF_TP_ADD_TCS(tp)
 {
+    /* Add the tests for the "stream" type. */
+    ATF_TP_ADD_TC(tp, stream_init_capture);
+    ATF_TP_ADD_TC(tp, stream_init_inherit);
+    ATF_TP_ADD_TC(tp, stream_init_redirect_fd);
+    ATF_TP_ADD_TC(tp, stream_init_redirect_path);
+
+    /* Add the tests for the "status" type. */
+    ATF_TP_ADD_TC(tp, status_exited);
+    ATF_TP_ADD_TC(tp, status_signaled);
+    ATF_TP_ADD_TC(tp, status_coredump);
+
     /* Add the tests for the free functions. */
-    ATF_TP_ADD_TC(tp, fork);
-    ATF_TP_ADD_TC(tp, system);
+    ATF_TP_ADD_TC(tp, fork_cookie);
+    ATF_TP_ADD_TC(tp, fork_outcapture_errinherit);
+    ATF_TP_ADD_TC(tp, fork_outinherit_errinherit);
+    ATF_TP_ADD_TC(tp, fork_outredirectfd_errinherit);
+    ATF_TP_ADD_TC(tp, fork_outredirectpath_errinherit);

     /* Add the test cases for the header file. */
     ATF_TP_ADD_TC(tp, include);
============================================================
--- tests/atf/atf-c/t_sanity.c	f198b24a03b91d99d034dd4e6c8b629b56e470a8
+++ tests/atf/atf-c/t_sanity.c	f357d5b3f5ad0742a725b18970a3e60d40549bc5
@@ -79,7 +79,7 @@ do_test(enum type t, bool cond)

     ATF_REQUIRE(pipe(fds) != -1);

-    RE(atf_process_fork(&pid));
+    RE(atf_process_oldfork(&pid));
     if (pid == 0) {
         ATF_REQUIRE(dup2(fds[1], STDERR_FILENO) != -1);
         close(fds[0]);
============================================================
--- tests/atf/atf-c/t_signals.c	5db33b1465a5d1185ecf442e6cfe8f5d24a2eb52
+++ tests/atf/atf-c/t_signals.c	de2770b573a42267cff064efa928e170bc154dc0
@@ -253,7 +253,7 @@ ATF_TC_BODY(signal_reset, tc)
 ATF_TC_BODY(signal_reset, tc)
 {
     pid_t pid;
-    RE(atf_process_fork(&pid));
+    RE(atf_process_oldfork(&pid));
     if (pid == 0) {
         struct sigaction sa;