I’m trying to bolster my understanding of things related to fork
, exec
, dup
, and redirecting stdin
/stdout
/stderr
by writing the following popen
-type function:
// main.c #include <pthread.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define INVALID_FD (-1) typedef enum PipeEnd { READ_END = 0, WRITE_END = 1 } PipeEnd; typedef int Pipe[2]; /** Encapsulates information about a created child process. */ typedef struct popen2_t { bool success; ///< true if the child process was spawned. Pipe stdin; ///< parent -> stdin[WRITE_END] -> child's stdin Pipe stdout; ///< child -> stdout[WRITE_END] -> parent reads stdout[READ_END] Pipe stderr; ///< child -> stderr[WRITE_END] -> parent reads stderr[READ_END] pid_t pid; ///< child process' pid } popen2_t; /** dup2( p[pe] ) then close and invalidate both ends of p */ static void dupFd( Pipe p, const PipeEnd pe, const int fd ) { dup2( p[pe], fd); close( p[READ_END] ); close( p[WRITE_END] ); p[READ_END] = INVALID_FD; p[WRITE_END] = INVALID_FD; } popen2_t popen2( const char* cmd ) { popen2_t r = { false, { INVALID_FD, INVALID_FD } }; if ( -1 == pipe( r.stdin ) ) { goto end; } if ( -1 == pipe( r.stdout ) ) { goto end; } if ( -1 == pipe( r.stderr ) ) { goto end; } switch ( (r.pid = fork()) ) { case -1: // Error goto end; case 0: // Child process dupFd( r.stdin, READ_END, STDIN_FILENO ); dupFd( r.stdout, WRITE_END, STDOUT_FILENO ); dupFd( r.stderr, WRITE_END, STDERR_FILENO ); { char* argv[] = { "sh", "-c", (char*)cmd, NULL }; if ( -1 == execvp( argv[0], argv ) ) { exit(0); } } } // Parent process close( r.stdin[READ_END] ); r.stdin[READ_END] = INVALID_FD; close( r.stdout[WRITE_END] ); r.stdout[WRITE_END] = INVALID_FD; close( r.stderr[WRITE_END] ); r.stderr[WRITE_END] = INVALID_FD; r.success = true; end: if ( ! r.success ) { if ( INVALID_FD != r.stdin[READ_END] ) { close( r.stdin[READ_END] ); } if ( INVALID_FD != r.stdin[WRITE_END] ) { close( r.stdin[WRITE_END] ); } if ( INVALID_FD != r.stdout[READ_END] ) { close( r.stdout[READ_END] ); } if ( INVALID_FD != r.stdout[WRITE_END] ) { close( r.stdout[WRITE_END] ); } if ( INVALID_FD != r.stderr[READ_END] ) { close( r.stderr[READ_END] ); } if ( INVALID_FD != r.stderr[WRITE_END] ) { close( r.stderr[WRITE_END] ); } r.stdin[READ_END] = r.stdin[WRITE_END] = r.stdout[READ_END] = r.stdout[WRITE_END] = r.stderr[READ_END] = r.stderr[WRITE_END] = INVALID_FD; } return r; } int main( int argc, char* argv[] ) { popen2_t p = popen2( "./child.out" ); { int status = 0; sleep( 2 ); { char buf[1024] = { '' }; read( p.stdout[READ_END], buf, sizeof buf ); printf( "%s", buf ); } //pid_t wpid = waitpid( p.pid, &status, 0 ); //return wpid == p.pid && WIFEXITED( status ) ? WEXITSTATUS( status ) : -1; } }
// child.c #include <stdio.h> #include <unistd.h> int main( int argc, char* argv[] ) { printf( "%s:%dn", __FILE__, __LINE__ ); sleep( 1 ); printf( "%s:%dn", __FILE__, __LINE__ ); sleep( 1 ); printf( "%s:%dn", __FILE__, __LINE__ ); sleep( 1 ); printf( "%s:%dn", __FILE__, __LINE__ ); sleep( 1 ); return 0; }
Compilation and execution:
$ gcc --version && gcc -g ./child.c -o ./child.out && gcc -g ./main.c && ./a.out gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516 Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ./child.c:6 ./child.c:8 ./child.c:10 ./child.c:12 $
My question is about the read()
– I don’t quite grok why does the read()
is seemingly block until the child process has completed (thereby closing its end of the pipe)?
Is it coincidence? You can see I’ve tried to “make” the main process do its read in the middle of the child process’ execution with the sleep( 2 )
statement.
In total, the child process dumps 50 chars to its (redirected) stdout. Isn’t it possible that the main process might do its read()
in the middle of the child’s execution and read only N of 50 of those chars, and that therefore the main process’ printf() wouldn’t print all four lines from the child process in its entirety?
(Functionality-wise, everything is fine – my question is to better my understanding of read()
)
Advertisement
Answer
By default, stdout
is fully buffered when it’s not writing to a terminal. So nothing is being written to the pipe by your printf()
calls in the child until the buffer is flushed. This will happen when the buffer fills (probably 1K or 4K bytes) or the process exits.
You can flush the buffer immediately with fflush(stdout);
. Add that after each of your printf()
calls and you’ll be able to read them in the parent without waiting for the process to exit.