|
@@ -1,106 +1,556 @@
|
|
|
+#include <stdint.h>
|
|
|
+#include <stdarg.h>
|
|
|
#include <sys/wait.h>
|
|
|
#include <stdio.h>
|
|
|
#include <string.h>
|
|
|
+#include <fcntl.h>
|
|
|
#include <unistd.h>
|
|
|
|
|
|
-#define print(str) write(STDOUT_FILENO, str, strlen(str))
|
|
|
-
|
|
|
-// struct cmd {
|
|
|
-// enum {
|
|
|
-// pipe,
|
|
|
-// list,
|
|
|
-// } op;
|
|
|
-// struct cmd* left;
|
|
|
-// struct cmd* right;
|
|
|
-// struct token* tk;
|
|
|
-// };
|
|
|
-
|
|
|
-// struct token {
|
|
|
-// char* start;
|
|
|
-// size_t len;
|
|
|
-
|
|
|
-// struct token* next;
|
|
|
-// };
|
|
|
-
|
|
|
-// static inline int betwn(char c, char s, char e)
|
|
|
-// {
|
|
|
-// return c >= s && c <= e;
|
|
|
-// }
|
|
|
-// static inline int is_num(char c)
|
|
|
-// {
|
|
|
-// return betwn(c, '0', '9');
|
|
|
-// }
|
|
|
-// static inline int check_token(char c)
|
|
|
-// {
|
|
|
-// return betwn(c, 'a', 'z') || betwn(c, 'A', 'Z') || (c == '_');
|
|
|
-// }
|
|
|
-// static inline int read_token(char* buf, struct token** cur)
|
|
|
-// {
|
|
|
-// char* p = buf;
|
|
|
-// for (; *p && (check_token(*p) || (p != buf && is_num(*p))); ++p) {
|
|
|
-// (*cur)->start = buf;
|
|
|
-// ++(*cur)->len;
|
|
|
+int printf(const char* fmt, ...)
|
|
|
+{
|
|
|
+ va_list args;
|
|
|
+ va_start(args, fmt);
|
|
|
+
|
|
|
+ char buf[256] = {};
|
|
|
+ int len = snprintf(buf, sizeof(buf), fmt, args);
|
|
|
+
|
|
|
+ len = write(STDOUT_FILENO, buf, len);
|
|
|
+
|
|
|
+ va_end(args);
|
|
|
+
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+char* strchr(const char* str, int c)
|
|
|
+{
|
|
|
+ const char* p = str;
|
|
|
+ while (*p) {
|
|
|
+ if (*p == c)
|
|
|
+ return (char*)p;
|
|
|
+ ++p;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+char* gets(char* buf, int bufsize)
|
|
|
+{
|
|
|
+ int n = read(STDIN_FILENO, buf, bufsize);
|
|
|
+ if (n > 0 && buf[n-1] == '\n') {
|
|
|
+ buf[n-1] = 0;
|
|
|
+ return buf;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+int puts(const char* str)
|
|
|
+{
|
|
|
+ int len = strlen(str);
|
|
|
+ write(STDOUT_FILENO, str, len);
|
|
|
+ return len + 1;
|
|
|
+}
|
|
|
+
|
|
|
+void* malloc(size_t n)
|
|
|
+{
|
|
|
+ static char mems[1024];
|
|
|
+ static int pos = 0;
|
|
|
+ if (n == 0) {
|
|
|
+ pos = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ int orig_pos = pos;
|
|
|
+ pos += n;
|
|
|
+ return mems + orig_pos;
|
|
|
+}
|
|
|
+
|
|
|
+// Parsed command representation
|
|
|
+#define EXEC 1
|
|
|
+#define REDIR 2
|
|
|
+#define PIPE 3
|
|
|
+#define LIST 4
|
|
|
+#define BACK 5
|
|
|
+
|
|
|
+#define MAXARGS 10
|
|
|
+
|
|
|
+struct cmd {
|
|
|
+ int type;
|
|
|
+};
|
|
|
+
|
|
|
+struct execcmd {
|
|
|
+ int type;
|
|
|
+ char *argv[MAXARGS];
|
|
|
+ char *eargv[MAXARGS];
|
|
|
+};
|
|
|
+
|
|
|
+struct redircmd {
|
|
|
+ int type;
|
|
|
+ struct cmd *cmd;
|
|
|
+ char *file;
|
|
|
+ char *efile;
|
|
|
+ int mode;
|
|
|
+ int fd;
|
|
|
+};
|
|
|
+
|
|
|
+struct pipecmd {
|
|
|
+ int type;
|
|
|
+ struct cmd *left;
|
|
|
+ struct cmd *right;
|
|
|
+};
|
|
|
+
|
|
|
+struct listcmd {
|
|
|
+ int type;
|
|
|
+ struct cmd *left;
|
|
|
+ struct cmd *right;
|
|
|
+};
|
|
|
+
|
|
|
+struct backcmd {
|
|
|
+ int type;
|
|
|
+ struct cmd *cmd;
|
|
|
+};
|
|
|
+
|
|
|
+int fork1(void); // Fork but panics on failure.
|
|
|
+void panic(char*);
|
|
|
+struct cmd *parsecmd(char*);
|
|
|
+
|
|
|
+// Execute cmd. Never returns.
|
|
|
+void
|
|
|
+runcmd(struct cmd *cmd)
|
|
|
+{
|
|
|
+// int p[2];
|
|
|
+ struct backcmd *bcmd;
|
|
|
+ struct execcmd *ecmd;
|
|
|
+ struct listcmd *lcmd;
|
|
|
+// struct pipecmd *pcmd;
|
|
|
+// struct redircmd *rcmd;
|
|
|
+
|
|
|
+ if(cmd == 0)
|
|
|
+ _exit(-1);
|
|
|
+
|
|
|
+ switch(cmd->type){
|
|
|
+ default:
|
|
|
+ panic("runcmd");
|
|
|
+
|
|
|
+ case EXEC:
|
|
|
+ ecmd = (struct execcmd*)cmd;
|
|
|
+ if(ecmd->argv[0] == 0)
|
|
|
+ _exit(-1);
|
|
|
+ char* const envp[1] = { NULL };
|
|
|
+ execve(ecmd->argv[0], ecmd->argv, envp);
|
|
|
+ printf("exec %s failed\n", ecmd->argv[0]);
|
|
|
+ break;
|
|
|
+
|
|
|
+// case REDIR:
|
|
|
+// rcmd = (struct redircmd*)cmd;
|
|
|
+// close(rcmd->fd);
|
|
|
+// if(open(rcmd->file, rcmd->mode) < 0){
|
|
|
+// printf("open %s failed\n", rcmd->file);
|
|
|
+// _exit(-1);
|
|
|
// }
|
|
|
-// if ((*cur)->start) {
|
|
|
-// (*cur)->next = *cur + 1;
|
|
|
-// *cur = (*cur)->next;
|
|
|
+// runcmd(rcmd->cmd);
|
|
|
+// break;
|
|
|
+
|
|
|
+ case LIST:
|
|
|
+ lcmd = (struct listcmd*)cmd;
|
|
|
+ if(fork1() == 0)
|
|
|
+ runcmd(lcmd->left);
|
|
|
+ int code;
|
|
|
+ wait(&code);
|
|
|
+ runcmd(lcmd->right);
|
|
|
+ break;
|
|
|
+
|
|
|
+// case PIPE:
|
|
|
+// pcmd = (struct pipecmd*)cmd;
|
|
|
+// if(pipe(p) < 0)
|
|
|
+// panic("pipe");
|
|
|
+// if(fork1() == 0){
|
|
|
+// close(1);
|
|
|
+// dup(p[1]);
|
|
|
+// close(p[0]);
|
|
|
+// close(p[1]);
|
|
|
+// runcmd(pcmd->left);
|
|
|
+// }
|
|
|
+// if(fork1() == 0){
|
|
|
+// close(0);
|
|
|
+// dup(p[0]);
|
|
|
+// close(p[0]);
|
|
|
+// close(p[1]);
|
|
|
+// runcmd(pcmd->right);
|
|
|
+// }
|
|
|
+// close(p[0]);
|
|
|
+// close(p[1]);
|
|
|
+// wait();
|
|
|
+// wait();
|
|
|
+// break;
|
|
|
+
|
|
|
+ case BACK:
|
|
|
+ bcmd = (struct backcmd*)cmd;
|
|
|
+ if(fork1() == 0)
|
|
|
+ runcmd(bcmd->cmd);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ _exit(0);
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+getcmd(char *buf, int nbuf)
|
|
|
+{
|
|
|
+ printf("$ ");
|
|
|
+ memset(buf, 0, nbuf);
|
|
|
+ gets(buf, nbuf);
|
|
|
+ if(buf[0] == 0) // EOF
|
|
|
+ return -1;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+main(void)
|
|
|
+{
|
|
|
+ void* _ = malloc(0);
|
|
|
+ (void)_;
|
|
|
+ static char buf[100];
|
|
|
+
|
|
|
+// // Assumes three file descriptors open.
|
|
|
+// while((fd = open("console", 0)) >= 0){
|
|
|
+// if(fd >= 3){
|
|
|
+// close(fd);
|
|
|
+// break;
|
|
|
// }
|
|
|
-// return p - buf;
|
|
|
-// }
|
|
|
-
|
|
|
-int main(int argc, const char** argv)
|
|
|
-{
|
|
|
- (void)argc, (void)argv;
|
|
|
- char buf[512] = {};
|
|
|
-
|
|
|
- print("sh # ");
|
|
|
-
|
|
|
- for (;;) {
|
|
|
- int n = read(STDIN_FILENO, buf, sizeof(buf));
|
|
|
-
|
|
|
- char token[1024] = {};
|
|
|
- char* args[128] = { token, 0 };
|
|
|
- int j = 0;
|
|
|
- int k = 1;
|
|
|
-
|
|
|
- for (int i = 0; i < n; ++i) {
|
|
|
- if (buf[i] == ' ') {
|
|
|
- token[j++] = 0;
|
|
|
- args[k++] = token + j;
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (buf[i] == '\n') {
|
|
|
- token[j++] = 0;
|
|
|
-
|
|
|
- if (strcmp(args[0], "exit") == 0)
|
|
|
- return 0;
|
|
|
-
|
|
|
- pid_t pid = fork();
|
|
|
- if (pid == 0) {
|
|
|
- char* envp[] = { NULL };
|
|
|
- int ret = execve(args[0], args, envp);
|
|
|
- char _b[128];
|
|
|
- snprintf(_b, sizeof(_b), "sh: execve() failed with code %d\n", ret);
|
|
|
- print(_b);
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- int code;
|
|
|
- wait(&code);
|
|
|
-
|
|
|
- char _b[128];
|
|
|
- snprintf(_b, sizeof(_b), "sh (%d) # ", code);
|
|
|
- print(_b);
|
|
|
-
|
|
|
- j = 0;
|
|
|
- k = 1;
|
|
|
- memset(args, 0x00, sizeof(args));
|
|
|
- args[0] = token;
|
|
|
- continue;
|
|
|
- }
|
|
|
- token[j++] = buf[i];
|
|
|
- }
|
|
|
+// }
|
|
|
+
|
|
|
+ // Read and run input commands.
|
|
|
+ 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)
|
|
|
+ // printf("cannot cd %s\n", buf+3);
|
|
|
+ // continue;
|
|
|
+ // }
|
|
|
+ if(fork1() == 0)
|
|
|
+ runcmd(parsecmd(buf));
|
|
|
+ int code;
|
|
|
+ wait(&code);
|
|
|
+ }
|
|
|
+ _exit(0);
|
|
|
+}
|
|
|
+
|
|
|
+void __attribute__((noreturn))
|
|
|
+panic(char *s)
|
|
|
+{
|
|
|
+ printf("%s\n", s);
|
|
|
+ _exit(-1);
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+fork1(void)
|
|
|
+{
|
|
|
+ int pid;
|
|
|
+
|
|
|
+ pid = fork();
|
|
|
+ if(pid == -1)
|
|
|
+ panic("fork");
|
|
|
+ return pid;
|
|
|
+}
|
|
|
+
|
|
|
+//PAGEBREAK!
|
|
|
+// Constructors
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+execcmd(void)
|
|
|
+{
|
|
|
+ struct execcmd *cmd;
|
|
|
+
|
|
|
+ cmd = malloc(sizeof(*cmd));
|
|
|
+ memset(cmd, 0, sizeof(*cmd));
|
|
|
+ cmd->type = EXEC;
|
|
|
+ return (struct cmd*)cmd;
|
|
|
+}
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+redircmd(struct cmd *subcmd, char *file, char *efile, int mode, int fd)
|
|
|
+{
|
|
|
+ struct redircmd *cmd;
|
|
|
+
|
|
|
+ cmd = malloc(sizeof(*cmd));
|
|
|
+ memset(cmd, 0, sizeof(*cmd));
|
|
|
+ cmd->type = REDIR;
|
|
|
+ cmd->cmd = subcmd;
|
|
|
+ cmd->file = file;
|
|
|
+ cmd->efile = efile;
|
|
|
+ cmd->mode = mode;
|
|
|
+ cmd->fd = fd;
|
|
|
+ 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 = PIPE;
|
|
|
+ cmd->left = left;
|
|
|
+ cmd->right = right;
|
|
|
+ return (struct cmd*)cmd;
|
|
|
+}
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+listcmd(struct cmd *left, struct cmd *right)
|
|
|
+{
|
|
|
+ struct listcmd *cmd;
|
|
|
+
|
|
|
+ cmd = malloc(sizeof(*cmd));
|
|
|
+ memset(cmd, 0, sizeof(*cmd));
|
|
|
+ cmd->type = LIST;
|
|
|
+ cmd->left = left;
|
|
|
+ cmd->right = right;
|
|
|
+ return (struct cmd*)cmd;
|
|
|
+}
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+backcmd(struct cmd *subcmd)
|
|
|
+{
|
|
|
+ struct backcmd *cmd;
|
|
|
+
|
|
|
+ cmd = malloc(sizeof(*cmd));
|
|
|
+ memset(cmd, 0, sizeof(*cmd));
|
|
|
+ cmd->type = BACK;
|
|
|
+ cmd->cmd = subcmd;
|
|
|
+ return (struct cmd*)cmd;
|
|
|
+}
|
|
|
+//PAGEBREAK!
|
|
|
+// 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 '(':
|
|
|
+ case ')':
|
|
|
+ case ';':
|
|
|
+ case '&':
|
|
|
+ case '<':
|
|
|
+ s++;
|
|
|
+ break;
|
|
|
+ case '>':
|
|
|
+ s++;
|
|
|
+ if(*s == '>'){
|
|
|
+ ret = '+';
|
|
|
+ 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*);
|
|
|
+struct cmd *nulterminate(struct cmd*);
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+parsecmd(char *s)
|
|
|
+{
|
|
|
+ char *es;
|
|
|
+ struct cmd *cmd;
|
|
|
+
|
|
|
+ es = s + strlen(s);
|
|
|
+ cmd = parseline(&s, es);
|
|
|
+ peek(&s, es, "");
|
|
|
+ if(s != es){
|
|
|
+ printf("leftovers: %s\n", s);
|
|
|
+ panic("syntax");
|
|
|
+ }
|
|
|
+ nulterminate(cmd);
|
|
|
+ return cmd;
|
|
|
+}
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+parseline(char **ps, char *es)
|
|
|
+{
|
|
|
+ struct cmd *cmd;
|
|
|
+
|
|
|
+ cmd = parsepipe(ps, es);
|
|
|
+ while(peek(ps, es, "&")){
|
|
|
+ gettoken(ps, es, 0, 0);
|
|
|
+ cmd = backcmd(cmd);
|
|
|
+ }
|
|
|
+ if(peek(ps, es, ";")){
|
|
|
+ gettoken(ps, es, 0, 0);
|
|
|
+ cmd = listcmd(cmd, parseline(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')
|
|
|
+ panic("missing file for redirection");
|
|
|
+ switch(tok){
|
|
|
+ case '<':
|
|
|
+ cmd = redircmd(cmd, q, eq, 0, 0);
|
|
|
+ break;
|
|
|
+ case '>':
|
|
|
+ cmd = redircmd(cmd, q, eq, 0, 1);
|
|
|
+ break;
|
|
|
+ case '+': // >>
|
|
|
+ cmd = redircmd(cmd, q, eq, 0, 1);
|
|
|
+ break;
|
|
|
}
|
|
|
+ }
|
|
|
+ return cmd;
|
|
|
+}
|
|
|
|
|
|
+struct cmd*
|
|
|
+parseblock(char **ps, char *es)
|
|
|
+{
|
|
|
+ struct cmd *cmd;
|
|
|
+
|
|
|
+ if(!peek(ps, es, "("))
|
|
|
+ panic("parseblock");
|
|
|
+ gettoken(ps, es, 0, 0);
|
|
|
+ cmd = parseline(ps, es);
|
|
|
+ if(!peek(ps, es, ")"))
|
|
|
+ panic("syntax - missing )");
|
|
|
+ gettoken(ps, es, 0, 0);
|
|
|
+ cmd = parseredirs(cmd, ps, es);
|
|
|
+ return cmd;
|
|
|
+}
|
|
|
+
|
|
|
+struct cmd*
|
|
|
+parseexec(char **ps, char *es)
|
|
|
+{
|
|
|
+ char *q, *eq;
|
|
|
+ int tok, argc;
|
|
|
+ struct execcmd *cmd;
|
|
|
+ struct cmd *ret;
|
|
|
+
|
|
|
+ if(peek(ps, es, "("))
|
|
|
+ return parseblock(ps, es);
|
|
|
+
|
|
|
+ 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')
|
|
|
+ panic("syntax");
|
|
|
+ cmd->argv[argc] = q;
|
|
|
+ cmd->eargv[argc] = eq;
|
|
|
+ argc++;
|
|
|
+ if(argc >= MAXARGS)
|
|
|
+ panic("too many args");
|
|
|
+ ret = parseredirs(ret, ps, es);
|
|
|
+ }
|
|
|
+ cmd->argv[argc] = 0;
|
|
|
+ cmd->eargv[argc] = 0;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+// NUL-terminate all the counted strings.
|
|
|
+struct cmd*
|
|
|
+nulterminate(struct cmd *cmd)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ struct backcmd *bcmd;
|
|
|
+ struct execcmd *ecmd;
|
|
|
+ struct listcmd *lcmd;
|
|
|
+ struct pipecmd *pcmd;
|
|
|
+ struct redircmd *rcmd;
|
|
|
+
|
|
|
+ if(cmd == 0)
|
|
|
return 0;
|
|
|
+
|
|
|
+ switch(cmd->type){
|
|
|
+ case EXEC:
|
|
|
+ ecmd = (struct execcmd*)cmd;
|
|
|
+ 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;
|
|
|
+
|
|
|
+ case LIST:
|
|
|
+ lcmd = (struct listcmd*)cmd;
|
|
|
+ nulterminate(lcmd->left);
|
|
|
+ nulterminate(lcmd->right);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case BACK:
|
|
|
+ bcmd = (struct backcmd*)cmd;
|
|
|
+ nulterminate(bcmd->cmd);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return cmd;
|
|
|
}
|