/ shell.c
shell.c
  1  #include <stdio.h>
  2  #include <sys/wait.h>
  3  #include <sys/stat.h>
  4  #include <stdlib.h>
  5  #include <string.h>
  6  #include <unistd.h>
  7  #include <errno.h>
  8  
  9  const int MAX_ARGS = 48;
 10  const int MAX_PATH_ENTRIES = 48;
 11  char DEFAULT_PATH[] = "/usr/bin:/run/current-system/sw/bin";
 12  const char PROMPT[] = "> ";
 13  
 14  struct Cmd {
 15    int argc;
 16    char **argv;
 17  };
 18  
 19  struct Cmd *line_to_cmd(char *line) {
 20    char *p = strtok(line, " ");
 21    int count = 0;
 22    char **argv = malloc(MAX_ARGS * (sizeof(p)));
 23  
 24    while (p != NULL) {
 25      argv[count++] = p;
 26      p = strtok(NULL, " ");
 27      if (count == MAX_ARGS) {
 28        fprintf(stderr, "Max args reached!\n");
 29        break;
 30      }
 31    }
 32  
 33    if (count == 0)
 34      return NULL;
 35  
 36    argv[count] = NULL;
 37  
 38    struct Cmd *cmd = malloc(sizeof(struct Cmd));
 39    cmd->argc = count;
 40    cmd->argv = argv;
 41  
 42    return cmd;
 43  }
 44  
 45  char **path_from_env(char *default_path) {
 46    char *path_s = getenv("PATH");
 47    if (!path_s) {
 48      fprintf(stderr, "PATH not set! Falling back to default %s\n", default_path);
 49      path_s = default_path;
 50    }
 51  
 52    char **path = malloc(MAX_PATH_ENTRIES * sizeof(path_s));
 53    char *dir = strtok(path_s, ":");
 54    int i = 0;
 55    while (dir != NULL) {
 56      path[i++] = dir;
 57      if (i == MAX_PATH_ENTRIES) {
 58        fprintf(stderr, "MAX_PATH_ENTRIES reached!");
 59        break;
 60      }
 61      dir = strtok(NULL, ":");
 62    }
 63    return path;
 64  }
 65  
 66  char *resolve_path(char *cmd, char **path) {
 67    if (cmd == NULL)
 68      return cmd;
 69  
 70    struct stat sb;
 71    int i = 0;
 72    char *dir = path[i];
 73    while (dir != NULL) {
 74      char *full_path = malloc(strlen(dir) + strlen(cmd) + 2);
 75      sprintf(full_path, "%s/%s", dir, cmd);
 76      if (lstat(full_path, &sb) == -1) {
 77        free(full_path);
 78        dir = path[i++];
 79        continue;
 80      }
 81      return full_path;
 82    }
 83    return cmd;
 84  }
 85  
 86  int eval(char *line, char **PATH) {
 87    struct Cmd *cmd = line_to_cmd(line);
 88    if (!cmd)
 89      return 0;
 90  
 91    char *path = resolve_path(cmd->argv[0], PATH);
 92    if (!path) {
 93      fprintf(stderr, "Command '%s' not found\n", line);
 94      return -1;
 95    }
 96  
 97    int pid = fork();
 98    if (pid < 1) {
 99      execve(path, cmd->argv, NULL);
100      perror("execve");
101    } else {
102      free(path);
103      free(cmd->argv);
104      free(cmd);
105      return pid;
106    }
107  }
108  
109  int readline(char *buf, size_t buf_size) {
110    int nbytes = getline(&buf, &buf_size, stdin);
111    if (nbytes > 0) {
112      nbytes = strcspn(buf, "\n");
113      buf[nbytes] = '\0';
114    }
115    return nbytes;
116  }
117  
118  int main(int argc, char *argv[]) {
119    size_t INPUT_BUFFER_SIZE = 1024;
120    char *line = malloc(INPUT_BUFFER_SIZE);
121    if (!line) {
122      perror("malloc");
123      exit(1);
124    }
125  
126    char **path = path_from_env(DEFAULT_PATH);
127  
128    while (1) {
129      printf("%s", PROMPT);
130      int res = readline(line, INPUT_BUFFER_SIZE);
131      if (res < 0) {
132        exit(1);
133      } else if (res == 0) {
134        continue;
135      };
136      int pid = eval(line, path);
137      if (pid > 0) {
138        wait(&pid);
139      }
140      fflush(stdout);
141    }
142  
143    free(line);
144  }