Search code examples
cspawnlibuv

Capture a child process's stdout with libuv


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.


Solution

  • 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;
    }