C. Merabi Shmulik
C. Merabi Shmulik

Reputation: 282

Writing my own Linux shell with unnamed pipes

I'm experimenting with Linux and I'm working currently on writing a program which simulates the Linux shell.

I have a main function which parses the input and for my question is irrelevant for now. After each line is parsed the process line method is called which handles everything. For now I'm supporting regular processes, background processes and currently working on unnamed pipes that contain only 2 commands (cmd1 | cmd2).

Here is the code:

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

void wait_for_background_process(void* arg) {
  int status;
  pid_t* pid = (pid_t*) arg;
  do {
    printf("Waiting for %d\n", *pid);
    waitpid(*pid, &status, WUNTRACED);
  } while (!WIFEXITED(status) && !WIFSIGNALED(status));
}

/************************
function: void pipeCommand(char** cmd1, char** cmd2)
comment: This pipes the output of cmd1 into cmd2.
**************************/
void pipe_command(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  if(pipe(fds) < 0) {
    perror("myShell");
    exit(EXIT_FAILURE);
  }
  pid_t pid;

  pid = fork();
  if(pid == 0) {
    dup2(fds[1], 1);
    if(execvp(cmd1[0], cmd1) < 0) {
      perror("myShell");
      exit(EXIT_FAILURE);
    }
    execvp(cmd1[0], cmd1);
  } else if(pid < 0) {
    perror("myShell");
    exit(EXIT_FAILURE);
  } else {
    wait(NULL);
    dup2(fds[0], 0);
    if(execvp(cmd2[0], cmd2)) {
      perror("myShell");
      exit(EXIT_FAILURE);
    }
  }
}
/*
* Checks if the command is pipe command, if so we will return the
* index of the pipe
*/
int is_pipe_command(char** arglist, int count) {
  int i = 0;
  for(i = 0; i < count; i++) {
    if(strcmp(arglist[i], "|") == 0) {
      return i;
    }
  }
  return 0;
}

int process_arglist(int count, char** arglist) {
    pid_t pid;
    int pipe_index;
    pid = fork();
    if (pid == 0) {
      // Child process
      if(strcmp(arglist[count-1],"&") == 0) {
        char** background_arglist = (char**) malloc((count)*sizeof(char*));
        if(background_arglist == NULL) {
          printf("malloc failed: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
        }
        int i = 0;
        for(i = 0; i < count - 1; i++) {
          background_arglist[i] = arglist[i];
        }
        background_arglist[count - 1] = NULL;
        if (execvp(background_arglist[0], background_arglist) == -1) {
          perror("myShell");
        }
      } else if(pipe_index = is_pipe_command(arglist, count)) {
          char** cmd1 = (char**) malloc((pipe_index+1)*sizeof(char*));
          if(cmd1 == NULL) {
            printf("malloc failed: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
          }
          int i;
          int cmd1index = 0;
          for(i = 0; i < pipe_index; i++) {
            cmd1[cmd1index] = arglist[i];
            cmd1index++;
          }
          cmd1[pipe_index] = NULL;
          char** cmd2 = (char**) malloc((count - pipe_index)*sizeof(char*));
          if(cmd2 == NULL) {
            printf("malloc failed: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
          }
          int cmd2index = 0;
          for(i = pipe_index+1; i < count; i++) {
            cmd2[cmd2index] = arglist[i];
            cmd2index++;
          }
          cmd2[count-pipe_index-1] = NULL;
          pipe_command(cmd1, cmd2);
      } else {
          if (execvp(arglist[0], arglist) == -1) {
            perror("myShell");
          }
      }
      exit(EXIT_FAILURE);
    } else if (pid < 0) {
      // Error forking
      perror("myShell");
      exit(EXIT_FAILURE);
    } else {
      // Parent process
      if(strcmp(arglist[count-1],"&") == 0) {
        // The child is a background process
        pthread_t thread;
        pthread_create(&thread, NULL, wait_for_background_process, &pid);
      }
      else {
        // Regular process
      }
    }

    return 1;
}

We can focus on the pipe_command function, which gets the 2 commands correctly, I can't understand why I'm not getting any output for calling for example ls -l | sort or ls -l | grep "a".

Thanks.

Upvotes: 4

Views: 441

Answers (1)

kaylum
kaylum

Reputation: 14046

After dup you need to close fds[1]. Otherwise the second command process (sort or grep in your example) will not get an EOF from its stdin read. The EOF is needed so that the process knows when to stop processing and exit.

Explicitly, the else block in pipe_command should have a close in it like thus:

} else {
    wait(NULL);
    dup2(fds[0], 0);
    close(fds[1]); /* ADDED THIS LINE */
    if(execvp(cmd2[0], cmd2)) {
       perror("myShell");
       exit(EXIT_FAILURE);
    }
}

One more thing to note. Normally the first command process also needs a similar close(fds[0]);. But it is not needed in your case as the second process waits for the first process to exit before calling execvp which results in the first process implicitly closing all its open file descriptors.

Upvotes: 2

Related Questions