0
votes

Im quite new to C and am unsure how to proceed.

With this code I was attempting to create multiple child processes that would send their stdout to their parents stdin and have their stdin available to be written to with fdprintf of the pointer location in an array.

The code seems to not work, when executed with a basic program that reads stdin and prints to its stdout (which should be piped back). (In a different section of the main code I fprintf to where the pipe starts and then read stdin waiting for what should be written back).

int plumber(int *pipes[], int numChildren, char* command[]) {
    int i;
    char id;
    int nullSpace = open("/dev/null", O_WRONLY);

    for(i = 0; i < numChildren; ++i) {
        id = 'A' + i;
        pipe(pipes[2 * i]);
        pipe(pipes[2 * i + 1]);
        switch(fork()) {
            case (-1):
                fprintf(stderr, "Unable to start subprocess\n");
                exit(4);
                break;
            case 0:
                //child
                //close child's write, dupe its stdin to read
                //close childs old read
                close(pipes[2 * i][1]);
                if(dup2(pipes[2 * i][0], 0) == -1) {
                    fprintf(stderr, "Unable to start subprocess\n");
                    exit(4);
                }
                close(pipes[2 * i][0]);
                //close child's read, dupe its stdout to write
                //close childs old write
                close(pipes[2 * i + 1][0]);
                if(dup2(pipes[2 * i + 1][1], 1) == -1) {
                    fprintf(stderr, "Unable to start subprocess\n");
                    exit(4);
                }
                close(pipes[2 * i + 1][1]);
                close(1);
                //child stderr to nullspace
                if(dup2(nullSpace, 2) == -1) {
                    fprintf(stderr, "Unable to start subprocess\n");
                    exit(4);
                }
                close(2);
                execlp(command[i], "childprocess", numChildren, id, NULL);
                break;
            default:
                //parent
                //close read pipe from writing pipe
                close(pipes[2 * i][0]);
                //close write pipes and dupe stdin to read
                //close parents old read
                close(pipes[2 * i + 1][1]);
                if(dup2(pipes[2 * i + 1][0], 0) == -1) {
                    fprintf(stderr, "Unable to start subprocess\n");
                    exit(4);
                }
                close(pipes[2 * i + 1][0]);
        }
    }
    close(nullSpace);
    return 0;
}

The command is just to run the child process which also takes the number of children and an id from A to D. *pipes[] is numChildren*2 by 2 (so going along its child 1 read pipe, child1 write pipe, child2 read, child2 write etc. Please help and thanks in advance.

1

1 Answers

0
votes

The parent can have only one stdin. Each time the parent does:

dup2(pipes[2 * i + 1][0], 0)

it is closing its previous stdin and replacing it with the read end of the pipe. That means that all of the children, except the last, will have a stdout with a closed read end, which should cause them to receive a SIGPIPE (or an EPIPE error if SIGPIPE is ignored) if they attempt to generate any output. Also, the parent has now lost its original stdin, which may or may not be important.

You should also check the execlp didn't return an error. If it does, you will get very weird behavior, since the failed child will begin to spawn all of the remaining children concurrently with the parent.

If you really want all of the children's output to appear on a single stdin, then they must all use the same pipe. However, this will cause their output to become randomly intermixed, so it will be impossible for the parent to know who sent what. It would be better if the parent just kept the read end of each pipe in its original descriptor instead of trying to dup2 them to 0. In that way, the parent would retain its original stdin, and it could use select (or poll, or ...) to determine when it is getting input, and from which child.

It is generally a bad idea to connect a child's stdin and stdout to the same process (the parent) since it can easily lead to deadlock. The pipe can buffer only a limited amount of data, and will back pressure (i.e. block) the writer when full. If both the parent and child write a large amount of data into their stdouts, they will both be back pressured, which will prevent them from reading their inputs and draining the pipes. You must either make sure that the child consumes all of the parent's output before generating any output of its own, or find a way to guarantee that you can always read from stdin even if stdout is blocked. The latter can be accomplished by having separate threads deal with stdin and stdout, or by enabling non-blocking I/O and using select etc to determine when the pipe can be written.