Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using pipe while executing command through the parent

Tags:

c

linux

pipe

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);
    }

}
like image 491
mike Avatar asked May 23 '15 17:05

mike


People also ask

Which pipe allows communication between parent and child processes?

Here is a simple Python program to demonstrate communication between the parent process and child process using the pipe method. pipe() System call : The method pipe() creates a pipe and returns a pair of file descriptors (r, w) usable for reading and writing, respectively.

Can you use pipe in shell script?

Pipe may be the most useful tool in your shell scripting toolbox. It is one of the most used, but also, one of the most misunderstood. As a result, it is often overused or misused. This should help you use a pipe correctly and hopefully make your shell scripts much faster and more efficient.

How do you pipe commands together?

You can make it do so by using the pipe character '|'. Pipe is used to combine two or more commands, and in this, the output of one command acts as input to another command, and this command's output may act as input to the next command and so on.


1 Answers

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;
}
like image 110
Filipe Gonçalves Avatar answered Oct 05 '22 14:10

Filipe Gonçalves