/*1* Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 20092* The President and Fellows of Harvard College.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12* 3. Neither the name of the University nor the names of its contributors13* may be used to endorse or promote products derived from this software14* without specific prior written permission.15*16* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/2829/*30* sh - shell31*32* Usage:33* sh34* sh -c command35*/3637#include <sys/types.h>38#include <sys/wait.h>39#include <assert.h>40#include <unistd.h>41#include <stdlib.h>42#include <stdio.h>43#include <string.h>44#include <limits.h>45#include <errno.h>46#include <err.h>4748#ifdef HOST49#include "hostcompat.h"50#endif5152#ifndef NARG_MAX53/* no NARG_MAX on most unixes */54#define NARG_MAX 102455#endif5657/* avoid making this unreasonably large; causes problems under dumbvm */58#if ARG_MAX > 409659#define CMDLINE_MAX 409660#else61#define CMDLINE_MAX ARG_MAX62#endif6364/* set to nonzero if __time syscall seems to work */65static int timing = 0;6667/* array of backgrounded jobs (allows "foregrounding") */68#define MAXBG 12869static pid_t bgpids[MAXBG];7071/*72* can_bg73* just checks for an open slot.74*/75static76int77can_bg(void)78{79int i;8081for (i = 0; i < MAXBG; i++) {82if (bgpids[i] == 0) {83return 1;84}85}8687return 0;88}8990/*91* remember_bg92* sticks the pid in an open slot in the background array. note the assert --93* better check can_bg before calling this.94*/95static96void97remember_bg(pid_t pid)98{99int i;100for (i = 0; i < MAXBG; i++) {101if (bgpids[i] == 0) {102bgpids[i] = pid;103return;104}105}106assert(0);107}108109/*110* printstatus111* print results from wait112*/113static114void115printstatus(int status)116{117if (WIFEXITED(status)) {118printf("Exit %d", WEXITSTATUS(status));119}120else if (WIFSIGNALED(status) && WCOREDUMP(status)) {121printf("Signal %d (core dumped)", WTERMSIG(status));122}123else if (WIFSIGNALED(status)) {124printf("Signal %d", WTERMSIG(status));125}126else if (WIFSTOPPED(status)) {127printf("Stopped on signal %d", WSTOPSIG(status));128}129else {130printf("Invalid status code %d", status);131}132}133134/*135* dowait136* just does a waitpid.137*/138static139void140dowait(pid_t pid)141{142int status;143if (waitpid(pid, &status, 0)<0) {144warn("pid %d", pid);145}146else {147printf("pid %d: ", pid);148printstatus(status);149printf("\n");150}151}152153#ifdef WNOHANG154/*155* dowaitpoll156* like dowait, but uses WNOHANG. returns true if we got something.157*/158static159int160dowaitpoll(pid_t pid)161{162int status;163pid_t result;164result = waitpid(pid, &status, WNOHANG);165if (result<0) {166warn("pid %d", pid);167}168else if (result!=0) {169printf("pid %d: ", pid);170printstatus(status);171printf("\n");172return 1;173}174return 0;175}176177/*178* waitpoll179* poll all background jobs for having exited.180*/181static182void183waitpoll(void)184{185int i;186for (i=0; i < MAXBG; i++) {187if (bgpids[i] != 0) {188if (dowaitpoll(bgpids[i])) {189bgpids[i] = 0;190}191}192}193}194#endif /* WNOHANG */195196/*197* wait198* allows the user to "foreground" a process by waiting on it. without ps to199* know the pids, this is a little tough to use with an arg, but without an200* arg it will wait for all the background jobs.201*/202static203int204cmd_wait(int ac, char *av[])205{206int i;207pid_t pid;208209if (ac == 2) {210pid = atoi(av[1]);211dowait(pid);212for (i = 0; i < MAXBG; i++) {213if (bgpids[i]==pid) {214bgpids[i] = 0;215}216}217return 0;218}219else if (ac == 1) {220for (i=0; i < MAXBG; i++) {221if (bgpids[i] != 0) {222dowait(bgpids[i]);223bgpids[i] = 0;224}225}226return 0;227}228printf("Usage: wait [pid]\n");229return 1;230}231232/*233* chdir234* just an interface to the system call. no concept of home directory, so235* require the directory.236*/237static238int239cmd_chdir(int ac, char *av[])240{241if (ac == 2) {242if (chdir(av[1])) {243warn("chdir");244return 1;245}246return 0;247}248printf("Usage: chdir dir\n");249return 1;250}251252/*253* exit254* pretty simple. allow the user to choose the exit code if they want,255* otherwise default to 0 (success).256*/257static258int259cmd_exit(int ac, char *av[])260{261int code;262263if (ac == 1) {264code = 0;265}266else if (ac == 2) {267code = atoi(av[1]);268}269else {270printf("Usage: exit [code]\n");271return 1;272}273274exit(code);275276return 0; /* quell the compiler warning */277}278279/*280* a struct of the builtins associates the builtin name with the function that281* executes it. they must all take an argc and argv.282*/283static struct {284const char *name;285int (*func)(int, char **);286} builtins[] = {287{ "cd", cmd_chdir },288{ "chdir", cmd_chdir },289{ "exit", cmd_exit },290{ "wait", cmd_wait },291{ NULL, NULL }292};293294/*295* docommand296* tokenizes the command line using strtok. if there aren't any commands,297* simply returns. checks to see if it's a builtin, running it if it is.298* otherwise, it's a standard command. check for the '&', try to background299* the job if possible, otherwise just run it and wait on it.300*/301static302int303docommand(char *buf)304{305char *args[NARG_MAX + 1];306int nargs, i;307char *s;308pid_t pid;309int status;310int bg=0;311time_t startsecs, endsecs;312unsigned long startnsecs, endnsecs;313314nargs = 0;315for (s = strtok(buf, " \t\r\n"); s; s = strtok(NULL, " \t\r\n")) {316if (nargs >= NARG_MAX) {317printf("%s: Too many arguments "318"(exceeds system limit)\n",319args[0]);320return 1;321}322args[nargs++] = s;323}324args[nargs] = NULL;325326if (nargs==0) {327/* empty line */328return 0;329}330331for (i=0; builtins[i].name; i++) {332if (!strcmp(builtins[i].name, args[0])) {333return builtins[i].func(nargs, args);334}335}336337/* Not a builtin; run it */338339if (nargs > 0 && !strcmp(args[nargs-1], "&")) {340/* background */341if (!can_bg()) {342printf("%s: Too many background jobs; wait for "343"some to finish before starting more\n",344args[0]);345return -1;346}347nargs--;348args[nargs] = NULL;349bg = 1;350}351352if (timing) {353__time(&startsecs, &startnsecs);354}355356pid = fork();357switch (pid) {358case -1:359/* error */360warn("fork");361return _MKWAIT_EXIT(255);362case 0:363/* child */364execv(args[0], args);365warn("%s", args[0]);366/*367* Use _exit() instead of exit() in the child368* process to avoid calling atexit() functions,369* which would cause hostcompat (if present) to370* reset the tty state and mess up our input371* handling.372*/373_exit(1);374default:375break;376}377378/* parent */379if (bg) {380/* background this command */381remember_bg(pid);382printf("[%d] %s ... &\n", pid, args[0]);383return 0;384}385386if (waitpid(pid, &status, 0) < 0) {387warn("waitpid");388status = -1;389}390391if (timing) {392__time(&endsecs, &endnsecs);393if (endnsecs < startnsecs) {394endnsecs += 1000000000;395endsecs--;396}397endnsecs -= startnsecs;398endsecs -= startsecs;399warnx("subprocess time: %lu.%09lu seconds",400(unsigned long) endsecs, (unsigned long) endnsecs);401}402403return status;404}405406/*407* getcmd408* pulls valid characters off the console, filling the buffer.409* backspace deletes a character, simply by moving the position back.410* a newline or carriage return breaks the loop, which terminates411* the string and returns.412*413* if there's an invalid character or a backspace when there's nothing414* in the buffer, putchars an alert (bell).415*/416static417void418getcmd(char *buf, size_t len)419{420size_t pos = 0;421int done=0, ch;422423/*424* In the absence of a <ctype.h>, assume input is 7-bit ASCII.425*/426427while (!done) {428ch = getchar();429if ((ch == '\b' || ch == 127) && pos > 0) {430putchar('\b');431putchar(' ');432putchar('\b');433pos--;434}435else if (ch == '\r' || ch == '\n') {436putchar('\r');437putchar('\n');438done = 1;439}440else if (ch >= 32 && ch < 127 && pos < len-1) {441buf[pos++] = ch;442putchar(ch);443}444else {445/* alert (bell) character */446putchar('\a');447}448}449buf[pos] = 0;450}451452/*453* interactive454* runs the interactive shell. basically, just infinitely loops, grabbing455* commands and running them (and printing the exit status if it's not456* success.)457*/458static459void460interactive(void)461{462char buf[CMDLINE_MAX];463int status;464465while (1) {466printf("OS/161$ ");467getcmd(buf, sizeof(buf));468status = docommand(buf);469if (status) {470printstatus(status);471printf("\n");472}473#ifdef WNOHANG474waitpoll();475#endif476}477}478479static480void481check_timing(void)482{483time_t secs;484unsigned long nsecs;485if (__time(&secs, &nsecs) != -1) {486timing = 1;487warnx("Timing enabled.");488}489}490491/*492* main493* if there are no arguments, run interactively, otherwise, run a program494* from within the shell, but immediately exit.495*/496int497main(int argc, char *argv[])498{499#ifdef HOST500hostcompat_init(argc, argv);501#endif502check_timing();503504/*505* Allow argc to be 0 in case we're running on a broken kernel,506* or one that doesn't set argv when starting the first shell.507*/508if (argc == 0 || argc == 1) {509interactive();510}511else if (argc == 3 && !strcmp(argv[1], "-c")) {512return docommand(argv[2]);513}514else {515errx(1, "Usage: sh [-c command]");516}517return 0;518}519520521