UPDATE . So my original answer was out of the database. I did not quite understand what you wanted to do so far.
Your code is mostly right. This does not work for a very simple reason: shells assume that the command is executed when the process that started its completion.
Let me explain with an example. Imagine running ./mypipe a - b - c .
Here's what happens under the hood:
The shell starts the process to execute mypipe . This is when your program starts execution.
Your software forks, and the parent calls exec(a) . The child continues to analyze other arguments and process the creation of pipes as intended.
So now, at the moment, you have the parent program a running - which, in the eyes of the shell, is a process that matches the ./mypipe , and you have a child process that the shell completely neglects while doing mypipe work - setting up the pipes , reading the rest of the program to run, etc.
So now you have a race condition. Since the process behind mypipe been replaced by program a , as soon as a ends, the shell assumes that the command you typed is executed (i.e. assumes that mypipe is mypipe ), and prints a prompt, expecting you to type the following a team.
Problem: a terminates quickly, and your child process still loops through the list of other programs and tunes channels, redirects input and output and all that. So, for example, a child process can still work on creating channels and analyzing other programs, and by then a had already finished and wrote everything in stdout - Oops!
How do you fix this? Simple: invert the order of the command. Start by executing the last command, then the second to the last, etc. Why? Because if you do this, the parent will be the last in the pipeline, so it blocks waiting for input to enter the channel's read channel. When the parent ends, it means that the last command in the pipeline is completed, what exactly we want. There are no conditions for the race, and the shell is not deceived, thinking that our team is executed when it really is not.
So, it’s all about parsing lists of programs from right to left, and not from left to right. Also, if you do, you will no longer need the args helper array!
Here the code is checked and works:
#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; }