Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <ctype.h>
- #include <errno.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <signal.h>
- #include <sys/wait.h>
- #include <termios.h>
- #include <unistd.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <linux/limits.h>
- #include <stdlib.h>
- #include "tokenizer.h"
- #include "vector.h"
- /* Convenience macro to silence compiler warnings about unused function parameters. */
- #define unused __attribute__((unused))
- /* Error Message used for perror() */
- #define err_msg "shell"
- /* Permissions for new files */
- #define PERMISSION 0644 //-rw-r--r--
- /* Whether the shell is connected to an actual terminal or not. */
- bool shell_is_interactive;
- /* File descriptor for the shell input */
- int shell_terminal;
- /* Terminal mode settings for the shell */
- struct termios shell_tmodes;
- /* Process group id for the shell */
- pid_t shell_pgid;
- /* Vector To save Processes */
- vector proc_vec;
- /* Struct vor processes */
- typedef struct proc
- {
- pid_t pid;
- struct termios shell_tmodes;
- } proc;
- int cmd_exit(struct tokens *tokens);
- int cmd_help(struct tokens *tokens);
- int cmd_pwd(struct tokens *tokens);
- int cmd_cd(struct tokens *tokens);
- int cmd_wait(struct tokens *tokens);
- int cmd_fg(struct tokens *tokens);
- int cmd_bg(struct tokens *tokens);
- char *exec_search_in_path(char *exe);
- void exec_redirect(char *file_name, int flags, int std);
- void exec_parse_arguments(unused struct tokens *tokens, int argc, char **argv, int *output, int *input, int *command_end);
- int exec_set_fg_process(pid_t fd, pid_t pgrp);
- int exec_chekc_for_args(unused struct tokens *tokens);
- int exec_check_for_bg(unused struct tokens *tokens);
- int exec_program(unused struct tokens *tokens);
- /* Built-in command functions take token array (see parse.h) and return int */
- typedef int cmd_fun_t(struct tokens *tokens);
- /* Built-in command struct and lookup table */
- typedef struct fun_desc
- {
- cmd_fun_t *fun;
- char *cmd;
- char *doc;
- } fun_desc_t;
- fun_desc_t cmd_table[] = {
- {cmd_help, "?", "show this help menu"},
- {cmd_exit, "exit", "exit the command shell"},
- {cmd_pwd, "pwd", "print woking directory"},
- {cmd_cd, "cd", "change directory"},
- {cmd_wait, "wait", "wait for background processes to finish"},
- {cmd_fg, "fg", "fg [pid] – Move the process with id pid to the foreground"},
- {cmd_bg, "bg", "bg [pid] – Resume a paused background process"}};
- /* Prints a helpful description for the given command */
- int cmd_help(unused struct tokens *tokens)
- {
- for (unsigned int i = 0; i < sizeof(cmd_table) / sizeof(fun_desc_t); i++)
- printf("%s - %s\n", cmd_table[i].cmd, cmd_table[i].doc);
- return 1;
- }
- /* Exits this shell */
- int cmd_exit(unused struct tokens *tokens)
- {
- exit(0);
- }
- /* Prints working directory */
- int cmd_pwd(unused struct tokens *tokens)
- {
- char cwd[PATH_MAX];
- char *res = getcwd(cwd, sizeof(cwd));
- if (!res)
- {
- perror(err_msg);
- return -1;
- }
- printf("%s\n", cwd);
- return 0;
- }
- /* Changes directory */
- int cmd_cd(unused struct tokens *tokens)
- {
- size_t count = tokens_get_length(tokens);
- if (count > 2)
- {
- printf("shell: cd: too many arguments\n");
- return -1;
- }
- char *dir_path;
- if (count == 1) // if no arguments are passed cd takes to home directory
- dir_path = getenv("HOME");
- else
- dir_path = tokens_get_token(tokens, 1);
- int res = chdir(dir_path);
- if (res == -1)
- {
- perror(err_msg);
- return -1;
- }
- return 0;
- }
- /* Waits for all background processes to finish */
- int cmd_wait(unused struct tokens *tokens)
- {
- while ((wait(NULL)) > 0)
- ;
- return 0;
- }
- int cmd_fg(struct tokens *tokens)
- {
- size_t count = tokens_get_length(tokens);
- if (count > 2)
- {
- printf("Usage : fg [pid] or fg\n");
- return -1;
- }
- pid_t pid;
- if (count == 2)
- {
- char *char_pid = tokens_get_token(tokens, 1);
- pid = atoi(char_pid);
- }
- if (count == 1)
- {
- int vector_len = VectorLength(&proc_vec);
- if (!vector_len)
- {
- printf("bash: fg: current: no such job\n");
- return -1;
- }
- else
- {
- proc *last_elem = (proc *)VectorDelete(&proc_vec, vector_len - 1);
- pid = last_elem->pid;
- tcsetattr(shell_terminal, 0, &last_elem->shell_tmodes);
- }
- }
- exec_set_fg_process(0, pid);
- kill(pid, SIGCONT);
- int wstatus;
- waitpid(pid, &wstatus, WUNTRACED);
- if (WIFSTOPPED(wstatus) && WSTOPSIG(wstatus))
- {
- printf("\n[%d]+ stopped PID : %d\n", 1, pid);
- proc paused_proc = {pid, shell_tmodes};
- tcgetattr(shell_terminal, &(paused_proc.shell_tmodes));
- VectorAppend(&proc_vec, &paused_proc);
- }
- exec_set_fg_process(0, getpid());
- return 0;
- }
- int cmd_bg(struct tokens *tokens)
- {
- size_t count = tokens_get_length(tokens);
- if (count > 2)
- {
- printf("Usage : bg [pid] or fg\n");
- return -1;
- }
- pid_t pid;
- if (count == 2)
- {
- char *char_pid = tokens_get_token(tokens, 1);
- pid = atoi(char_pid);
- }
- if (count == 1)
- {
- int vector_len = VectorLength(&proc_vec);
- printf("%d\n", vector_len);
- if (!vector_len)
- {
- printf("bash: bg: current: no such job\n");
- return -1;
- }
- else
- {
- proc *last_elem = (proc *)VectorDelete(&proc_vec, vector_len - 1);
- pid = last_elem->pid;
- tcsetattr(shell_terminal, 0, &last_elem->shell_tmodes);
- }
- }
- kill(pid, SIGCONT);
- return 0;
- }
- /* Looks up the built-in command, if it exists. */
- int lookup(char cmd[])
- {
- for (unsigned int i = 0; i < sizeof(cmd_table) / sizeof(fun_desc_t); i++)
- if (cmd && (strcmp(cmd_table[i].cmd, cmd) == 0))
- return i;
- return -1;
- }
- /* Intialization procedures for this shell */
- void init_shell()
- {
- signal(SIGTTOU, SIG_IGN);
- VectorNew(&proc_vec, sizeof(proc), 10);
- /* Our shell is connected to standard input. */
- shell_terminal = STDIN_FILENO;
- /* Check if we are running interactively */
- shell_is_interactive = isatty(shell_terminal);
- if (shell_is_interactive)
- {
- /* If the shell is not currently in the foreground, we must pause the shell until it becomes a
- * foreground process. We use SIGTTIN to pause the shell. When the shell gets moved to the
- * foreground, we'll receive a SIGCONT. */
- while (tcgetpgrp(shell_terminal) != (shell_pgid = getpgrp()))
- kill(-shell_pgid, SIGTTIN);
- /* Saves the shell's process id */
- shell_pgid = getpid();
- /* Take control of the terminal */
- tcsetpgrp(shell_terminal, shell_pgid);
- /* Save the current termios to a variable, so it can be restored later. */
- tcgetattr(shell_terminal, &shell_tmodes);
- }
- }
- int exec_chekc_for_args(unused struct tokens *tokens)
- {
- int tok_len = tokens_get_length(tokens);
- char *last_arg = tokens_get_token(tokens, tok_len - 1);
- return (strcmp(last_arg, "<") && strcmp(last_arg, ">"));
- }
- char *exec_search_in_path(char *exe)
- {
- char *env_var_PATH = strdup(getenv("PATH"));
- char *cur_dir = strtok(env_var_PATH, ":");
- while (cur_dir)
- {
- char *file_name = malloc(strlen(cur_dir) + strlen(exe) + 1);
- strcat(file_name, cur_dir);
- strcat(file_name, "/");
- strcat(file_name, exe);
- if (access(file_name, F_OK) != -1) // found bin executable
- return file_name;
- cur_dir = strtok(NULL, ":");
- }
- return NULL;
- }
- int exec_check_for_bg(unused struct tokens *tokens)
- {
- int tok_len = tokens_get_length(tokens);
- if (tok_len < 1)
- return 0;
- char *last_arg = tokens_get_token(tokens, tok_len - 1);
- return !strcmp(last_arg, "&");
- }
- void exec_redirect(char *file_name, int flags, int std)
- {
- if (file_name)
- {
- int fd = open(file_name, flags, PERMISSION);
- if (fd == -1)
- {
- perror(err_msg);
- exit(0);
- }
- int new_fd = dup2(fd, std);
- if (new_fd == -1)
- {
- perror(err_msg);
- exit(0);
- }
- close(fd);
- }
- }
- int exec_set_fg_process(pid_t fd, pid_t pgrp)
- {
- if (tcsetpgrp(fd, pgrp) != 0)
- {
- perror(err_msg);
- return -1;
- }
- return 0;
- }
- void exec_parse_arguments(unused struct tokens *tokens, int argc, char **argv, int *output, int *input, int *command_end)
- {
- for (int i = 0; i < argc - 1; i++)
- {
- argv[i] = tokens_get_token(tokens, i);
- if (strlen(argv[i]) == 1 && argv[i][0] == '>')
- {
- *output = i;
- int fd = open(tokens_get_token(tokens, i + 1), O_CREAT | O_TRUNC, PERMISSION);
- if (fd == -1)
- {
- perror(err_msg);
- exit(-1);
- }
- int c_fd = close(fd);
- if (c_fd == -1)
- {
- perror(err_msg);
- exit(-1);
- }
- *command_end = *command_end < i ? *command_end : i;
- }
- if (strlen(argv[i]) == 1 && argv[i][0] == '<')
- {
- *input = i;
- if (access(tokens_get_token(tokens, i + 1), F_OK) == -1)
- {
- perror(err_msg);
- exit(-1);
- }
- *command_end = *command_end < i ? *command_end : i;
- }
- if (argv[i][0] == '&')
- *command_end = *command_end < i ? *command_end : i;
- }
- }
- int exec_program(unused struct tokens *tokens)
- {
- int bg_proc = exec_check_for_bg(tokens); // background process control
- pid_t pid = fork();
- if (pid == -1)
- {
- perror(err_msg);
- return -1;
- }
- if (pid == 0) // CHILD Process
- {
- // change group PID of child process
- if (setpgid(0, 0) != 0)
- perror(err_msg);
- // Bring Back default Signal handler
- signal(SIGTTOU, SIG_DFL);
- // check if command end with unexpected token
- if (!exec_chekc_for_args(tokens))
- {
- printf("%s: syntax error near unexpected token `newline'\n", err_msg);
- exit(-1);
- }
- // prepare arguments for execve
- int argc = tokens_get_length(tokens) + 1;
- int input = -1, output = -1, command_end = argc - 1;
- char *argv[argc];
- exec_parse_arguments(tokens, argc, argv, &output, &input, &command_end);
- argv[command_end] = NULL;
- // Redirect output to output_file
- char *output_file = (output == -1) ? NULL : argv[output + 1];
- exec_redirect(output_file, O_WRONLY, STDOUT_FILENO);
- // Redirect input from input_file
- char *input_file = (input == -1) ? NULL : argv[input + 1];
- exec_redirect(input_file, O_RDONLY, STDIN_FILENO);
- // find programm path
- char *file_name = NULL;
- if (access(argv[0], F_OK) != -1) // check without path
- file_name = argv[0];
- else
- file_name = exec_search_in_path(argv[0]); // check in path folders
- // check if such program is found
- if (!file_name)
- {
- printf("%s: %s: command not found\n", err_msg, argv[0]);
- exit(-1);
- }
- // execute program
- execv(file_name, argv);
- // if code reaches this line that means execve failed
- perror(err_msg);
- exit(-1);
- }
- else // PARENT Process
- {
- // if child process is not background, set it foreground, wait until it's finished, retrieve foreground
- if (!bg_proc)
- {
- if (setpgid(pid, pid) != 0)
- perror(err_msg);
- exec_set_fg_process(0, pid);
- int wstatus;
- waitpid(pid, &wstatus, WUNTRACED);
- if (WIFSTOPPED(wstatus) && WSTOPSIG(wstatus))
- {
- printf("\n[%d]+ stopped PID : %d\n", 1, pid);
- proc paused_proc = {pid, shell_tmodes};
- tcgetattr(shell_terminal, &(paused_proc.shell_tmodes));
- VectorAppend(&proc_vec, &paused_proc);
- }
- exec_set_fg_process(0, getpid());
- }
- else
- {
- printf("\n[%d] PID : %d\n", 1, pid);
- }
- }
- return 0;
- }
- int main(unused int argc, unused char *argv[])
- {
- init_shell();
- static char line[4096];
- int line_num = 0;
- /* Please only print shell prompts when standard input is not a tty */
- if (shell_is_interactive)
- fprintf(stdout, "%d: ", line_num);
- while (fgets(line, 4096, stdin))
- {
- /* Split our line into words. */
- struct tokens *tokens = tokenize(line);
- /* Find which built-in function to run. */
- int fundex = lookup(tokens_get_token(tokens, 0));
- if (fundex >= 0)
- cmd_table[fundex].fun(tokens);
- else
- exec_program(tokens);
- if (shell_is_interactive)
- /* Please only print shell prompts when standard input is not a tty */
- fprintf(stdout, "%d: ", ++line_num);
- /* Clean up memory */
- tokens_destroy(tokens);
- }
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement