Pipe, Fork, and Exec - Two Way Communication Between Parent and Child Process

15,622

Solution 1

Besides the issue mentioned by Jonathon Reinhart, most probably the call to execv() fails.

To test this modify these lines

execvp("two_way_pipes", argv2);
_exit(0);

to be

...
#include <errno.h>
...


execvp("two_way_pipes", argv2); /* On sucess exec*() functions never return. */
perror("execvp() failed); /* Getting here means execvp() failed. */
_exit(errno);

Expect to receive

execvp() failed: No such file or directory

To fix this change

execvp("two_way_pipes", argv2);

to be

execvp("./two_way_pipes", argv2);

Also if the child was not exec*()ed then this line

read(PARENT_READ, buff, 4); // should read "test" which was written by the child to stdout

fails and in turn buff is not initialised and therefore this line

fprintf(stderr, "in parent | message received: %s\n", buff);  

provokes undefined behaviour.

To fix this at least properly initialise buff by changing

char buff[5];

to be

char buff[5] = "";

Solution 2

The first entry in your argv2 should be the name of the executable (just like your incoming argv[0].

char *argv2[] = {"two_way_pipes", "some random arg...", NULL};
execvp("two_way_pipes", argv2);

From the execvp man page:

The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer.

Solution 3

As Jonathon Reinhart says you should change this lines:

char *argv2[] = {"some random arg to make sure that argc == 2 in the child", NULL};
execvp("two_way_pipes", argv2);

to:

char *argv2[] = {argv[0], "some random arg...", NULL};
execvp(argv[0], argv2);

then it works as expected:

echo test | ./two_way_pipes
in parent | message received: test

In your program you wrote "two_way_pipes" but it is not in your PATH so you really need the extra ./ so argv[0] then is ("./two_way_pipes").

Share:
15,622
Anthony Jack
Author by

Anthony Jack

Updated on June 04, 2022

Comments

  • Anthony Jack
    Anthony Jack almost 2 years

    An assignment in my Operating Systems class requires me to build a binary process tree by recursively calling exec on the same program. The goal is to split some arbitrary task into separate processes. The parent should communicate with the children, and the children with the parent only via unnamed pipes. The idea is that the parent sends each child half of the work and this continues recursively until a base case is met where the length of the string being passed to each child is <= 2. The child then processes this data and sends the results back to the parent via pipes.

    To get a better understanding of how two way communication works with pipes in c I created the following simple program before moving on to the actual assignment. The parent never reads the data from the child process though. I'm expecting the output...

    in parent | message received: test

    Instead, when I print I get...

    in parent | message received:

    It seems that buff is empty and not reading from the child process. Can someone please explain what I'm doing wrong and/or the standard way of

    1. writing to exec'd child from parent
    2. reading from parent in exec'd child
    3. writing back to parent from exec'd child
    4. reading from exec'd child in parent

    I am required to use exec(), pipe(), fork(). Thank you.

    /**
     * *********************************
     * two_way_pipes.c
     * *********************************
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <math.h>
    #include <sys/time.h> 
    #include <sys/types.h> 
    #include <unistd.h>
    
    #define PARENT_READ read_pipe[0]
    #define PARENT_WRITE write_pipe[1]
    #define CHILD_WRITE read_pipe[1]
    #define CHILD_READ  write_pipe[0]
    
    #define DEBUGGING 1
    
    int main(int argc, char **argv) {
        char buff[5];
    
        // in the child process that was exec'd on the orginal call to two_way_pipes
        if(argc == 2) {
            read(STDIN_FILENO, buff, 4); // this should read "test" from stdin
            buff[4] = '\0';
            fprintf(stdout, "%s\n", buff); // this should right "test" to stdout and be read by the parent process
        // int the root process, the original call to two_way_pipes with no args
        } else {
            int pid;
            int read_pipe[2];
            int write_pipe[2];
    
            pipe(read_pipe);
            pipe(write_pipe);
    
            pid = fork();
    
            // parent process
            if(pid > 0) {
                close(CHILD_READ);
                close(CHILD_WRITE);
    
                write(PARENT_WRITE, "test", 4); // attempting to write this to the child
    
                struct timeval tv;
                fd_set readfds;
                tv.tv_sec = 10;
                tv.tv_usec = 0;
                FD_ZERO(&readfds);
                FD_SET(PARENT_READ, &readfds);
                select(PARENT_READ + 1, &readfds, NULL, NULL, &tv);
    
                if(FD_ISSET(PARENT_READ, &readfds)) {
                    read(PARENT_READ, buff, 4); // should read "test" which was written by the child to stdout
                    buff[4] = '\0';
                    close(PARENT_READ);
                    close(PARENT_WRITE);
                    fprintf(stderr, "in parent | message received: %s\n", buff);  // "test" is not in buff
                }
    
            // child process
            } else if(pid == 0) {
                close(PARENT_READ);
                close(PARENT_WRITE);
    
                dup2(CHILD_READ, STDIN_FILENO);
                dup2(CHILD_WRITE, STDOUT_FILENO);
                close(CHILD_READ);
                close(CHILD_WRITE);
    
                char *argv2[] = {"some random arg to make sure that argc == 2 in the child", NULL};
                execvp("two_way_pipes", argv2);
                _exit(0);
            // error forking child process
            } else {
                fprintf(stderr, "error forking the child\n");
            }
        }
    }
    

    Update

    Based on Jonathon's answer I modified the arg2 array being passed into execvp to...

    char *argv2[] = {"two_way_pipes", "1", NULL};
    execvp("two_way_pipes", argv2);
    

    This didn't fix the issue. The parent still wasn't able to read "test" back from the client. However, in response to Jonathon's answer and William's comment I started tweaking my exec call and for some reason changing it to the line show below worked.

    execl("two_way_pipes", "two_way_pipes", "1", NULL);
    

    I'll gladly accept any answers explaining why the execvp call wouldn't work but the execl call did.

  • Anthony Jack
    Anthony Jack over 10 years
    Thanks Jonathan. I modified argv2 based on your answer but the parent still isn't getting the data from the exec'd child.
  • Seng Cheong
    Seng Cheong over 10 years
    It's really not a big deal, as it happens all the time - But it's impressive how often people will misspell someone's name - even when it is a couple hundred pixels away on the same page.
  • Anthony Jack
    Anthony Jack over 10 years
    Sorry about that. I suspect that if I were named Anthany people would often misspell it too :-)