tags:

views:

92

answers:

2

Hi,

I wrote a code in C/C++ which forks a child process, duplicates the stdin/stdout into a pipe ends and calls execvp.

Everything is working fine (i.e. the output from stdin/err/out is captured by the parent process)

The problem is that the child stdout is buffered.

so if the child code looks like this:

printf("Enter any key and hit ENTER:\n");
fgets(line);
printf("read: %s\n", line);
exit(0);

In the parent process I don't see the line 'Enter any key:' - it will be "flushed" only after the program calls exit (which auto flushes the stdout buffer) or an explicit call to 'flush(stdout)' is added

I did some research and tried adding a call to disable the stdout buffering by adding a call to:

setvbuf(stdout, NULL, _IONBF, 0); just before calling execvp(...) in the parent process

so the relevant code looks now like this:

int rc = fork();
if ( rc == 0 ) {
    // Child process
    if(workingDirectory.IsEmpty() == false) {
        wxSetWorkingDirectory( workingDirectory );
    }
    int stdin_file  = fileno( stdin  );
    int stdout_file = fileno( stdout );
    int stderr_file = fileno( stderr );

    // Replace stdin/out with our pipe ends
    dup2 ( stdin_pipe_read,  stdin_file );
    close( stdin_pipe_write );

    dup2 ( stdout_pipe_write, stdout_file);
    dup2 ( stdout_pipe_write, stderr_file);
    close( stdout_pipe_read );

    setvbuf(stdout, NULL, _IONBF, 0);

    // execute the process
    execvp(argv[0], argv);
    exit(0);

}

With no luck.

Any ideas?

EDIT:

here is a sample of the parent code, the only thing needs changing is the path to the child executable:

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/wait.h>
#include <string>
#include <string.h>
#include <cstdio>

static int   read_handle(-1);
static pid_t pid;

bool read_from_child(std::string& buff) {
    fd_set  rs;
    timeval timeout;

    memset(&rs, 0, sizeof(rs));
    FD_SET(read_handle, &rs);
    timeout.tv_sec  = 1; // 1 second
    timeout.tv_usec = 0;

    int rc = select(read_handle+1, &rs, NULL, NULL, &timeout);
    if ( rc == 0 ) {
        // timeout
        return true;

    } else if ( rc > 0 ) {
        // there is something to read
        char buffer[1024*64]; // our read buffer
        memset(buffer, 0, sizeof(buffer));
        if(read(read_handle, buffer, sizeof(buffer)) > 0) {
            buff.clear();
            buff.append( buffer );
            return true;
        }

        return false;
    } else { /* == 0 */
        if ( rc == EINTR || rc == EAGAIN ) {
            return true;
        }

        // Process terminated
        int status(0);
        waitpid(pid, &status, 0);
        return false;
    }
}

void execute() {
    char *argv[] = {"/home/eran/devl/TestMain/Debug/TestMain", NULL};
    int    argc = 1;

    int filedes[2];
    int filedes2[2];

    // create a pipe
    int d;
    d = pipe(filedes);
    d = pipe(filedes2);

    int stdin_pipe_write = filedes[1];
    int stdin_pipe_read  = filedes[0];

    int stdout_pipe_write = filedes2[1];
    int stdout_pipe_read  = filedes2[0];

    int rc = fork();
    if ( rc == 0 ) {

        // Child process
        int stdin_file  = fileno( stdin  );
        int stdout_file = fileno( stdout );
        int stderr_file = fileno( stderr );

        // Replace stdin/out with our pipe ends
        dup2 ( stdin_pipe_read,  stdin_file );
        close( stdin_pipe_write );

        dup2 ( stdout_pipe_write, stdout_file);
        dup2 ( stdout_pipe_write, stderr_file);
        close( stdout_pipe_read );

        setvbuf(stdout, NULL, _IONBF, 0);

        // execute the process
        execvp(argv[0], argv);

    } else if ( rc < 0 ) {
        perror("fork");
        return;

    } else {
        // Parent
        std::string buf;
        read_handle = stdout_pipe_read;
        while(read_from_child(buf)) {
            if(buf.empty() == false) {
                printf("Received: %s\n", buf.c_str());
            }
            buf.clear();
        }
    }
}

