1

I am currently trying to build a custom shell for a class. I'm able to execute commands issued from this shell without problem, but if I pipe my command into my shell, I end up with an infinite-loop. I am out of ideas why this might happen. The example below would cause an infinite loop.

echo "ls" | ./myshell

Of course I have to redirect the output of programs, if a pipe within a command would occur, e.g. ls | grep test. Within my shell, this works flawlessly. I am using fork(), execv() and of course pipe + dup for redirecting streams between child processes.

It seems, as if fgets() would not clear the STDIN_FILENO from the last issued command.

For the command echo "ls" | ./myshell my program would do the following: (minimal working example)

EDIT: Minimal working example

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

int running = 1;
int exit_code = 0;

char cwd[256];
char command[256];
char args[10][256];
char buffer[256] __attribute__((aligned(4096)));

void handle_command(char* buffer, int buffer_size)
{
  int c = 0;
  int argsCount = -1;
  int lastIndex = 0;

  for (c = 0; c < buffer_size && buffer[c]; c++)
  {
    if (argsCount > 10)
    {
      argsCount = 10;
      printf("Argument Count is limited to 10 (no dynamic memory allocation) all other arguments will be ignored\n");
      break;
    }
    if (buffer[c] == '\r' || buffer[c] == '\n' || buffer[c] == ' ')
    {
      if (argsCount == -1)
      {
        memcpy(command, buffer + lastIndex, c - lastIndex);
        command[c - lastIndex] = 0;
      }
      else
      {
        memcpy(args[argsCount], buffer + lastIndex, c - lastIndex);
        args[argsCount][c - lastIndex] = 0;
      }
      argsCount++;
      lastIndex = c + 1;

    }
  }

  if (strcmp(command, "exit") == 0)
  {
    c = 4;
    while (buffer[c] == ' ')
      c++;
    exit_code = atoi(&buffer[c]);
    printf("Exiting Shell with exit_code %d\n", exit_code);
    running = 0;
  }
  else if (strcmp(command, "") != 0)
  {
    // -------------- Add structure to commands --------------------------------
    struct command_struct{
      char *options[10];
    } sub_commands[1];
    // Simplified code, there would be a dynamic amount of sub_commands
    // and further logic to handle pipes and < > >>

    // initialize first command, would work dynamically
    sub_commands[0].options[0] = command;
    sub_commands[0].options[1] = NULL;

    int status;
    int pid = fork();
    if (pid == 0) {
      execvp(sub_commands[0].options[0], sub_commands[0].options);
      perror("Error: Reached code after execvp\n");
    } else if (pid < 0) {
      perror("Cannot fork!\n");
    }
    wait(&status);
  }
}


int main(int argc, char *argv[])
{
  cwd[0] = '/';

  do
  {
    printf("\n%s %s%s", "SHELL:", cwd, "> ");
    fgets(buffer, 255, stdin);
    buffer[255] = 0;
    handle_command(buffer, 256);
    for (size_t a = 0; a < 256; a++)
      buffer[a] = 0;

  } while (running);

  return exit_code;
}

EDIT I have to point out, that parts of this code were given in this class.

Any help whatsoever would be greatly appreciated!

thruun
  • 461
  • 5
  • 15
  • You've given us a few bits and pieces, but nothing like enough to confidently answer the question. Provide an MCVE for us to examine. – John Bollinger Nov 13 '15 at 15:59
  • 3
    I can observe already, however, that you have a problem with not catching error conditions. For example, if `fgets()` is unable to read a string (maybe because the end of `stdin` has been reached) then it will indicate so by returning `NULL`, but you do not look for that case. Likewise, if your `execvp()` fails then the child process will fall out of the `if` block you presented, and continure to run as a copy of the parent shell. – John Bollinger Nov 13 '15 at 16:03
  • @JohnBollinger thank you for your remark, I thought stripping it down to the bare minimum would increase the chance for an answer -- I will post an MCVE asap. – thruun Nov 13 '15 at 16:04
  • @JohnBollinger Yes, i did some error handling, I stripped it away in this code. I can see your point, but it should work if I entered only "ls" as command. – thruun Nov 13 '15 at 16:07
  • So, then, your question boils down to "why does my program, some of whose bits resemble these different bits, not behave as I expect?". I hope you see the problem with that. – John Bollinger Nov 13 '15 at 16:11
  • 1
    By the way it don't seems useful to cleanup that way `buffer`. The `fgets` will add a trainling '\0' so it's a waste of time. You *may* just put a '\0' in first postition so that you are sure to have an empty string, but catching `fgets` errors (as said by @JohnBollinger) will prevent bad usage of this buffer. – hexasoft Nov 13 '15 at 16:15
  • 1
    Suggest `fgets(buffer, 255, stdin); buffer[255] = 0;` --> `if (fgets(buffer, sizeof buffer, stdin) == NULL) break; buffer[strcspn(buffer,"\r\n")]=0;` – chux - Reinstate Monica Nov 13 '15 at 16:21
  • 1
    I agree with @JohnBollinger when he pointed that it may be a EOF problem. As you don't handle `fgets` returned value you will never knows that `stdin` is over (as it will of course happens with a `pipe`). In that case `fgets` returns immediatly without modifying `buffer`, which is empty. I guess that `handle_command` will do nothing with that empty buffer and you will loop again. – hexasoft Nov 13 '15 at 16:26
  • @JohnBollinger I guess you are right, I have a problem with EOF. I will try a few things and post my results. – thruun Nov 13 '15 at 16:35
  • @chux Thank you for your suggestion, this seems to at least partially solve my problem! The only problem now is, that my shell won't output anything. I have to read up on `strcspn` – thruun Nov 13 '15 at 16:37

1 Answers1

4

OP code will only quit the main() do loop when running == 0.

running = 0; only occurs when strcmp(command, "exit") == 0 and it is not clear that with string parsing that just "exit" is ever loaded in command. Note: command is not cleanly initialized per each handle_command() call and command does not need to be a global variable.

Adjust code to quit when fgets() returns NULL and review line processing. Suggest:

do {
  printf("\n%s %s%s", "SHELL:", cwd, "> ");
  if (fgets(buffer, sizeof buffer, stdin) == NULL) {
    break; 
  }
  // lop off potential end-of-line character(s)
  buffer[strcspn(buffer,"\r\n")] = '\0';
  handle_command(buffer, sizeof buffer);
} while (running);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Thanks, this did the trick! But i guess that `buffer[strcspn(buffer,"\r\n")] = '\0';` does not work as expected - but I found my own way around. Thanks a lot for your efforts! – thruun Nov 13 '15 at 16:52
  • 1
    @Pethor Alternate: http://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input/27729970#27729970 – chux - Reinstate Monica Nov 13 '15 at 16:53