/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- can_bg
- remember_bg
- printstatus
- dowait
- dowaitpoll
- waitpoll
- cmd_wait
- cmd_chdir
- cmd_exit
- docommand
- getcmd
- interactive
- check_timing
- main
1 /*
2 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009
3 * The President and Fellows of Harvard College.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 /*
31 * sh - shell
32 *
33 * Usage:
34 * sh
35 * sh -c command
36 */
37
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <assert.h>
41 #include <unistd.h>
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <limits.h>
46 #include <errno.h>
47 #include <err.h>
48
49 #ifdef HOST
50 #include "hostcompat.h"
51 #endif
52
53 #ifndef NARG_MAX
54 /* no NARG_MAX on most unixes */
55 #define NARG_MAX 1024
56 #endif
57
58 /* avoid making this unreasonably large; causes problems under dumbvm */
59 #if ARG_MAX > 4096
60 #define CMDLINE_MAX 4096
61 #else
62 #define CMDLINE_MAX ARG_MAX
63 #endif
64
65 /* set to nonzero if __time syscall seems to work */
66 static int timing = 0;
67
68 /* array of backgrounded jobs (allows "foregrounding") */
69 #define MAXBG 128
70 static pid_t bgpids[MAXBG];
71
72 /*
73 * can_bg
74 * just checks for an open slot.
75 */
76 static
77 int
78 can_bg(void)
79 {
80 int i;
81
82 for (i = 0; i < MAXBG; i++) {
83 if (bgpids[i] == 0) {
84 return 1;
85 }
86 }
87
88 return 0;
89 }
90
91 /*
92 * remember_bg
93 * sticks the pid in an open slot in the background array. note the assert --
94 * better check can_bg before calling this.
95 */
96 static
97 void
98 remember_bg(pid_t pid)
99 {
100 int i;
101 for (i = 0; i < MAXBG; i++) {
102 if (bgpids[i] == 0) {
103 bgpids[i] = pid;
104 return;
105 }
106 }
107 assert(0);
108 }
109
110 /*
111 * printstatus
112 * print results from wait
113 */
114 static
115 void
116 printstatus(int status)
117 {
118 if (WIFEXITED(status)) {
119 printf("Exit %d", WEXITSTATUS(status));
120 }
121 else if (WIFSIGNALED(status) && WCOREDUMP(status)) {
122 printf("Signal %d (core dumped)", WTERMSIG(status));
123 }
124 else if (WIFSIGNALED(status)) {
125 printf("Signal %d", WTERMSIG(status));
126 }
127 else if (WIFSTOPPED(status)) {
128 printf("Stopped on signal %d", WSTOPSIG(status));
129 }
130 else {
131 printf("Invalid status code %d", status);
132 }
133 }
134
135 /*
136 * dowait
137 * just does a waitpid.
138 */
139 static
140 void
141 dowait(pid_t pid)
142 {
143 int status;
144 if (waitpid(pid, &status, 0)<0) {
145 warn("pid %d", pid);
146 }
147 else {
148 printf("pid %d: ", pid);
149 printstatus(status);
150 printf("\n");
151 }
152 }
153
154 #ifdef WNOHANG
155 /*
156 * dowaitpoll
157 * like dowait, but uses WNOHANG. returns true if we got something.
158 */
159 static
160 int
161 dowaitpoll(pid_t pid)
162 {
163 int status;
164 pid_t result;
165 result = waitpid(pid, &status, WNOHANG);
166 if (result<0) {
167 warn("pid %d", pid);
168 }
169 else if (result!=0) {
170 printf("pid %d: ", pid);
171 printstatus(status);
172 printf("\n");
173 return 1;
174 }
175 return 0;
176 }
177
178 /*
179 * waitpoll
180 * poll all background jobs for having exited.
181 */
182 static
183 void
184 waitpoll(void)
185 {
186 int i;
187 for (i=0; i < MAXBG; i++) {
188 if (bgpids[i] != 0) {
189 if (dowaitpoll(bgpids[i])) {
190 bgpids[i] = 0;
191 }
192 }
193 }
194 }
195 #endif /* WNOHANG */
196
197 /*
198 * wait
199 * allows the user to "foreground" a process by waiting on it. without ps to
200 * know the pids, this is a little tough to use with an arg, but without an
201 * arg it will wait for all the background jobs.
202 */
203 static
204 int
205 cmd_wait(int ac, char *av[])
206 {
207 int i;
208 pid_t pid;
209
210 if (ac == 2) {
211 pid = atoi(av[1]);
212 dowait(pid);
213 for (i = 0; i < MAXBG; i++) {
214 if (bgpids[i]==pid) {
215 bgpids[i] = 0;
216 }
217 }
218 return 0;
219 }
220 else if (ac == 1) {
221 for (i=0; i < MAXBG; i++) {
222 if (bgpids[i] != 0) {
223 dowait(bgpids[i]);
224 bgpids[i] = 0;
225 }
226 }
227 return 0;
228 }
229 printf("Usage: wait [pid]\n");
230 return 1;
231 }
232
233 /*
234 * chdir
235 * just an interface to the system call. no concept of home directory, so
236 * require the directory.
237 */
238 static
239 int
240 cmd_chdir(int ac, char *av[])
241 {
242 if (ac == 2) {
243 if (chdir(av[1])) {
244 warn("chdir");
245 return 1;
246 }
247 return 0;
248 }
249 printf("Usage: chdir dir\n");
250 return 1;
251 }
252
253 /*
254 * exit
255 * pretty simple. allow the user to choose the exit code if they want,
256 * otherwise default to 0 (success).
257 */
258 static
259 int
260 cmd_exit(int ac, char *av[])
261 {
262 int code;
263
264 if (ac == 1) {
265 code = 0;
266 }
267 else if (ac == 2) {
268 code = atoi(av[1]);
269 }
270 else {
271 printf("Usage: exit [code]\n");
272 return 1;
273 }
274
275 exit(code);
276
277 return 0; /* quell the compiler warning */
278 }
279
280 /*
281 * a struct of the builtins associates the builtin name with the function that
282 * executes it. they must all take an argc and argv.
283 */
284 static struct {
285 const char *name;
286 int (*func)(int, char **);
287 } builtins[] = {
288 { "cd", cmd_chdir },
289 { "chdir", cmd_chdir },
290 { "exit", cmd_exit },
291 { "wait", cmd_wait },
292 { NULL, NULL }
293 };
294
295 /*
296 * docommand
297 * tokenizes the command line using strtok. if there aren't any commands,
298 * simply returns. checks to see if it's a builtin, running it if it is.
299 * otherwise, it's a standard command. check for the '&', try to background
300 * the job if possible, otherwise just run it and wait on it.
301 */
302 static
303 int
304 docommand(char *buf)
305 {
306 char *args[NARG_MAX + 1];
307 int nargs, i;
308 char *s;
309 pid_t pid;
310 int status;
311 int bg=0;
312 time_t startsecs, endsecs;
313 unsigned long startnsecs, endnsecs;
314
315 nargs = 0;
316 for (s = strtok(buf, " \t\r\n"); s; s = strtok(NULL, " \t\r\n")) {
317 if (nargs >= NARG_MAX) {
318 printf("%s: Too many arguments "
319 "(exceeds system limit)\n",
320 args[0]);
321 return 1;
322 }
323 args[nargs++] = s;
324 }
325 args[nargs] = NULL;
326
327 if (nargs==0) {
328 /* empty line */
329 return 0;
330 }
331
332 for (i=0; builtins[i].name; i++) {
333 if (!strcmp(builtins[i].name, args[0])) {
334 return builtins[i].func(nargs, args);
335 }
336 }
337
338 /* Not a builtin; run it */
339
340 if (nargs > 0 && !strcmp(args[nargs-1], "&")) {
341 /* background */
342 if (!can_bg()) {
343 printf("%s: Too many background jobs; wait for "
344 "some to finish before starting more\n",
345 args[0]);
346 return -1;
347 }
348 nargs--;
349 args[nargs] = NULL;
350 bg = 1;
351 }
352
353 if (timing) {
354 __time(&startsecs, &startnsecs);
355 }
356
357 pid = fork();
358 switch (pid) {
359 case -1:
360 /* error */
361 warn("fork");
362 return _MKWAIT_EXIT(255);
363 case 0:
364 /* child */
365 execv(args[0], args);
366 warn("%s", args[0]);
367 /*
368 * Use _exit() instead of exit() in the child
369 * process to avoid calling atexit() functions,
370 * which would cause hostcompat (if present) to
371 * reset the tty state and mess up our input
372 * handling.
373 */
374 _exit(1);
375 default:
376 break;
377 }
378
379 /* parent */
380 if (bg) {
381 /* background this command */
382 remember_bg(pid);
383 printf("[%d] %s ... &\n", pid, args[0]);
384 return 0;
385 }
386
387 if (waitpid(pid, &status, 0) < 0) {
388 warn("waitpid");
389 status = -1;
390 }
391
392 if (timing) {
393 __time(&endsecs, &endnsecs);
394 if (endnsecs < startnsecs) {
395 endnsecs += 1000000000;
396 endsecs--;
397 }
398 endnsecs -= startnsecs;
399 endsecs -= startsecs;
400 warnx("subprocess time: %lu.%09lu seconds",
401 (unsigned long) endsecs, (unsigned long) endnsecs);
402 }
403
404 return status;
405 }
406
407 /*
408 * getcmd
409 * pulls valid characters off the console, filling the buffer.
410 * backspace deletes a character, simply by moving the position back.
411 * a newline or carriage return breaks the loop, which terminates
412 * the string and returns.
413 *
414 * if there's an invalid character or a backspace when there's nothing
415 * in the buffer, putchars an alert (bell).
416 */
417 static
418 void
419 getcmd(char *buf, size_t len)
420 {
421 size_t pos = 0;
422 int done=0, ch;
423
424 /*
425 * In the absence of a <ctype.h>, assume input is 7-bit ASCII.
426 */
427
428 while (!done) {
429 ch = getchar();
430 if ((ch == '\b' || ch == 127) && pos > 0) {
431 putchar('\b');
432 putchar(' ');
433 putchar('\b');
434 pos--;
435 }
436 else if (ch == '\r' || ch == '\n') {
437 putchar('\r');
438 putchar('\n');
439 done = 1;
440 }
441 else if (ch >= 32 && ch < 127 && pos < len-1) {
442 buf[pos++] = ch;
443 putchar(ch);
444 }
445 else {
446 /* alert (bell) character */
447 putchar('\a');
448 }
449 }
450 buf[pos] = 0;
451 }
452
453 /*
454 * interactive
455 * runs the interactive shell. basically, just infinitely loops, grabbing
456 * commands and running them (and printing the exit status if it's not
457 * success.)
458 */
459 static
460 void
461 interactive(void)
462 {
463 char buf[CMDLINE_MAX];
464 int status;
465
466 while (1) {
467 printf("OS/161$ ");
468 getcmd(buf, sizeof(buf));
469 status = docommand(buf);
470 if (status) {
471 printstatus(status);
472 printf("\n");
473 }
474 #ifdef WNOHANG
475 waitpoll();
476 #endif
477 }
478 }
479
480 static
481 void
482 check_timing(void)
483 {
484 time_t secs;
485 unsigned long nsecs;
486 if (__time(&secs, &nsecs) != -1) {
487 timing = 1;
488 warnx("Timing enabled.");
489 }
490 }
491
492 /*
493 * main
494 * if there are no arguments, run interactively, otherwise, run a program
495 * from within the shell, but immediately exit.
496 */
497 int
498 main(int argc, char *argv[])
499 {
500 #ifdef HOST
501 hostcompat_init(argc, argv);
502 #endif
503 check_timing();
504
505 /*
506 * Allow argc to be 0 in case we're running on a broken kernel,
507 * or one that doesn't set argv when starting the first shell.
508 */
509 if (argc == 0 || argc == 1) {
510 interactive();
511 }
512 else if (argc == 3 && !strcmp(argv[1], "-c")) {
513 return docommand(argv[2]);
514 }
515 else {
516 errx(1, "Usage: sh [-c command]");
517 }
518 return 0;
519 }