/* Simple shell program by Bryce Schroeder */ #include #include #include #include #include #include #include #include #include "history_array.h" #include "shell_args.h" /* Adjustable parameters */ #define HISTORY_SIZE 10 #define LINE_BUFFER_SIZE 160 #define PROMPT "> " /* Global to store the history. A global is justified because the * * signal handler cannot take arbitrary parameters. */ history_array_t *command_history; /* Set up the signal handler */ void signal_suspend_handler(int sig); /* Foreward declaration */ void setup_signal_suspend(void) { struct sigaction sig; sig.sa_handler = signal_suspend_handler; sig.sa_flags = SA_RESTART|SA_RESETHAND; /* TODO: what should sig.sa_action be? Not, apparently, NULL. */ sigemptyset(&sig.sa_mask); /* SIGTSTP is masked implicitly. */ assert(sigaction(SIGTSTP, &sig, NULL) >= 0); } /* Signal Handler for ^Z */ void signal_suspend_handler(int sig) { struct termios ttycmd; char choice[2] = ""; const char *historical_line; command_t *historical_command; int index; printf("Command History:\n"); if (!history_len(command_history)) { printf(" (The history is currently empty.)\n"); /* Justification: No support for finally/exceptions in plain C, * * which this program is written in. Alternate choices would be * * writing redundant code (far worse a maintanance headache) or * * making a new function highly specific to the particular code * * which is not a very attractive or satsifactory option either. */ goto cleanup; } printf("Type the nubmer of your selection:\n"); history_show(command_history); //fflush(NULL); /* Set up stdin for unbuffered/raw input so that the user * * does not have to press return. */ assert(!tcgetattr(STDIN_FILENO, &ttycmd)); ttycmd.c_lflag &= ~(ICANON|ECHO); assert(!tcsetattr(STDIN_FILENO, TCSANOW, &ttycmd)); // system("stty cbreak"); while (!choice[0]) fgets(choice, 2, stdin); //scanf("%c", &choice); if (choice[0] > '9' || choice[0] < '0') { printf("Invalid selection '%c'.\n", choice[0]); goto cleanup; } index = (int) choice[0] - '0'; if (index >= history_len(command_history)) { printf("There isn't an item #%d yet.\n", index); goto cleanup; } /* The item is now validated, so we can execute it. */ historical_line = history_get(command_history, index); historical_command = command_new(historical_line); command_exec(historical_command); command_free(historical_command); cleanup: /* This is a 'sink state', it doesn't matter how we got here * * once we are here, so this use of goto is not a structural * * problem in the code. See also http://c2.com/cgi/wiki?GoTo */ printf(PROMPT); assert(!tcgetattr(fileno(stdin), &ttycmd)); ttycmd.c_lflag |= ICANON|ECHO; assert(!tcsetattr(STDIN_FILENO, TCSANOW, &ttycmd)); fflush(NULL); setup_signal_suspend(); } // Should be free of race conditions, never fire twice. int main (int argc, char *argv[]) { char line_buffer[LINE_BUFFER_SIZE]; command_t *command = NULL; printf("Bryce Schroeder's Shell Program.\n" "^C to quit, ^Z for command history.\n"); command_history = history_new(HISTORY_SIZE); setup_signal_suspend(); while (1) { printf(PROMPT); fgets(line_buffer, LINE_BUFFER_SIZE, stdin); /* User pressed ^D? */ if (feof(stdin)) exit(0); command = command_new(line_buffer); if (NULL != command && !history_check(command_history,line_buffer)) history_append(command_history,line_buffer); //printf("----------- '%s'\n", line_buffer); command_exec(command); command_free(command); } history_free(command_history); return 0; }