Capture a child process's stdout with libuv
Asked Answered
D

2

12

I'm using libuv. I've read http://nikhilm.github.com/uvbook/processes.html and still cannot work out how to capture the stdout of a child process so that it is available in the parent (but not in place of the parent's stdin).

My code is currently:

#include <stdio.h>
#include <stdlib.h>
#include "../../libuv/include/uv.h"

uv_loop_t *loop;
uv_process_t child_req;
uv_process_options_t options;
uv_pipe_t apipe;

void on_child_exit(uv_process_t *req, int exit_status, int term_signal) {
    fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);
    uv_close((uv_handle_t*) req, NULL);
}

uv_buf_t alloc_buffer(uv_handle_t *handle, size_t len) {
    printf("alloc_buffer called\n");
    uv_buf_t buf;
    buf.base = malloc(len);
    buf.len = len;
    return buf;
}

void read_apipe(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) {
    printf("read %li bytes from the child process\n", nread);
}

int main(int argc, char *argv[]) {
    printf("spawn_test\n");
    loop = uv_default_loop();

    char* args[3];
    args[0] = "dummy";
    args[1] = NULL;
    args[2] = NULL;

    uv_pipe_init(loop, &apipe, 0);
    uv_pipe_open(&apipe, 0);

    options.stdio_count = 3;
    uv_stdio_container_t child_stdio[3];
    child_stdio[0].flags = UV_IGNORE;
    child_stdio[1].flags = UV_INHERIT_STREAM;
    child_stdio[1].data.stream = (uv_stream_t *) &apipe;
    child_stdio[2].flags = UV_IGNORE;
    options.stdio = child_stdio;

    options.exit_cb = on_child_exit;
    options.file = args[0];
    options.args = args;

    uv_read_start((uv_stream_t*)&apipe, alloc_buffer, read_apipe);
    if (uv_spawn(loop, &child_req, options)) {
        fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
        return 1;
    }

    return uv_run(loop, UV_RUN_DEFAULT);
}

dummy.c:

#include <unistd.h>
#include <stdio.h>

int main() {
    printf("child starting\n");
    sleep(1);
    printf("child running\n");
    sleep(2);
    printf("child ending\n");
    return 0;
}

I have the nagging feeling that I do not quite understand the point of libuv's pipes yet.

Diagnose answered 7/2, 2013 at 12:48 Comment(0)
D
9

I've found the solution:

  1. I had the wrong flags, they should have been UV_CREATE_PIPE | UV_READABLE_PIPE not UV_INHERIT_STREAM.
  2. I needed to call uv_read_start after uv_spawn. I assume that there's no chance of data loss, as uv_run has not yet been called.
  3. The above two fixes showed all the output from dummy to arriving at once, rather than in three lumps (as it does on the command line). An fflush in dummy.c fixed this.

spawn_test:

#include <stdio.h>
#include <stdlib.h>
#include "../../libuv/include/uv.h"

uv_loop_t *loop;
uv_process_t child_req;
uv_process_options_t options;
uv_pipe_t apipe;

void on_child_exit(uv_process_t *req, int exit_status, int term_signal) {
    fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);
    uv_close((uv_handle_t*) req, NULL);
}

uv_buf_t alloc_buffer(uv_handle_t *handle, size_t len) {
    printf("alloc_buffer called, requesting a %lu byte buffer\n");
    uv_buf_t buf;
    buf.base = malloc(len);
    buf.len = len;
    return buf;
}

void read_apipe(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) {
    printf("read %li bytes in a %lu byte buffer\n", nread, buf.len);
    if (nread + 1 > buf.len) return;
    buf.base[nread] = '\0'; // turn it into a cstring
    printf("read: |%s|", buf.base);
}

int main(int argc, char *argv[]) {
    printf("spawn_test\n");
    loop = uv_default_loop();

    char* args[3];
    args[0] = "dummy";
    args[1] = NULL;
    args[2] = NULL;

    uv_pipe_init(loop, &apipe, 0);
    uv_pipe_open(&apipe, 0);

    options.stdio_count = 3;
    uv_stdio_container_t child_stdio[3];
    child_stdio[0].flags = UV_IGNORE;
    child_stdio[1].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
    child_stdio[1].data.stream = (uv_stream_t *) &apipe;
    child_stdio[2].flags = UV_IGNORE;
    options.stdio = child_stdio;

    options.exit_cb = on_child_exit;
    options.file = args[0];
    options.args = args;

    if (uv_spawn(loop, &child_req, options)) {
        fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
        return 1;
    }
    uv_read_start((uv_stream_t*)&apipe, alloc_buffer, read_apipe);

    return uv_run(loop, UV_RUN_DEFAULT);
}

dummy.c:

#include <unistd.h>
#include <stdio.h>

int main() {
    printf("child starting\n");
    fflush(stdout);
    sleep(1);
    printf("child running\n");
    fflush(stdout);
    sleep(2);
    printf("child ending\n");
    fflush(stdout);
    return 0;
}
Diagnose answered 7/2, 2013 at 14:6 Comment(0)
I
7

See how they do it in the libuv unit test libuv/test/test-stdio-over-pipes.c:

  • Don't call uv_pipe_open
  • Flags for child's stdin: UV_CREATE_PIPE | UV_READABLE_PIPE
  • Flags for child's stdout and stderr: UV_CREATE_PIPE | UV_WRITABLE_PIPE

There is also an issue on Windows, where uv_spawn might return zero even though it encountered an error, and in those cases, you need to check process.spawn_error, which only exists on Windows.

Integral answered 28/7, 2013 at 17:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.