90s

Minimalist, customizable shell written in C99 with syntax highlighting
git clone https://codeberg.org/night0721/90s
Log | Files | Refs | README | LICENSE

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 }