Scott Haseley 0ab862abfa 1) Moved tprintf and related functions to their own file in common/libc/printf/tprintf.c.
This file is included by both libc and hostcompat.

2) Changed printf -> tprintf in all testbin programs
2016-01-15 13:33:11 -05:00

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) {
printsf("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;
}