-NAME=gsh
+NAME=ysh
CC=gcc
CFLAGS=-O0 -g -Wall -fPIE -Werror -Wextra -Wno-unused -rdynamic -std=gnu11 -I.
LDFLAGS=-fPIE -rdynamic
%.o: %.c
$(CC) -c -o $@ $(CFLAGS) $(CPPFLAGS) $<
-all: game
+all: ysh
-game: $(OBJ) $(MODOBJ)
+ysh: $(OBJ) $(MODOBJ)
$(CC) -o $(NAME) $(LDFLAGS) $(OBJ) $(MODOBJ) $(MAIN) $(LIBS)
.PHONY: clean
clean:
- rm -f *.o */*.o */*/*.o gsh
+ rm -f *.o */*.o */*/*.o ysh
--- /dev/null
+#ifndef FLAG_VALS_H
+#define FLAG_VALS_H
+
+extern int obscene_debug;
+
+#endif
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "parse.h"
+#include "util.h"
+#include "flag_vals.h"
+
+void ast_dump_print(ast_t* ast, size_t indent) {
+ if (ast->type == AST_ROOT) {
+ for(size_t i=0; i < indent; i++)
+ printf(" ");
+
+ printf("root [%ld]\n", ast->size);
+ for(size_t id = 0; id < ast->size; id++) {
+ ast_dump_print(&((ast_t*)ast->ptr)[id], indent+1);
+ }
+ } else if (ast->type == AST_GRP) {
+ for(size_t i=0; i < indent; i++)
+ printf(" ");
+
+ printf("grp [%ld]\n", ast->size);
+ for(size_t id = 0; id < ast->size; id++) {
+ ast_dump_print(&((ast_t*)ast->ptr)[id], indent+1);
+ }
+ } else if (ast->type == AST_STR) {
+ for(size_t i=0; i < indent; i++)
+ printf(" ");
+
+ printf("str '");
+ char * str = ast->ptr;
+ size_t max = ast->size;
+ for(size_t s = 0; s < max; s++)
+ printf("%c", str[s]);
+ printf("'\n");
+ } else {
+ assert(0);
+ }
+}
+
+void split_line(char* line, size_t len, ast_t** ast, size_t* siz, int mode) {
+ ast_t *ast_grp = malloc_trap(sizeof(ast_t));
+ ast_grp[0].type = AST_UNSET;
+ ast_t *group;
+ size_t count = 0;
+ size_t ss_count = 0, ss_size = 0;
+ size_t in_q = 0, q_size = 0;
+ char *ss_str, *q_str;
+ int last = AST_UNSET;
+ for(size_t i=0; line[i] != 0, i < len; i++) {
+ switch(line[i]) {
+ case '\'':
+ if (ss_count || in_q || mode == 1) break;
+ // We're in a single quote. No escapes are allowed,
+ // nor subshells. Therefore, we'll simply seek to the
+ // next single quote available and insert it as AST_STR.
+ ++i;
+ last = ast_grp[count].type = AST_STR;
+ ast_grp[count].ptr = &line[i];
+ ast_grp[count].size = i; // Temporary save.
+ while(line[i] != '\'' && i < len) {
+ if (line[i] == 0) {
+ printf("syntax error: unclosed single quote\n");
+ free(ast_grp);
+ return;
+ }
+ ++i;
+ }
+ ast_grp[count].size = (i - ast_grp[count].size);
+ ++count;
+ ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
+ ast_grp[count].type = AST_UNSET;
+ break;
+ case '"': // Quotes
+ if (ss_count || mode == 1) break;
+ if (!in_q) {
+ in_q = 1; // Quote begin.
+ last = ast_grp[count].type = AST_GRP;
+ q_str = &line[i+1];
+ } else {
+ in_q = 0; // Quote end.
+ q_size = &line[i-1] - q_str + 1;
+
+ split_line(q_str, q_size, (ast_t**)&ast_grp[count].ptr, &ast_grp[count].size, 1);
+
+ ++count;
+ ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
+ ast_grp[count].type = AST_UNSET;
+ }
+ break;
+ case '{': // Subshell begin.
+ ss_count++;
+ if (in_q) break;
+ if (ss_count == 1) {
+ if (mode == 1 && ast_grp[count].type != AST_UNSET) {
+ ++count;
+ ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
+ }
+ // The first open brace.
+ last = ast_grp[count].type = AST_ROOT;
+ ss_str = &line[i+1];
+ }
+ break;
+ case '}': // Subshell end
+ if (ss_count) {
+ ss_count--;
+ if (in_q) break;
+ if (ss_count == 0) {
+ // Tis the end of the subshell.
+ ss_size = &line[i-1] - ss_str + 1;
+
+ // And now, for something different; we need to run this
+ // function recursively over the subshell string.
+ split_line(ss_str, ss_size, (ast_t**)&ast_grp[count].ptr, &ast_grp[count].size, 0);
+
+ ++count;
+ ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
+ ast_grp[count].type = AST_UNSET;
+ if (mode == 1) ++i;
+ }
+ }
+ break;
+ case ' ':
+ case '\n':
+ case '\t':
+ break;
+ default:
+ if (ss_count || in_q || mode == 1) break;
+ if ((i && isspace(line[i-1])) || i == 0) {
+ last = ast_grp[count].type = AST_STR;
+ ast_grp[count].ptr = &line[i];
+ ast_grp[count].size = i; // Temporary save.
+ while(!isspace(line[i]) && i < len) {
+ if (line[i] == 0) {
+ i--;
+ break;
+ }
+ ++i;
+ }
+ ast_grp[count].size = (i - ast_grp[count].size);
+ ++count;
+ ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
+ ast_grp[count].type = AST_UNSET;
+ }
+ break;
+ }
+ if (mode == 1 && ss_count == 0 && i < len) {
+ if (ast_grp[count].type == AST_UNSET) {
+ last = ast_grp[count].type = AST_STR;
+ ast_grp[count].ptr = &line[i];
+ ast_grp[count].size = 0; // Temporary save.
+ }
+ if (ast_grp[count].type == AST_STR) {
+ ast_grp[count].size++; // Temporary save.
+ }
+ }
+ }
+
+ if (mode == 1 && last == AST_STR) ++count;
+
+ if (ss_count)
+ printf("warn: unterminated subshell\n");
+
+ *ast = ast_grp;
+ *siz = count;
+}
+
+ast_t* parse(char* data) {
+ ast_t *ast = malloc_trap(sizeof(ast_t));
+ ast->type = AST_ROOT;
+ split_line(data, strlen(data), (ast_t**)&ast->ptr, &ast->size, 0);
+
+ ast_t *ptr = ast->ptr;
+
+ if (obscene_debug) ast_dump_print(ast, 0);
+
+ return ast;
+}
+
+void expand_vars(ast_t* ast) {
+ assert(ast->type == AST_STR);
+
+ char *ptr_old = (char*) ast->ptr;
+ size_t ptr_sz = ast->size;
+ size_t new_sz = 0;
+ size_t v_at = 0, v_sz = 0;
+ char *new = NULL, *var = NULL;
+
+ int mode = 0;
+ for(size_t i=0; i < ptr_sz && ptr_old[i] != 0; i++) {
+ switch (ptr_old[i]) {
+ case '$':
+ i++;
+ v_at = i;
+ while((isalpha(ptr_old[i]) || ptr_old[i] == '_') &&
+ i < ptr_sz && ptr_old[i] != 0)
+ i++;
+ v_sz = i - v_at;
+ if (ptr_old[i] != '$') {
+ i--;
+ }
+ var = malloc_trap(v_sz + 1);
+ memset(var, 0, v_sz + 1);
+ memcpy(var, &ptr_old[v_at], v_sz);
+
+ // TODO - get map for $var and insert to output
+
+ break;
+ default:
+ new_sz++;
+ new = realloc_trap(new, new_sz);
+ new[new_sz-1] = ptr_old[i];
+ break;
+ }
+ }
+
+ ast->ptr = new;
+ ast->size = new_sz;
+
+// printf("(%ld) %s", ast->size, (char*)ast->ptr);
+}
+
+void ast_resolve_subs(ast_t* ast, int master) {
+ for(size_t id = 0; id < ast->size; id++) {
+ ast_t* chk = &((ast_t*)ast->ptr)[id];
+ if (chk->type == AST_ROOT || chk->type == AST_GRP)
+ ast_resolve_subs(chk, 0);
+ // If needed, expand variables in strings.
+ if (chk->type == AST_STR)
+ expand_vars(chk);
+ }
+
+ if (obscene_debug) ast_dump_print(ast, 0);
+
+ // No more AST_ROOT or AST_GRP left to fix up. Now, depending
+ // on type, we need to do the following:
+ // for AST_ROOT: execute and capture stdout, then use stdout as new AST_STR
+ // for AST_GRP: concatenate strings and replace AST_GRP with AST_STR
+
+ if (master) return; // Don't fuck the tree's root node.
+
+ if (ast->type == AST_ROOT) {
+ char *output = NULL;
+ execute(ast, &output);
+ free(ast->ptr);
+ ast->type = AST_STR;
+ ast->ptr = output;
+ ast->size = strlen(output);
+ } else if (ast->type == AST_GRP) {
+ size_t total = 0;
+ size_t at = 0;
+ char *buf = malloc_trap(1);
+ for(size_t id = 0; id < ast->size; id++) {
+ ast_t* chk = &((ast_t*)ast->ptr)[id];
+
+ total += chk->size;
+ buf = realloc_trap(buf, total);
+
+ memcpy(&buf[at], chk->ptr, chk->size);
+ at += chk->size;
+ }
+ free(ast->ptr);
+ ast->type = AST_STR;
+ ast->ptr = buf;
+ ast->size = total;
+ }
+}
+
+ast_t* resolve(ast_t* tree) {
+ // This traverses the tree, executing subshell commands,
+ // expanding escape sequences within strings, etc
+ // until only the top-level AST_ROOT remains with no more
+ // expansion needed.
+
+ ast_resolve_subs(tree, 1);
+
+ if (obscene_debug) ast_dump_print(tree, 0);
+
+ return tree;
+}
--- /dev/null
+#ifndef PARSE_H
+#define PARSE_H
+
+// Unset. Not used in practice.
+#define AST_UNSET 0
+// Root element split to parameters.
+#define AST_ROOT 1
+// A fixed size string; ptr should be a char*
+#define AST_STR 2
+// Multiple elements which must be concatentated together to form a complete string.
+#define AST_GRP 3
+
+// Suppose the following input:
+// echo $(printf %x $(echo 42)) "hi world"
+
+// The parse tree should be:
+
+// AST_ROOT
+// AST_STR "echo"
+// AST_ROOT
+// AST_STR "printf"
+// AST_STR "%x"
+// AST_ROOT
+// AST_STR "echo"
+// AST_STR "42"
+// AST_STR "hi world"
+
+// In order to evaluate this, all AST_ROOT subelements must be
+// evaluated such that no AST_ROOT elements remain aside from the tree's
+// primary element.
+
+// Postnote; implementation is a bit different as one can see in --obscene
+// mode.
+
+typedef struct ast_s {
+ int type;
+ size_t size; // Number of elements for AST_ROOT|AST_GRP,
+ // and number of characters for AST_STR
+ void* ptr; // either ast_s* or char*
+} ast_t;
+
+void ast_dump_print(ast_t* ast, size_t indent);
+void split_line(char* line, size_t len, ast_t** ast, size_t* siz, int mode);
+ast_t* parse(char* data);
+void expand_vars(ast_t* ast);
+void ast_resolve_subs(ast_t* ast, int master);
+ast_t* resolve(ast_t* tree);
+
+#endif
#include <sys/types.h>
#include <sys/wait.h>
-#define BUF_CHUNKSIZ 64
+#include "parse.h"
+#include "util.h"
// Exit the main interactive loop
int shell_do_exit = 0;
// Various flags.
int obscene_debug = 0;
-void* malloc_trap(size_t malloc_size) {
- void* ret = malloc(malloc_size);
- if (!ret) {
- perror("err: malloc_trap: \n");
- exit(EXIT_FAILURE);
- }
- return ret;
-}
-
-void* realloc_trap(void *ptr, size_t malloc_size) {
- void* ret = realloc(ptr, malloc_size);
- if (!ret) {
- perror("err: realloc_trap: \n");
- exit(EXIT_FAILURE);
- }
- return ret;
-}
-
-/* Reads input from the user; this includes single lines, as well as
- * escaped multi-line input.
- *
- * This is basically a slightly smarter implementation of getline.
- */
-char *read_input() {
- // We do not know the size of the buffer ahead of time; therefore,
- // we must reallocate as we need more space.
-
- // The calling loop will free the allocated string when we are finished,
- // so we do not decrease our buffer size, only increase.
-
- // To avoid allocation overhead, we work in a "chunk" size specified by
- // BUF_CHUNKSIZ, and each time the buffer must be grown, we double
- // the currently allocated size to try and avoid problems.
- size_t buffer_sz = BUF_CHUNKSIZ;
- char *buffer = (char*)malloc_trap(buffer_sz);
- size_t pos = 0;
- int c = 0, last_c = 0;
-
- while (1) {
- c = getchar();
-
- if (c == '\b') {
- // Backspace
- if (pos && buffer[pos] != '\n') { // Can't delete newlines.
- --pos;
- if (pos)
- last_c = buffer[pos-1];
- }
- continue;
- } else if (c == '\n' && last_c == '\\') {
- // Terminate reading input when EOF or if we receive an "unescaped" newline.
- --pos; // Don't copy the '\\' into output.
- fflush(stdout);
- } else if (c == EOF || c == '\n') {
- buffer[pos] = 0;
- return buffer;
- }
-
- buffer[pos] = c;
- ++pos;
-
- if (pos >= buffer_sz) {
- buffer_sz *= 2;
- buffer = realloc_trap(buffer, buffer_sz);
- }
-
- last_c = c;
- }
-}
-
-// Unset. Not used in practice.
-#define AST_UNSET 0
-// Root element split to parameters.
-#define AST_ROOT 1
-// A fixed size string; ptr should be a char*
-#define AST_STR 2
-// Multiple elements which must be concatentated together to form a complete string.
-#define AST_GRP 3
-
-// Suppose the following input:
-// echo $(printf %x $(echo 42)) "hi world"
-
-// The parse tree should be:
-
-// AST_ROOT
-// AST_STR "echo"
-// AST_ROOT
-// AST_STR "printf"
-// AST_STR "%x"
-// AST_ROOT
-// AST_STR "echo"
-// AST_STR "42"
-// AST_STR "hi world"
-
-// In order to evaluate this, all AST_ROOT subelements must be
-// evaluated such that no AST_ROOT elements remain aside from the tree's
-// primary element.
-
-// Postnote; implementation is a bit different as one can see in --obscene
-// mode.
-
-typedef struct ast_s {
- int type;
- size_t size; // Number of elements for AST_ROOT|AST_GRP,
- // and number of characters for AST_STR
- void* ptr; // either ast_s* or char*
-} ast_t;
-
-void split_line(char* line, size_t len, ast_t** ast, size_t* siz, int mode) {
-#if 0
- printf("split(%ld): ", len);
- for(size_t i=0; i < len; i++) {
- printf("%c", line[i]);
- }
- printf("\n");
-#endif
- ast_t *ast_grp = malloc_trap(sizeof(ast_t));
- ast_grp[0].type = AST_UNSET;
- ast_t *group;
- size_t count = 0;
- size_t ss_count = 0, ss_size = 0;
- size_t in_q = 0, q_size = 0;
- char *ss_str, *q_str;
- int last = AST_UNSET;
- for(size_t i=0; line[i] != 0, i < len; i++) {
- switch(line[i]) {
- case '\'':
- if (ss_count || in_q || mode == 1) break;
- // We're in a single quote. No escapes are allowed,
- // nor subshells. Therefore, we'll simply seek to the
- // next single quote available and insert it as AST_STR.
- ++i;
- last = ast_grp[count].type = AST_STR;
- ast_grp[count].ptr = &line[i];
- ast_grp[count].size = i; // Temporary save.
- while(line[i] != '\'' && i < len) {
- if (line[i] == 0) {
- printf("syntax error: unclosed single quote\n");
- free(ast_grp);
- return;
- }
- ++i;
- }
- ast_grp[count].size = (i - ast_grp[count].size);
- ++count;
- ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
- ast_grp[count].type = AST_UNSET;
- break;
- case '"': // Quotes
- if (ss_count || mode == 1) break;
- if (!in_q) {
- in_q = 1; // Quote begin.
- last = ast_grp[count].type = AST_GRP;
- q_str = &line[i+1];
- } else {
- in_q = 0; // Quote end.
- q_size = &line[i-1] - q_str + 1;
-
- split_line(q_str, q_size, (ast_t**)&ast_grp[count].ptr, &ast_grp[count].size, 1);
-
- ++count;
- ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
- ast_grp[count].type = AST_UNSET;
- }
- break;
- case '{': // Subshell begin.
- ss_count++;
- if (in_q) break;
- if (ss_count == 1) {
- if (mode == 1 && ast_grp[count].type != AST_UNSET) {
- ++count;
- ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
- }
- // The first open brace.
- last = ast_grp[count].type = AST_ROOT;
- ss_str = &line[i+1];
- }
- break;
- case '}': // Subshell end
- if (ss_count) {
- ss_count--;
- if (in_q) break;
- if (ss_count == 0) {
- // Tis the end of the subshell.
- ss_size = &line[i-1] - ss_str + 1;
-
- // And now, for something different; we need to run this
- // function recursively over the subshell string.
- split_line(ss_str, ss_size, (ast_t**)&ast_grp[count].ptr, &ast_grp[count].size, 0);
-
- ++count;
- ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
- ast_grp[count].type = AST_UNSET;
- if (mode == 1) ++i;
- }
- }
- break;
- case ' ':
- case '\n':
- case '\t':
- break;
- default:
- if (ss_count || in_q || mode == 1) break;
- if ((i && isspace(line[i-1])) || i == 0) {
- last = ast_grp[count].type = AST_STR;
- ast_grp[count].ptr = &line[i];
- ast_grp[count].size = i; // Temporary save.
- while(!isspace(line[i]) && i < len) {
- if (line[i] == 0) {
- i--;
- break;
- }
- ++i;
- }
- ast_grp[count].size = (i - ast_grp[count].size);
- ++count;
- ast_grp = realloc_trap(ast_grp, sizeof(ast_t) * (count+1));
- ast_grp[count].type = AST_UNSET;
- }
- break;
- }
- if (mode == 1 && ss_count == 0 && i < len) {
- if (ast_grp[count].type == AST_UNSET) {
- last = ast_grp[count].type = AST_STR;
- ast_grp[count].ptr = &line[i];
- ast_grp[count].size = 0; // Temporary save.
- }
- if (ast_grp[count].type == AST_STR) {
- ast_grp[count].size++; // Temporary save.
- }
- }
- }
-
- if (mode == 1 && last == AST_STR) ++count;
-
- if (ss_count)
- printf("warn: unterminated subshell\n");
-
- *ast = ast_grp;
- *siz = count;
-}
-
-void ast_dump_print(ast_t* ast, size_t indent) {
- if (ast->type == AST_ROOT) {
- for(size_t i=0; i < indent; i++)
- printf(" ");
-
- printf("root [%ld]\n", ast->size);
- for(size_t id = 0; id < ast->size; id++) {
- ast_dump_print(&((ast_t*)ast->ptr)[id], indent+1);
- }
- } else if (ast->type == AST_GRP) {
- for(size_t i=0; i < indent; i++)
- printf(" ");
-
- printf("grp [%ld]\n", ast->size);
- for(size_t id = 0; id < ast->size; id++) {
- ast_dump_print(&((ast_t*)ast->ptr)[id], indent+1);
- }
- } else if (ast->type == AST_STR) {
- for(size_t i=0; i < indent; i++)
- printf(" ");
-
- printf("str '");
- char * str = ast->ptr;
- size_t max = ast->size;
- for(size_t s = 0; s < max; s++)
- printf("%c", str[s]);
- printf("'\n");
- } else {
- assert(0);
- }
-}
-
-ast_t* parse(char* data) {
- ast_t *ast = malloc_trap(sizeof(ast_t));
- ast->type = AST_ROOT;
- split_line(data, strlen(data), (ast_t**)&ast->ptr, &ast->size, 0);
-
- ast_t *ptr = ast->ptr;
-
- if (obscene_debug) ast_dump_print(ast, 0);
-
- return ast;
-}
-
-#define BUFFER
-
-pid_t fork_and_execvp(const char *file, char *const argv[], char** stdout) {
- pid_t pid;
-
- int pipefd[2];
- if (stdout)
- pipe(pipefd);
-
- pid = fork();
-
- if (pid == 0) {
- if (stdout) {
- // Close the rx pipe in child.
- close(pipefd[0]);
- dup2(pipefd[1], 1); // stdout -> pipe
- close(pipefd[1]);
- }
-
- execvp(file, argv);
- } else {
- if (stdout) {
- size_t stdout_sz = 4096, stdout_at = 0;
- *stdout = NULL;
- char * cur = *stdout;
-
- // Close the tx pipe in parent.
- close(pipefd[1]);
- ssize_t bytes = 0;
- do {
- stdout_at++;
- *stdout = realloc_trap(*stdout, stdout_sz * stdout_at);
- cur = *stdout + (stdout_sz * (stdout_at-1));
- memset(cur, 0, 4096);
-
- bytes = read(pipefd[0], cur, stdout_sz);
- } while (bytes != 0);
-
- // Strip the last \n from output, if applicable.
- size_t len = strlen(*stdout);
- if ((*stdout)[len-1] == '\n')
- (*stdout)[len-1] = 0;
- }
-
- }
- return pid;
-}
-
-typedef int (*builtin_fn_t)(char*, char**, char**);
-
-int builtin_chdir(char* nam, char** argv, char** stdout);
-
-typedef struct {
- char name[64];
- builtin_fn_t func;
-} builtin_info_t;
-
-builtin_info_t builtin_info[] = {
- { "cd", builtin_chdir },
- { "", NULL },
-};
-
-int check_builtin(char* name) {
- for (int i = 0; builtin_info[i].func != NULL; i++) {
- if (!strcmp(name, builtin_info[i].name)) {
- return i;
- }
- }
- return -1;
-}
-
-void execute(ast_t* tree, char** stdout) {
- // Important note; this function is only for fully resolved trees of commands.
- // If any unresolved subshells or groups exist, this function is undefined.
- // Additionally, tree must be of type AST_ROOT.
- assert(tree->type == AST_ROOT);
-
- // This function will eventually also perform shortest-unique-path
- // expansions. For example, typing /b/busy will resolve to /bin/busybox.
-
- char* prog;
- char** argv = malloc_trap((tree->size + 1) * sizeof(char*));
- for (size_t i = 0; i < tree->size; i++) {
- ast_t* str = &((ast_t*)tree->ptr)[i];
- char *str_s = malloc_trap(str->size + 1);
- memset(str_s, 0, str->size + 1);
- memcpy(str_s, str->ptr, str->size);
- argv[i] = str_s;
- }
- argv[tree->size] = NULL;
- prog = argv[0];
-
- int builtin_chk = check_builtin(prog);
- if (builtin_chk != -1) {
- builtin_info[builtin_chk].func(prog, argv, stdout);
- } else {
- pid_t pid = fork_and_execvp(prog, argv, stdout);
- int wstatus;
- pid = waitpid(pid, &wstatus, 0);
- }
-}
-
-void expand_vars(ast_t* ast) {
- assert(ast->type == AST_STR);
-
- char *ptr_old = (char*) ast->ptr;
- size_t ptr_sz = ast->size;
- size_t new_sz = 0;
- size_t v_at = 0, v_sz = 0;
- char *new = NULL, *var = NULL;
-
- int mode = 0;
- for(size_t i=0; i < ptr_sz && ptr_old[i] != 0; i++) {
- switch (ptr_old[i]) {
- case '$':
- i++;
- v_at = i;
- while((isalpha(ptr_old[i]) || ptr_old[i] == '_') &&
- i < ptr_sz && ptr_old[i] != 0)
- i++;
- v_sz = i - v_at;
- if (ptr_old[i] != '$') {
- i--;
- }
- var = malloc_trap(v_sz + 1);
- memset(var, 0, v_sz + 1);
- memcpy(var, &ptr_old[v_at], v_sz);
-
- // TODO - get map for $var and insert to output
-
- break;
- default:
- new_sz++;
- new = realloc_trap(new, new_sz);
- new[new_sz-1] = ptr_old[i];
- break;
- }
- }
-
- ast->ptr = new;
- ast->size = new_sz;
-
-// printf("(%ld) %s", ast->size, (char*)ast->ptr);
-}
-
-void ast_resolve_subs(ast_t* ast, int master) {
- for(size_t id = 0; id < ast->size; id++) {
- ast_t* chk = &((ast_t*)ast->ptr)[id];
- if (chk->type == AST_ROOT || chk->type == AST_GRP)
- ast_resolve_subs(chk, 0);
- // If needed, expand variables in strings.
- if (chk->type == AST_STR)
- expand_vars(chk);
- }
-
- if (obscene_debug) ast_dump_print(ast, 0);
-
- // No more AST_ROOT or AST_GRP left to fix up. Now, depending
- // on type, we need to do the following:
- // for AST_ROOT: execute and capture stdout, then use stdout as new AST_STR
- // for AST_GRP: concatenate strings and replace AST_GRP with AST_STR
-
- if (master) return; // Don't fuck the tree's root node.
-
- if (ast->type == AST_ROOT) {
- char *output = NULL;
- execute(ast, &output);
- free(ast->ptr);
- ast->type = AST_STR;
- ast->ptr = output;
- ast->size = strlen(output);
- } else if (ast->type == AST_GRP) {
- size_t total = 0;
- size_t at = 0;
- char *buf = malloc_trap(1);
- for(size_t id = 0; id < ast->size; id++) {
- ast_t* chk = &((ast_t*)ast->ptr)[id];
-
- total += chk->size;
- buf = realloc_trap(buf, total);
-
- memcpy(&buf[at], chk->ptr, chk->size);
- at += chk->size;
- }
- free(ast->ptr);
- ast->type = AST_STR;
- ast->ptr = buf;
- ast->size = total;
- }
-}
-
-ast_t* resolve(ast_t* tree) {
- // This traverses the tree, executing subshell commands,
- // expanding escape sequences within strings, etc
- // until only the top-level AST_ROOT remains with no more
- // expansion needed.
-
- ast_resolve_subs(tree, 1);
-
- if (obscene_debug) ast_dump_print(tree, 0);
-
- return tree;
-}
-
int main(int argc, char **argv) {
char* run_str = NULL;
// Options.
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "parse.h"
+#include "util.h"
+
+builtin_info_t builtin_info[] = {
+ { "cd", builtin_chdir },
+ { "", NULL },
+};
+
+void* malloc_trap(size_t malloc_size) {
+ void* ret = malloc(malloc_size);
+ if (!ret) {
+ perror("err: malloc_trap: \n");
+ exit(EXIT_FAILURE);
+ }
+ return ret;
+}
+
+void* realloc_trap(void *ptr, size_t malloc_size) {
+ void* ret = realloc(ptr, malloc_size);
+ if (!ret) {
+ perror("err: realloc_trap: \n");
+ exit(EXIT_FAILURE);
+ }
+ return ret;
+}
+
+/* Reads input from the user; this includes single lines, as well as
+ * escaped multi-line input.
+ *
+ * This is essentially an implementation of getline.
+ */
+char *read_input() {
+ // We do not know the size of the buffer ahead of time; therefore,
+ // we must reallocate as we need more space.
+
+ // The calling loop will free the allocated string when we are finished,
+ // so we do not decrease our buffer size, only increase.
+
+ // To avoid allocation overhead, we work in a "chunk" size specified by
+ // BUF_CHUNKSIZ, and each time the buffer must be grown, we double
+ // the currently allocated size to try and avoid problems.
+ size_t buffer_sz = BUF_CHUNKSIZ;
+ char *buffer = (char*)malloc_trap(buffer_sz);
+ size_t pos = 0;
+ int c = 0, last_c = 0;
+
+ while (1) {
+ c = getchar();
+
+ if (c == '\b') {
+ // Backspace
+ if (pos && buffer[pos] != '\n') { // Can't delete newlines.
+ --pos;
+ if (pos)
+ last_c = buffer[pos-1];
+ }
+ continue;
+ } else if (c == '\n' && last_c == '\\') {
+ // Terminate reading input when EOF or if we receive an "unescaped" newline.
+ --pos; // Don't copy the '\\' into output.
+ fflush(stdout);
+ } else if (c == EOF || c == '\n') {
+ buffer[pos] = 0;
+ return buffer;
+ }
+
+ buffer[pos] = c;
+ ++pos;
+
+ if (pos >= buffer_sz) {
+ buffer_sz *= 2;
+ buffer = realloc_trap(buffer, buffer_sz);
+ }
+
+ last_c = c;
+ }
+}
+
+int check_builtin(char* name) {
+ for (int i = 0; builtin_info[i].func != NULL; i++) {
+ if (!strcmp(name, builtin_info[i].name)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+pid_t fork_and_execvp(const char *file, char *const argv[], char** stdout) {
+ pid_t pid;
+
+ int pipefd[2];
+ if (stdout)
+ pipe(pipefd);
+
+ pid = fork();
+
+ if (pid == 0) {
+ if (stdout) {
+ // Close the rx pipe in child.
+ close(pipefd[0]);
+ dup2(pipefd[1], 1); // stdout -> pipe
+ close(pipefd[1]);
+ }
+
+ execvp(file, argv);
+ } else {
+ if (stdout) {
+ size_t stdout_sz = 4096, stdout_at = 0;
+ *stdout = NULL;
+ char * cur = *stdout;
+
+ // Close the tx pipe in parent.
+ close(pipefd[1]);
+ ssize_t bytes = 0;
+ do {
+ stdout_at++;
+ *stdout = realloc_trap(*stdout, stdout_sz * stdout_at);
+ cur = *stdout + (stdout_sz * (stdout_at-1));
+ memset(cur, 0, 4096);
+
+ bytes = read(pipefd[0], cur, stdout_sz);
+ } while (bytes != 0);
+
+ // Strip the last \n from output, if applicable.
+ size_t len = strlen(*stdout);
+ if ((*stdout)[len-1] == '\n')
+ (*stdout)[len-1] = 0;
+ }
+
+ }
+ return pid;
+}
+
+void execute(ast_t* tree, char** stdout) {
+ // Important note; this function is only for fully resolved trees of commands.
+ // If any unresolved subshells or groups exist, this function is undefined.
+ // Additionally, tree must be of type AST_ROOT.
+ assert(tree->type == AST_ROOT);
+
+ // This function will eventually also perform shortest-unique-path
+ // expansions. For example, typing /b/busy will resolve to /bin/busybox.
+
+ char* prog;
+ char** argv = malloc_trap((tree->size + 1) * sizeof(char*));
+ for (size_t i = 0; i < tree->size; i++) {
+ ast_t* str = &((ast_t*)tree->ptr)[i];
+ char *str_s = malloc_trap(str->size + 1);
+ memset(str_s, 0, str->size + 1);
+ memcpy(str_s, str->ptr, str->size);
+ argv[i] = str_s;
+ }
+ argv[tree->size] = NULL;
+ prog = argv[0];
+
+ int builtin_chk = check_builtin(prog);
+ if (builtin_chk != -1) {
+ builtin_info[builtin_chk].func(prog, argv, stdout);
+ } else {
+ pid_t pid = fork_and_execvp(prog, argv, stdout);
+ int wstatus;
+ pid = waitpid(pid, &wstatus, 0);
+ }
+}
--- /dev/null
+#ifndef UTIL_H
+#define UTIL_H
+
+#define BUF_CHUNKSIZ 64
+
+typedef int (*builtin_fn_t)(char*, char**, char**);
+
+typedef struct {
+ char name[64];
+ builtin_fn_t func;
+} builtin_info_t;
+
+void* malloc_trap(size_t malloc_size);
+void* realloc_trap(void *ptr, size_t malloc_size);
+char *read_input();
+pid_t fork_and_execvp(const char *file, char *const argv[], char** stdout);
+void execute(ast_t* tree, char** stdout);
+
+// Builtins; these are in the builtin subdir, and all must have the builtin_fn_t prototype
+int builtin_chdir(char* nam, char** argv, char** stdout);
+
+#endif