Lab 2 Shell
Write a simple shell in OS
WORK-IN-PROGRESS
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fcntl.h"
# define MAX_ARGV_LEN 10
struct cmd {
int type;
};
// Parsed command representation
#define EXEC 1
#define REDIR 2
#define PIPE 3
struct execcmd {
int type;
char *argv[MAX_ARGV_LEN];
char *eargv[MAX_ARGV_LEN];
};
struct pipecmd {
int type;
struct cmd *left;
struct cmd *right;
};
struct redircmd {
int type;
struct cmd * cmd;
char* file;
char* efile;
int fd;
int mode;
};
// cosntructor
struct cmd * execcmd(void) {
struct execcmd *cmd;
cmd = malloc(sizeof(*cmd));
memset(cmd, 0, sizeof(*cmd));
cmd->type = EXEC;
// we cast back to struct cmd and return.
return (struct cmd*)cmd;
}
struct cmd* pipecmd(void) {
struct pipecmd *cmd;
cmd = malloc(sizeof(*cmd));
memset(cmd, 0, sizeof(*cmd));
cmd->type = PIPE;
return (struct cmd*) cmd;
}
struct cmd* redircmd(void) {
struct redircmd *cmd;
cmd = malloc(sizeof(*cmd));
memset(cmd, 0, sizeof(*cmd));
cmd->type = REDIR;
return (struct cmd*) cmd;
}
char WHITECHARS[] = "\t\n ";
int
my_strlen(const char *s)
{
int n;
for(n = 0; s[n]; n++)
;
return n;
}
char*
my_strchr(const char *s, char c)
{
for(; *s; s++)
if(*s == c)
return (char*)s;
return 0;
}
int
gettoken(char **ps, char *es, char **q, char **eq)
{
char *s = *ps;
while (s < es && my_strchr(WHITECHARS, *s))
s++;
if (q)
*q = s;
int ret = *s;
// now we have a non-whitespace char that s points to.
switch(*s)
{
case 0:
break;
case '|':
case '<':
case '>':
s++; // skip it.
break;
default:
ret = 'a';
// loop str stops when see whitespace.
while (s < es && !my_strchr(WHITECHARS, *s))
s++;
break;
}
if (eq)
*eq = s;
// Make new ps points to non-whitespace char.
while (s < es && my_strchr(WHITECHARS, *s))
s++;
*ps = s;
return ret;
}
void test_get_token()
{
/*
INPUT: ls mush | wc
(xiaying | wc)(ls mush | wc)( mush | wc)
(| wc)(mush | wc)( | wc)
(wc)(| wc)( wc)
()(wc)()
After null terminated eargv.
Done. (ls)()
Done. (mush)()
Done. (|)()
Done. (wc)()
Note:
eargv is array of pointers. We null the 1st char of each array.
So our argv can end properly.
*/
char buf[100] = "ls xiaying | wc";
char *argv[10];
char *eargv[10];
char *s = buf;
char *q, *eq;
char *es = s + my_strlen(s);
int i = 0;
while (gettoken(&s, es, &q, &eq) != '\0') {
printf("(%s)(%s)(%s)\n", s, q, eq);
argv[i] = q;
eargv[i] = eq;
i++;
}
for (int k=0; k<i; k++) {
*eargv[k] = 0;
}
for (int j=0; j<i;j++) {
printf("Done. (%s)(%s)\n", argv[j], eargv[j]);
}
}
int
peek(char **ps, char *es, char *toks)
{
// Move to non-space char, check if exists tokens specified in params.
char *s;
s = *ps;
while(s < es && strchr(WHITECHARS, *s))
s++;
*ps = s;
return *s && strchr(toks, *s);
}
struct cmd* parsecmd(char *s);
struct cmd*
nulterminate(struct cmd *cmd);
struct cmd* parsepipe(struct cmd* cmd, char **s)
{
//printf("pasing pipe...\n");
struct cmd* ret = pipecmd();
struct pipecmd *pc = (struct pipecmd*)ret;
pc->left = cmd;
pc->right = parsecmd(*s);
return ret;
}
struct cmd* parseredir(struct cmd* cmd, char **ps, char *es) {
if (!peek(ps, es, "<>")) {
return cmd;
}
int fd;
int mode;
int tok = gettoken(ps, es, 0, 0);
if (tok == '<') {
// read mode
fd = 0;
mode = O_RDONLY;
} else {
// '>' is write mode
fd = 1;
mode = O_WRONLY|O_CREATE;
}
//printf("pasing redir: %s\n", *ps);
char *q, *eq;
gettoken(ps, es, &q, &eq);
struct cmd* ret = redircmd();
struct redircmd *rc = (struct redircmd*)ret;
rc->fd = fd;
rc->cmd = cmd;
rc->mode = mode;
rc->file = q;
rc->efile = eq;
return ret;
}
struct cmd* parsecmd(char *s)
{
// ctor() of execcmd returns a cmd ptr. We need to case it back to use.
// but we still want to return the raw cmd ptr.
struct cmd* ret = execcmd();
struct execcmd *ec = (struct execcmd*)ret;
char *q, *eq;
char *es = s + my_strlen(s);
int i = 0;
while (!peek(&s, es, "|")) {
if (gettoken(&s, es, &q, &eq) == '\0') {
break;
}
ec->argv[i] = q;
ec->eargv[i] = eq;
i++;
ret = parseredir(ret, &s, es);
}
// parse pipe
if (peek(&s, es, "|")) {
// Skip the '|' char.
gettoken(&s, es, 0, 0);
ret = parsepipe(ret, &s);
}
ec->argv[i] = 0;
ec->eargv[i] = 0;
nulterminate(ret);
return ret;
}
// NUL-terminate all the counted strings.
struct cmd*
nulterminate(struct cmd *cmd)
{
// Copy from sh.c
int i;
struct execcmd *ecmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;
if(cmd == 0)
return 0;
switch(cmd->type){
case EXEC:
ecmd = (struct execcmd*)cmd;
// It is critical to set the 1st char of the argv[k] ptr points to
// as null, not the whole pointer to null!
for(i=0; ecmd->argv[i]; i++)
*ecmd->eargv[i] = 0;
break;
case REDIR:
rcmd = (struct redircmd*)cmd;
nulterminate(rcmd->cmd);
*rcmd->efile = 0;
break;
case PIPE:
pcmd = (struct pipecmd*)cmd;
nulterminate(pcmd->left);
nulterminate(pcmd->right);
break;
}
return cmd;
}
void runcmd(struct cmd *cmd) {
int type = cmd->type;
//printf("Type is %d\n", type);
if (type == 0)
return;
// If not defined here, got err: a label can only be part of a statement
// and a declaration is not a statement.
struct execcmd *ec;
struct pipecmd *pc;
struct redircmd *rc;
switch(type)
{
case EXEC:
ec = (struct execcmd*)cmd;
exec(ec->argv[0], ec->argv);
break;
case PIPE:
pc = (struct pipecmd *) cmd;
int p[2];
pipe(p);
if (fork() == 0) {
close(1);
//http://man7.org/linux/man-pages/man2/dup.2.html
dup(p[1]); // stdout
close(p[0]);
close(p[1]);
runcmd(pc->left);
}
if (fork() == 0) {
close(0);
dup(p[0]);
close(p[1]);
close(p[0]);
runcmd(pc->right);
}
close(p[0]);
close(p[1]);
wait(0);
wait(0);
break;
case REDIR:
rc = (struct redircmd *) cmd;
close(rc->fd);
if(open(rc->file, rc->mode) < 0){
fprintf(2, "open %s failed\n", rc->file);
exit(1);
}
runcmd(rc->cmd);
break;
default:
printf("unknown type!\n");
break;
}
}
int main(void)
{
static char buf[1000];
int fd;
// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
if(fd >= 3){
close(fd);
break;
}
}
while(1) {
memset(buf, 0, sizeof(buf));
printf("@ ");
gets(buf, sizeof(buf));
if (buf[0] == 0) // EOF
{
printf("\n");
break;
}
//printf("buf is %s", buf);
if (strcmp("quit()\n", buf) == 0) {
printf("Exit nsh..\n");
break;
}
if (fork() == 0)
runcmd(parsecmd(buf));
wait(0);
}
exit(1);
}
/*
STATUS
$ testsh nsh
simple echo: PASS
simple grep: PASS
two commands: PASS
output redirection: PASS
input redirection: PASS
both redirections: testsh: saw expected output, but too much else as well
FAIL
simple pipe: PASS
pipe and redirects: PASS
lots of commands: FAIL
Last updated