-1

I don't know what is wrong in this code. There are multiple errors with its output. This program is supposed to imitate a UNIX shell. It can run any command fine as long as it does not contain any pipes. However, when I include pipes, funny things start to occur.

For example: When I type in sort < myshell1,c | grep main | cat > o.txt

it creates an extra process. You can see this because in the code, perror(in) gets executed 4 times (as per GDB):

COP4338$ sort < myshell1.c | grep main | cat > o.txt
Detaching after fork from child process 16465.
Process ID: 16465
Process ID: 0
in: Success
Detaching after fork from child process 16466.
Process ID: 16466
Process ID: 0
in: Success
Something happened in i != numcommands - 1: Success
Detaching after fork from child process 16468.
Process ID: 16468
COP4338$ Process ID: 0
in: Success
ELSE STATEMENT!
Detaching after fork from child process 17403.
in: Bad address

And then, the program goes to a new line and doesnt print COP4338$: like it should. Undefined behavior is exhibited. My guess as to why this is happening is because that 4th process is also continuing and thus heading back to main(), just like the parent, but I can't pinpoint why it is even being created.

Undefined behavior is also exhibited when I try to run ls -l | cat > o.txt

This is what GDB outputs:

As far as trying to fix the issue, I have yet to know for sure what is causing this. I have tried to use gdb debugger to debug the 3rd child process by typing "set follow-fork-mode child" and using a breakpoint at the 1st line of the 3rd child process and nothing happens. I can only debug the 1st child process by doing this.

/* This code  was written by Dr. Raju Rangaswami and was expanded upon as per the instructions in Assignment3, by Michael Duboc(PID: 5706538)*/

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ARGS 20
#define BUFSIZ 1024


int get_args(char* cmdline, char* args[]) 
{
  int i = 0;

  /* if no args */
  if((args[0] = strtok(cmdline, "\n\t ")) == NULL) 
    return 0; 

  while((args[++i] = strtok(NULL, "\n\t ")) != NULL) 
  {
    if(i >= MAX_ARGS) 
    {
      printf("Too many arguments!\n");
      exit(1);
    }
  }
  /* the last one is always NULL */
  return i;
}




int customStrCpy(char** line1, char* line2)
{
    int strlen1 = strlen(*line1), strlen2 = strlen(line2);
    if(strlen1 < strlen2)
    {
        //Creates a dynamically allocated array that is big enough to store the contents of line2.
        *line1 = calloc(strlen2, sizeof(char));

    }
    strcpy(*line1, line2);



}




void adjustArray(char* args[], int args_itr, int* nargs)
{

    int i, j;

    for(i = 0; i < 2; i++)
    {

        for(j = args_itr; j < (*nargs) - 1; j++)
        {
            customStrCpy(&args[j],args[j+1]);
        }
        args[(*nargs) - 1] = 0;
        (*nargs)--;
        }

} 

int process(int* greaterthan, int* d_greaterthan, char* args_pipe[], int* 
nargs_p, int* fileno_out, int* fileno_in, int* lessthan, FILE** fout, 
FILE** fin)
{
    int greaterthan_strcmp = strcmp(args_pipe[args_itr], ">");
    int d_greaterthan_strcmp = strcmp(args_pipe[args_itr], ">>");
            if(greaterthan_strcmp == 0 || d_greaterthan_strcmp  == 0)
            {
                    if(greaterthan_strcmp == 0)
                            *fout = fopen(args_pipe[args_itr + 1], "w");
                    else
                            *fout = fopen(args_pipe[args_itr + 1], "a");
                    *fileno_out = fileno(*fout);

                    *greaterthan = 1;
                    adjustArray(args_pipe, args_itr, nargs_p);
                    args_itr--;
                    int print_arr;
                    for(print_arr = 0; print_arr 
                      (sizeof(args_pipe)/sizeof(args_pipe[0])); print_arr++) 
                      printf("%s ",args_pipe[print_arr]);
                    printf("\n");
            }





         else if(strcmp(args_pipe[args_itr], "<") == 0)
         {


            *fin = fopen(args_pipe[args_itr + 1], "r");
            *fileno_in = fileno(*fin);

            *lessthan = 1;
            adjustArray(args_pipe, args_itr, nargs_p);
            args_itr--;

        }

    }


    return 0;


}




