os161-1.99
 All Data Structures
sh.c
00001 /*
00002  * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009
00003  *      The President and Fellows of Harvard College.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  * 1. Redistributions of source code must retain the above copyright
00009  *    notice, this list of conditions and the following disclaimer.
00010  * 2. Redistributions in binary form must reproduce the above copyright
00011  *    notice, this list of conditions and the following disclaimer in the
00012  *    documentation and/or other materials provided with the distribution.
00013  * 3. Neither the name of the University nor the names of its contributors
00014  *    may be used to endorse or promote products derived from this software
00015  *    without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
00018  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020  * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
00021  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00022  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00023  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00024  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00025  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00026  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00027  * SUCH DAMAGE.
00028  */
00029 
00030 /*
00031  * sh - shell
00032  *
00033  * Usage:
00034  *     sh
00035  *     sh -c command
00036  */
00037 
00038 #include <sys/types.h>
00039 #include <sys/wait.h>
00040 #include <assert.h>
00041 #include <unistd.h>
00042 #include <stdlib.h>
00043 #include <stdio.h>
00044 #include <string.h>
00045 #include <limits.h>
00046 #include <errno.h>
00047 #include <err.h>
00048 
00049 #ifdef HOST
00050 #include "hostcompat.h"
00051 #endif
00052 
00053 #ifndef NARG_MAX
00054 /* no NARG_MAX on most unixes */
00055 #define NARG_MAX 1024
00056 #endif
00057 
00058 /* avoid making this unreasonably large; causes problems under dumbvm */
00059 #if ARG_MAX > 4096
00060 #define CMDLINE_MAX 4096
00061 #else
00062 #define CMDLINE_MAX ARG_MAX
00063 #endif
00064 
00065 /* set to nonzero if __time syscall seems to work */
00066 static int timing = 0;
00067 
00068 /* array of backgrounded jobs (allows "foregrounding") */
00069 #define MAXBG 128
00070 static pid_t bgpids[MAXBG];
00071 
00072 /*
00073  * can_bg
00074  * just checks for an open slot.
00075  */
00076 static
00077 int
00078 can_bg(void)
00079 {
00080         int i;
00081         
00082         for (i = 0; i < MAXBG; i++) {
00083                 if (bgpids[i] == 0) {
00084                         return 1;
00085                 }
00086         }
00087         
00088         return 0;
00089 }
00090 
00091 /* 
00092  * remember_bg
00093  * sticks the pid in an open slot in the background array.  note the assert --
00094  * better check can_bg before calling this.
00095  */
00096 static
00097 void
00098 remember_bg(pid_t pid)
00099 {
00100         int i;
00101         for (i = 0; i < MAXBG; i++) {
00102                 if (bgpids[i] == 0) {
00103                         bgpids[i] = pid;
00104                         return;
00105                 }
00106         }
00107         assert(0);
00108 }
00109 
00110 /*
00111  * printstatus
00112  * print results from wait
00113  */
00114 static
00115 void
00116 printstatus(int status)
00117 {
00118         if (WIFEXITED(status)) {
00119                 printf("Exit %d", WEXITSTATUS(status));
00120         }
00121         else if (WIFSIGNALED(status) && WCOREDUMP(status)) {
00122                 printf("Signal %d (core dumped)", WTERMSIG(status));
00123         }
00124         else if (WIFSIGNALED(status)) {
00125                 printf("Signal %d", WTERMSIG(status));
00126         }
00127         else if (WIFSTOPPED(status)) {
00128                 printf("Stopped on signal %d", WSTOPSIG(status));
00129         }
00130         else {
00131                 printf("Invalid status code %d", status);
00132         }
00133 }
00134 
00135 /*
00136  * dowait
00137  * just does a waitpid.
00138  */
00139 static
00140 void
00141 dowait(pid_t pid)
00142 {
00143         int status;
00144         if (waitpid(pid, &status, 0)<0) {
00145                 warn("pid %d", pid);
00146         }
00147         else {
00148                 printf("pid %d: ", pid);
00149                 printstatus(status);
00150                 printf("\n");
00151         }
00152 }
00153 
00154 #ifdef WNOHANG
00155 /*
00156  * dowaitpoll
00157  * like dowait, but uses WNOHANG. returns true if we got something.
00158  */
00159 static
00160 int
00161 dowaitpoll(pid_t pid)
00162 {
00163         int status;
00164         pid_t result;
00165         result = waitpid(pid, &status, WNOHANG);
00166         if (result<0) {
00167                 warn("pid %d", pid);
00168         }
00169         else if (result!=0) {
00170                 printf("pid %d: ", pid);
00171                 printstatus(status);
00172                 printf("\n");
00173                 return 1;
00174         }
00175         return 0;
00176 }
00177 
00178 /*
00179  * waitpoll
00180  * poll all background jobs for having exited.
00181  */
00182 static
00183 void
00184 waitpoll(void)
00185 {
00186         int i;
00187         for (i=0; i < MAXBG; i++) {
00188                 if (bgpids[i] != 0) {
00189                         if (dowaitpoll(bgpids[i])) {
00190                                 bgpids[i] = 0;
00191                         }
00192                 }
00193         }
00194 }
00195 #endif /* WNOHANG */
00196 
00197 /*
00198  * wait
00199  * allows the user to "foreground" a process by waiting on it.  without ps to
00200  * know the pids, this is a little tough to use with an arg, but without an
00201  * arg it will wait for all the background jobs.
00202  */
00203 static
00204 int
00205 cmd_wait(int ac, char *av[])
00206 {
00207         int i;
00208         pid_t pid;
00209 
00210         if (ac == 2) {
00211                 pid = atoi(av[1]);
00212                 dowait(pid);
00213                 for (i = 0; i < MAXBG; i++) {
00214                         if (bgpids[i]==pid) {
00215                                 bgpids[i] = 0;
00216                         }
00217                 }
00218                 return 0;
00219         }
00220         else if (ac == 1) {
00221                 for (i=0; i < MAXBG; i++) {
00222                         if (bgpids[i] != 0) {
00223                                 dowait(bgpids[i]);
00224                                 bgpids[i] = 0;
00225                         }
00226                 }
00227                 return 0;
00228         }
00229         printf("Usage: wait [pid]\n");
00230         return 1;
00231 }
00232 
00233 /*
00234  * chdir
00235  * just an interface to the system call.  no concept of home directory, so
00236  * require the directory.
00237  */
00238 static
00239 int
00240 cmd_chdir(int ac, char *av[])
00241 {
00242         if (ac == 2) {
00243                 if (chdir(av[1])) {
00244                         warn("chdir");
00245                         return 1;
00246                 }
00247                 return 0;
00248         }
00249         printf("Usage: chdir dir\n");
00250         return 1;
00251 }
00252 
00253 /*
00254  * exit
00255  * pretty simple.  allow the user to choose the exit code if they want,
00256  * otherwise default to 0 (success).
00257  */
00258 static
00259 int
00260 cmd_exit(int ac, char *av[])
00261 {
00262         int code;
00263 
00264         if (ac == 1) {
00265                 code = 0;
00266         }
00267         else if (ac == 2) {
00268                 code = atoi(av[1]);
00269         }
00270         else {
00271                 printf("Usage: exit [code]\n");
00272                 return 1;
00273         }
00274         
00275         exit(code);
00276         
00277         return 0; /* quell the compiler warning */
00278 }
00279 
00280 /*
00281  * a struct of the builtins associates the builtin name with the function that
00282  * executes it.  they must all take an argc and argv.
00283  */
00284 static struct {
00285         const char *name;
00286         int (*func)(int, char **);
00287 } builtins[] = {
00288         { "cd",    cmd_chdir },
00289         { "chdir", cmd_chdir },
00290         { "exit",  cmd_exit },
00291         { "wait",  cmd_wait },
00292         { NULL, NULL }
00293 };
00294 
00295 /*
00296  * docommand
00297  * tokenizes the command line using strtok.  if there aren't any commands,
00298  * simply returns.  checks to see if it's a builtin, running it if it is.
00299  * otherwise, it's a standard command.  check for the '&', try to background
00300  * the job if possible, otherwise just run it and wait on it.
00301  */
00302 static
00303 int
00304 docommand(char *buf)
00305 {
00306         char *args[NARG_MAX + 1];
00307         int nargs, i;
00308         char *s;
00309         pid_t pid;
00310         int status;
00311         int bg=0;
00312         time_t startsecs, endsecs;
00313         unsigned long startnsecs, endnsecs;
00314 
00315         nargs = 0;
00316         for (s = strtok(buf, " \t\r\n"); s; s = strtok(NULL, " \t\r\n")) {
00317                 if (nargs >= NARG_MAX) {
00318                         printf("%s: Too many arguments "
00319                                "(exceeds system limit)\n",
00320                                args[0]);
00321                         return 1;
00322                 }
00323                 args[nargs++] = s;
00324         }
00325         args[nargs] = NULL;
00326 
00327         if (nargs==0) {
00328                 /* empty line */
00329                 return 0;
00330         }
00331 
00332         for (i=0; builtins[i].name; i++) {
00333                 if (!strcmp(builtins[i].name, args[0])) {
00334                         return builtins[i].func(nargs, args);
00335                 }
00336         }
00337 
00338         /* Not a builtin; run it */
00339 
00340         if (nargs > 0 && !strcmp(args[nargs-1], "&")) {
00341                 /* background */
00342                 if (!can_bg()) {
00343                         printf("%s: Too many background jobs; wait for "
00344                                "some to finish before starting more\n",
00345                                args[0]);
00346                         return -1;
00347                 }
00348                 nargs--;
00349                 args[nargs] = NULL;
00350                 bg = 1;
00351         }
00352 
00353         if (timing) {
00354                 __time(&startsecs, &startnsecs);
00355         }
00356 
00357         pid = fork();
00358         switch (pid) {
00359                 case -1:
00360                         /* error */
00361                         warn("fork");
00362                         return _MKWAIT_EXIT(255);
00363                 case 0:
00364                         /* child */
00365                         execv(args[0], args);
00366                         warn("%s", args[0]);
00367                         /*
00368                          * Use _exit() instead of exit() in the child
00369                          * process to avoid calling atexit() functions,
00370                          * which would cause hostcompat (if present) to
00371                          * reset the tty state and mess up our input
00372                          * handling.
00373                          */
00374                         _exit(1);
00375                 default:
00376                         break;
00377         }
00378 
00379         /* parent */
00380         if (bg) {
00381                 /* background this command */
00382                 remember_bg(pid);
00383                 printf("[%d] %s ... &\n", pid, args[0]);
00384                 return 0;
00385         }
00386 
00387         if (waitpid(pid, &status, 0) < 0) {
00388                 warn("waitpid");
00389                 status = -1;
00390         }
00391 
00392         if (timing) {
00393                 __time(&endsecs, &endnsecs);
00394                 if (endnsecs < startnsecs) {
00395                         endnsecs += 1000000000;
00396                         endsecs--;
00397                 }
00398                 endnsecs -= startnsecs;
00399                 endsecs -= startsecs;
00400                 warnx("subprocess time: %lu.%09lu seconds",
00401                       (unsigned long) endsecs, (unsigned long) endnsecs);
00402         }
00403 
00404         return status;
00405 }
00406 
00407 /*
00408  * getcmd
00409  * pulls valid characters off the console, filling the buffer.  
00410  * backspace deletes a character, simply by moving the position back.
00411  * a newline or carriage return breaks the loop, which terminates
00412  * the string and returns.
00413  *
00414  * if there's an invalid character or a backspace when there's nothing 
00415  * in the buffer, putchars an alert (bell).
00416  */
00417 static
00418 void
00419 getcmd(char *buf, size_t len)
00420 {
00421         size_t pos = 0;
00422         int done=0, ch;
00423 
00424         /*
00425          * In the absence of a <ctype.h>, assume input is 7-bit ASCII.
00426          */
00427 
00428         while (!done) {
00429                 ch = getchar();
00430                 if ((ch == '\b' || ch == 127) && pos > 0) {
00431                         putchar('\b');
00432                         putchar(' ');
00433                         putchar('\b');
00434                         pos--;
00435                 }
00436                 else if (ch == '\r' || ch == '\n') {
00437                         putchar('\r');
00438                         putchar('\n');
00439                         done = 1;
00440                 }
00441                 else if (ch >= 32 && ch < 127 && pos < len-1) {
00442                         buf[pos++] = ch;
00443                         putchar(ch);
00444                 }
00445                 else {
00446                         /* alert (bell) character */
00447                         putchar('\a');
00448                 }
00449         }
00450         buf[pos] = 0;
00451 }       
00452 
00453 /*
00454  * interactive
00455  * runs the interactive shell.  basically, just infinitely loops, grabbing
00456  * commands and running them (and printing the exit status if it's not
00457  * success.)
00458  */
00459 static
00460 void
00461 interactive(void)
00462 {
00463         char buf[CMDLINE_MAX];
00464         int status;
00465 
00466         while (1) {
00467                 printf("OS/161$ ");
00468                 getcmd(buf, sizeof(buf));
00469                 status = docommand(buf);
00470                 if (status) {
00471                         printstatus(status);
00472                         printf("\n");
00473                 }
00474 #ifdef WNOHANG
00475                 waitpoll();
00476 #endif
00477         }
00478 }
00479 
00480 static
00481 void
00482 check_timing(void)
00483 {
00484         time_t secs;
00485         unsigned long nsecs;
00486         if (__time(&secs, &nsecs) != -1) {
00487                 timing = 1;
00488                 warnx("Timing enabled.");
00489         }
00490 }
00491 
00492 /* 
00493  * main
00494  * if there are no arguments, run interactively, otherwise, run a program
00495  * from within the shell, but immediately exit.
00496  */
00497 int
00498 main(int argc, char *argv[])
00499 {
00500 #ifdef HOST
00501         hostcompat_init(argc, argv);
00502 #endif
00503         check_timing();
00504 
00505         /*
00506          * Allow argc to be 0 in case we're running on a broken kernel,
00507          * or one that doesn't set argv when starting the first shell.
00508          */
00509         if (argc == 0 || argc == 1) {
00510                 interactive();
00511         }
00512         else if (argc == 3 && !strcmp(argv[1], "-c")) {
00513                 return docommand(argv[2]);
00514         }
00515         else {
00516                 errx(1, "Usage: sh [-c command]");
00517         }
00518         return 0;
00519 }
 All Data Structures