Last active
November 17, 2020 10:23
-
-
Save czxttkl/76b2ce86760318b4510f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
============================================================================ | |
Name : sh.c | |
Author : Zhengxing Chen | |
Version : | |
Copyright : czxttkl. Achieve Shell basic functions: execution, IO redirection and Pipe. | |
============================================================================ | |
*/ | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <stdio.h> | |
#include <fcntl.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <dirent.h> | |
// Simplifed xv6 shell. | |
#define MAXARGS 10 | |
// All commands have at least a type. Have looked at the type, the code | |
// typically casts the *cmd to some specific cmd type. | |
struct cmd { | |
int type; // ' ' (exec), | (pipe), '<' or '>' for redirection | |
}; | |
struct execcmd { | |
int type; // ' ' | |
char *argv[MAXARGS]; // arguments to the command to be exec-ed | |
}; | |
struct redircmd { | |
int type; // < or > | |
struct cmd *cmd; // the command to be run (e.g., an execcmd) | |
char *file; // the input/output file | |
int mode; // the mode to open the file with | |
int fd; // the file descriptor number to use for the file | |
}; | |
struct pipecmd { | |
int type; // | | |
struct cmd *left; // left side of pipe | |
struct cmd *right; // right side of pipe | |
}; | |
int fork1(void); // Fork but exits on failure. | |
struct cmd *parsecmd(char*); | |
// Execute cmd. Never returns. | |
void runcmd(struct cmd *cmd) { | |
int p[2], r; | |
struct execcmd *ecmd; | |
struct pipecmd *pcmd; | |
struct redircmd *rcmd; | |
if (cmd == 0) | |
exit(0); | |
switch (cmd->type) { | |
default: | |
fprintf(stderr, "unknown runcmd\n"); | |
exit(-1); | |
case ' ': | |
ecmd = (struct execcmd*) cmd; | |
if (ecmd->argv[0] == 0) | |
exit(0); | |
execvp(ecmd->argv[0], ecmd->argv); | |
break; | |
case '>': | |
case '<': | |
rcmd = (struct redircmd*) cmd; | |
// Redirected file descriptor | |
int rfd; | |
if ((rfd = open(rcmd->file, rcmd->mode, 0777)) != -1) { | |
dup2(rfd, rcmd->fd); | |
close(rfd); | |
} else { | |
fprintf(stderr, "Can't redirect to the file"); | |
} | |
runcmd(rcmd->cmd); | |
break; | |
case '|': | |
pcmd = (struct pipecmd*) cmd; | |
//Bytes written on pipefd[1] can be read from pipefd[0]. | |
int pipefd[2]; | |
if (pipe(pipefd) == -1) { | |
perror("pipe error"); | |
exit(EXIT_FAILURE); | |
} | |
if (!fork1()) { | |
// Child process runs the left command | |
// Map stdout to pipefd[1] | |
close(STDOUT_FILENO); | |
dup(pipefd[1]); | |
runcmd(pcmd->left); | |
} else { | |
// Parent process runs the right command | |
// Map stdin to pipefd[0] | |
close(STDIN_FILENO); | |
dup(pipefd[0]); | |
close(pipefd[1]); | |
runcmd(pcmd->right); | |
} | |
break; | |
} | |
exit(0); | |
} | |
int getcmd(char *buf, int nbuf) { | |
if (isatty(fileno(stdin))) | |
fprintf(stdout, "$ "); | |
memset(buf, 0, nbuf); | |
fgets(buf, nbuf, stdin); | |
if (buf[0] == 0) // EOF | |
return -1; | |
return 0; | |
} | |
int main(void) { | |
static char buf[100]; | |
int fd, r; | |
// Read and run input commands.l | |
while (getcmd(buf, sizeof(buf)) >= 0) { | |
if (buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' ') { | |
// Clumsy but will have to do for now. | |
// Chdir has no effect on the parent if run in the child. | |
buf[strlen(buf) - 1] = 0; // chop \n | |
if (chdir(buf + 3) < 0) | |
fprintf(stderr, "cannot cd %s\n", buf + 3); | |
continue; | |
} | |
if (fork1() == 0) | |
runcmd(parsecmd(buf)); | |
wait(&r); | |
} | |
exit(0); | |
} | |
int fork1(void) { | |
int pid; | |
pid = fork(); | |
if (pid == -1) | |
perror("fork"); | |
return pid; | |
} | |
struct cmd* | |
execcmd(void) { | |
struct execcmd *cmd; | |
cmd = malloc(sizeof(*cmd)); | |
memset(cmd, 0, sizeof(*cmd)); | |
cmd->type = ' '; | |
return (struct cmd*) cmd; | |
} | |
struct cmd* | |
redircmd(struct cmd *subcmd, char *file, int type) { | |
struct redircmd *cmd; | |
cmd = malloc(sizeof(*cmd)); | |
memset(cmd, 0, sizeof(*cmd)); | |
cmd->type = type; | |
cmd->cmd = subcmd; | |
cmd->file = file; | |
cmd->mode = (type == '<') ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC; | |
cmd->fd = (type == '<') ? 0 : 1; | |
return (struct cmd*) cmd; | |
} | |
struct cmd* | |
pipecmd(struct cmd *left, struct cmd *right) { | |
struct pipecmd *cmd; | |
cmd = malloc(sizeof(*cmd)); | |
memset(cmd, 0, sizeof(*cmd)); | |
cmd->type = '|'; | |
cmd->left = left; | |
cmd->right = right; | |
return (struct cmd*) cmd; | |
} | |
// Parsing | |
char whitespace[] = " \t\r\n\v"; | |
char symbols[] = "<|>"; | |
int gettoken(char **ps, char *es, char **q, char **eq) { | |
char *s; | |
int ret; | |
s = *ps; | |
while (s < es && strchr(whitespace, *s)) | |
s++; | |
if (q) | |
*q = s; | |
ret = *s; | |
switch (*s) { | |
case 0: | |
break; | |
case '|': | |
case '<': | |
s++; | |
break; | |
case '>': | |
s++; | |
break; | |
default: | |
ret = 'a'; | |
while (s < es && !strchr(whitespace, *s) && !strchr(symbols, *s)) | |
s++; | |
break; | |
} | |
if (eq) | |
*eq = s; | |
while (s < es && strchr(whitespace, *s)) | |
s++; | |
*ps = s; | |
return ret; | |
} | |
int peek(char **ps, char *es, char *toks) { | |
char *s; | |
s = *ps; | |
while (s < es && strchr(whitespace, *s)) | |
s++; | |
*ps = s; | |
return *s && strchr(toks, *s); | |
} | |
struct cmd *parseline(char**, char*); | |
struct cmd *parsepipe(char**, char*); | |
struct cmd *parseexec(char**, char*); | |
// make a copy of the characters in the input buffer, starting from s through es. | |
// null-terminate the copy to make it a string. | |
char *mkcopy(char *s, char *es) { | |
int n = es - s; | |
char *c = malloc(n + 1); | |
assert(c); | |
strncpy(c, s, n); | |
c[n] = 0; | |
return c; | |
} | |
struct cmd* | |
parsecmd(char *s) { | |
char *es; | |
struct cmd *cmd; | |
es = s + strlen(s); | |
cmd = parseline(&s, es); | |
peek(&s, es, ""); | |
if (s != es) { | |
fprintf(stderr, "leftovers: %s\n", s); | |
exit(-1); | |
} | |
return cmd; | |
} | |
struct cmd* | |
parseline(char **ps, char *es) { | |
struct cmd *cmd; | |
cmd = parsepipe(ps, es); | |
return cmd; | |
} | |
struct cmd* | |
parsepipe(char **ps, char *es) { | |
struct cmd *cmd; | |
cmd = parseexec(ps, es); | |
if (peek(ps, es, "|")) { | |
gettoken(ps, es, 0, 0); | |
cmd = pipecmd(cmd, parsepipe(ps, es)); | |
} | |
return cmd; | |
} | |
struct cmd* | |
parseredirs(struct cmd *cmd, char **ps, char *es) { | |
int tok; | |
char *q, *eq; | |
while (peek(ps, es, "<>")) { | |
tok = gettoken(ps, es, 0, 0); | |
if (gettoken(ps, es, &q, &eq) != 'a') { | |
fprintf(stderr, "missing file for redirection\n"); | |
exit(-1); | |
} | |
switch (tok) { | |
case '<': | |
cmd = redircmd(cmd, mkcopy(q, eq), '<'); | |
break; | |
case '>': | |
cmd = redircmd(cmd, mkcopy(q, eq), '>'); | |
break; | |
} | |
} | |
return cmd; | |
} | |
struct cmd* | |
parseexec(char **ps, char *es) { | |
char *q, *eq; | |
int tok, argc; | |
struct execcmd *cmd; | |
struct cmd *ret; | |
ret = execcmd(); | |
cmd = (struct execcmd*) ret; | |
argc = 0; | |
ret = parseredirs(ret, ps, es); | |
while (!peek(ps, es, "|")) { | |
if ((tok = gettoken(ps, es, &q, &eq)) == 0) | |
break; | |
if (tok != 'a') { | |
fprintf(stderr, "syntax error\n"); | |
exit(-1); | |
} | |
cmd->argv[argc] = mkcopy(q, eq); | |
argc++; | |
if (argc >= MAXARGS) { | |
fprintf(stderr, "too many args\n"); | |
exit(-1); | |
} | |
ret = parseredirs(ret, ps, es); | |
} | |
cmd->argv[argc] = 0; | |
return ret; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome!