/*
Program that demonstrates how two processes can communicate using a 
UNIX pipe.

Overview:

    This process (the parent) calls pipe() to create a pipe for 
    communication.

    This process calls fork() creating a second process.

    The new process connects its standard output to the pipe,
    then it calls execl("ls") which transforms that process into one
    that is running ls.

    After the fork, the parent connects its input to the pipe,
    then it calls execl("sort") which transforms this process (the
    parent) into one that is running sort.

    Since the process running ls is writing to the pipe
    -and-
    the process running sort is reading from the pipe

        the net result is like executing the following from a shell:

            ls | sort

Details:

    The arguments to execl are all strings (null-terminated).  They are:

        command
        argument 0 (which should be the command itself)
        argument 1
        ...
        argument n
        0

    In other words, you pass a null-terminated list of arguments 
    to execl().

    execlp() is just like execl() but it looks in all the directories
    in PATH for the given command
    NOTE: for the shell assignment you may not use execlp()

    dup2(fd1, fd2) causes the filedescriptor fd2 to refer to the same
    file as fd1.  It is used below to redefine the standard input and
    standard output of the processes so the second (sort) reads it input
    from the output of the first (ls).

    Remember to close all file descriptors that you don't need because 
    there are some limits on open files.

    When a process is forked, it is identicle to the parent process, 
    except that the call to fork() returns 0.  This means that the 
    pipe created by the parent, still exists for the new process.

    When a process calls execl(), almost everything about the process
    goes away and is replaced by the command pass to execl(), with
    one important exception, all open file handlers are not replace.
    That is why we can redefine stdin and stdout before a call to
    execl() and have the "new" process use our redefined i/o.

    NOTE: since the parent process performs an exec() call, it never 
    calls wait for its child process.  I admit this is sloppy programming
    but it makes the program a little shorter.

*/

#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;

int
main()
{
    int file_descriptor[2];

    pid_t child_pid;

    // create a pipe for the first command to send output to the second cmd
    //
    // pipe() creates a pair of file descriptors, pointing to a pipe inode,
    // and places them in the array pointed to by the argument.
    // The first is for reading (file_descriptor[0] in this example).
    // The second is for writing (file_desriptor[1] in this example).
    if (pipe(file_descriptor) != 0)
    {
        perror("calling pipe()");
        exit(1);
    }

    // fork a child process to run "ls"
    if ((child_pid = fork()) == 0)
    {
        cout << "hello from child processes" << endl;

        // make standard output "point to" the pipe's writing descriptor
        if (dup2(file_descriptor[1], STDOUT_FILENO) == -1)
        {
            perror("child call to dup2()");
            exit(1);
        }
        // this process does not need the pipe's reading descriptor
        if (close(file_descriptor[0]) == -1)
        {
            perror("child close() on file_descriptor[0]");
            exit(1);
        }
        // since standard output "points to" this file descriptor (dup2() above)
        // we don't need this copy anymore
        if (close(file_descriptor[1]) == -1)
        {
            perror("child close() on file_descriptor[1]");
            exit(1);
        }

        // make this process run ls with the given command line args
        // NOTE: you must use execl() or execv() for the shell assignment
        // you may not use execlp() or execvp()
        if (execlp("ls", "ls", "-l", 0) == -1)
        {
            perror("child execlp(\"ls\", ...) failed");
            exit(1);
        }
    }
    else
    {
        cout << "hello from parent after fork()" << endl;
        // make standard input "point to" the pipe's reading descriptor
        if (dup2(file_descriptor[0], STDIN_FILENO) == -1)
        {
            perror("parent call to dup2()");
            exit(1);
        }

        // since standard input "points to" this file descriptor (dup2() above)
        // we don't need this copy anymore
        if (close(file_descriptor[0]) == -1)
        {
            perror("parent close() on file_descriptor[0]");
            exit(1);
        }

        // this process does not need the pipe's writing descriptor
        if (close(file_descriptor[1]) == -1)
        {
            perror("parent close() on file_descriptor[1]");
            exit(1);
        }

        // make this process run sort with the given command line args
        // NOTE: you must use execl() or execv() for the shell assignment
        // you may not use execlp() or execvp()
        if (execlp("sort", "sort", "-r", "-k 4", 0))
        {
            perror("parent execlp(\"sort\", ...) failed");
            exit(1);
        }
    }
}

