574 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			574 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * shll - sh that drops characters for automation testing
 | 
						|
 *
 | 
						|
 * Usage:
 | 
						|
 *     shll [-p percent]
 | 
						|
 *         -p percent of characters to drop (default 5)
 | 
						|
 */
 | 
						|
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/wait.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <string.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <err.h>
 | 
						|
 | 
						|
#ifdef HOST
 | 
						|
#include "hostcompat.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef NARG_MAX
 | 
						|
/* no NARG_MAX on most unixes */
 | 
						|
#define NARG_MAX 1024
 | 
						|
#endif
 | 
						|
 | 
						|
/* avoid making this unreasonably large; causes problems under dumbvm */
 | 
						|
#define CMDLINE_MAX 4096
 | 
						|
 | 
						|
/* struct to (portably) hold exit info */
 | 
						|
struct exitinfo {
 | 
						|
	unsigned val:8,
 | 
						|
		signaled:1,
 | 
						|
		stopped:1,
 | 
						|
		coredump:1;
 | 
						|
};
 | 
						|
 | 
						|
/* set to nonzero if __time syscall seems to work */
 | 
						|
static int timing = 0;
 | 
						|
 | 
						|
/* array of backgrounded jobs (allows "foregrounding") */
 | 
						|
#define MAXBG 128
 | 
						|
static pid_t bgpids[MAXBG];
 | 
						|
 | 
						|
/* percent of characters to drop */
 | 
						|
static int percent = 5;
 | 
						|
 | 
						|
/*
 | 
						|
 * can_bg
 | 
						|
 * just checks for an open slot.
 | 
						|
 */
 | 
						|
static
 | 
						|
int
 | 
						|
can_bg(void)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
 | 
						|
	for (i = 0; i < MAXBG; i++) {
 | 
						|
		if (bgpids[i] == 0) {
 | 
						|
			return 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * remember_bg
 | 
						|
 * sticks the pid in an open slot in the background array.  note the assert --
 | 
						|
 * better check can_bg before calling this.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
remember_bg(pid_t pid)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	for (i = 0; i < MAXBG; i++) {
 | 
						|
		if (bgpids[i] == 0) {
 | 
						|
			bgpids[i] = pid;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	assert(0);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * constructor for exitinfo
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
exitinfo_exit(struct exitinfo *ei, int code)
 | 
						|
{
 | 
						|
	ei->val = code;
 | 
						|
	ei->signaled = 0;
 | 
						|
	ei->stopped = 0;
 | 
						|
	ei->coredump = 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * readstatus
 | 
						|
 * unpack results from wait
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
readstatus(int status, struct exitinfo *ei)
 | 
						|
{
 | 
						|
	if (WIFEXITED(status)) {
 | 
						|
		ei->val = WEXITSTATUS(status);
 | 
						|
		ei->signaled = 0;
 | 
						|
		ei->stopped = 0;
 | 
						|
		ei->coredump = 0;
 | 
						|
	}
 | 
						|
	else if (WIFSIGNALED(status) && WCOREDUMP(status)) {
 | 
						|
		ei->val = WTERMSIG(status);
 | 
						|
		ei->signaled = 1;
 | 
						|
		ei->stopped = 0;
 | 
						|
		ei->coredump = 1;
 | 
						|
	}
 | 
						|
	else if (WIFSIGNALED(status)) {
 | 
						|
		ei->val = WTERMSIG(status);
 | 
						|
		ei->signaled = 1;
 | 
						|
		ei->stopped = 0;
 | 
						|
		ei->coredump = 0;
 | 
						|
	}
 | 
						|
	else if (WIFSTOPPED(status)) {
 | 
						|
		ei->val = WSTOPSIG(status);
 | 
						|
		ei->signaled = 0;
 | 
						|
		ei->stopped = 1;
 | 
						|
		ei->coredump = 0;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		tprintf("Invalid status code %d", status);
 | 
						|
		ei->val = status;
 | 
						|
		ei->signaled = 0;
 | 
						|
		ei->stopped = 0;
 | 
						|
		ei->coredump = 0;
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * printstatus
 | 
						|
 * print results from wait
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
printstatus(const struct exitinfo *ei, int printexitzero)
 | 
						|
{
 | 
						|
	if (ei->signaled && ei->coredump) {
 | 
						|
		tprintf("Signal %d (core dumped)\n", ei->val);
 | 
						|
	}
 | 
						|
	else if (ei->signaled) {
 | 
						|
		tprintf("Signal %d\n", ei->val);
 | 
						|
	}
 | 
						|
	else if (ei->stopped) {
 | 
						|
		tprintf("Stopped on signal %d\n", ei->val);
 | 
						|
	}
 | 
						|
	else if (printexitzero || ei->val != 0) {
 | 
						|
		tprintf("Exit %d\n", ei->val);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * dowait
 | 
						|
 * just does a waitpid.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
dowait(pid_t pid)
 | 
						|
{
 | 
						|
	struct exitinfo ei;
 | 
						|
	int status;
 | 
						|
 | 
						|
	if (waitpid(pid, &status, 0) < 0) {
 | 
						|
		warn("pid %d", pid);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		tprintf("pid %d: ", pid);
 | 
						|
		readstatus(status, &ei);
 | 
						|
		printstatus(&ei, 1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#ifdef WNOHANG
 | 
						|
/*
 | 
						|
 * dowaitpoll
 | 
						|
 * like dowait, but uses WNOHANG. returns true if we got something.
 | 
						|
 */
 | 
						|
static
 | 
						|
int
 | 
						|
dowaitpoll(pid_t pid)
 | 
						|
{
 | 
						|
	struct exitinfo ei;
 | 
						|
	pid_t foundpid;
 | 
						|
	int status;
 | 
						|
 | 
						|
	foundpid = waitpid(pid, &status, WNOHANG);
 | 
						|
	if (foundpid < 0) {
 | 
						|
		warn("pid %d", pid);
 | 
						|
	}
 | 
						|
	else if (foundpid != 0) {
 | 
						|
		tprintf("pid %d: ", pid);
 | 
						|
		readstatus(status, &ei);
 | 
						|
		printstatus(&ei, 1);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * waitpoll
 | 
						|
 * poll all background jobs for having exited.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
waitpoll(void)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	for (i=0; i < MAXBG; i++) {
 | 
						|
		if (bgpids[i] != 0) {
 | 
						|
			if (dowaitpoll(bgpids[i])) {
 | 
						|
				bgpids[i] = 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif /* WNOHANG */
 | 
						|
 | 
						|
/*
 | 
						|
 * wait
 | 
						|
 * allows the user to "foreground" a process by waiting on it.  without ps to
 | 
						|
 * know the pids, this is a little tough to use with an arg, but without an
 | 
						|
 * arg it will wait for all the background jobs.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
cmd_wait(int ac, char *av[], struct exitinfo *ei)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	pid_t pid;
 | 
						|
 | 
						|
	if (ac == 2) {
 | 
						|
		pid = atoi(av[1]);
 | 
						|
		dowait(pid);
 | 
						|
		for (i = 0; i < MAXBG; i++) {
 | 
						|
			if (bgpids[i]==pid) {
 | 
						|
				bgpids[i] = 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		exitinfo_exit(ei, 0);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	else if (ac == 1) {
 | 
						|
		for (i=0; i < MAXBG; i++) {
 | 
						|
			if (bgpids[i] != 0) {
 | 
						|
				dowait(bgpids[i]);
 | 
						|
				bgpids[i] = 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		exitinfo_exit(ei, 0);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	tprintf("Usage: wait [pid]\n");
 | 
						|
	exitinfo_exit(ei, 1);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * chdir
 | 
						|
 * just an interface to the system call.  no concept of home directory, so
 | 
						|
 * require the directory.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
cmd_chdir(int ac, char *av[], struct exitinfo *ei)
 | 
						|
{
 | 
						|
	if (ac == 2) {
 | 
						|
		if (chdir(av[1])) {
 | 
						|
			warn("chdir: %s", av[1]);
 | 
						|
			exitinfo_exit(ei, 1);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		exitinfo_exit(ei, 0);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	tprintf("Usage: chdir dir\n");
 | 
						|
	exitinfo_exit(ei, 1);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * exit
 | 
						|
 * pretty simple.  allow the user to choose the exit code if they want,
 | 
						|
 * otherwise default to 0 (success).
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
cmd_exit(int ac, char *av[], struct exitinfo *ei)
 | 
						|
{
 | 
						|
	int code;
 | 
						|
 | 
						|
	if (ac == 1) {
 | 
						|
		code = 0;
 | 
						|
	}
 | 
						|
	else if (ac == 2) {
 | 
						|
		code = atoi(av[1]);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		tprintf("Usage: exit [code]\n");
 | 
						|
		exitinfo_exit(ei, 1);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	exit(code);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * a struct of the builtins associates the builtin name with the function that
 | 
						|
 * executes it.  they must all take an argc and argv.
 | 
						|
 */
 | 
						|
static struct {
 | 
						|
	const char *name;
 | 
						|
	void (*func)(int, char **, struct exitinfo *);
 | 
						|
} builtins[] = {
 | 
						|
	{ "cd",    cmd_chdir },
 | 
						|
	{ "chdir", cmd_chdir },
 | 
						|
	{ "exit",  cmd_exit },
 | 
						|
	{ "wait",  cmd_wait },
 | 
						|
	{ NULL, NULL }
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * docommand
 | 
						|
 * tokenizes the command line using strtok.  if there aren't any commands,
 | 
						|
 * simply returns.  checks to see if it's a builtin, running it if it is.
 | 
						|
 * otherwise, it's a standard command.  check for the '&', try to background
 | 
						|
 * the job if possible, otherwise just run it and wait on it.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
docommand(char *buf, struct exitinfo *ei)
 | 
						|
{
 | 
						|
	char *args[NARG_MAX + 1];
 | 
						|
	int nargs, i;
 | 
						|
	char *s;
 | 
						|
	pid_t pid;
 | 
						|
	int status;
 | 
						|
	int bg=0;
 | 
						|
	time_t startsecs, endsecs;
 | 
						|
	unsigned long startnsecs, endnsecs;
 | 
						|
 | 
						|
	nargs = 0;
 | 
						|
	for (s = strtok(buf, " \t\r\n"); s; s = strtok(NULL, " \t\r\n")) {
 | 
						|
		if (nargs >= NARG_MAX) {
 | 
						|
			tprintf("%s: Too many arguments "
 | 
						|
			       "(exceeds system limit)\n",
 | 
						|
			       args[0]);
 | 
						|
			exitinfo_exit(ei, 1);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		args[nargs++] = s;
 | 
						|
	}
 | 
						|
	args[nargs] = NULL;
 | 
						|
 | 
						|
	if (nargs==0) {
 | 
						|
		/* empty line */
 | 
						|
		exitinfo_exit(ei, 0);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i=0; builtins[i].name; i++) {
 | 
						|
		if (!strcmp(builtins[i].name, args[0])) {
 | 
						|
			builtins[i].func(nargs, args, ei);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Not a builtin; run it */
 | 
						|
 | 
						|
	if (nargs > 0 && !strcmp(args[nargs-1], "&")) {
 | 
						|
		/* background */
 | 
						|
		if (!can_bg()) {
 | 
						|
			tprintf("%s: Too many background jobs; wait for "
 | 
						|
			       "some to finish before starting more\n",
 | 
						|
			       args[0]);
 | 
						|
			exitinfo_exit(ei, 1);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		nargs--;
 | 
						|
		args[nargs] = NULL;
 | 
						|
		bg = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (timing) {
 | 
						|
		__time(&startsecs, &startnsecs);
 | 
						|
	}
 | 
						|
 | 
						|
	pid = fork();
 | 
						|
	switch (pid) {
 | 
						|
		case -1:
 | 
						|
			/* error */
 | 
						|
			warn("fork");
 | 
						|
			exitinfo_exit(ei, 255);
 | 
						|
			return;
 | 
						|
		case 0:
 | 
						|
			/* child */
 | 
						|
			execvp(args[0], args);
 | 
						|
			warn("%s", args[0]);
 | 
						|
			/*
 | 
						|
			 * Use _exit() instead of exit() in the child
 | 
						|
			 * process to avoid calling atexit() functions,
 | 
						|
			 * which would cause hostcompat (if present) to
 | 
						|
			 * reset the tty state and mess up our input
 | 
						|
			 * handling.
 | 
						|
			 */
 | 
						|
			_exit(1);
 | 
						|
		default:
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	/* parent */
 | 
						|
	if (bg) {
 | 
						|
		/* background this command */
 | 
						|
		remember_bg(pid);
 | 
						|
		tprintf("[%d] %s ... &\n", pid, args[0]);
 | 
						|
		exitinfo_exit(ei, 0);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (waitpid(pid, &status, 0) < 0) {
 | 
						|
		warn("waitpid");
 | 
						|
		exitinfo_exit(ei, 255);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		readstatus(status, ei);
 | 
						|
	}
 | 
						|
 | 
						|
	if (timing) {
 | 
						|
		__time(&endsecs, &endnsecs);
 | 
						|
		if (endnsecs < startnsecs) {
 | 
						|
			endnsecs += 1000000000;
 | 
						|
			endsecs--;
 | 
						|
		}
 | 
						|
		endnsecs -= startnsecs;
 | 
						|
		endsecs -= startsecs;
 | 
						|
		warnx("subprocess time: %lu.%09lu seconds",
 | 
						|
		      (unsigned long) endsecs, (unsigned long) endnsecs);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * getcmd
 | 
						|
 * pulls valid characters off the console, filling the buffer.
 | 
						|
 * backspace deletes a character, simply by moving the position back.
 | 
						|
 * a newline or carriage return breaks the loop, which terminates
 | 
						|
 * the string and returns.
 | 
						|
 *
 | 
						|
 * if there's an invalid character or a backspace when there's nothing
 | 
						|
 * in the buffer, putchars an alert (bell).
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
getcmd(char *buf, size_t len)
 | 
						|
{
 | 
						|
	size_t pos = 0;
 | 
						|
	int done=0, ch;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * In the absence of a <ctype.h>, assume input is 7-bit ASCII.
 | 
						|
	 */
 | 
						|
 | 
						|
	while (!done) {
 | 
						|
		ch = getchar();
 | 
						|
		if (random() % 100 < percent) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		if ((ch == '\b' || ch == 127) && pos > 0) {
 | 
						|
			putchar('\b');
 | 
						|
			putchar(' ');
 | 
						|
			putchar('\b');
 | 
						|
			pos--;
 | 
						|
		}
 | 
						|
		else if (ch == '\r' || ch == '\n') {
 | 
						|
			putchar('\r');
 | 
						|
			putchar('\n');
 | 
						|
			done = 1;
 | 
						|
		}
 | 
						|
		else if (ch >= 32 && ch < 127 && pos < len-1) {
 | 
						|
			buf[pos++] = ch;
 | 
						|
			putchar(ch);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			/* alert (bell) character */
 | 
						|
			putchar('\a');
 | 
						|
		}
 | 
						|
	}
 | 
						|
	buf[pos] = 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * interactive
 | 
						|
 * runs the interactive shell.  basically, just infinitely loops, grabbing
 | 
						|
 * commands and running them (and printing the exit status if it's not
 | 
						|
 * success.)
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
interactive(void)
 | 
						|
{
 | 
						|
	char buf[CMDLINE_MAX];
 | 
						|
	struct exitinfo ei;
 | 
						|
 | 
						|
	while (1) {
 | 
						|
		printf("OS/161$ ");
 | 
						|
		getcmd(buf, sizeof(buf));
 | 
						|
		docommand(buf, &ei);
 | 
						|
		printstatus(&ei, 0);
 | 
						|
#ifdef WNOHANG
 | 
						|
		waitpoll();
 | 
						|
#endif
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
check_timing(void)
 | 
						|
{
 | 
						|
	time_t secs;
 | 
						|
	unsigned long nsecs;
 | 
						|
	if (__time(&secs, &nsecs) != -1) {
 | 
						|
		timing = 1;
 | 
						|
		warnx("Timing enabled.");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
usage(void)
 | 
						|
{
 | 
						|
	tprintf("Usage: shll [-p percent]\n");
 | 
						|
	tprintf("   -p   percent of characters to drop (default 5)\n");
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * main
 | 
						|
 * if there are no arguments, run interactively, otherwise, run a program
 | 
						|
 * from within the shell, but immediately exit.
 | 
						|
 */
 | 
						|
 | 
						|
int
 | 
						|
main(int argc, char *argv[])
 | 
						|
{
 | 
						|
#ifdef HOST
 | 
						|
	hostcompat_init(argc, argv);
 | 
						|
#endif
 | 
						|
	check_timing();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Allow argc to be 0 in case we're running on a broken kernel,
 | 
						|
	 * or one that doesn't set argv when starting the first shell.
 | 
						|
	 */
 | 
						|
 | 
						|
	if (argc == 0 || argc == 1) {
 | 
						|
		interactive();
 | 
						|
	} else if (argc == 3 && !strcmp(argv[1], "-p")) {
 | 
						|
		percent = atoi(argv[2]);
 | 
						|
		interactive();
 | 
						|
	} else {
 | 
						|
		usage();
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 |