void execute(char* cmdline) 
{
  int pid, async, lessthan = 0;
  int greaterthan = 0, pipef = 0, d_greaterthan = 0;
  int args_itr, pipe_flag = 0;
  int flag_count = 0, fileno_in, fileno_out;
  char* args_pipe[MAX_ARGS];/*5 and 3 are test numbers.*/
  char* args[MAX_ARGS];


  int nargs = get_args(cmdline, args);
  if(nargs <= 0) return;

  if(!strcmp(args[0], "quit") || !strcmp(args[0], "exit")) 
  {

    exit(0);
  }

  /* check if async call */
  if(!strcmp(args[nargs-1], "&")) { async = 1; args[--nargs] = 0; }
  else async = 0;
  FILE* fout = stdout;
  FILE* fin = stdin; 
  for(args_itr = 0; args_itr < nargs; args_itr++)
  {
    if(!strcmp(args[args_itr], "|")) {pipe_flag = 1; flag_count++;}
    }



  if(pipe_flag)
  {
    int num_commands = flag_count + 1, i = 0, j = 0;
    int fd[num_commands][2];


    for(i = 0; i < flag_count; i++) {pipe(fd[i]);}

    for(i = 0; i < num_commands; i++)
    {


        int nargs_p = 0, args_pipe_itr = 0;
        while(j < nargs && strcmp(args[j], "|")) 
        {//Possibly make into for loop.
            args_pipe[args_pipe_itr] = args[j]; 
            args_pipe_itr++; 
            j++; 
            nargs_p++;
        }

        j++;    


        int pid = fork();

        signal(SIGTTIN, SIG_IGN);
        signal(SIGTTOU, SIG_IGN);   
        printf("Process ID: %d\n", pid);    

        if(pid < 0)
        {
            perror("Error forking!");
            return;
        }

        else if(pid > 0) {continue;}

        else //pid == 0
        {

            perror("in");
            if(i == 0)
            {   
                process(&greaterthan, &d_greaterthan, &args_pipe[i] ,&nargs_p, &fileno_out, &fileno_in, &lessthan, &fout, &fin);
                printf("Lessthan = %d", lessthan);                               
                if(lessthan) dup2(fileno_in, STDIN_FILENO);
                dup2(fd[i][1], STDOUT_FILENO);

                }

            else if(i != num_commands - 1)
            {

                 dup2(fd[i - 1][1], STDIN_FILENO);

            //process(&greaterthan, &d_greaterthan, &args_pipe[i] ,&nargs_p, &fileno_out, &fileno_in, &lessthan, &fout, &fin);

                dup2(fd[i][1], STDOUT_FILENO);
            }

            else
            {           
                dup2(fd[i - 1][1], STDIN_FILENO);   
                process(&greaterthan, &d_greaterthan, &args_pipe[i] ,&nargs_p, &fileno_out, &fileno_in, &lessthan, &fout, &fin);

                printf("greaterthan = %d", greaterthan);
                printf("d_greaterthan = %d", d_greaterthan);
                if(greaterthan || d_greaterthan) dup2(fileno_out, STDOUT_FILENO);    
                }

            int close_pipes;
            for(close_pipes = 0; close_pipes < flag_count; close_pipes++) 
            {
                //close(fd[i][0]); close(fd[i][1]); JUST, WHY?!? THIS IS WHAT HAPPENS WHEN YOU DON'T THINK!

                close(fd[close_pipes][0]);
                close(fd[close_pipes][1]);
            }
            if(fout != stdout) fclose(fout);
            if(fin != stdin) fclose(fin);
            execvp(args_pipe[0], args_pipe);
            perror("Something happened.");
            exit(-1);
       }//end child.




    }   
        for(i = 0; i < flag_count; i++) {close(fd[i][0]); close(fd[i][1]);}
        return;

    }


 }

int main (int argc, char* argv [])
{
      for(;;)
      {
          printf("COP4338$ ");

         if(fgets(cmdline, BUFSIZ, stdin) == NULL)
         {

             perror("fgets failed");
             exit(1);
         }
            execute(cmdline);
            int corpse;
            int status;
            while(corpse = wait(%status)) > 0)
                perror(pid %d exited with status: 0x%.4X\n, corpse, status);
       }

}

The program is supposed to print out "int main (int argc, char* argv [])" to o.txt, but o.txt doesn't get changed at all.

