diff --git a/Makefile b/Makefile index 8086e48..d096122 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,9 @@ lowercase: sysexits src/lowercase.c nonzero: src/nonzero.c $(CC) $(CFLAGS) -o bin/nonzero src/nonzero.c +nutshell: src/nutshell.c src/nutshell.h src/nutshell_builtins.c + $(CC) $(CFLAGS) -o bin/nutshell src/nutshell.c + runlength: noargvzero sysexits src/runlength.c $(CC) $(CFLAGS) -o bin/runlength src/runlength.c diff --git a/src/nutshell.c b/src/nutshell.c new file mode 100644 index 0000000..612b5cd --- /dev/null +++ b/src/nutshell.c @@ -0,0 +1,115 @@ +#include "nutshell.h" +#include "nutshell_builtins.c" + +int +fdputd(int fd, int d){ + if(d < 0){ + putc('-', stdout); + d *= -1; + } + return printf("%d\n", d); +} + +/* Probably not buggy. */ +int +fdputs(int fd, char *s){ + int r; + + r = write(1, s, strlen(s)); + r += write(1, "\n", 1); + + return r; +} + +static int +parse_argv(char *buffer, char **argv, int argc_max){ + int i; + int j; + int s; + + for(i = 0, j = 0, s = 1; buffer[i] != '\0' && j < argc_max; ++i){ + while(isspace(buffer[i])){ + s = 1; + buffer[i++] = '\0'; + } + if(s) + argv[j++] = buffer + i; + } + + return j; +} + +static char * +read_buffer(char *buf, size_t buf_s){ + int i; + + for(i = 0; ; ++i){ + /* ..., '\0', buf_s */ + /* This isn't perfect but is simple and doesn't matter for + * longer buffers that much. */ + if(i == buf_s - 1 - 1){ + buf[i] = '\0'; + break; + } + + buf[i] = getc(stdin); + + if(buf[i] == EOF) + return NULL; + + else if(buf[i] == '\n'){ + buf[i + 1] = '\0'; + break; + } + } + + return buf; +} + +static int +run(char *buf){ + int argc; + static char *argv[ARGV_MAX]; + int i; + + /* get argv and argc */ + for(i = 0; i < ARRAYLEN(argv); ++i){ + if(argv[i] == NULL) + break; + argv[i] = NULL; + } + + argc = parse_argv(buf, argv, ARRAYLEN(argv)); + + /* builtins get priority */ + for(i = 0; i < ARRAYLEN(builtins); ++i) + if(strcmp(builtins[i].name, argv[0]) == 0) + return builtins[i].f(argc, argv); + + if(state.jailed) + goto runerr; + +runerr: + /* This is what busybox ash(1) does. */ + return 127; +} + +int main(int argc, char *argv[]){ + static char buf[BUF_MAX]; + + srand(time(NULL)); + builtin_init(0, empty_argv); + + for(;;){ + write(1, prompt, ARRAYLEN(prompt)); + if(read_buffer(buf, ARRAYLEN(buf)) == NULL){ + write(1, "EOF\n", 4); + state.status = 0; + break; + } + + state.status = run(buf); + } + + return state.status; +} diff --git a/src/nutshell.h b/src/nutshell.h new file mode 100644 index 0000000..62349bd --- /dev/null +++ b/src/nutshell.h @@ -0,0 +1,49 @@ +#include /* + isspace(3) +*/ +#include /* + NULL +*/ +#include /* + getc(3) +*/ +#include /* + getenv(3) + rand(3) + srand(3) +*/ +#include /* + strcmp(3) + strlen(3) +*/ +#include /* + wait(2) +*/ +#include /* + time(3) +*/ +#include /* + fork(2) + write(2) +*/ + +#define ARRAYLEN(a) sizeof(a) / sizeof(*(a)) + +/* conservative defaults */ +#define ARGV_MAX 10 +#define BUF_MAX 100 + +char *empty_argv[1] = { NULL }; +char *nethack_name = "nethack"; +char prompt[] = "^_^?: "; + +struct { + int jailed; + int status; +}state = { + 0, + 0 +}; + +int fdputd(int fd, int d); +int fdputs(int fd, char *s); diff --git a/src/nutshell_builtins.c b/src/nutshell_builtins.c new file mode 100644 index 0000000..3d1d96c --- /dev/null +++ b/src/nutshell_builtins.c @@ -0,0 +1,72 @@ +int builtin_escape (int, char **); +int builtin_init (int, char **); +int builtin_jail (int, char **); +int builtin_nethack (int, char **); +int builtin_reassure (int, char **); +int builtin_status (int, char **); + +struct { + char *name; + int (*f)(int, char **); +}builtins[] = { + {"escape", builtin_escape}, + {"init", builtin_init}, + {"jail", builtin_jail}, + {"nethack", builtin_nethack}, + {"status", builtin_status} +}; + +int +builtin_escape(int argc, char **argv){ + char *s; + + if((s = getenv("SHELL")) == NULL){ + fdputs(1, "$SHELL seems to be empty. This is an OS error.\n"); + }else if(fork() == 0) + execl(s, s, NULL); + else + wait(NULL); + + return 0; +} + +int +builtin_init(int argc, char **argv){ + fdputs(1,"\ +\ + A voice whispers to you. \"Play NetHack...\" (\"nethack\" [enter]).\n\ + What would you like to do?" + ); + return 0; +} + +int +builtin_jail(int argc, char **argv){ + printf("%s\n", argv[1]); + if(argv[1] == NULL) + fdputd(1, state.jailed); + else if(strcmp(argv[1], "break") == 0 || strcmp(argv[1], "build") == 0) + state.jailed = (argv[1][1] - 'r') / ('u' - 'r'); /* cheap */ + else + return 1; + return 0; +} + +int +builtin_nethack(int argc, char **argv){ + char *a[2]; + if(fork() == 0){ + a[0] = nethack_name; + a[1] = NULL; + execvp("nethack", a); + }else + wait(NULL); + + return 0; +} + +int +builtin_status(int argc, char **argv){ + fdputd(1, state.status); + return 0; +}