Advertisement
CSenshi

Shell (OS_hw1)

Mar 30th, 2019
1,272
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 9.13 KB | None | 0 0
  1. #include <ctype.h>
  2. #include <errno.h>
  3. #include <stdbool.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/types.h>
  8. #include <signal.h>
  9. #include <sys/wait.h>
  10. #include <termios.h>
  11. #include <unistd.h>
  12. #include <sys/stat.h>
  13. #include <fcntl.h>
  14. #include <linux/limits.h>
  15. #include <stdlib.h>
  16. #include "tokenizer.h"
  17. #include "vector.h"
  18.  
  19. /* Convenience macro to silence compiler warnings about unused function parameters. */
  20. #define unused __attribute__((unused))
  21.  
  22. /* Error Message used for perror() */
  23. #define err_msg "shell"
  24.  
  25. /* Permissions for new files */
  26. #define PERMISSION 0644 //-rw-r--r--
  27.  
  28. /* Whether the shell is connected to an actual terminal or not. */
  29. bool shell_is_interactive;
  30.  
  31. /* File descriptor for the shell input */
  32. int shell_terminal;
  33.  
  34. /* Terminal mode settings for the shell */
  35. struct termios shell_tmodes;
  36.  
  37. /* Process group id for the shell */
  38. pid_t shell_pgid;
  39.  
  40. /* Vector To save Processes */
  41. vector proc_vec;
  42.  
  43. /* Struct vor processes */
  44. typedef struct proc
  45. {
  46.   pid_t pid;
  47.   struct termios shell_tmodes;
  48. } proc;
  49.  
  50. int cmd_exit(struct tokens *tokens);
  51. int cmd_help(struct tokens *tokens);
  52. int cmd_pwd(struct tokens *tokens);
  53. int cmd_cd(struct tokens *tokens);
  54. int cmd_wait(struct tokens *tokens);
  55. int cmd_fg(struct tokens *tokens);
  56. int cmd_bg(struct tokens *tokens);
  57.  
  58. char *exec_search_in_path(char *exe);
  59. void exec_redirect(char *file_name, int flags, int std);
  60. void exec_parse_arguments(unused struct tokens *tokens, int argc, char **argv, int *output, int *input, int *command_end);
  61. int exec_set_fg_process(pid_t fd, pid_t pgrp);
  62. int exec_chekc_for_args(unused struct tokens *tokens);
  63. int exec_check_for_bg(unused struct tokens *tokens);
  64. int exec_program(unused struct tokens *tokens);
  65.  
  66. /* Built-in command functions take token array (see parse.h) and return int */
  67. typedef int cmd_fun_t(struct tokens *tokens);
  68.  
  69. /* Built-in command struct and lookup table */
  70. typedef struct fun_desc
  71. {
  72.   cmd_fun_t *fun;
  73.   char *cmd;
  74.   char *doc;
  75. } fun_desc_t;
  76.  
  77. fun_desc_t cmd_table[] = {
  78.     {cmd_help, "?", "show this help menu"},
  79.     {cmd_exit, "exit", "exit the command shell"},
  80.     {cmd_pwd, "pwd", "print woking directory"},
  81.     {cmd_cd, "cd", "change directory"},
  82.     {cmd_wait, "wait", "wait for background processes to finish"},
  83.     {cmd_fg, "fg", "fg [pid] – Move the process with id pid to the foreground"},
  84.     {cmd_bg, "bg", "bg [pid] – Resume a paused background process"}};
  85.  
  86. /* Prints a helpful description for the given command */
  87. int cmd_help(unused struct tokens *tokens)
  88. {
  89.   for (unsigned int i = 0; i < sizeof(cmd_table) / sizeof(fun_desc_t); i++)
  90.     printf("%s - %s\n", cmd_table[i].cmd, cmd_table[i].doc);
  91.   return 1;
  92. }
  93.  
  94. /* Exits this shell */
  95. int cmd_exit(unused struct tokens *tokens)
  96. {
  97.   exit(0);
  98. }
  99.  
  100. /* Prints working directory */
  101. int cmd_pwd(unused struct tokens *tokens)
  102. {
  103.   char cwd[PATH_MAX];
  104.   char *res = getcwd(cwd, sizeof(cwd));
  105.   if (!res)
  106.   {
  107.     perror(err_msg);
  108.     return -1;
  109.   }
  110.   printf("%s\n", cwd);
  111.   return 0;
  112. }
  113.  
  114. /* Changes directory */
  115. int cmd_cd(unused struct tokens *tokens)
  116. {
  117.   size_t count = tokens_get_length(tokens);
  118.   if (count > 2)
  119.   {
  120.     printf("shell: cd: too many arguments\n");
  121.     return -1;
  122.   }
  123.   char *dir_path;
  124.   if (count == 1) // if no arguments are passed cd takes to home directory
  125.     dir_path = getenv("HOME");
  126.   else
  127.     dir_path = tokens_get_token(tokens, 1);
  128.   int res = chdir(dir_path);
  129.   if (res == -1)
  130.   {
  131.     perror(err_msg);
  132.     return -1;
  133.   }
  134.   return 0;
  135. }
  136.  
  137. /* Waits for all background processes to finish */
  138. int cmd_wait(unused struct tokens *tokens)
  139. {
  140.   while ((wait(NULL)) > 0)
  141.     ;
  142.   return 0;
  143. }
  144.  
  145. int cmd_fg(struct tokens *tokens)
  146. {
  147.   size_t count = tokens_get_length(tokens);
  148.   if (count > 2)
  149.   {
  150.     printf("Usage : fg [pid] or fg\n");
  151.     return -1;
  152.   }
  153.   pid_t pid;
  154.   if (count == 2)
  155.   {
  156.     char *char_pid = tokens_get_token(tokens, 1);
  157.     pid = atoi(char_pid);
  158.   }
  159.   if (count == 1)
  160.   {
  161.     int vector_len = VectorLength(&proc_vec);
  162.     if (!vector_len)
  163.     {
  164.       printf("bash: fg: current: no such job\n");
  165.       return -1;
  166.     }
  167.     else
  168.     {
  169.       proc *last_elem = (proc *)VectorDelete(&proc_vec, vector_len - 1);
  170.       pid = last_elem->pid;
  171.       tcsetattr(shell_terminal, 0, &last_elem->shell_tmodes);
  172.     }
  173.   }
  174.   exec_set_fg_process(0, pid);
  175.   kill(pid, SIGCONT);
  176.   int wstatus;
  177.   waitpid(pid, &wstatus, WUNTRACED);
  178.   if (WIFSTOPPED(wstatus) && WSTOPSIG(wstatus))
  179.   {
  180.     printf("\n[%d]+ stopped   PID : %d\n", 1, pid);
  181.     proc paused_proc = {pid, shell_tmodes};
  182.     tcgetattr(shell_terminal, &(paused_proc.shell_tmodes));
  183.     VectorAppend(&proc_vec, &paused_proc);
  184.   }
  185.   exec_set_fg_process(0, getpid());
  186.   return 0;
  187. }
  188.  
  189. int cmd_bg(struct tokens *tokens)
  190. {
  191.   size_t count = tokens_get_length(tokens);
  192.   if (count > 2)
  193.   {
  194.     printf("Usage : bg [pid] or fg\n");
  195.     return -1;
  196.   }
  197.   pid_t pid;
  198.   if (count == 2)
  199.   {
  200.     char *char_pid = tokens_get_token(tokens, 1);
  201.     pid = atoi(char_pid);
  202.   }
  203.   if (count == 1)
  204.   {
  205.     int vector_len = VectorLength(&proc_vec);
  206.     printf("%d\n", vector_len);
  207.     if (!vector_len)
  208.     {
  209.       printf("bash: bg: current: no such job\n");
  210.       return -1;
  211.     }
  212.     else
  213.     {
  214.       proc *last_elem = (proc *)VectorDelete(&proc_vec, vector_len - 1);
  215.       pid = last_elem->pid;
  216.       tcsetattr(shell_terminal, 0, &last_elem->shell_tmodes);
  217.     }
  218.   }
  219.   kill(pid, SIGCONT);
  220.   return 0;
  221. }
  222.  
  223. /* Looks up the built-in command, if it exists. */
  224. int lookup(char cmd[])
  225. {
  226.   for (unsigned int i = 0; i < sizeof(cmd_table) / sizeof(fun_desc_t); i++)
  227.     if (cmd && (strcmp(cmd_table[i].cmd, cmd) == 0))
  228.       return i;
  229.   return -1;
  230. }
  231.  
  232. /* Intialization procedures for this shell */
  233. void init_shell()
  234. {
  235.   signal(SIGTTOU, SIG_IGN);
  236.   VectorNew(&proc_vec, sizeof(proc), 10);
  237.  
  238.   /* Our shell is connected to standard input. */
  239.   shell_terminal = STDIN_FILENO;
  240.  
  241.   /* Check if we are running interactively */
  242.   shell_is_interactive = isatty(shell_terminal);
  243.  
  244.   if (shell_is_interactive)
  245.   {
  246.     /* If the shell is not currently in the foreground, we must pause the shell until it becomes a
  247.      * foreground process. We use SIGTTIN to pause the shell. When the shell gets moved to the
  248.      * foreground, we'll receive a SIGCONT. */
  249.     while (tcgetpgrp(shell_terminal) != (shell_pgid = getpgrp()))
  250.       kill(-shell_pgid, SIGTTIN);
  251.  
  252.     /* Saves the shell's process id */
  253.     shell_pgid = getpid();
  254.  
  255.     /* Take control of the terminal */
  256.     tcsetpgrp(shell_terminal, shell_pgid);
  257.  
  258.     /* Save the current termios to a variable, so it can be restored later. */
  259.     tcgetattr(shell_terminal, &shell_tmodes);
  260.   }
  261. }
  262.  
  263. int exec_chekc_for_args(unused struct tokens *tokens)
  264. {
  265.   int tok_len = tokens_get_length(tokens);
  266.   char *last_arg = tokens_get_token(tokens, tok_len - 1);
  267.   return (strcmp(last_arg, "<") && strcmp(last_arg, ">"));
  268. }
  269.  
  270. char *exec_search_in_path(char *exe)
  271. {
  272.   char *env_var_PATH = strdup(getenv("PATH"));
  273.   char *cur_dir = strtok(env_var_PATH, ":");
  274.   while (cur_dir)
  275.   {
  276.     char *file_name = malloc(strlen(cur_dir) + strlen(exe) + 1);
  277.     strcat(file_name, cur_dir);
  278.     strcat(file_name, "/");
  279.     strcat(file_name, exe);
  280.     if (access(file_name, F_OK) != -1) // found bin executable
  281.       return file_name;
  282.     cur_dir = strtok(NULL, ":");
  283.   }
  284.   return NULL;
  285. }
  286.  
  287. int exec_check_for_bg(unused struct tokens *tokens)
  288. {
  289.   int tok_len = tokens_get_length(tokens);
  290.   if (tok_len < 1)
  291.     return 0;
  292.   char *last_arg = tokens_get_token(tokens, tok_len - 1);
  293.   return !strcmp(last_arg, "&");
  294. }
  295.  
  296. void exec_redirect(char *file_name, int flags, int std)
  297. {
  298.   if (file_name)
  299.   {
  300.     int fd = open(file_name, flags, PERMISSION);
  301.     if (fd == -1)
  302.     {
  303.       perror(err_msg);
  304.       exit(0);
  305.     }
  306.     int new_fd = dup2(fd, std);
  307.     if (new_fd == -1)
  308.     {
  309.       perror(err_msg);
  310.       exit(0);
  311.     }
  312.     close(fd);
  313.   }
  314. }
  315.  
  316. int exec_set_fg_process(pid_t fd, pid_t pgrp)
  317. {
  318.   if (tcsetpgrp(fd, pgrp) != 0)
  319.   {
  320.     perror(err_msg);
  321.     return -1;
  322.   }
  323.   return 0;
  324. }
  325.  
  326. void exec_parse_arguments(unused struct tokens *tokens, int argc, char **argv, int *output, int *input, int *command_end)
  327. {
  328.   for (int i = 0; i < argc - 1; i++)
  329.   {
  330.     argv[i] = tokens_get_token(tokens, i);
  331.     if (strlen(argv[i]) == 1 && argv[i][0] == '>')
  332.     {
  333.       *output = i;
  334.       int fd = open(tokens_get_token(tokens, i + 1), O_CREAT | O_TRUNC, PERMISSION);
  335.       if (fd == -1)
  336.       {
  337.         perror(err_msg);
  338.         exit(-1);
  339.       }
  340.       int c_fd = close(fd);
  341.       if (c_fd == -1)
  342.       {
  343.         perror(err_msg);
  344.         exit(-1);
  345.       }
  346.       *command_end = *command_end < i ? *command_end : i;
  347.     }
  348.     if (strlen(argv[i]) == 1 && argv[i][0] == '<')
  349.     {
  350.       *input = i;
  351.       if (access(tokens_get_token(tokens, i + 1), F_OK) == -1)
  352.       {
  353.         perror(err_msg);
  354.         exit(-1);
  355.       }
  356.       *command_end = *command_end < i ? *command_end : i;
  357.     }
  358.     if (argv[i][0] == '&')
  359.       *command_end = *command_end < i ? *command_end : i;
  360.   }
  361. }
  362.  
  363. int exec_program(unused struct tokens *tokens)
  364. {
  365.   int bg_proc = exec_check_for_bg(tokens); // background process control
  366.   pid_t pid = fork();
  367.   if (pid == -1)
  368.   {
  369.     perror(err_msg);
  370.     return -1;
  371.   }
  372.  
  373.   if (pid == 0) // CHILD Process
  374.   {
  375.     // change group PID of child process
  376.     if (setpgid(0, 0) != 0)
  377.       perror(err_msg);
  378.  
  379.     // Bring Back default Signal handler
  380.     signal(SIGTTOU, SIG_DFL);
  381.  
  382.     // check if command end with unexpected token
  383.     if (!exec_chekc_for_args(tokens))
  384.     {
  385.       printf("%s: syntax error near unexpected token `newline'\n", err_msg);
  386.       exit(-1);
  387.     }
  388.  
  389.     // prepare arguments for execve
  390.     int argc = tokens_get_length(tokens) + 1;
  391.     int input = -1, output = -1, command_end = argc - 1;
  392.     char *argv[argc];
  393.     exec_parse_arguments(tokens, argc, argv, &output, &input, &command_end);
  394.     argv[command_end] = NULL;
  395.  
  396.     // Redirect output to output_file
  397.     char *output_file = (output == -1) ? NULL : argv[output + 1];
  398.     exec_redirect(output_file, O_WRONLY, STDOUT_FILENO);
  399.  
  400.     // Redirect input from input_file
  401.     char *input_file = (input == -1) ? NULL : argv[input + 1];
  402.     exec_redirect(input_file, O_RDONLY, STDIN_FILENO);
  403.  
  404.     // find programm path
  405.     char *file_name = NULL;
  406.     if (access(argv[0], F_OK) != -1) // check without path
  407.       file_name = argv[0];
  408.     else
  409.       file_name = exec_search_in_path(argv[0]); // check in path folders
  410.  
  411.     // check if such program is found
  412.     if (!file_name)
  413.     {
  414.       printf("%s: %s: command not found\n", err_msg, argv[0]);
  415.       exit(-1);
  416.     }
  417.  
  418.     // execute program
  419.     execv(file_name, argv);
  420.  
  421.     // if code reaches this line that means execve failed
  422.     perror(err_msg);
  423.     exit(-1);
  424.   }
  425.   else // PARENT Process
  426.   {
  427.     // if child process is not background, set it foreground, wait until it's finished, retrieve foreground
  428.     if (!bg_proc)
  429.     {
  430.       if (setpgid(pid, pid) != 0)
  431.         perror(err_msg);
  432.  
  433.       exec_set_fg_process(0, pid);
  434.       int wstatus;
  435.       waitpid(pid, &wstatus, WUNTRACED);
  436.       if (WIFSTOPPED(wstatus) && WSTOPSIG(wstatus))
  437.       {
  438.         printf("\n[%d]+ stopped   PID : %d\n", 1, pid);
  439.         proc paused_proc = {pid, shell_tmodes};
  440.         tcgetattr(shell_terminal, &(paused_proc.shell_tmodes));
  441.         VectorAppend(&proc_vec, &paused_proc);
  442.       }
  443.       exec_set_fg_process(0, getpid());
  444.     }
  445.     else
  446.     {
  447.       printf("\n[%d] PID : %d\n", 1, pid);
  448.     }
  449.   }
  450.  
  451.   return 0;
  452. }
  453.  
  454. int main(unused int argc, unused char *argv[])
  455. {
  456.   init_shell();
  457.  
  458.   static char line[4096];
  459.   int line_num = 0;
  460.  
  461.   /* Please only print shell prompts when standard input is not a tty */
  462.   if (shell_is_interactive)
  463.     fprintf(stdout, "%d: ", line_num);
  464.  
  465.   while (fgets(line, 4096, stdin))
  466.   {
  467.     /* Split our line into words. */
  468.     struct tokens *tokens = tokenize(line);
  469.  
  470.     /* Find which built-in function to run. */
  471.     int fundex = lookup(tokens_get_token(tokens, 0));
  472.  
  473.     if (fundex >= 0)
  474.       cmd_table[fundex].fun(tokens);
  475.     else
  476.       exec_program(tokens);
  477.  
  478.     if (shell_is_interactive)
  479.       /* Please only print shell prompts when standard input is not a tty */
  480.       fprintf(stdout, "%d: ", ++line_num);
  481.  
  482.     /* Clean up memory */
  483.     tokens_destroy(tokens);
  484.   }
  485.  
  486.   return 0;
  487. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement