6

I am to implement a nameless pipe, and I must execute the command in the parent process, not in any of his child. every "-" equals a call for a pipeline ("|"), also part of the assignment I have this code. can someone explain to me why it doesn't work?

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h> // for open flags
#include <time.h> // for time measurement
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

void my_exec(char* cmd, char** argv)
{
    int pipefd[2], f;

    if (pipe(pipefd) < 0)
        perror("pipe creation failed");

    f = fork();
    assert(f >= 0);

    if (f == 0) {
        // inside son process - connecting STDOUT to pipe write
      if (dup2(pipefd[1], STDOUT_FILENO) < 0)
            perror("dup2 failed");

        close(pipefd[0]);
        close((int)stdout);


    } else {
        // inside parent process - connecting STDIN to pipe read  and execute command with args
      if (dup2(pipefd[0], STDIN_FILENO) < 0)
            perror("dup2 failed");

        close(pipefd[1]);
       close((int)stdin);



if (execvp(cmd, argv) < 0)
                perror("execvp failed");
        }

}

int main(int argc, char** argv)
{
    assert(strcmp(argv[argc-1], "-"));

    int i;
    for (i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "-")) {
            argv[i] = NULL;
            my_exec(argv[1], &argv[1]);
            argv = &argv[i];
            argc -= i;
            i = 0;
        }
    }

    char* args[argc];
    args[argc-1] = NULL;

    for (i = 1; i < argc; ++i) {
        args[i-1] = argv[i];
    }

    if (execvp(args[0], args) == -1)
        perror("execvp failed");
    return;
}

the command :

./mypipe.o ls -l - grep "pipe"

returns

total 24
-rw-rw-r-- 1 omer omer 1463 May 23 19:38 mypipe.c
-rwxrwxr-x 1 omer omer 7563 May 23 19:37 mypipe.o
-rw-rw-rw- 1 omer omer  873 May 23 20:01 nice.c
-rwxrwxr-x 1 omer omer 7417 May 23 19:44 nice.o
-rw-rw-r-- 1 omer omer    0 May 23 17:10 try

which obviouslly means the pipe didnt work... any ideas?

I need to make sure that Each call to np_exec starts a single child process that continues parsing the rest of the arguments, while the original parent process executes the given program and arguments (using execvp),

EDIT: i think i found the mistake: i switch the "read- write " ends of the pipe.

the correct function:

void np_exec(char* cmd, char** argv)
{
    int pipefd[2];
    int file;

    if (pipe(pipefd) < 0)
        perror("failed to create pipe");

    file = fork();
    assert(file >= 0);

    if (file != 0) {
        // inside parent process - connecting STDOUT to pipe write and execute command with args
      if (dup2(pipefd[WRITE], STDOUT_FILENO) < 0)
            perror("the function dup2 failed");

        close(pipefd[READ]);
        close((int)stdout);

        if (execvp(cmd, argv) < 0)
            perror("the function execvp failed");

    } else {
        // inside son process - connecting STDIN to pipe read
      if (dup2(pipefd[READ], STDIN_FILENO) < 0)
            perror("the function dup2 failed");

        close(pipefd[WRITE]);
       close((int)stdin);
    }

}
mike
  • 767
  • 2
  • 10
  • 18
  • Wait, which command do you want to execute in the parent, and which do you want to execute in the child? – Filipe Gonçalves May 23 '15 at 17:37
  • i need to make sure that Each call to np_exec starts a single child process that continues parsing the rest of the arguments, while the original parent process executes the given program and arguments (using execvp), – mike May 23 '15 at 17:40
  • "while the original parent process executes the given program and arguments (using execvp" I don't think that makes sense: if the parent process calls execvp, it will be replaced by whatever execvp end up calling. – Frederik Deweerdt May 23 '15 at 17:44
  • yes, the but i want the child to keep going to the next arguments, while the parent execute the current command. (and please correct me if i'm wrong, after the parent uses execvp, the process ends?) – mike May 23 '15 at 17:59

1 Answers1

5

UPDATE: So, turns out my original answer was way off base. I didn't fully understand what you wanted to do until now.

Your code is mostly right. It doesn't work for a very simple reason: shells assume that a command is done when the process that started it finishes.

Allow me to explain with an example. Imagine you run ./mypipe a - b - c.

Here's what happens under the hood:

  • The shell forks a process to execute mypipe. This is when your program starts execution.

  • Your program forks, and the parent calls exec(a). The child keeps parsing the other arguments and handling pipe creation, as it is supposed to.

So, now, at this point, you have the parent running program a - which, to the eyes of the shell, is the process that corresponds to the command ./mypipe, and you have a child process, which the shell completely neglects, doing the job of mypipe - setting up pipes, reading the rest of the programs to execute, etc.

So, now you have a race condition. Because the process behind mypipe has been replaced by the program a, as soon as a terminates, the shell assumes that the command you typed is done (that is, it assumes mypipe is done), and prints the prompt, expecting you to type the next command.

The problem: a terminates quickly, and your child process is still going over the other programs list and setting up pipes, redirecting input and output, and all that. So, for example, the child process may still be working on creating pipes and parsing the other programs, while by that time a has already finished and written everything to stdout - Oops!

How do you fix this? Simple: invert the order of command execution. Start by executing the last command, then the second to last, etc. Why? Because if you do that, the parent will be the last on the pipeline, so it blocks waiting for input to arrive in the pipe read channel. When the parent terminates, it means the last command in the pipeline has terminated, which is exactly what we want. There are no race conditions, and the shell isn't tricked into thinking our command is done when it actually isn't.

So, it's all a matter of parsing the programs list from right to left instead of left to right. Plus, if you do it, you won't need the auxiliary args array anymore, which is great!

Here's the code - tested and working:

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h> // for open flags
#include <time.h> // for time measurement
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

void my_exec(char* cmd, char** argv)
{
    int pipefd[2], f;

    if (pipe(pipefd) < 0)
        perror("pipe creation failed");

    f = fork();
    assert(f >= 0);

    if (f == 0) {
        if (dup2(pipefd[1], STDOUT_FILENO) < 0)
            perror("dup2 failed");

        close(pipefd[0]);

    } else {
        if (dup2(pipefd[0], STDIN_FILENO) < 0)
            perror("dup2 failed");

        close(pipefd[1]);

        if (execvp(cmd, argv) < 0)
            perror("execvp failed");
        }
}

int main(int argc, char** argv)
{
    assert(strcmp(argv[argc-1], "-"));

    int i;
    for (i = argc-1; i >= 1; i--) {
        if (!strcmp(argv[i], "-")) {
            argv[i] = NULL;
            my_exec(argv[i+1], &argv[i+1]);
            argc = i;
        }
    }

    if (execvp(argv[0], &argv[1]) == -1)
        perror("execvp failed");

    return 0;
}
Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • thank you for answering. it does make more sense to put the command exec in the child. however, in this assigment i am forced to use execvp() inside the parent. – mike May 23 '15 at 18:13
  • @mike You can't always `exec()` on the parent - are you sure you understood the assignment requirements correctly? Pipelines are inherently designed to be executed by a group of cooperating forked processes. In a pipe with N commands, you will always have N-1 forked processes executing the other commands and the last command being executed by the original parent. That's how bash and other shells do it. The exact implementation details of which process executes what may vary, but that's the general idea. – Filipe Gonçalves May 23 '15 at 18:16
  • i still want n-1 forked processes. but in this assigment, just so life wont be too easy, the child and the parent needs to "swtich places". the child will continue the loop, while the parent will exectute the given command – mike May 23 '15 at 18:22
  • @mike Hah, I get it! That's neat, what a great exercise. You're almost there -- I'm updating my answer. – Filipe Gonçalves May 23 '15 at 19:35
  • i think i got it as well. i am editing the answer (: – mike May 23 '15 at 19:47
  • @mike See my updated answer, I just finished testing the code. – Filipe Gonçalves May 23 '15 at 19:48
  • i believe we have acquired the same answer my friend (: – mike May 23 '15 at 19:51
  • just for the sport - could you explain me exacly what the lines (dup2(pipefd[0], STDIN_FILENO) and (dup2(pipefd[WRITE], STDOUT_FILENO) means? i dont understand what dup2 does good enough – mike May 23 '15 at 19:51
  • @mike `dup2()` duplicates a file descriptor into another, closing the other if necessary. In that case, it duplicates `pipefd[0]`, the read channel, to the process' standard input - the result is that reading from stdin is now equivalent to reading from the pipe. A similar thing happens with `STDOUT_FILENO` and `pipefd[1]`. – Filipe Gonçalves May 23 '15 at 19:53
  • isnt stdin and stdout are the file discreptions of the console? doesnt that mean that the pipe write it down the console each time? – mike May 23 '15 at 19:55
  • @mike In general, stdin and stdout are tied to the controlling terminal, but they are just file descriptors, there's nothing special about them. You can redirect them to any other file - be it a disk file, a socket, a pipe, you name it. One of the ways to perform this redirection to another file is with `dup2()`. In this case, `dup2()` is saying: "now, stdin doesn't read from the terminal anymore. It reads from the pipe". It's that simple, really. I hope you get it now :) – Filipe Gonçalves May 23 '15 at 20:01
  • @mike In general, you can look at `dup2(X, Y)` to be the same as *From now on, reading from Y is the same as reading from X*. – Filipe Gonçalves May 23 '15 at 20:03