commands.c (11941B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <stdbool.h> 6 #include <errno.h> 7 #include <sys/wait.h> 8 9 #include "constants.h" 10 #include "history.h" 11 #include "90s.h" 12 #include "job.h" 13 14 int execute(char **args); 15 16 // Function declarations for builtin commands 17 int cd(char **args); 18 int help(char **args); 19 int quit(char **args); 20 int history(char **args); 21 int export(char **args); 22 int source(char **args); 23 int j(char **args); 24 int bg(char **args); 25 26 // List of builtin commands' names 27 char *builtin_cmds[] = { 28 "cd", 29 "help", 30 "exit", 31 "history", 32 "export", 33 "source", 34 "j", 35 "bg", 36 }; 37 38 int (*builtin_func[]) (char **) = { 39 &cd, 40 &help, 41 &quit, // cant name it exit as it is taken 42 &history, 43 &export, 44 &source, 45 &j, 46 &bg, 47 }; 48 49 char *shortcut_dirs[] = { 50 "90s", 51 "bin", 52 "localbin", 53 }; 54 55 char *shortcut_expand_dirs[] = { 56 "~/.nky/Coding/C/90s", 57 "~/.local/bin", 58 "/usr/local/bin", 59 }; 60 61 char *gethome() { 62 char *home = getenv("HOME"); 63 if (home == NULL) { 64 fprintf(stderr, "Error: HOME environment variable not set.\n"); 65 exit(EXIT_FAILURE); 66 } 67 return home; 68 } 69 70 char *replace_home_dir(char *str) { 71 char *home_path = gethome(); 72 73 int path_len = strlen(str); 74 int home_len = strlen(home_path); 75 76 // Allocate memory for the new path 77 char* new_path = memalloc(sizeof(char) * (path_len + home_len + 1)); 78 79 int i = 0, j = 0; 80 while (str[i] != '\0') { 81 if (str[i] == '~') { 82 // Copy HOME environment variable value 83 for (int k = 0; k < home_len; k++) { 84 new_path[j++] = home_path[k]; 85 } 86 i++; 87 } else { 88 new_path[j++] = str[i++]; 89 } 90 } 91 92 new_path[j] = '\0'; 93 return new_path; 94 } 95 96 char *replace_absolute_home(char *str) { 97 char *home_path = gethome(); 98 99 int path_len = strlen(str); 100 int home_len = strlen(home_path); 101 102 // Allocate memory for the new path 103 char* new_path = memalloc(sizeof(char) * (path_len - home_len + 2)); 104 105 int i = 0, j = 0; 106 while (str[i] != '\0') { 107 if (strncmp(&str[i], home_path, home_len) == 0) { 108 // Copy HOME environment variable value 109 new_path[j++] = '~'; 110 i += home_len; 111 } else { 112 new_path[j++] = str[i++]; 113 } 114 } 115 116 new_path[j] = '\0'; 117 return new_path; 118 } 119 120 // number of built in commands 121 int num_builtins() { 122 return sizeof(builtin_cmds) / sizeof(char *); 123 } 124 125 // autojump 126 int j(char **args) { 127 if (args[1] == NULL) { 128 fprintf(stderr, "90s: not enough arguments\n"); 129 return -1; 130 } 131 for (int i = 0; i < sizeof(shortcut_dirs) / sizeof(char *); i++) { 132 int len = strlen(shortcut_dirs[i]); 133 if (strncmp(args[1], shortcut_dirs[i], len) == 0) { 134 char **merged_cd = memalloc(sizeof(char *) * 3); 135 merged_cd[0] = "cd"; 136 merged_cd[1] = shortcut_expand_dirs[i]; 137 merged_cd[2] = NULL; 138 cd(merged_cd); 139 printf("jumped to %s\n", shortcut_expand_dirs[i]); 140 return 1; 141 } 142 } 143 return 1; 144 } 145 146 // change directory 147 int cd(char **args) { 148 int i = 0; 149 if (args[1] == NULL) { 150 char *home = gethome(); 151 if (chdir(home) != 0) { 152 perror("90s"); 153 } 154 } else { 155 while (args[1][i] != '\0') { 156 if (args[1][i] == '~') { 157 args[1] = replace_home_dir(args[1]); 158 break; 159 } 160 i++; 161 } 162 if (chdir(args[1]) != 0) { 163 perror("90s"); 164 } 165 } 166 return 1; 167 } 168 169 // show help menu 170 int help(char **args) { 171 printf("90s %s\n", VERSION); 172 printf("Built in commands:\n"); 173 174 for (int i = 0; i < num_builtins(); i++) { 175 printf(" %s\n", builtin_cmds[i]); 176 } 177 178 printf("Use 'man' to read manual of programs\n"); 179 printf("Licensed under GPL v3\n"); 180 return 1; 181 } 182 183 int quit(char **args) { 184 return 0; // exit prompting loop, which also the shell 185 } 186 187 int history(char **args) { 188 char **history = get_all_history(true); 189 190 for (int i = 0; history[i] != NULL; ++i) { 191 printf("%s\n", history[i]); 192 free(history[i]); 193 } 194 195 free(history); 196 return 1; 197 } 198 199 int export(char **args) { 200 args++; // skip the command 201 while (*args != NULL) { 202 char *variable = strtok(*args, "=\n"); 203 char *value = strtok(NULL, "=\n"); 204 if (variable != NULL && value != NULL) { 205 if (setenv(variable, value, 1) != 0) { 206 fprintf(stderr, "90s: Error setting environment variable\n"); 207 return 0; 208 } 209 } else { 210 fprintf(stderr, "90s: Syntax error when setting environment variable\nUse \"export VARIABLE=VALUE\"\n"); 211 return 0; 212 } 213 args++; 214 } 215 return 1; 216 } 217 218 int source(char **args) { 219 if (args[1] == NULL) { 220 fprintf(stderr, "90s: not enough arguments\n"); 221 return -1; 222 } 223 224 FILE *file = fopen(args[1], "r"); 225 226 if (file == NULL) { 227 fprintf(stderr, "90s: no such file or directory '%s'\n", args[1]); 228 return -1; 229 } 230 231 char line[RL_BUFSIZE]; 232 int status; 233 while (fgets(line, sizeof(line), file) != NULL) { 234 // Remove newline character if present 235 size_t len = strlen(line); 236 if (len > 0 && line[len - 1] == '\n') { 237 line[len - 1] = '\0'; 238 } 239 240 char **args = argsplit(line); 241 status = execute(args); 242 } 243 244 fclose(file); 245 return status; // Indicate success 246 } 247 248 int bg(char **args) { 249 if (args[1] == NULL) { 250 fprintf(stderr, "90s: not enough arguments\n"); 251 return -1; 252 } 253 int job_index = atoi(args[1]); 254 if (job_index == 0) { 255 fprintf(stderr, "90s: invalid job index\n"); 256 return -1; 257 } 258 job *search = get_job(job_index - 1); 259 if (search == NULL) { 260 fprintf(stderr, "90s: no such job\n"); 261 return -1; 262 } 263 printf("Job %i: %s\n", job_index, search->command); 264 return 1; 265 } 266 bool is_builtin(char *command) { 267 for (int i = 0; i < num_builtins(); i++) { 268 if (strcmp(command, builtin_cmds[i]) == 0) { 269 return true; 270 } 271 } 272 return false; 273 } 274 275 int launch(char **args, int fd, int options) { 276 int is_bgj = (options & OPT_BGJ) ? 1 : 0; 277 int redirect_stdout = (options & OPT_STDOUT) ? 1 : 0; 278 int redirect_stdin = (options & OPT_STDIN) ? 1 : 0; 279 int redirect_stderr = (options & OPT_STDERR) ? 1 : 0; 280 281 pid_t pid; 282 283 int status; 284 if ((pid = fork()) == 0) { 285 // Child process 286 if (fd > 2) { 287 // not stdin, stdout, or stderr 288 if (redirect_stdout) { 289 if (dup2(fd, STDOUT_FILENO) == -1) { 290 perror("90s"); 291 } 292 } 293 if (redirect_stdin) { 294 if (dup2(fd, STDIN_FILENO) == -1) { 295 perror("90s"); 296 } 297 } 298 if (redirect_stderr) { 299 if (dup2(fd, STDERR_FILENO) == -1) { 300 perror("90s"); 301 } 302 } 303 close(fd); // close fd as it is duplicated already 304 } 305 if (execvp(args[0], args) == -1) { 306 if (errno == ENOENT) { 307 fprintf(stderr, "90s: command not found: %s\n", args[0]); 308 } 309 } 310 exit(EXIT_FAILURE); // exit the child 311 } else if (pid < 0) { 312 perror("fork failed"); 313 } else { 314 // Parent process 315 if (is_bgj) { 316 int job_index = add_job(pid, args[0], true); 317 printf("[Job: %i] [Process ID: %i] [Command: %s]\n", job_index + 1, pid, args[0]); 318 return 1; 319 } else { 320 do { 321 waitpid(pid, &status, WUNTRACED); // wait child to be exited to return to prompt 322 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 323 } 324 } 325 return 1; 326 } 327 // execute built in commands or launch commands and wait it to terminate, return 1 to keep shell running 328 int execute(char **args) { 329 if (args[0] == NULL) { // An empty command was entered. 330 return 1; 331 } 332 333 // prioritize builtin commands 334 for (int i = 0; i < num_builtins(); i++) { 335 if (strcmp(args[0], builtin_cmds[i]) == 0) { 336 return (*builtin_func[i])(args); 337 } 338 } 339 int num_arg = 0; 340 341 while (args[num_arg] != NULL) { 342 if (strncmp(args[num_arg], "&", 1) == 0) { 343 args[num_arg] = NULL; 344 if (args[num_arg + 1] != NULL) { 345 // have commands after & 346 execute(&args[num_arg + 1]); 347 } 348 launch(args, STDOUT_FILENO, OPT_BGJ); 349 return 1; 350 } 351 if (strncmp(args[num_arg], ">", 1) == 0) { 352 int fd = fileno(fopen(args[num_arg + 1], "w+")); 353 if (fd == -1) { 354 perror("90s"); 355 return 1; 356 } 357 args[num_arg] = NULL; 358 int asdf = 0; 359 while (args[asdf] != NULL) { 360 fprintf(stderr, "args[%i]: %s\n", asdf, args[asdf]); 361 asdf++; 362 } 363 364 return launch(args, fd, OPT_FGJ | OPT_STDOUT); 365 } 366 if (strncmp(args[num_arg], "<", 1) == 0) { 367 int fd = fileno(fopen(args[num_arg + 1], "r")); 368 if (fd == -1) { 369 perror("90s"); 370 return 1; 371 } 372 args[num_arg] = NULL; 373 return launch(args, fd, OPT_FGJ | OPT_STDIN); 374 } 375 if (strncmp(args[num_arg], "2>", 2) == 0) { 376 int fd = fileno(fopen(args[num_arg + 1], "w+")); 377 if (fd == -1) { 378 perror("90s"); 379 return 1; 380 } 381 args[num_arg] = NULL; 382 return launch(args, fd, OPT_FGJ | OPT_STDERR); 383 } 384 if (strncmp(args[num_arg], ">&", 2) == 0) { 385 int fd = fileno(fopen(args[num_arg + 1], "w+")); 386 if (fd == -1) { 387 perror("90s"); 388 return 1; 389 } 390 args[num_arg] = NULL; 391 return launch(args, fd, OPT_FGJ | OPT_STDOUT | OPT_STDERR); 392 } 393 if (strncmp(args[num_arg], ">>", 2) == 0) { 394 int fd = fileno(fopen(args[num_arg + 1], "a+")); 395 if (fd == -1) { 396 perror("90s"); 397 return 1; 398 } 399 args[num_arg] = NULL; 400 return launch(args, fd, OPT_FGJ | OPT_STDOUT); 401 } 402 403 num_arg++; // count number of args 404 } 405 406 return launch(args, STDOUT_FILENO, OPT_FGJ); 407 } 408 409 // execute_pipe with as many pipes as needed 410 int execute_pipe(char ***args) { 411 int pipefd[2]; 412 pid_t pid; 413 int in = 0; 414 415 int num_cmds = 0; 416 while (args[num_cmds] != NULL) { 417 int num_args = 0; 418 while (args[num_cmds][num_args] != NULL) { 419 num_args++; 420 } 421 num_cmds++; 422 } 423 424 for (int i = 0; i < num_cmds - 1; i++) { 425 pipe(pipefd); 426 if ((pid = fork()) == 0) { 427 // then this (child) 428 dup2(in, STDIN_FILENO); // get input from previous command 429 if (i < num_cmds - 1) { 430 dup2(pipefd[1], STDOUT_FILENO); // make output go to pipe (next output) 431 } 432 close(pipefd[0]); // close original input 433 execute(args[i]); 434 exit(EXIT_SUCCESS); 435 } else if (pid < 0) { 436 perror("fork failed"); 437 } 438 // this will be executed first 439 close(pipefd[1]); // close output 440 in = pipefd[0]; // save the input for the next command 441 } 442 443 if ((pid = fork()) == 0) { 444 dup2(in, STDIN_FILENO); // get input from pipe 445 execute(args[num_cmds - 1]); 446 exit(EXIT_SUCCESS); 447 } else if (pid < 0) { 448 perror("fork failed"); 449 } 450 451 close(in); 452 for (int i = 0; i < num_cmds; i++) { 453 waitpid(pid, NULL, 0); 454 } 455 return 1; 456 }