323 lines
6.1 KiB
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);
|
|
}
|
|
}
|
|
|