fork download
  1. /********************************************************************************************************************/
  2.  
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <glob.h>
  6. #include <unistd.h>
  7. #include <assert.h>
  8.  
  9. /********************************************************************************************************************/
  10.  
  11. #include <stdio.h>
  12. #include <fcntl.h>
  13. #include <errno.h>
  14.  
  15. /********************************************************************************************************************/
  16.  
  17. #include <sys/wait.h> // Declares the functions used for holding processes / waiting.
  18. #include <sys/types.h>
  19. #include <sys/stat.h>
  20.  
  21. /********************************************************************************************************************/
  22.  
  23. #ifndef BUFFER_SIZE
  24. #define BUFFER_SIZE 1024
  25. #endif // To define amount of memory allocated for the temporary storage of data.
  26.  
  27. /********************************************************************************************************************/
  28.  
  29. // Declaring all function prototypes for this program which will be defined later in the program.
  30. int parse_command(char *command, char ***tokens); // To parse throught the given command.
  31. int execute_command(char **tokens, int status); // To execute the given command to shell.
  32.  
  33. /********************************************************************************************************************/
  34.  
  35. int main(int argc, char **argv) { // One input loop and command parsing algorithm that works for both the modes.
  36.  
  37. int fd = STDIN_FILENO; // fd is the file descriptor which is defined to be set to standard input.
  38. char *command = NULL; char **tokens = NULL;
  39. int status = 0; // To check the exit status of last command
  40.  
  41. if (argc > 1) { fd = open(argv[1], O_RDONLY); // To check if shell is in batch mode.
  42. if (fd == -1) { perror("open"); exit(EXIT_FAILURE); } // Opening specified file.
  43. } else { fd = 0; }
  44.  
  45. if (isatty(fd)) { // To check if shell is in interactive mode, reading from stdin.
  46. write(STDOUT_FILENO, "Welcome to mysh!\n", 17);
  47. }
  48.  
  49. if (status != 0) { write(STDOUT_FILENO, "!mysh> ", strlen("!mysh> ")); }
  50. else { write(STDOUT_FILENO, "mysh> ", strlen("mysh> ")); }
  51.  
  52. ssize_t bytes_read; char buffer[BUFFER_SIZE]; // Buffer to store the prompt from the shell.
  53. // int lenCommand;
  54.  
  55. while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) { // Append buffer to command
  56. // if (command == NULL) { lenCommand = 0; } else { lenCommand = strlen(command); }
  57. command = realloc(command, BUFFER_SIZE + bytes_read + 1);
  58.  
  59. if (command == NULL) { perror("realloc"); exit(EXIT_FAILURE); }
  60. memcpy(command, buffer, bytes_read);
  61.  
  62. char *line; // Now, we will parse and execute the commands
  63.  
  64. while ((line = strsep(&command, "\n")) != NULL) { // Split the string into individual command lines
  65. if (*line == '\0') { continue; } // Checking if it is an empty line or not.
  66.  
  67. /* If not, it calls a function called parse_command() to tokenize the command line into an array
  68.   of individual tokens, which are then passed to a function called execute_command() along with the
  69.   current status code (initially 0). */
  70.  
  71. if (parse_command(line, &tokens) == -1) { status = 1; continue; }
  72. status = execute_command(tokens, status); free(tokens);
  73. }
  74.  
  75. if (status != 0) { write(STDOUT_FILENO, "!mysh> ", strlen("!mysh> ")); }
  76. else { write(STDOUT_FILENO, "mysh> ", strlen("mysh> ")); }
  77. }
  78.  
  79. if (bytes_read == -1) { perror("read"); exit(EXIT_FAILURE); }
  80.  
  81. printf("Exiting mysh.\n"); write(STDOUT_FILENO, "Exiting mysh.\n", strlen("Exiting mysh.\n"));
  82. free(command); close(fd); exit(EXIT_SUCCESS); // Finally, cleaning, closing and exiting shell.
  83. }
  84.  
  85. /********************************************************************************************************************/
  86.  
  87. int parse_command(char *command, char **tokens[]) { // Returns a parsed version of the command as an array of tokens.
  88. // It also performs file and pipe redirection, and glob expansion, as specified in the command.
  89.  
  90. char *token; int num_tokens = 0; // To store the total number of tokens parsed.
  91. glob_t glob_result; // A glob structure for holding glob expansion results.
  92. int out_pipe = 0; // A flag for whether there is a pipe output redirection.
  93. int pipefd[2]; // A pipe file descriptor.
  94.  
  95. *tokens = malloc(sizeof(char *)); // Allocating an initial array of one string token.
  96. if (*tokens == NULL) { perror("malloc"); exit(EXIT_FAILURE); }
  97.  
  98. /* Parsing the command into tokens using the strsep function, which splits the command string into tokens using
  99.   the given delimiter (" \t\n" in this case). It then checks whether the token is empty and skips it if it is. */
  100.  
  101. while ((token = strsep(&command, " \t\n")) != NULL) { if (*token == '\0') { continue; }
  102.  
  103. if (strcmp(token, "<") == 0 || strcmp(token, ">") == 0 || strcmp(token, "|") == 0) {
  104. // To check whether the token is a file or pipe redirection.
  105.  
  106. if ((token = strsep(&command, " \t\n")) == NULL) {
  107. fprintf(stderr, "mysh: syntax error: missing file or command name\n"); return -1;
  108. }
  109.  
  110. if (strcmp(token, "|") == 0) { // If the token is for pipe redirection (denoted by '|').
  111.  
  112. if (out_pipe) { // Checks multiple consecutive pipe characters
  113. fprintf(stderr, "mysh: syntax error: unexpected pipe character\n"); return -1;
  114. } out_pipe = 1;
  115.  
  116. if (pipe(pipefd) == -1) { perror("pipe"); return -1; }
  117. }
  118.  
  119. else { int fd; // If the token is for file (input/output) redirection (denoted by either '<' or '>')
  120.  
  121. /* The dup2() system call redirects the standard input or output of the command to the file descriptor
  122.   returned by the open() call. */
  123.  
  124. if (strcmp((*tokens)[num_tokens - 1], "<") == 0) { fd = open(token, O_RDONLY); }
  125. else { fd = open(token, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP); }
  126.  
  127. if (fd == -1) { perror("open"); return -1; }
  128.  
  129. // If the redirection token is '<', the standard input is redirected using dup2(fd, STDIN_FILENO).
  130. if (strcmp((*tokens)[num_tokens - 1], "<") == 0) {
  131. if (dup2(fd, STDIN_FILENO) == -1) { perror("dup2"); return -1; }
  132. }
  133.  
  134. // If the redirection token is '>', the standard output is redirected using dup2(fd, STDOUT_FILENO).
  135. else { if (dup2(fd, STDOUT_FILENO) == -1) { perror("dup2"); return -1; } }
  136.  
  137. close(fd);
  138. }
  139. }
  140.  
  141. else { // If not, it is a token containing an asterisk (*), which is a wildcard (or glob) : Glob expansion
  142. int num_matches;
  143.  
  144. if (strchr(token, '*') != NULL) { // Checks if current token being processed contains an asterisk ('*')
  145.  
  146. /* This line of code is calling the glob() function, which performs glob expansion on a given token
  147.   (i.e., a string containing an asterisk wildcard character) */
  148.  
  149. if (glob(token, 0, NULL, &glob_result) != 0) {
  150. // If it returns a value of zero, it means that the glob expansion was successful.
  151. fprintf(stderr, "mysh: glob error: %s\n", strerror(errno)); return -1;
  152. }
  153.  
  154. num_matches = glob_result.gl_pathc; // To store the number of matching file names from the structure.
  155. for (int i = 0; i < num_matches; i++) {
  156. (*tokens)[num_tokens] = strdup(glob_result.gl_pathv[i]);
  157.  
  158. if ((*tokens)[num_tokens] == NULL) { perror("malloc"); exit(EXIT_FAILURE); }
  159. num_tokens++;
  160.  
  161. *tokens = realloc(*tokens, (num_tokens + 1) * sizeof(char *));
  162. if (*tokens == NULL) { perror("realloc"); exit(EXIT_FAILURE); }
  163. }
  164. globfree(&glob_result); // Program frees the memory used by the glob_result object using globfree()
  165. }
  166.  
  167. else { (*tokens)[num_tokens] = strdup(token);
  168.  
  169. if ((*tokens)[num_tokens] == NULL) { perror("malloc"); exit(EXIT_FAILURE); }
  170. num_tokens++;
  171.  
  172. *tokens = realloc(*tokens, (num_tokens + 1) * sizeof(char *));
  173. if (*tokens == NULL) { perror("realloc"); exit(EXIT_FAILURE); }
  174. }
  175. }
  176. }
  177.  
  178. // NULL-terminate token array : terminates the token array with a NULL pointer, indicating the end of the array.
  179. (*tokens)[num_tokens] = NULL;
  180.  
  181. return num_tokens;
  182. }
  183.  
  184. /********************************************************************************************************************/
  185.  
  186. int execute_command(char **tokens, int status) { // Execute a command entered by the user in a shell program.
  187.  
  188. // Checking if the command entered by the user is a built-in command or not by comparing the first token.
  189. if (strcmp(tokens[0], "cd") == 0) {
  190.  
  191. if (tokens[1] == NULL) { // Checks if a second token exists or not. If not, prints error.
  192. fprintf(stderr, "mysh: syntax error: missing argument to \"cd\"\n"); return 1;
  193. }
  194.  
  195. // Changes current working directory to the directory specified by second token using the 'chdir' function.
  196. if (chdir(tokens[1]) == -1) { perror("chdir"); return 1; }
  197. return 0;
  198. }
  199.  
  200. if (strcmp(tokens[0], "exit") == 0) { exit(0); }
  201.  
  202. if (strcmp(tokens[0], "pwd") == 0) { char cwd[1024]; // Buffer to store the current working directory.
  203.  
  204. if (getcwd(cwd, sizeof(cwd)) != NULL) { printf("%s\n", cwd); return 0; }
  205. else { perror("getcwd"); return 1; }
  206. }
  207.  
  208. pid_t pid = fork(); // Forking a child process and setting up file redirection for the child process.
  209. if (pid == -1) { perror("fork"); return 1; }
  210.  
  211. if (pid == 0) { // If pid is 0, then the current process is the child process. Now, setting up file redirection.
  212. for (int i = 1; tokens[i] != NULL; i++) {
  213.  
  214. if (strcmp(tokens[i], "<") == 0) { int fd = open(tokens[i+1], O_RDONLY);
  215.  
  216. if (fd == -1) { perror("open"); exit(EXIT_FAILURE); }
  217. if (dup2(fd, STDIN_FILENO) == -1) { perror("dup2"); exit(EXIT_FAILURE); }
  218. close(fd); tokens[i] = NULL;
  219. }
  220.  
  221. else if (strcmp(tokens[i], ">") == 0) {
  222. int fd = open(tokens[i+1], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
  223.  
  224. if (fd == -1) { perror("open"); exit(EXIT_FAILURE); }
  225. if (dup2(fd, STDOUT_FILENO) == -1) { perror("dup2"); exit(EXIT_FAILURE); }
  226. close(fd); tokens[i] = NULL;
  227. }
  228. }
  229.  
  230. if (execvp(tokens[0], tokens) == -1) { perror("execvp"); exit(EXIT_FAILURE); } // Execute the command.
  231. }
  232.  
  233. int child_status; // Parent process waits for the child process to finish.
  234. if (waitpid(pid, &child_status, 0) == -1) { perror("waitpid"); return 1; }
  235.  
  236. if (WIFEXITED(child_status)) { status = WEXITSTATUS(child_status); } // Update status
  237. return status;
  238. }
  239.  
  240. /********************************************************************************************************************/
Success #stdin #stdout 0s 5360KB
stdin
Standard input is empty
stdout
mysh> Exiting mysh.
Exiting mysh.