90s.c (16567B)
1 #include <termios.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <linux/limits.h> 7 #include <time.h> 8 #include <stdbool.h> 9 #include <signal.h> 10 #include <ctype.h> 11 12 #include "color.h" 13 #include "constants.h" 14 #include "history.h" 15 #include "commands.h" 16 17 void *memalloc(size_t size) { 18 void *ptr = malloc(size); 19 if (!ptr) { 20 fputs("90s: Error allocating memory\n", stderr); 21 exit(EXIT_FAILURE); 22 } 23 return ptr; 24 } 25 26 void change_terminal_attribute(int option) { 27 static struct termios oldt, newt; 28 tcgetattr(STDIN_FILENO, &oldt); 29 if (option) { 30 newt = oldt; 31 newt.c_lflag &= ~(ICANON | ECHO); // allows getchar without pressing enter key and echoing the character twice 32 tcsetattr(STDIN_FILENO, TCSANOW, &newt); // set settings to stdin 33 } else { 34 tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // restore to old settings 35 } 36 } 37 38 char **setup_path_variable() { 39 char *envpath = getenv("PATH"); 40 if (envpath == NULL) { 41 fprintf(stderr, "90s: PATH environment variable is missing\n"); 42 exit(EXIT_FAILURE); 43 } 44 char *path_cpy = memalloc(sizeof(char) * (strlen(envpath) + 1)); 45 char *path = memalloc(sizeof(char) * (strlen(envpath) + 1)); 46 strcpy(path_cpy, envpath); 47 strcpy(path, envpath); 48 int path_count = 0; 49 while (*path_cpy != '\0') { 50 // count number of : to count number of elements 51 if (*path_cpy == ':') { 52 path_count++; 53 } 54 path_cpy++; 55 } 56 path_count += 2; // adding one to be correct and one for terminator 57 char **paths = memalloc(sizeof(char *) * path_count); 58 char *token = strtok(path, ":"); 59 int counter = 0; 60 while (token != NULL) { 61 paths[counter] = token; // set element to the pointer of start of path 62 token = strtok(NULL, ":"); 63 counter++; 64 } 65 paths[counter] = NULL; 66 return paths; 67 } 68 69 bool find_command(char **paths, char *command) { 70 if (strncmp(command, "", 1) == 0) { 71 return false; 72 } 73 while (*paths != NULL) { 74 char current_path[PATH_MAX]; 75 current_path[0] = '\0'; 76 sprintf(current_path, "%s/%s", *paths, command); 77 if (access(current_path, X_OK) == 0) { 78 // command is executable 79 return true; 80 } else { 81 if (is_builtin(command)) { 82 return true; 83 } 84 } 85 paths++; 86 } 87 return false; 88 } 89 90 void shiftleft(int chars) { 91 printf("\033[%dD", chars); 92 } 93 94 void shiftright(int chars) { 95 printf("\033[%dC", chars); 96 } 97 98 void clearline() { 99 printf("\033[K"); // clear line to the right of cursor 100 } 101 102 void highlight(char *buffer, char **paths) { 103 char *cmd_part = strchr(buffer, ' '); 104 char *command_without_arg = NULL; 105 int cmd_len = 0; 106 bool valid; 107 108 if (cmd_part != NULL) { 109 cmd_len = cmd_part - buffer; 110 char *cmd = memalloc(sizeof(char) * (cmd_len + 1)); 111 command_without_arg = memalloc(sizeof(char) * (cmd_len + 1)); 112 for (int i = 0; i < (cmd_part - buffer); i++) { 113 cmd[i] = buffer[i]; 114 } 115 strcpy(command_without_arg, cmd); 116 cmd[cmd_len] = '\0'; 117 command_without_arg[cmd_len] = '\0'; 118 valid = find_command(paths, cmd); 119 free(cmd); 120 } else { 121 valid = find_command(paths, buffer); 122 } 123 124 if (valid) { 125 if (command_without_arg != NULL) { 126 buffer += cmd_len; 127 printf("\x1b[38;2;137;180;250m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments 128 buffer -= cmd_len; 129 } else { 130 printf("\x1b[38;2;137;180;250m%s\x1b[0m", buffer); // print green as valid command 131 } 132 } else { 133 if (command_without_arg != NULL) { 134 buffer += cmd_len; 135 printf("\x1b[38;2;243;139;168m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments 136 buffer -= cmd_len; 137 } else { 138 printf("\x1b[38;2;243;139;168m%s\x1b[0m", buffer); // print red as invalid command 139 } 140 } 141 free(command_without_arg); 142 } 143 144 char *readline(char **paths) { 145 int bufsize = RL_BUFSIZE; 146 int position = 0; 147 char *buffer = memalloc(sizeof(char) * bufsize); 148 149 bool moved = false; 150 bool backspaced = false; 151 bool navigated = false; 152 bool insertatmiddle = false; 153 bool replaced = false; 154 155 buffer[0] = '\0'; 156 while (1) { 157 int c = getchar(); // read a character 158 int buf_len = strlen(buffer); 159 160 // check each character user has input 161 switch (c) { 162 case EOF: 163 exit(EXIT_SUCCESS); 164 case 10: { 165 // enter/new line feed 166 if (buf_len == 0) { 167 return NULL; 168 } 169 // check if command includes !! 170 if (strstr(buffer, "!!") != NULL) { 171 char *last_command = read_command(1); 172 if (last_command != NULL) { 173 // replace !! with the last command 174 char *replace = strstr(buffer, "!!"); 175 int replace_len = strlen(replace); 176 int last_command_len = strlen(last_command); 177 int buffer_len = strlen(buffer); 178 if (last_command_len > replace_len) { 179 buffer = realloc(buffer, buffer_len + last_command_len - replace_len + 1); 180 if (!buffer) { 181 fprintf(stderr, "90s: Error allocating memory\n"); 182 exit(EXIT_FAILURE); 183 } 184 } 185 memmove(replace + last_command_len, replace + replace_len, buffer_len - (replace - buffer) - replace_len + 1); 186 memcpy(replace, last_command, last_command_len); 187 position += last_command_len - replace_len; 188 shiftright(last_command_len - replace_len); 189 replaced = true; 190 break; 191 } 192 } else { 193 buffer[buf_len] = '\0'; 194 // clear all characters after the command 195 for (int start = buf_len + 1; buffer[start] != '\0'; start++) { 196 buffer[start] = '\0'; 197 } 198 printf("\n"); // give space for response 199 position = 0; 200 return buffer; 201 } 202 } 203 case 127: // backspace 204 if (buf_len >= 1) { 205 position--; 206 for (int i = position; i < buf_len; i++) { 207 // shift the buffer 208 buffer[i] = buffer[i + 1]; 209 } 210 backspaced = true; 211 moved = false; 212 } 213 break; 214 case 27: // arrow keys comes at three characters, 27, 91, then 65-68 215 if (getchar() == 91) { 216 int arrow_key = getchar(); 217 if (arrow_key == 65) { // up 218 // read history file and fill prompt with latest command 219 char *last_command = read_command(1); 220 if (last_command != NULL) { 221 strcpy(buffer, last_command); 222 } 223 navigated = true; 224 moved = false; 225 break; 226 } else if (arrow_key == 66) { // down 227 char *last_command = read_command(0); 228 if (last_command != NULL) { 229 strcpy(buffer, last_command); 230 } 231 navigated = true; 232 moved = false; 233 break; 234 } else if (arrow_key == 67) { // right 235 if (position < buf_len) { 236 shiftright(1); 237 position++; 238 } 239 moved = true; 240 break; 241 } else if (arrow_key == 68) { // left 242 if (position >= 1) { 243 shiftleft(1); 244 position--; 245 } 246 moved = true; 247 break; 248 } 249 } 250 default: 251 if (c > 31 && c < 127) { 252 if (position == buf_len) { 253 // Append character to the end of the buffer 254 buffer[buf_len] = c; 255 buffer[buf_len + 1] = '\0'; 256 moved = false; 257 navigated = false; 258 } else { 259 // Insert character at the current position 260 memmove(&buffer[position + 1], &buffer[position], buf_len - position + 1); 261 buffer[position] = c; 262 shiftright(1); 263 insertatmiddle = true; 264 } 265 position++; 266 } 267 } 268 269 buf_len = strlen(buffer); 270 271 if (replaced) { 272 shiftleft(buf_len); 273 clearline(); 274 } 275 276 if (navigated && buf_len >= 1) { 277 if (position > 0) { 278 shiftleft(position); // move cursor to the beginning 279 clearline(); 280 } 281 position = buf_len; 282 } 283 284 if (moved) { 285 moved = false; 286 continue; 287 } 288 289 if (!navigated && !replaced) { 290 if (position != buf_len) { 291 // not at normal place 292 if (backspaced) { 293 shiftleft(position + 1); 294 } else { 295 shiftleft(position); // move cursor to the beginning 296 } 297 } else if (buf_len > 1) { 298 if (backspaced) { 299 shiftleft(buf_len + 1); // move cursor to the beginning 300 } else { 301 shiftleft(buf_len - 1); // move cursor to the beginning 302 } 303 } else if (buf_len == 1) { 304 if (backspaced) { 305 shiftleft(2); 306 } 307 } else if (buf_len == 0) { 308 if (backspaced) { 309 shiftleft(1); 310 } 311 } 312 clearline(); 313 } else { 314 navigated = false; 315 } 316 317 highlight(buffer, paths); 318 319 if (backspaced) { 320 if (buf_len != position) { 321 shiftleft(buf_len - position); 322 } 323 backspaced = false; 324 } 325 if (insertatmiddle) { 326 shiftleft(buf_len - position); // move cursor back to where it was 327 insertatmiddle = false; 328 } 329 if (replaced) { 330 replaced = false; 331 } 332 333 // If we have exceeded the buffer, reallocate. 334 if ((buf_len + 1) >= bufsize) { 335 bufsize += RL_BUFSIZE; 336 buffer = realloc(buffer, bufsize); 337 if (!buffer) { 338 fprintf(stderr, "90s: Error allocating memory\n"); 339 exit(EXIT_FAILURE); 340 } 341 } 342 } 343 } 344 345 // split line into arguments 346 char **argsplit(char *line) { 347 int bufsize = TOK_BUFSIZE, position = 0; 348 char **tokens = memalloc(bufsize * sizeof(char*)); 349 char *token; 350 351 token = strtok(line, TOK_DELIM); 352 while (token != NULL) { 353 tokens[position] = token; 354 position++; 355 356 if (position >= bufsize) { 357 bufsize += TOK_BUFSIZE; 358 tokens = realloc(tokens, bufsize * sizeof(char*)); 359 if (!tokens) { 360 fprintf(stderr, "90s: Error allocating memory\n"); 361 exit(EXIT_FAILURE); 362 } 363 } 364 365 token = strtok(NULL, TOK_DELIM); 366 } 367 tokens[position] = NULL; 368 return tokens; 369 } 370 371 char **modifyargs(char **args) { 372 int num_arg = 0; 373 374 // check if command is ls, diff, or grep, if so, add --color=auto to the arguments 375 // this is to make ls, diff, and grep have color without user typing it 376 // this is to make the shell more user friendly 377 while (args[num_arg] != NULL) { 378 num_arg++; 379 } 380 for (int i = 0; i < num_arg; i++) { 381 // makes ls and diff and grep have color without user typing it 382 if (strncmp(args[i], "ls", 2) == 0 || strncmp(args[i], "diff", 4) == 0 || strncmp(args[i], "grep", 4) == 0) { 383 for (int j = num_arg; j > i; j--) { 384 args[j + 1] = args[j]; 385 } 386 args[i + 1] = "--color=auto"; 387 num_arg++; 388 } 389 } 390 391 return args; 392 } 393 394 char *trimws(char *str) { 395 char *end; 396 while (isspace((unsigned char) *str)) 397 str++; 398 if(*str == 0) 399 return str; 400 end = str + strlen(str) - 1; 401 while (end > str && isspace((unsigned char) *end)) 402 end--; 403 *(end+1) = 0; 404 return str; 405 } 406 407 char ***pipe_argsplit(char *line) { 408 char ***cmdv = memalloc(sizeof(char **) * 128); // 127 commands, 1 for NULL 409 char **cmds = memalloc(sizeof(char *) * 128); // 127 arguments, 1 for NULL 410 int num_arg = 0; 411 char *pipe = strtok(line, "|"); 412 while (pipe != NULL) { 413 pipe = trimws(pipe); 414 cmds[num_arg] = strdup(pipe); 415 pipe = strtok(NULL, "|"); 416 num_arg++; 417 } 418 cmds[num_arg] = NULL; 419 420 for (int i = 0; i < num_arg; i++) { 421 char **splitted = argsplit(cmds[i]); 422 cmdv[i] = modifyargs(splitted); 423 424 } 425 cmdv[num_arg] = NULL; 426 free(cmds); 427 return cmdv; 428 } 429 430 // continously prompt for command and execute it 431 void command_loop(char **paths) { 432 char *line; 433 char **args; 434 int status = 1; 435 436 while (status) { 437 time_t t = time(NULL); 438 struct tm* current_time = localtime(&t); // get current time 439 char timestr[256]; 440 char cwdstr[PATH_MAX]; 441 if (strftime(timestr, sizeof(timestr), "[%H:%M:%S]", current_time) == 0) { // format time string 442 return; 443 } 444 if (getcwd(cwdstr, sizeof(cwdstr)) == NULL) { // get current working directory 445 return; 446 } 447 char time[256]; 448 strcpy(time, timestr); 449 char *modtime = memalloc(sizeof(char) * 256); 450 modtime = color_text(time, lavender); // lavender colored time string 451 char *cwd = memalloc(sizeof(char) * (PATH_MAX + 2)); 452 sprintf(cwd, "[%s]", cwdstr); 453 cwd = replace_absolute_home(cwd); 454 cwd = color_text(cwd, pink); // pink colored current directory 455 char *arrow = memalloc(sizeof(char) * 32); 456 strcpy(arrow, "ยป"); 457 arrow = color_text(arrow, blue); 458 printf("%s %s %s ", modtime, cwd, arrow); 459 460 cmd_count = 0; // upward arrow key resets command count 461 line = readline(paths); 462 if (line == NULL) { 463 printf("\n"); 464 continue; 465 } 466 save_command_history(line); 467 bool has_pipe = false; 468 for (int i = 0; line[i] != '\0'; i++) { 469 if (line[i] == '|') { 470 has_pipe = true; 471 break; 472 } 473 } 474 if (has_pipe) { 475 char ***pipe_args = pipe_argsplit(line); 476 status = execute_pipe(pipe_args); 477 while (*pipe_args != NULL) { 478 free(*pipe_args); 479 pipe_args++; 480 } 481 } else { 482 args = argsplit(line); 483 args = modifyargs(args); 484 status = execute(args, STDOUT_FILENO, OPT_FGJ); 485 free(args); 486 } 487 free(line); 488 free(modtime); 489 free(cwd); 490 free(arrow); 491 }; 492 } 493 494 void quit_sig(int sig) { 495 exit(EXIT_SUCCESS); 496 } 497 498 int main(int argc, char **argv) { 499 // setup 500 signal(SIGINT, quit_sig); 501 signal(SIGTERM, quit_sig); 502 signal(SIGQUIT, quit_sig); 503 check_history_file(); 504 char **paths = setup_path_variable(); 505 change_terminal_attribute(1); // turn off echoing and disabling getchar requires pressing enter key to return 506 507 command_loop(paths); 508 509 // cleanup 510 free(paths); 511 change_terminal_attribute(0); // change back to default settings 512 return EXIT_SUCCESS; 513 }