a small Unix shell written in C
| 1 | #define _POSIX_C_SOURCE 200809L |
| 2 | #include <ctype.h> |
| 3 | #include <errno.h> |
| 4 | #include <fcntl.h> |
| 5 | #include <signal.h> |
| 6 | #include <stdio.h> |
| 7 | #include <stdlib.h> |
| 8 | #include <string.h> |
| 9 | #include <sys/wait.h> |
| 10 | #include <unistd.h> |
| 11 | |
| 12 | /* Deliberate limits keep parsing storage and ownership straightforward. */ |
| 13 | #define MAXTOK 256 |
| 14 | #define MAXWORD 8192 |
| 15 | |
| 16 | enum { WORD, INPUT, OUTPUT, APPEND, PIPE, BACKGROUND }; |
| 17 | struct token { int type; char *text; }; |
| 18 | struct command { |
| 19 | char *argv[MAXTOK + 1]; |
| 20 | int first, last; /* Token range, including redirections. */ |
| 21 | }; |
| 22 | static struct token tokens[MAXTOK]; |
| 23 | static struct command commands[MAXTOK]; |
| 24 | static volatile sig_atomic_t interrupted; |
| 25 | |
| 26 | static void on_signal(int signo) |
| 27 | { |
| 28 | if (signo == SIGINT) { |
| 29 | interrupted = 1; |
| 30 | (void)write(STDOUT_FILENO, "\n", 1); |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | static int append(char *word, size_t *len, const char *s, size_t n) |
| 35 | { |
| 36 | if (n >= MAXWORD - *len) { |
| 37 | fprintf(stderr, "shell: word too long\n"); |
| 38 | return -1; |
| 39 | } |
| 40 | memcpy(word + *len, s, n); |
| 41 | *len += n; |
| 42 | word[*len] = '\0'; |
| 43 | return 0; |
| 44 | } |
| 45 | |
| 46 | /* Quotes and backslashes group words. Expansion never splits words or |
| 47 | * introduces operators; globbing and command substitution are not supported. */ |
| 48 | static int tokenize(const char *s, int status, int *count) |
| 49 | { |
| 50 | *count = 0; |
| 51 | while (*s) { |
| 52 | char word[MAXWORD] = ""; |
| 53 | size_t len = 0; |
| 54 | int quote = 0, type = WORD; |
| 55 | while (isspace((unsigned char)*s)) s++; |
| 56 | if (!*s) break; |
| 57 | if (*count == MAXTOK) { |
| 58 | fprintf(stderr, "shell: too many tokens\n"); |
| 59 | return -1; |
| 60 | } |
| 61 | if (strchr("<>|&", *s)) { |
| 62 | switch (*s++) { |
| 63 | case '<': type = INPUT; break; |
| 64 | case '>': type = OUTPUT; if (*s == '>') { type = APPEND; s++; } break; |
| 65 | case '|': type = PIPE; break; |
| 66 | case '&': type = BACKGROUND; break; |
| 67 | } |
| 68 | } else while (*s) { |
| 69 | if (!quote && (isspace((unsigned char)*s) || strchr("<>|&", *s))) break; |
| 70 | if (*s == quote) { quote = 0; s++; continue; } |
| 71 | if (!quote && (*s == '\'' || *s == '"')) { quote = *s++; continue; } |
| 72 | if (*s == '\\' && quote != '\'') { |
| 73 | s++; |
| 74 | if (!*s) { fprintf(stderr, "shell: trailing backslash\n"); return -1; } |
| 75 | /* Inside double quotes, only escape $, quote, and backslash. */ |
| 76 | if (quote == '"' && !strchr("$\"\\", *s)) |
| 77 | if (append(word, &len, "\\", 1) < 0) return -1; |
| 78 | } else if (*s == '$' && quote != '\'') { |
| 79 | const char *value = NULL; |
| 80 | char name[MAXWORD], number[32]; |
| 81 | size_t n = 0; |
| 82 | s++; |
| 83 | if (*s == '?') { |
| 84 | snprintf(number, sizeof number, "%d", status); |
| 85 | value = number; |
| 86 | s++; |
| 87 | } else if (isalpha((unsigned char)*s) || *s == '_') { |
| 88 | while (isalnum((unsigned char)*s) || *s == '_') { |
| 89 | if (n == sizeof name - 1) { |
| 90 | fprintf(stderr, "shell: variable name too long\n"); return -1; |
| 91 | } |
| 92 | name[n++] = *s++; |
| 93 | } |
| 94 | name[n] = '\0'; |
| 95 | value = getenv(name); |
| 96 | if (!value) value = ""; |
| 97 | } else value = "$"; |
| 98 | if (append(word, &len, value, strlen(value)) < 0) return -1; |
| 99 | continue; |
| 100 | } |
| 101 | if (append(word, &len, s++, 1) < 0) return -1; |
| 102 | } |
| 103 | if (quote) { fprintf(stderr, "shell: unclosed quote\n"); return -1; } |
| 104 | tokens[*count].type = type; |
| 105 | tokens[*count].text = type == WORD ? strdup(word) : NULL; |
| 106 | if (type == WORD && !tokens[*count].text) { perror("strdup"); return -1; } |
| 107 | (*count)++; |
| 108 | } |
| 109 | return 0; |
| 110 | } |
| 111 | |
| 112 | static int parse(int nt, int *background) |
| 113 | { |
| 114 | int nc = 0, argc = 0; |
| 115 | *background = nt && tokens[nt - 1].type == BACKGROUND; |
| 116 | if (*background) nt--; |
| 117 | if (!nt) goto syntax; |
| 118 | memset(commands, 0, sizeof commands); |
| 119 | for (int i = 0; i < nt; i++) { |
| 120 | int type = tokens[i].type; |
| 121 | if (type == WORD) commands[nc].argv[argc++] = tokens[i].text; |
| 122 | else if (type == INPUT || type == OUTPUT || type == APPEND) { |
| 123 | if (++i == nt || tokens[i].type != WORD) goto syntax; |
| 124 | } else if (type == PIPE) { |
| 125 | if (!argc) goto syntax; |
| 126 | commands[nc++].last = i; |
| 127 | commands[nc].first = i + 1; |
| 128 | argc = 0; |
| 129 | } else goto syntax; |
| 130 | } |
| 131 | if (!argc) goto syntax; |
| 132 | commands[nc++].last = nt; |
| 133 | return nc; |
| 134 | syntax: |
| 135 | fprintf(stderr, "shell: expected a command, redirection filename, or final '&'\n"); |
| 136 | return -1; |
| 137 | } |
| 138 | |
| 139 | /* Apply in source order, after pipeline descriptors have been connected. */ |
| 140 | static int redirect(const struct command *cmd) |
| 141 | { |
| 142 | for (int i = cmd->first; i < cmd->last; i++) { |
| 143 | int type = tokens[i].type, fd, target, flags; |
| 144 | if (type == WORD) continue; |
| 145 | target = type == INPUT ? STDIN_FILENO : STDOUT_FILENO; |
| 146 | flags = type == INPUT ? O_RDONLY : O_WRONLY | O_CREAT | |
| 147 | (type == APPEND ? O_APPEND : O_TRUNC); |
| 148 | fd = open(tokens[++i].text, flags, 0666); |
| 149 | if (fd < 0) { perror(tokens[i].text); return -1; } |
| 150 | if (fd != target) { |
| 151 | if (dup2(fd, target) < 0) { perror("dup2"); close(fd); return -1; } |
| 152 | close(fd); |
| 153 | } |
| 154 | } |
| 155 | return 0; |
| 156 | } |
| 157 | |
| 158 | static int is_builtin(const char *name) |
| 159 | { |
| 160 | return !strcmp(name, "cd") || !strcmp(name, "pwd") || !strcmp(name, "exit"); |
| 161 | } |
| 162 | |
| 163 | /* exit requests termination of this process only; pipeline/background built-ins |
| 164 | * run in children, so they cannot change the parent shell's directory or life. */ |
| 165 | static int builtin(char **argv, int status, int *quit) |
| 166 | { |
| 167 | if (!strcmp(argv[0], "exit")) { |
| 168 | char *end; |
| 169 | long code = status; |
| 170 | if (argv[1]) { |
| 171 | errno = 0; |
| 172 | code = strtol(argv[1], &end, 10); |
| 173 | if (errno || !*argv[1] || *end) { |
| 174 | fprintf(stderr, "exit: expected an integer\n"); return 2; |
| 175 | } |
| 176 | if (argv[2]) { fprintf(stderr, "exit: too many arguments\n"); return 2; } |
| 177 | } |
| 178 | *quit = 1; |
| 179 | return (unsigned char)code; |
| 180 | } |
| 181 | if (!strcmp(argv[0], "cd")) { |
| 182 | const char *path = argv[1] ? argv[1] : getenv("HOME"); |
| 183 | if (argv[1] && argv[2]) { fprintf(stderr, "cd: too many arguments\n"); return 2; } |
| 184 | if (!path) { fprintf(stderr, "cd: HOME is unset\n"); return 1; } |
| 185 | if (chdir(path) < 0) { perror("cd"); return 1; } |
| 186 | return 0; |
| 187 | } |
| 188 | if (argv[1]) { fprintf(stderr, "pwd: no arguments supported\n"); return 2; } |
| 189 | /* Grow only when the actual path exceeds the current buffer. */ |
| 190 | for (size_t size = 256; ; size *= 2) { |
| 191 | char *path = malloc(size); |
| 192 | int error; |
| 193 | if (!path) { perror("malloc"); return 1; } |
| 194 | if (getcwd(path, size)) { |
| 195 | int result = puts(path) == EOF; |
| 196 | free(path); |
| 197 | return result; |
| 198 | } |
| 199 | error = errno; |
| 200 | free(path); |
| 201 | if (error != ERANGE) { errno = error; perror("pwd"); return 1; } |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | static int execute(int nc, int background, int status, int *quit) |
| 206 | { |
| 207 | pid_t pids[MAXTOK]; |
| 208 | int previous = -1, launched = 0, failed = 0, result = 1; |
| 209 | if (nc == 1 && !background && is_builtin(commands[0].argv[0])) { |
| 210 | int saved_in = dup(STDIN_FILENO), saved_out = dup(STDOUT_FILENO); |
| 211 | if (saved_in < 0 || saved_out < 0) { |
| 212 | perror("dup"); |
| 213 | if (saved_in >= 0) close(saved_in); |
| 214 | if (saved_out >= 0) close(saved_out); |
| 215 | return 1; |
| 216 | } |
| 217 | result = redirect(&commands[0]) < 0 ? 1 : builtin(commands[0].argv, status, quit); |
| 218 | if (fflush(stdout) == EOF) { perror("stdout"); result = 1; } |
| 219 | if (dup2(saved_in, STDIN_FILENO) < 0 || dup2(saved_out, STDOUT_FILENO) < 0) { |
| 220 | perror("dup2"); *quit = 1; result = 1; |
| 221 | } |
| 222 | close(saved_in); |
| 223 | close(saved_out); |
| 224 | clearerr(stdout); |
| 225 | return result; |
| 226 | } |
| 227 | fflush(NULL); |
| 228 | for (int i = 0; i < nc; i++) { |
| 229 | int next[2] = {-1, -1}; |
| 230 | pid_t pid; |
| 231 | if (i + 1 < nc && pipe(next) < 0) { perror("pipe"); failed = 1; break; } |
| 232 | pid = fork(); |
| 233 | if (pid == 0) { |
| 234 | signal(SIGINT, background ? SIG_IGN : SIG_DFL); |
| 235 | signal(SIGQUIT, background ? SIG_IGN : SIG_DFL); |
| 236 | if (background && i == 0) { |
| 237 | previous = open("/dev/null", O_RDONLY); |
| 238 | if (previous < 0) { perror("/dev/null"); _exit(1); } |
| 239 | } |
| 240 | if ((previous >= 0 && dup2(previous, STDIN_FILENO) < 0) || |
| 241 | (next[1] >= 0 && dup2(next[1], STDOUT_FILENO) < 0)) { |
| 242 | perror("dup2"); _exit(1); |
| 243 | } |
| 244 | if (previous >= 0) close(previous); |
| 245 | if (next[0] >= 0) close(next[0]); |
| 246 | if (next[1] >= 0) close(next[1]); |
| 247 | if (redirect(&commands[i]) < 0) _exit(1); |
| 248 | if (is_builtin(commands[i].argv[0])) { |
| 249 | int child_quit = 0; |
| 250 | int code = builtin(commands[i].argv, status, &child_quit); |
| 251 | if (fflush(stdout) == EOF) code = 1; |
| 252 | _exit(code); |
| 253 | } |
| 254 | execvp(commands[i].argv[0], commands[i].argv); |
| 255 | int code = errno == ENOENT ? 127 : 126; |
| 256 | perror(commands[i].argv[0]); |
| 257 | _exit(code); |
| 258 | } |
| 259 | if (next[1] >= 0) close(next[1]); |
| 260 | if (pid < 0) { |
| 261 | perror("fork"); |
| 262 | if (next[0] >= 0) close(next[0]); |
| 263 | failed = 1; |
| 264 | break; |
| 265 | } |
| 266 | pids[launched++] = pid; |
| 267 | if (previous >= 0) close(previous); |
| 268 | previous = next[0]; |
| 269 | } |
| 270 | if (previous >= 0) close(previous); |
| 271 | if (failed) for (int i = 0; i < launched; i++) kill(pids[i], SIGKILL); |
| 272 | if (background && !failed) return 0; |
| 273 | for (int i = 0; i < launched; i++) { |
| 274 | int ws; |
| 275 | pid_t waited; |
| 276 | do { waited = waitpid(pids[i], &ws, 0); } while (waited < 0 && errno == EINTR); |
| 277 | if (waited < 0) { perror("waitpid"); failed = 1; } |
| 278 | else if (i == launched - 1) |
| 279 | result = WIFEXITED(ws) ? WEXITSTATUS(ws) : 128 + WTERMSIG(ws); |
| 280 | } |
| 281 | return failed ? 1 : result; |
| 282 | } |
| 283 | |
| 284 | int main(void) |
| 285 | { |
| 286 | char *line = NULL; |
| 287 | size_t capacity = 0; |
| 288 | int status = 0, quit = 0, interactive = isatty(STDIN_FILENO); |
| 289 | struct sigaction action = {0}; |
| 290 | action.sa_handler = on_signal; |
| 291 | sigemptyset(&action.sa_mask); |
| 292 | if (sigaction(SIGINT, &action, NULL) < 0) { |
| 293 | perror("sigaction"); return 1; |
| 294 | } |
| 295 | signal(SIGQUIT, SIG_IGN); |
| 296 | /* Unbuffered input prevents read-ahead from stealing a child's stdin. */ |
| 297 | setvbuf(stdin, NULL, _IONBF, 0); |
| 298 | while (!quit) { |
| 299 | int nt = 0, nc, background; |
| 300 | /* Reap completed background children between commands. */ |
| 301 | while (waitpid(-1, NULL, WNOHANG) > 0) {} |
| 302 | interrupted = 0; |
| 303 | if (interactive) { fputs("$ ", stdout); fflush(stdout); } |
| 304 | for (;;) { |
| 305 | errno = 0; |
| 306 | if (getline(&line, &capacity, stdin) >= 0) break; |
| 307 | if (errno == EINTR) { |
| 308 | clearerr(stdin); |
| 309 | while (waitpid(-1, NULL, WNOHANG) > 0) {} |
| 310 | if (interrupted) { status = 130; break; } |
| 311 | continue; |
| 312 | } |
| 313 | if (ferror(stdin)) { perror("getline"); status = 1; } |
| 314 | quit = 1; |
| 315 | break; |
| 316 | } |
| 317 | if (quit || interrupted) continue; |
| 318 | if (tokenize(line, status, &nt) < 0) status = 2; |
| 319 | else if (nt) { |
| 320 | nc = parse(nt, &background); |
| 321 | status = nc < 0 ? 2 : execute(nc, background, status, &quit); |
| 322 | } |
| 323 | for (int i = 0; i < nt; i++) free(tokens[i].text); |
| 324 | } |
| 325 | free(line); |
| 326 | return status; |
| 327 | } |