Cloud Stack Ninja

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] = { '\0' };

      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:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  printf( "%s:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  printf( "%s:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  printf( "%s:%d\n", __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())



Read more here: https://stackoverflow.com/questions/64164339/why-does-read-block-on-a-pipe-until-the-write-end-is-closed

Content Attribution

This content was originally published by StoneThrow at Recent Questions - Stack Overflow, and is syndicated here via their RSS feed. You can read the original post over there.

%d bloggers like this: