jos/user/sh.c

323 lines
6.1 KiB
C

#include <inc/lib.h>
#define BUFSIZ 1024 /* Find the buffer overrun bug! */
int debug = 0;
// gettoken(s, 0) prepares gettoken for subsequent calls and returns 0.
// gettoken(0, token) parses a shell token from the previously set string,
// null-terminates that token, stores the token pointer in '*token',
// and returns a token ID (0, '<', '>', '|', or 'w').
// Subsequent calls to 'gettoken(0, token)' will return subsequent
// tokens from the string.
int gettoken(char *s, char **token);
// Parse a shell command from string 's' and execute it.
// Do not return until the shell command is finished.
// runcmd() is called in a forked child,
// so it's OK to manipulate file descriptor state.
#define MAXARGS 16
void
runcmd(char* s)
{
char *argv[MAXARGS], *t, argv0buf[BUFSIZ];
int argc, c, i, r, p[2], fd, pipe_child;
pipe_child = 0;
gettoken(s, 0);
again:
argc = 0;
while (1) {
switch ((c = gettoken(0, &t))) {
case 'w': // Add an argument
if (argc == MAXARGS) {
cprintf("too many arguments\n");
exit();
}
argv[argc++] = t;
break;
case '<': // Input redirection
// Grab the filename from the argument list
if (gettoken(0, &t) != 'w') {
cprintf("syntax error: < not followed by word\n");
exit();
}
// Open 't' for reading as file descriptor 0
// (which environments use as standard input).
// We can't open a file onto a particular descriptor,
// so open the file as 'fd',
// then check whether 'fd' is 0.
// If not, dup 'fd' onto file descriptor 0,
// then close the original 'fd'.
// LAB 5: Your code here.
panic("< redirection not implemented");
break;
case '>': // Output redirection
// Grab the filename from the argument list
if (gettoken(0, &t) != 'w') {
cprintf("syntax error: > not followed by word\n");
exit();
}
if ((fd = open(t, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
cprintf("open %s for write: %e", t, fd);
exit();
}
if (fd != 1) {
dup(fd, 1);
close(fd);
}
break;
case '|': // Pipe
if ((r = pipe(p)) < 0) {
cprintf("pipe: %e", r);
exit();
}
if (debug)
cprintf("PIPE: %d %d\n", p[0], p[1]);
if ((r = fork()) < 0) {
cprintf("fork: %e", r);
exit();
}
if (r == 0) {
if (p[0] != 0) {
dup(p[0], 0);
close(p[0]);
}
close(p[1]);
goto again;
} else {
pipe_child = r;
if (p[1] != 1) {
dup(p[1], 1);
close(p[1]);
}
close(p[0]);
goto runit;
}
panic("| not implemented");
break;
case 0: // String is complete
// Run the current command!
goto runit;
default:
panic("bad return %d from gettoken", c);
break;
}
}
runit:
// Return immediately if command line was empty.
if(argc == 0) {
if (debug)
cprintf("EMPTY COMMAND\n");
return;
}
// Clean up command line.
// Read all commands from the filesystem: add an initial '/' to
// the command name.
// This essentially acts like 'PATH=/'.
if (argv[0][0] != '/') {
argv0buf[0] = '/';
strcpy(argv0buf + 1, argv[0]);
argv[0] = argv0buf;
}
argv[argc] = 0;
// Print the command.
if (debug) {
cprintf("[%08x] SPAWN:", thisenv->env_id);
for (i = 0; argv[i]; i++)
cprintf(" %s", argv[i]);
cprintf("\n");
}
// Spawn the command!
if ((r = spawn(argv[0], (const char**) argv)) < 0)
cprintf("spawn %s: %e\n", argv[0], r);
// In the parent, close all file descriptors and wait for the
// spawned command to exit.
close_all();
if (r >= 0) {
if (debug)
cprintf("[%08x] WAIT %s %08x\n", thisenv->env_id, argv[0], r);
wait(r);
if (debug)
cprintf("[%08x] wait finished\n", thisenv->env_id);
}
// If we were the left-hand part of a pipe,
// wait for the right-hand part to finish.
if (pipe_child) {
if (debug)
cprintf("[%08x] WAIT pipe_child %08x\n", thisenv->env_id, pipe_child);
wait(pipe_child);
if (debug)
cprintf("[%08x] wait finished\n", thisenv->env_id);
}
// Done!
exit();
}
// Get the next token from string s.
// Set *p1 to the beginning of the token and *p2 just past the token.
// Returns
// 0 for end-of-string;
// < for <;
// > for >;
// | for |;
// w for a word.
//
// Eventually (once we parse the space where the \0 will go),
// words get nul-terminated.
#define WHITESPACE " \t\r\n"
#define SYMBOLS "<|>&;()"
int
_gettoken(char *s, char **p1, char **p2)
{
int t;
if (s == 0) {
if (debug > 1)
cprintf("GETTOKEN NULL\n");
return 0;
}
if (debug > 1)
cprintf("GETTOKEN: %s\n", s);
*p1 = 0;
*p2 = 0;
while (strchr(WHITESPACE, *s))
*s++ = 0;
if (*s == 0) {
if (debug > 1)
cprintf("EOL\n");
return 0;
}
if (strchr(SYMBOLS, *s)) {
t = *s;
*p1 = s;
*s++ = 0;
*p2 = s;
if (debug > 1)
cprintf("TOK %c\n", t);
return t;
}
*p1 = s;
while (*s && !strchr(WHITESPACE SYMBOLS, *s))
s++;
*p2 = s;
if (debug > 1) {
t = **p2;
**p2 = 0;
cprintf("WORD: %s\n", *p1);
**p2 = t;
}
return 'w';
}
int
gettoken(char *s, char **p1)
{
static int c, nc;
static char* np1, *np2;
if (s) {
nc = _gettoken(s, &np1, &np2);
return 0;
}
c = nc;
*p1 = np1;
nc = _gettoken(np2, &np1, &np2);
return c;
}
void
usage(void)
{
cprintf("usage: sh [-dix] [command-file]\n");
exit();
}
void
umain(int argc, char **argv)
{
int r, interactive, echocmds;
struct Argstate args;
interactive = '?';
echocmds = 0;
argstart(&argc, argv, &args);
while ((r = argnext(&args)) >= 0)
switch (r) {
case 'd':
debug++;
break;
case 'i':
interactive = 1;
break;
case 'x':
echocmds = 1;
break;
default:
usage();
}
if (argc > 2)
usage();
if (argc == 2) {
close(0);
if ((r = open(argv[1], O_RDONLY)) < 0)
panic("open %s: %e", argv[1], r);
assert(r == 0);
}
if (interactive == '?')
interactive = iscons(0);
while (1) {
char *buf;
buf = readline(interactive ? "$ " : NULL);
if (buf == NULL) {
if (debug)
cprintf("EXITING\n");
exit(); // end of file
}
if (debug)
cprintf("LINE: %s\n", buf);
if (buf[0] == '#')
continue;
if (echocmds)
printf("# %s\n", buf);
if (debug)
cprintf("BEFORE FORK\n");
if ((r = fork()) < 0)
panic("fork: %e", r);
if (debug)
cprintf("FORK: %d\n", r);
if (r == 0) {
runcmd(buf);
exit();
} else
wait(r);
}
}