Search code examples
cioexecforkpipe

Force a program created using `exec` to perform unbuffered I/O


I am trying to interact with an external program in C using pipe, fork and exec. I want to force the external program to perform unbuffered I/O. Here's a relevant snippet from my code so far:

...
pid = fork();
if (pid == (pid_t) 0)
{
    /* child process */

    /* make pipe connections to standard streams */             
    dup2(wpipe[0], STDIN_FILENO);
    dup2(rpipe[1], STDOUT_FILENO);

    /* close pipe endings */
    close(wpipe[0]); close(rpipe[0]);
    close(wpipe[1]); close(rpipe[1]);

    /* unbuffered I/O */
    setvbuf(stdin, NULL, _IONBF, BUFSIZ);
    setvbuf(stdout, NULL, _IONBF, BUFSIZ); 

    if (execl("path/to/some_binary", "path/to/some_binary", (char *) NULL) == -1)
    {
        fprintf(stderr, "exec failed\n");
        return EXIT_FAILURE;
    }   
    return EXIT_SUCCESS;
}
...

This does not work because streams do not survive across exec calls. So using setvbuf to force unbuffered I/O does not work, because the program image (some_binary) creates stdin and stdout streams of its own, and does not use the streams that I called setvbuf on.

The program does work when I re-build some_binary after adding the setvbuf calls in its code. But how can this be done if you do not have any control over the binary you are passing to exec? How can this be made to work for, say, a unix command like cat or ls?


Solution

  • You cannot do what you want in the general case (unbuffering after execve(2) of arbitrary executables...)

    Buffering is done by code (e.g. by some libc code related to <stdio.h>). And the code is defined by the program being execve-ed.

    You might perhaps play with LD_PRELOAD tricks which might call setvbuf(stdin, NULL, _IONBF, BUFSIZ); after the execve (but before the main....); but this would work only with dynamically linked executables.

    Perhaps using some constructor function attribute in some initialization function of your LD_PRELOAD-ed shared object might sometimes do the trick. Or redefine printf, fopen, .... in that shared object...

    addenda

    You commented that you do :

    two-way communication with a sub-process using two pipes.

    Then your approach is wrong. The parent process should monitor the two pipes, probably with a multiplexing call like poll(2), then (according to the result of that multiplexing) decide to read or to write to the child process. In reality, you want some event loop: either implement a simple event loop yourself (with e.g. poll [iteratively called many times] inside a repeated loop) or use some existing one (see libevent or libev, or the one provided by some toolkit like GTK or Qt etc...)

    You might also multiplex with select(2) but I recommend poll because of the C10K problem

    You won't lose your time by reading Advanced Linux Programming.

    See also this answer to your next related question.