For those that are curious, the function process() scans through a string of arguments which constitute a command and sets flags depending on the symbols that the program sees.

  • 1
    Welcome to Stack Overflow. Please read the [About] and [Ask] pages. I'd like to help, but the question is almost unanswerable because the sample code is nothing like an MCVE ([MCVE]) — it's probably why no-one has responded after 4 hours. I'm not going to try and guess what's going on in your code — especially not in the code you haven't shown. The weird indentation scheme doesn't inspire confidence. Congratulations on one thing: I've not seen this indentation scheme before (and I've seen quite a lot of them). It makes it very hard to read the code when you use such an unorthodox system. – Jonathan Leffler May 01 '19 at 06:41
  • 2
    Please post MCVE. – Kamila Szewczyk May 01 '19 at 13:19
  • @JonathanLeffler Hey, I fixed the awful formatting. It was not my fault, I made sure at first to check that the formatting was good. I dont know why it all of a sudden got screwed up. Should I repost so that this question can get more attention? – NoobProgrammer May 01 '19 at 17:20
  • @KrzysztofSzewczyk Please check my reply to JonathanLeffer – NoobProgrammer May 01 '19 at 17:20
  • Those far right indented `}` are still weird. Classic styles put the close brace at the same level of indent as the starting keywords of the block of code; the only major debate is where the open brace goes. I prefer Allman; many prefer 1TBS. See Wikipedia on [Indentation Style](https://en.wikipedia.org/wiki/Indentation_style). – Jonathan Leffler May 01 '19 at 17:24
  • No; please don't repost the question. Do include enough code that we can see what you're doing — ideally, we should be able to compile and run what you post, and see the problem you're seeing. That's what an MCVE is all about. – Jonathan Leffler May 01 '19 at 17:25
  • I've just run your code through the code reformatter I use — [`uncrustify`](http://uncrustify.sourceforge.net/) with a configuration that suits my preferred style. I'd probably remove more blank lines, such as the ones before `else` lines. If you don't like it, revert my change (click on the timestamp above the edit record to see the edits, and rollback to your preferred version). – Jonathan Leffler May 01 '19 at 17:31
  • It isn't clear that you are ensuring that the argument list for a command is properly null terminated (the `argv` list needs a null pointer at the end). You only wait for one child to exit even when many exist. And you definitely do not close anywhere near enough file descriptors. – Jonathan Leffler May 01 '19 at 17:36
  • You aren't closing enough file descriptors in the children. **Rule of thumb**: If you [`dup2()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/dup2.html) one end of a pipe to standard input or standard output, close both of the original file descriptors from `pipe()` as soon as possible. In particular, that means before using any of the [`exec*()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html) family of functions. The rule also applies with either `dup()` or [`fcntl()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html) with `F_DUPFD`. – Jonathan Leffler May 01 '19 at 17:36
  • You also need to close the pipes in the parent before waiting for the children. Otherwise, the children won't necessarily see EOF when they should. Or they may block waiting for the parent to read from one of the pipes, even though the parent never will read from the pipe. – Jonathan Leffler May 01 '19 at 17:37
  • @JonathanLeffler Won't the exec not be able to read from the read side of each if I close them before execvp? – NoobProgrammer May 01 '19 at 17:45
  • If you duplicate the read end of a pipe to standard input, you can (should) close the file descriptor returned by `pipe()` — and you should close the write end of the pipe because it is an unusual process that writes to and reads from the same pipe (rare enough that it is practically safe to say "it is always wrong"). Similar considerations apply to the write end of a pipe and standard output. And the first process in the pipeline doesn't need the pipe for the last process when there's more than two processes in total. You need to write a lot of closes when you open pipes in a loop. – Jonathan Leffler May 01 '19 at 17:49
  • So does that mean that I have to close every end from every pipe in the array of pipes in every child except the ones that will duplicated(and consequently closed) by each child? – NoobProgrammer May 01 '19 at 23:26
  • @JonathanLeffler Hey, I am sorry, I forgot to mention you in my last comment. I just did exactly what I thought I needed to do and closed all the pipes in all of the children + the parent.I am still not getting any results. I added more code(and took out some parts of the existing code). – NoobProgrammer May 04 '19 at 21:08
  • @NoobProgrammer it still isn't a [mcve]. What is more, the formatting is again weird. – Antti Haapala -- Слава Україні May 05 '19 at 05:35

1 Answers1

0

There are a number of problems with your code:

  • No calls to wait() or equivalent.
  • Connecting the wrong end of a pipe to standard input or standard output.
  • Accessing the wrong parts of the set of arguments — command name and arguments.
  • Closing the wrong file descriptors for the pipes, potentially repeatedly.

I think there are issues with memory leakage and unnecessary memory allocation in the customStrCpy() function and the adjustArray() function, but I have not addressed these — the code seems to work adequately when you don't need to worry about leaks.

I needed to add diagnostics to find out what your code was doing wrong. Consequently, I used the error reporting code that is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory.

This code was based on the code shown in Revision 6 of the question — the code shown in the question has been modified since then.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#include "stderr.h"

#define MAX_ARGS 20
#define BUFSIZ 1024

static
int get_args(char *cmdline, char *args[])
{
    int i = 0;

    /* if no args */
    if ((args[0] = strtok(cmdline, "\n\t ")) == NULL)
        return 0;

    while ((args[++i] = strtok(NULL, "\n\t ")) != NULL)
    {
        if (i >= MAX_ARGS)
        {
            printf("Too many arguments!\n");
            exit(1);
        }
    }
    /* the last one is always NULL */
    return i;
}

static
void customStrCpy(char **line1, char *line2)
{
    int strlen1 = strlen(*line1), strlen2 = strlen(line2);
    if (strlen1 < strlen2)
    {
        // Creates a dynamically allocated array that is big enough to store the contents of line2.
        *line1 = calloc(strlen2, sizeof(char));
    }
    strcpy(*line1, line2);
}

static
void adjustArray(char *args[], int args_itr, int *nargs)
{
    int i, j;

    for (i = 0; i < 2; i++)
    {
        for (j = args_itr; j < (*nargs) - 1; j++)
        {
            customStrCpy(&args[j], args[j + 1]);
        }
        args[(*nargs) - 1] = 0;
        (*nargs)--;
    }
}

static
void process(int *greaterthan, int *d_greaterthan, char *args_pipe[],
             int *nargs_p, int *fileno_out, int *fileno_in, int *lessthan,
             FILE **fout, FILE **fin)
{
    int args_itr;
    err_remark("-->> %s():\n", __func__);
    for (args_itr = 0; args_itr < *nargs_p; args_itr++)
    {
        if (strcmp(args_pipe[args_itr], ">") == 0)
        {
            err_remark("---- %s(): > %s\n", __func__, args_pipe[args_itr + 1]);
            *fout = fopen(args_pipe[args_itr + 1], "w");
            if (fout == NULL)
                err_syserr("failed to open file '%s' for writing\n",
                           args_pipe[args_itr + 1]);
            *fileno_out = fileno(*fout);
            *greaterthan = 1;
            adjustArray(args_pipe, args_itr, nargs_p);
            args_itr--;
        }
        else if (strcmp(args_pipe[args_itr], ">>") == 0)
        {
            err_remark("---- %s(): >> %s\n", __func__, args_pipe[args_itr + 1]);
            *fout = fopen(args_pipe[args_itr + 1], "a");
            if (fout == NULL)
                err_syserr("failed to open file '%s' for appending\n",
                           args_pipe[args_itr + 1]);
            *fileno_out = fileno(*fout);
            *d_greaterthan = 1;
            adjustArray(args_pipe, args_itr, nargs_p);
            args_itr--;
        }
        else if (strcmp(args_pipe[args_itr], "<") == 0)
        {
            err_remark("---- %s(): < %s\n", __func__, args_pipe[args_itr + 1]);
            *fin = fopen(args_pipe[args_itr + 1], "r");
            if (fin == NULL)
                err_syserr("failed to open file '%s' for reading\n",
                           args_pipe[args_itr + 1]);
            *fileno_in = fileno(*fin);
            *lessthan = 1;
            adjustArray(args_pipe, args_itr, nargs_p);
            args_itr--;
        }
    }
    err_remark("<<-- %s()\n", __func__);
}

static
void execute(char *cmdline)
{
    int lessthan = 0;
    int greaterthan = 0, d_greaterthan = 0;
    int args_itr, pipe_flag = 0;
    int flag_count = 0, fileno_in, fileno_out;
    char *args_pipe[MAX_ARGS];/*5 and 3 are test numbers.*/
    char *args[MAX_ARGS];

    int nargs = get_args(cmdline, args);
    if (nargs <= 0)
        return;

    if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit"))
    {
        exit(0);
    }

    for (int x = 0; x < nargs; x++)
        err_remark("args[%d] = [%s]\n", x, args[x]);

    FILE *fout = stdout;
    FILE *fin = stdin;
    for (args_itr = 0; args_itr < nargs; args_itr++)
    {
        if (!strcmp(args[args_itr], "|"))
        {
            pipe_flag = 1;
            flag_count++;
        }
    }

    if (pipe_flag)
    {
        int num_commands = flag_count + 1, i = 0, j = 0;
        int fd[flag_count][2];

        for (i = 0; i < flag_count; i++)
        {
            pipe(fd[i]);
            err_remark("opened pipe - %d and %d\n", fd[i][0], fd[i][1]);
        }

        for (i = 0; i < num_commands; i++)
        {
            int nargs_p = 0, args_pipe_itr = 0;
            while (j < nargs && strcmp(args[j], "|"))
            {
                // Possibly make into for loop.
                args_pipe[args_pipe_itr] = args[j];
                args_pipe_itr++;
                j++;
                nargs_p++;
            }
            args_pipe[nargs_p] = NULL;  /* JL: Null-terminate argument list */

            j++;    /* Skip pipe argument */

            int pid = fork();

            signal(SIGTTIN, SIG_IGN);
            signal(SIGTTOU, SIG_IGN);

            if (pid < 0)
            {
                err_syserr("failed to fork a child process: ");
                /*NOTREACHED*/
            }
            if (pid > 0)
            {
                err_remark("Parent launched child %d\n", pid);
                continue;
            }
            else // if (pid == 0)
            {
                err_remark("child at work (i == %d)\n", i);
                if (i == 0)
                {
                    err_remark("first process in pipeline (i = %d, j = %d)\n", i, j);
                    process(&greaterthan, &d_greaterthan, args_pipe,
                            &nargs_p, &fileno_out, &fileno_in, &lessthan,
                            &fout, &fin);
                    if (lessthan)
                        dup2(fileno_in, STDIN_FILENO);
                    dup2(fd[i][1], STDOUT_FILENO);
                }
                else if (i != num_commands - 1)
                {
                    err_remark("middle process in pipeline (i = %d, j = %d)\n", i, j);
                    dup2(fd[i - 1][0], STDIN_FILENO);   /* JL */
                    /* Will need to process I/O redirections mid-pipeline */
                    /* However, they're always a bug in the shell script */
                    dup2(fd[i][1], STDOUT_FILENO);
                }
                else
                {
                    err_remark("final process in pipeline (i = %d, j = %d)\n", i, j);
                    dup2(fd[i - 1][0], STDIN_FILENO);   /* JL */
                    process(&greaterthan, &d_greaterthan, args_pipe,
                            &nargs_p, &fileno_out, &fileno_in, &lessthan,
                            &fout, &fin);
                    if (greaterthan || d_greaterthan)
                        err_remark("Redirection:\n"),
                        dup2(fileno_out, STDOUT_FILENO);
                }

                err_remark("close pipes\n");
                int close_pipes;
                for (close_pipes = 0; close_pipes < flag_count; close_pipes++)
                {
                    close(fd[close_pipes][0]);  /* JL */
                    close(fd[close_pipes][1]);  /* JL */
                }

                if (fout != stdout)
                    fclose(fout);
                if (fin != stdin)
                    fclose(fin);
                err_remark("execute command [%s]\n", args_pipe[0]);
                for (int i = 1; args_pipe[i] != NULL; i++)
                    err_remark("argument %d [%s]\n", i, args_pipe[i]);
                execvp(args_pipe[0], args_pipe);
                err_syserr("Failed to execute (i=%d) '%s': ", i, args_pipe[0]);
                /*NOTREACHED*/
            }// end child.
        }
        /* Parent process closes its copy of each pipe */
        for (i = 0; i < flag_count; i++)
        {
            close(fd[i][0]);
            close(fd[i][1]);
        }
        return;
    }
    else
        err_remark("No pipe in input - no command executed\n");
}

int main(int argc, char **argv)
{
    if (argc >= 0)
        err_setarg0(argv[0]);
    err_setlogopts(ERR_PID|ERR_MILLI);

    for ( ; ; )
    {
        printf("COP4338$ ");

        char cmdline[BUFSIZ];
        if (fgets(cmdline, BUFSIZ, stdin) == NULL)
        {
            printf("EOF\n");
            exit(1);
        }
        execute(cmdline);
        int status;
        int corpse;
        while ((corpse = wait(&status)) > 0)
            err_remark("PID %d exited with status 0x%.4X\n", corpse, status);
    }
}

The process() function shouldn't need to distinguish between > and >> in the argument list; it would be sufficient to handle the standard output correctly. The calling code would be simpler. I'm not convinced that you should use fopen() — it would be more sensible to use open() with appropriate control arguments.

However, this does at least seem to work. The source code was shell61.c, compiled to the program shell61.

$ shell61
COP4338$ sort < shell61.c | grep main | cat > o.txt
shell61: 2019-05-04 22:17:39.982 - pid=8150: args[0] = [sort]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[1] = [<]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[2] = [shell61.c]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[3] = [|]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[4] = [grep]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[5] = [main]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[6] = [|]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[7] = [cat]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[8] = [>]
shell61: 2019-05-04 22:17:39.983 - pid=8150: args[9] = [o.txt]
shell61: 2019-05-04 22:17:39.983 - pid=8150: opened pipe - 3 and 4
shell61: 2019-05-04 22:17:39.983 - pid=8150: opened pipe - 5 and 6
shell61: 2019-05-04 22:17:39.984 - pid=8150: Parent launched child 8153
shell61: 2019-05-04 22:17:39.984 - pid=8150: Parent launched child 8154
shell61: 2019-05-04 22:17:39.984 - pid=8150: Parent launched child 8155
shell61: 2019-05-04 22:17:39.984 - pid=8154: child at work (i == 1)
shell61: 2019-05-04 22:17:39.984 - pid=8153: child at work (i == 0)
shell61: 2019-05-04 22:17:39.984 - pid=8155: child at work (i == 2)
shell61: 2019-05-04 22:17:39.985 - pid=8154: middle process in pipeline (i = 1, j = 7)
shell61: 2019-05-04 22:17:39.985 - pid=8155: final process in pipeline (i = 2, j = 11)
shell61: 2019-05-04 22:17:39.985 - pid=8153: first process in pipeline (i = 0, j = 4)
shell61: 2019-05-04 22:17:39.985 - pid=8154: close pipes
shell61: 2019-05-04 22:17:39.985 - pid=8155: -->> process():
shell61: 2019-05-04 22:17:39.985 - pid=8153: -->> process():
shell61: 2019-05-04 22:17:39.986 - pid=8154: execute command [grep]
shell61: 2019-05-04 22:17:39.986 - pid=8155: ---- process(): > o.txt
shell61: 2019-05-04 22:17:39.986 - pid=8153: ---- process(): < shell61.c
shell61: 2019-05-04 22:17:39.986 - pid=8154: argument 1 [main]
shell61: 2019-05-04 22:17:39.986 - pid=8155: <<-- process()
shell61: 2019-05-04 22:17:39.986 - pid=8153: <<-- process()
shell61: 2019-05-04 22:17:39.987 - pid=8155: Redirection:
shell61: 2019-05-04 22:17:39.987 - pid=8153: close pipes
shell61: 2019-05-04 22:17:39.987 - pid=8155: close pipes
shell61: 2019-05-04 22:17:39.987 - pid=8153: execute command [sort]
shell61: 2019-05-04 22:17:39.988 - pid=8155: execute command [cat]
shell61: 2019-05-04 22:17:39.993 - pid=8150: PID 8153 exited with status 0x0000
shell61: 2019-05-04 22:17:39.993 - pid=8150: PID 8154 exited with status 0x0000
shell61: 2019-05-04 22:17:39.994 - pid=8150: PID 8155 exited with status 0x0000
COP4338$ cat o.txt | cat
shell61: 2019-05-04 22:18:08.339 - pid=8150: args[0] = [cat]
shell61: 2019-05-04 22:18:08.339 - pid=8150: args[1] = [o.txt]
shell61: 2019-05-04 22:18:08.339 - pid=8150: args[2] = [|]
shell61: 2019-05-04 22:18:08.339 - pid=8150: args[3] = [cat]
shell61: 2019-05-04 22:18:08.339 - pid=8150: opened pipe - 3 and 4
shell61: 2019-05-04 22:18:08.340 - pid=8150: Parent launched child 8157
shell61: 2019-05-04 22:18:08.340 - pid=8150: Parent launched child 8158
shell61: 2019-05-04 22:18:08.340 - pid=8157: child at work (i == 0)
shell61: 2019-05-04 22:18:08.340 - pid=8158: child at work (i == 1)
shell61: 2019-05-04 22:18:08.340 - pid=8157: first process in pipeline (i = 0, j = 3)
shell61: 2019-05-04 22:18:08.341 - pid=8157: -->> process():
shell61: 2019-05-04 22:18:08.341 - pid=8158: final process in pipeline (i = 1, j = 5)
shell61: 2019-05-04 22:18:08.341 - pid=8157: <<-- process()
shell61: 2019-05-04 22:18:08.341 - pid=8158: -->> process():
shell61: 2019-05-04 22:18:08.342 - pid=8157: close pipes
shell61: 2019-05-04 22:18:08.342 - pid=8158: <<-- process()
shell61: 2019-05-04 22:18:08.342 - pid=8157: execute command [cat]
shell61: 2019-05-04 22:18:08.342 - pid=8158: close pipes
shell61: 2019-05-04 22:18:08.343 - pid=8157: argument 1 [o.txt]
shell61: 2019-05-04 22:18:08.343 - pid=8158: execute command [cat]
int main(int argc, char **argv)
shell61: 2019-05-04 22:18:08.347 - pid=8150: PID 8158 exited with status 0x0000
shell61: 2019-05-04 22:18:08.347 - pid=8150: PID 8157 exited with status 0x0000
COP4338$ ls
shell61: 2019-05-04 22:18:41.516 - pid=8150: args[0] = [ls]
shell61: 2019-05-04 22:18:41.516 - pid=8150: No pipe in input - no command executed
COP4338$ exit
$

When I added the wait() code first, I got messages like:

$ shell61
COP4338$ ls | grep shell
shell61: 2019-05-04 14:29:23.201 - pid=5181: args[0] = [ls]
shell61: 2019-05-04 14:29:23.202 - pid=5181: args[1] = [|]
shell61: 2019-05-04 14:29:23.202 - pid=5181: args[2] = [grep]
shell61: 2019-05-04 14:29:23.202 - pid=5181: args[3] = [shell]
shell61: 2019-05-04 14:29:23.202 - pid=5181: opened pipe - 3 and 4
Process ID: 5182
Process ID: 5183
Process ID: 0
Process ID: 0
shell61: 2019-05-04 14:29:23.203 - pid=5183: child at work
PID 5183 exited with status 0x000B
shell61: 2019-05-04 14:29:23.203 - pid=5182: child at work
PID 5182 exited with status 0x000D
COP4338$ exit
$

The exit status from 5183 was 0x000B, which translates to "died from signal 11 — SIGSEGV"; the exit status from 5182 was 0x000D, which translates to "died form signal 13 — SIGPIPE". This was important information. The printing added, via err_remark() in particular, helped show where there were problems. Spotting that you were calling process() with &args_pipe[i] instead of &args_pipe[0] or args_pipe was key, too.

When the trace looked like:

$ shell61
COP4338$ ls | grep shell
shell61: 2019-05-04 16:24:24.297 - pid=6417: args[0] = [ls]
shell61: 2019-05-04 16:24:24.298 - pid=6417: args[1] = [|]
shell61: 2019-05-04 16:24:24.298 - pid=6417: args[2] = [grep]
shell61: 2019-05-04 16:24:24.298 - pid=6417: args[3] = [shell]
shell61: 2019-05-04 16:24:24.298 - pid=6417: opened pipe - 3 and 4
shell61: 2019-05-04 16:24:24.299 - pid=6417: Parent launched child 6419
shell61: 2019-05-04 16:24:24.299 - pid=6417: Parent launched child 6420
shell61: 2019-05-04 16:24:24.300 - pid=6420: child at work (i == 1)
shell61: 2019-05-04 16:24:24.299 - pid=6419: child at work (i == 0)
shell61: 2019-05-04 16:24:24.300 - pid=6420: final process in pipeline (i = 1, j = 5)
shell61: 2019-05-04 16:24:24.301 - pid=6419: first process in pipeline (i = 0, j = 2)
shell61: 2019-05-04 16:24:24.301 - pid=6420: -->> process():
shell61: 2019-05-04 16:24:24.302 - pid=6420: <<-- process()
greaterthan = 0
d_greaterthan = 0
shell61: 2019-05-04 16:24:24.302 - pid=6419: -->> process():
shell61: 2019-05-04 16:24:24.302 - pid=6420: close pipes
shell61: 2019-05-04 16:24:24.302 - pid=6419: <<-- process()
Lessthan = 0
shell61: 2019-05-04 16:24:24.303 - pid=6420: execute command [grep]
shell61: 2019-05-04 16:24:24.303 - pid=6420: argument 1 [shell]
shell61: 2019-05-04 16:24:24.303 - pid=6419: close pipes
shell61: 2019-05-04 16:24:24.304 - pid=6419: execute command [ls]
grep: (standard input): Bad file descriptor
PID 6420 exited with status 0x0100
PID 6419 exited with status 0x000D
COP4338$ exit
$

it was clear that there was a problem with the redirection — it wasn't clear if the standard input was closed or if grep was being asked to read from a write-only file descriptor; code review showed that the latter was actually the problem (using fd[i][1] where fd[i][0] needed to be used).

I can't emphasize enough how having powerful yet simple error reporting tools helps. I use the stderr.c and stderr.h code in most of my programs — it is rare in the extreme that I do not use them. You should either acquire a copy of them or devise your own equivalent, but be aware that there's 30 years of refinements to that code (though there were some years when I didn't make any changes to it — 2018 being the most recent such year, but it didn't change in 1992-1995, and in sundry other years since). The earliest version I still have was dated 1988; the code was actually started before that, but I had a failure to transfer the data successfully when moving jobs (8" floppy disk turned out to be a bad choice).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Where is the wait code that you added? I tried putting waitpid(pid, NULL, 0) so that the children are created and execute sequentially(one after the other) so that the parent has to wait for 1 child to terminate before continuing, but my program keeps hanging when I do that – NoobProgrammer May 09 '19 at 01:21
  • The wait loop is after the call to `execute()` in `main()`. – Jonathan Leffler May 09 '19 at 01:23
  • When you run a pipeline, the processes should run concurrently. A pipe has a finite and fairly small capacity — classically 5 KiB, minimally 4 KiB, often 64 KiB these days, but rarely more. So, you need to have all the processes running to prevent blockages. – Jonathan Leffler May 09 '19 at 01:26
  • But what is the wait loop exactly? What is main waiting for? I have been at this for a month already. I really dont know why this is so difficult for me. I also dont know what I was thinking when I decided to read from fd[i][1]. I changed that back after posting the edit. – NoobProgrammer May 09 '19 at 01:41
  • Programming a shell is a morbid business, with time spent waiting for dead children, and trying to avoid zombies. The wait loop is waiting for child processes to die (complete their execution and exit). When your shell runs a pipeline of four processes, it has to ensure that all four are started. If you’re not running them in background (usually an `&` at the end for that), then your main shell has to wait for at least the last process in the pipeline to complete. There are several ways to organize the pipeline. The Bash shell forks each child itself. Others have the children fork them. – Jonathan Leffler May 09 '19 at 05:36
  • Jesus Christ! Many of these errors were caused by stupid typos! The program does exactly what it needs to do now. But what I still dont understand is why it is that the processes exit in order when they each execute at different speeds? In your code for example, Process 8154 executed "grep main" first, without the input from process 8153(since that process is not done yet) and yet, it still finished after process 8153. – NoobProgrammer May 11 '19 at 18:41
  • Process 8154 reported that it was started before 8153, but it's pretty much guaranteed that 8153 was forked first. However, it wouldn't actually matter much if the processes were forked in reverse order (except for the parent waiting for the pipeline to complete); the `grep` won't exit until it gets EOF on its input, and it won't get that until the `sort` (and the parent process) close the write end of the pipe. It also depends on the child not having the write end of the pipe open. So, in the pipeline, if the file descriptors are managed properly, `grep` waits for `sort` to complete. – Jonathan Leffler May 11 '19 at 18:48