6 Final program - Aryalexa/42-pipex GitHub Wiki

Now that we know how processes work, how to create pipes, and to redirect inputs and outputs... We can create a program that replicates the behavior of doing < infile cmd1 | cmd2 > outfile in bash. Or even < infile cmd1 | ... | cmdn > outfile.

Our program pipex will receive al least 4 arguments where the first one will be the input file it will read from, and the last one will be the output file that will contain the final outputs.

./pipex infile cmd1 cmd2 cmd3 outfile

This is what our program will do step by step:

// check arguments received
check_args(argc, argv);

// fetch the files and the commands
char *infile = get_infile(argc, argv);
char *outfile = get_outfile(argc, argv);
char **cmds = get_commands(argc, argv);

// open the input file - handle possible errors (existence, permissions, ...)
int fdin = open(infile, ...)
dup2(fdin, 0); // set read from fdin by default

// execute the first command: cmds[0]
my_pipex(cmds[0], env);

// while there are more commands, execute the command i: cmds[i], except the last one
...

// for the last execution we want to link 1 to the output file before the execution.
// this execution reads from the last pipe and writes to the output file.
int fdout = open(outfile, ...) // create it if it does not exist already
dup2(fdout, 1);
my_exec(cmds[i], env);

my_pipex(cmd, env) executes a program using a pipe. The command will read from an already set up input, writes in a new pipe, and sets the stdin_filno (0) as the reading part of the pipe for the next command to use.

// the execute should be done in a child process
// we need to create a pipe so the output of the command is redirected to the pipe, 
// and the parent can have access to it
// the parent should set the read from the pipe by default, so the next execution reads from it.
int pfd[2];
if (pipe(pfd) == -1) // pipe error
  exit();
int pid = fork();
if (pid == -1) // fork error
  exit();
if (pid == 0) {
  close(pfd[0]); // close reading end from pipe
  dup2(pfd[1], 1); // set write on pipe
  close(pfd[1]);
  my_exec(cmd, env);
} else {
  close(pfd[1]);
  dup2(pfd[0], 0); // set read from pipe
  close(pfd[0]);
  wait(NULL); // wait for child to terminate, so it is finished before more commands
}

my_exec should use execve so we need to do some prepping

char **cmd_args = split(cmd, " "); // split the command into the name and arguments
char *cmd_path = get_path(cmd_args[0], env); // using the env received from main. 
// get_path uses the "PATH=path1:path2:..." variable in env. 
// It searches the path where the command is (path1/cmd? path2/cmd? ...) and returns it.
cmd_args[0] = cmd_path; // save the path as the first argument instead of only the name
execve(cmd_path, cmd_args, env);
perror(); // exec error

All this is just a way to understand what is happening and there is room for code optimization and error handling, of course. :)