c - spawned a bash shell. Shell died but pipe not broken?
<h1>Problem</h1>

<p>I'm trying to pipe contents from the main routine to a execvp'd bash shell. I'm encountering a problem where when I write "exit" into the subshell, it doesn't tell me that the pipe is really broken. It should be though - right? The process died and thus the pipe fd should also return an EOF or a SIGPIPE. It doesn't, however, and just keeps on reading/writing like normal. </p>

<h1>Code</h1>

<p>The code is attached here:</p>

<pre class="lang-c prettyprint-override"><code>/************************************************************
 * Includes:
 * ioctl - useless(?)
 * termios, tcsetattr, tcgetattr - are for setting the
 *      noncanonical, character-at-a-time terminal.
 * fork, exec - creating the child process for part 2.
 * pthread, pipe - creating the pipe process to communicate
 *      with the child shell.
 * kill - to exit the process
 * atexit - does some cleanups. Used in termios, tcsetattr,
 *      tcgetattr.
 ************************************************************/
#include &lt;sys/ioctl.h&gt; // ioctl
#include &lt;termios.h&gt; // termios, tcsetattr, tcgetattr
#include &lt;unistd.h&gt; // fork, exec, pipe
#include &lt;sys/wait.h&gt; // waitpid
#include &lt;pthread.h&gt; // pthread
#include &lt;signal.h&gt; // kill
#include &lt;stdlib.h&gt; // atexit
#include &lt;stdio.h&gt; // fprintf and other utility functions
#include &lt;getopt.h&gt; // getopt
/**********************
 * GLOBALS
 **********************/
pid_t pid;

/**********************
 * CONSTANTS
 **********************/
static const int BUFFER_SIZE = 16;
static const int STDIN_FD = 0;
static const int STDOUT_FD = 1;
static const int STDERR_FD = 2;

// these attributes are reverted to later
struct termios saved_attributes;
// to revert the saved attributes
void
reset_input_mode (void) {
    tcsetattr (STDIN_FILENO, TCSANOW, &amp;saved_attributes);
}

// to set the input mode to correct non-canonical mode.
void
set_input_mode (void) {
    struct termios tattr;

    /* Make sure stdin is a terminal. */
    if (!isatty (STDIN_FILENO))
    {
        fprintf (stderr, "Not a terminal.\n");
        exit (EXIT_FAILURE);
    }

    /* Save the terminal attributes so we can restore them later. */
    tcgetattr (STDIN_FILENO, &amp;saved_attributes);
    atexit (reset_input_mode);

    /* Set the funny terminal modes. */
    tcgetattr (STDIN_FILENO, &amp;tattr);
    tattr.c_lflag &amp;= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
    tattr.c_cc[VMIN] = 1;
    tattr.c_cc[VTIME] = 0;
    tcsetattr (STDIN_FILENO, TCSAFLUSH, &amp;tattr);
}

// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout). 
// It then prints out the contents.
void* thread_read(void* arg){
    int* pipe_fd = ((int *) arg);
    int read_fd = pipe_fd[0];
    int write_fd = pipe_fd[1];
    char c;
    while(1){
       int bytes_read = read(read_fd, &amp;c, 1);
       if(bytes_read &gt; 0){
            putchar(c);
       }
       else{
            close(read_fd);
            close(write_fd);
            fprintf(stdout, "The read broke.");
            fflush(stdout);
            break;
       }
    }
}

// pthread 2 will write to child_pipe_fd[1], which
// is really the child's stdin.
// but in addition to writing to child_pipe_fd[1],
// we must also print to stdout what our 
// argument was into the terminal. (so pthread 2
// does extra).
void* thread_write(void* arg){
    set_input_mode();
    int* pipe_args = ((int *) arg);
    int child_read_fd = pipe_args[0];
    int child_write_fd = pipe_args[1];
    int parent_read_fd = pipe_args[2];
    int parent_write_fd = pipe_args[3];
    char c;
    while(1) {
        int bytes_read = read(STDIN_FD, &amp;c, 1);
        write(child_write_fd, &amp;c, bytes_read);
        putchar(c);
        if(c == 0x04){
            // If an EOF has been detected, then 
            // we need to close the pipes.
            close(child_write_fd);
            close(child_read_fd);
            close(parent_write_fd);
            close(parent_read_fd);
            kill(pid, SIGHUP);
            break;
        }
    }
}

int main(int argc, char* argv[]) {
    /***************************
     * Getopt process here for --shell
     **************************/
    int child_pipe_fd[2];
    int parent_pipe_fd[2];
    pipe(child_pipe_fd);
    pipe(parent_pipe_fd);

    // We need to spawn a subshell.
    pid = fork();
    if(pid &lt; 0){
        perror("Forking was unsuccessful. Exiting");
        exit(EXIT_FAILURE);
    }
    else if(pid == 0){ // is the child. 
        // We dup the fd and close the pipe.

        close(0); // close stdin. child's pipe should read.
        dup(child_pipe_fd[0]); // pipe_fd[0] is the read. Make read the stdin.
        close(child_pipe_fd[0]);

        close(1); // close stdout
        dup(parent_pipe_fd[1]); // pipe_fd[1] is the write. Make write the stdout.
        close(parent_pipe_fd[1]);

        char* BASH[] = {"/bin/bash", NULL};
        execvp(BASH[0], BASH);
    }
    else{ // is the parent
        // We dup the fd and close the pipe.
        //
        // create 2 pthreads.
        // pthread 1 will read from pipe_fd[0], which
        // is really the child's pipe_fd[1](stdout). 
        // It then prints out the contents.
        //
        // pthread 2 will write to pipe_fd[1], which
        // is really the child's pipe_fd[0](stdin)
        // but in addition to writing to pipe_fd[1],
        // we must also print to stdout what our 
        // argument was into the terminal. (so pthread 2
        // does extra).
        //
        // We also need to take care of signal handling:
        signal(SIGINT, sigint_handler);
        /*signal(SIGPIPE, sigpipe_handler);*/
        int write_args[] = {child_pipe_fd[0], child_pipe_fd[1],
                             parent_pipe_fd[0], parent_pipe_fd[1]};

        pthread_t t[2];
        pthread_create(t, NULL, thread_read, parent_pipe_fd);
        pthread_create(t+1, NULL, thread_write, write_args);

        pthread_join(t[0], NULL);
        pthread_join(t[1], NULL);

        int status;
        if (waitpid(pid, &amp;status, 0) == -1) {
            perror("Waiting for child failed.");
            exit(EXIT_FAILURE);
        }

        printf("Subshell exited with the error code %d", status);
        exit(0);
    }    

    return 0;
}
</code></pre>



<p>The program basically pipes inputs from the terminal into the subshell and tries to execute them and return the outputs. To write to the pipe, I have a pthread that writes the stdin inputs into the subshell. To read to the pipe, I have a pthread that reads the pipe to the parent. To detect the broken pipe via the subshell dying(calling exit), I detect the EOF character from the read thread.</p>

<h1>My attempts</h1>

<p>I added a check for the 0x04 character(EOF), I checked for <code>read_bytes == 0</code> or <code>read_bytes &lt; 0</code>. It seems that it never gets the memo unless I explicitly close the pipes on the writing end. It only meets the EOF character if I send the character ^D(which, in my code, handles via closing all pipes of the child &amp; parent).</p>

<p>Any comments would be appreciated! Thank you.</p>