int main(int argc, char **argv) {
    execute();
    return 0;
}
+1  A: 

Would inserting a call to fflush(stdout) after the printf not suffice?

Otherwise setvbuf should do the trick:

setvbuf(stdout,NULL,_IOLBF,0);
awoodland
`fflush(NULL)` will flush all open `FILE`s and is probably a good idea before `fork`ing.
nategoose
I'd not noticed that before!
awoodland
I can add the fflush(stdout) to the sample code, however, I often 'fork' third party code which I don't have control over. I added a call to setvbuf(..) with all the possible options I could think off, with no luck...
Eran
Can you show the code for the parent? Ideally the smallest possible example you can create that shows the problem. It'd be easier to debug/test on my system with a starting point that illustrates the problem.
awoodland
@awoodland: I modified the original post and it now contains a minimized version of the parent code which executes + start a loop for reading the output from the child. Using the client sample I provided you can see the problem (at least it is visible under my Ubuntu 10.04 / 64bit)
Eran
Thanks, I'll try and look at it again later
awoodland
+2  A: 

Actually, after struggling with it a bit, it seems like the only solution to this problem is by making the 'parent' process pretending to be a terminal using the OS pseudo terminal API calls.

One should call 'openpty()' before the fork(), and inside the child code, he should call 'login_tty(slave)' the slave is then becoming the stdin/out and stderr.

By pretending to a terminal, the buffering of stdout is automatically set to 'line mode' (i.e. flush occurs when \n is encountered). The parent should use the 'master' descriptor for readin/writing with the child process.

The modified parent code (in case anyone will ever need this):

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/wait.h>
#include <string>
#include <string.h>
#include <cstdio>
#include <pty.h>
#include <utmp.h>
static int   read_handle(-1);
static pid_t pid;

bool read_from_child(std::string& buff) {
    fd_set  rs;
    timeval timeout;

    memset(&rs, 0, sizeof(rs));
    FD_SET(read_handle, &rs);
    timeout.tv_sec  = 1; // 1 second
    timeout.tv_usec = 0;

    int rc = select(read_handle+1, &rs, NULL, NULL, &timeout);
    if ( rc == 0 ) {
        // timeout
        return true;

    } else if ( rc > 0 ) {
        // there is something to read
        char buffer[1024*64]; // our read buffer
        memset(buffer, 0, sizeof(buffer));
        if(read(read_handle, buffer, sizeof(buffer)) > 0) {
            buff.clear();
            buff.append( buffer );
            return true;
        }

        return false;
    } else { /* == 0 */
        if ( rc == EINTR || rc == EAGAIN ) {
            return true;
        }

        // Process terminated
        int status(0);
        waitpid(pid, &status, 0);
        return false;
    }
}

void execute() {
    char *argv[] = {"/home/eran/devl/TestMain/Debug/TestMain", NULL};
    int    argc = 1;

    int master, slave;
    openpty(&master, &slave, NULL, NULL, NULL);

    int rc = fork();
    if ( rc == 0 ) {
        login_tty(slave);
        close(master);

        // execute the process
        if(execvp(argv[0], argv) != 0)
            perror("execvp");

    } else if ( rc < 0 ) {
        perror("fork");
        return;

    } else {
        // Parent
        std::string buf;
        close(slave);

        read_handle = master;
        while(read_from_child(buf)) {
            if(buf.empty() == false) {
                printf("Received: %s", buf.c_str());
            }
            buf.clear();
        }
    }
}

int main(int argc, char **argv) {
    execute();
    return 0;
}
Eran