shell

a small Unix shell written in C


shell / shell.c
shell.c (12K) raw | download

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
16enum { WORD, INPUT, OUTPUT, APPEND, PIPE, BACKGROUND };
17struct token { int type; char *text; };
18struct command {
19 char *argv[MAXTOK + 1];
20 int first, last; /* Token range, including redirections. */
21};
22static struct token tokens[MAXTOK];
23static struct command commands[MAXTOK];
24static volatile sig_atomic_t interrupted;
25
26static void on_signal(int signo)
27{
28 if (signo == SIGINT) {
29 interrupted = 1;
30 (void)write(STDOUT_FILENO, "\n", 1);
31 }
32}
33
34static 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. */
48static 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
112static 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;
134syntax:
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. */
140static 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
158static 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. */
165static 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
205static 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
284int 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}