From 44a51013ed647e4297da0a7e9a05f5e9cacfb672 Mon Sep 17 00:00:00 2001 From: overflowerror Date: Thu, 14 Dec 2017 00:41:41 +0100 Subject: [PATCH] first working version --- src/cli.c | 71 ++++++++++++ src/cli.h | 13 +++ src/global.c | 88 +++++++++++++++ src/global.h | 34 ++++++ src/interpreter/built-in/builtin.h | 6 + src/interpreter/built-in/cd.c | 25 +++++ src/interpreter/builtin.c | 16 +++ src/interpreter/interpreter.c | 171 +++++++++++++++++++++++++++++ src/interpreter/interpreter.h | 28 +++++ src/interpreter/splitter.c | 52 +++++++++ src/interpreter/splitter.h | 14 +++ src/main.c | 77 +++++++++++++ 12 files changed, 595 insertions(+) create mode 100644 src/cli.c create mode 100644 src/cli.h create mode 100644 src/global.c create mode 100644 src/global.h create mode 100644 src/interpreter/built-in/builtin.h create mode 100644 src/interpreter/built-in/cd.c create mode 100644 src/interpreter/builtin.c create mode 100644 src/interpreter/interpreter.c create mode 100644 src/interpreter/interpreter.h create mode 100644 src/interpreter/splitter.c create mode 100644 src/interpreter/splitter.h create mode 100644 src/main.c diff --git a/src/cli.c b/src/cli.c new file mode 100644 index 0000000..6676f7c --- /dev/null +++ b/src/cli.c @@ -0,0 +1,71 @@ +#include +#include +#include + +#include +#include + +#include + +#include "global.h" +#include "cli.h" +#include "interpreter/interpreter.h" + +settings_t* settings; + +void cli_init(settings_t* set) { + settings = set; +} + +#define HOSTNAME_MAX_LENGTH 1024 +#define CWD_MAX_LENGTH 1024 +char* cli_getPrompt(state_t* state) { + int length = 1; + const char* username = getlogin(); + if (username == NULL) + username = "[unknown]"; + + char hostname[HOSTNAME_MAX_LENGTH]; + if (gethostname(hostname, HOSTNAME_MAX_LENGTH) != 0) + bailOut(EXIT_FAILURE, "cli_getPrompt", "gethostname"); + hostname[HOSTNAME_MAX_LENGTH - 1] = '\0'; // for safety + + char* cwd = getcwd(NULL, CWD_MAX_LENGTH); + + char* sep = "$"; + if (getuid() == 0) + sep = "#"; + + length += strlen(username) + 1 + strlen(hostname) + 1 + strlen(cwd) + 1 + 1; + + char* prompt = malloc(length); + if (prompt == NULL) + bailOut(EXIT_FAILURE, "cli_getPromp", "malloc"); + + (void) strcpy(prompt, username); + (void) strcat(prompt, "@"); + (void) strcat(prompt, hostname); + (void) strcat(prompt, ":"); + (void) strcat(prompt, cwd); + (void) strcat(prompt, sep); + (void) strcat(prompt, " "); + + return prompt; +} + +char* cli_getLine(state_t* state) { + char* input = NULL; + char* prompt = cli_getPrompt(state); + + input = readline(prompt); + + free(prompt); + + if (input == NULL) + bailOut(EXIT_FAILURE, "cli_getLine", NULL); + return input; +} + +void cli_afterLine(char* input) { + free(input); +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..89d2eda --- /dev/null +++ b/src/cli.h @@ -0,0 +1,13 @@ +#ifndef CLI_H +#define CLI_H + +#include "global.h" +#include "interpreter/interpreter.h" + +void cli_init(settings_t*); + +char* cli_getLine(state_t*); + +void cli_afterLine(char*); + +#endif diff --git a/src/global.c b/src/global.c new file mode 100644 index 0000000..e2c9c1f --- /dev/null +++ b/src/global.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include "global.h" + +bool showShutdownDetails = false; + +#define HANDLER_ARRAY_RESIZE_STEP 5 + +typedef struct { + const char* name; + exit_handler_t handler; +} handler_t; + +struct cleanUpData { + int handlerCount; + int handlerArraySize; + handler_t* handlers; +} cleanUpData; + +int setup() { + cleanUpData = (struct cleanUpData) {0}; + + return 0; // TODO +} + +int addExitHandler(exit_handler_t handler, const char* name) { + if (cleanUpData.handlerCount <= cleanUpData.handlerArraySize) { + void* tmp = realloc(cleanUpData.handlers, (cleanUpData.handlerArraySize + HANDLER_ARRAY_RESIZE_STEP) * sizeof (handler_t)); + if (tmp == NULL) { + bailOut(EXIT_SETUP_FAILED, "addExitHandler", name); + } + cleanUpData.handlers = tmp; + cleanUpData.handlerArraySize += HANDLER_ARRAY_RESIZE_STEP; + } + cleanUpData.handlers[cleanUpData.handlerCount] = (handler_t) { + .name = name, + .handler = handler + }; + cleanUpData.handlerCount++; + + return 0; // TODO: we need something better here +} + +void cleanUp() { + for (int i = 0; i < cleanUpData.handlerCount; i++) { + int code = cleanUpData.handlers[i].handler(); + if (showShutdownDetails) { + if (code != 0) { + fprintf(stderr, "exit handler %s terminated with error (%d)\n", cleanUpData.handlers[i].name, code); + } else { + fprintf(stderr, "exit handler %s terminated successfully\n", cleanUpData.handlers[i].name); + } + } + } + fprintf(stderr, "freeing exit handler array (%d)\n", cleanUpData.handlerCount); +} + + +void bailOut(int exitcode, const char* module, const char* message) { + if (module != NULL) { + // output is not split into smaller quantities to + // ensure error messages of child and parent + // don't get mixed + if (message == NULL) { + if (errno == 0) { + (void) fprintf(stderr, "%s: %s\n", PROGRAM_NAME, module); + } else { + (void) fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, module, strerror(errno)); + } + } else { + if (errno == 0) { + (void) fprintf(stderr, "%s: %s (%s)\n", PROGRAM_NAME, module, message); + } else { + (void) fprintf(stderr, "%s: %s (%s): %s\n", PROGRAM_NAME, module, message, strerror(errno)); + } + } + } + + fprintf(stderr, "cleaning up..."); + cleanUp(); + + fprintf(stderr, "exiting with error code %d", exitcode); + exit(exitcode); +} diff --git a/src/global.h b/src/global.h new file mode 100644 index 0000000..4fadfc9 --- /dev/null +++ b/src/global.h @@ -0,0 +1,34 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define PROGRAM_NAME "gosh" +#define VERSION "0.1" + +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 +#define EXIT_SETUP_FAILED 10 + +#define EXIT_IMPOSSIBLE_STATE 255 + +#include + +typedef struct { + int verbose; +} settings_t; + +typedef int (*exit_handler_t)(); + +extern bool showShutdownDetails; + +/** + * @return 0 on success, 1 else + */ +int setup(); + +int addExitHandler(exit_handler_t, const char*); + +void cleanUp(); + +void bailOut(int, const char*, const char*); + +#endif diff --git a/src/interpreter/built-in/builtin.h b/src/interpreter/built-in/builtin.h new file mode 100644 index 0000000..ed7e8f0 --- /dev/null +++ b/src/interpreter/built-in/builtin.h @@ -0,0 +1,6 @@ +#ifndef BUILTIN_H +#define BUILTIN_H + +int cd(int, char**); + +#endif diff --git a/src/interpreter/built-in/cd.c b/src/interpreter/built-in/cd.c new file mode 100644 index 0000000..9328217 --- /dev/null +++ b/src/interpreter/built-in/cd.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +#include + +#include "builtin.h" + +int cd(int argc, char** argv) { + char* dir = getenv("HOME"); + if (argc == 2) { + dir = argv[1]; + } + if (argc > 2) { + fprintf(stderr, "cd: too many arguments\n"); + return 1; + } + + if (chdir(dir) < 0) { + fprintf(stderr, "cd: chdir: %s", strerror(errno)); + return 2; + } + return 0; +} diff --git a/src/interpreter/builtin.c b/src/interpreter/builtin.c new file mode 100644 index 0000000..21d52ed --- /dev/null +++ b/src/interpreter/builtin.c @@ -0,0 +1,16 @@ +#include + +#include "interpreter.h" +#include "built-in/builtin.h" + +int hello_world(int argc, char** argv) { + + printf("Hello World\n"); + + return 0; +} + +void builtin_add() { + interpreter_addBuiltIn("hello", hello_world); + interpreter_addBuiltIn("cd", cd); +} diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c new file mode 100644 index 0000000..510b1fa --- /dev/null +++ b/src/interpreter/interpreter.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +#include + +#include +#include + + +#include "../global.h" +#include "interpreter.h" +#include "splitter.h" + +settings_t* settings; + +int builtInCount = 0; +program_t* builtIn; + +void interpreter_addBuiltIn(const char* name, program_handler_t handler) { + void* tmp = realloc(builtIn, (builtInCount + 1) * sizeof(program_t)); + if (tmp == NULL) + bailOut(EXIT_SETUP_FAILED, "interpreter_addBuiltIn", NULL); + + builtIn = tmp; + + builtIn[builtInCount] = (program_t) { + .name = name, + .handler = handler + }; + + builtInCount++; +} + +void interpreter_init(settings_t* set) { + settings = set; + + builtin_add(); +} + +state_t interpreter_initState() { + return (state_t) { + .last = 0 + }; +} + +program_handler_t interpreter_searchBuiltIn(char* name) { + for (int i = 0; i < builtInCount; i++) { + if (strcmp(builtIn[i].name, name) == 0) + return builtIn[i].handler; + } + return NULL; +} + +int interpreter_searchBin(char* name, char** file) { + char* tmp = getenv("PATH"); + char* path = malloc(strlen(tmp) + 1); + strcpy(path, tmp); + tmp = path; + + char* token; + + int error = ENOENT; + while((token = strtok(tmp, ":")) != NULL) { + tmp = NULL; + + *file = realloc(*file, strlen(token) + 1 + strlen(name) + 1); + if (*file == NULL) + bailOut(EXIT_FAILURE, "interpreter_searchBin", NULL); + + (void) strcpy(*file, token); + (void) strcat(*file, "/"); + (void) strcat(*file, name); + + if (access(*file, R_OK | X_OK) < 0) { + if (errno == EACCES) + error = EACCES; + } else { + free(path); + return 0; + } + } + free(path); + *file = NULL; + return error; +} + +error_t interpreter_startBuiltIn(program_handler_t handler, args_t args) { + return handler(args.argc, args.argv); + + /* + * This is problematic: We cannot alter the env or the cwd of the shell-process from a child. + * TODO: Find a better solution. + */ + + /*int pid = fork(); + if (pid < 0) + bailOut(EXIT_FAILURE, "interpreter_forkBuiltIn", "fork"); + if (pid == 0) + exit(handler(args.argc, args.argv)); + + // parent + + int status; + if (waitpid(pid, &status, 0) < 0) + bailOut(EXIT_FAILURE, "interpreter_forkBuiltIn", "wait"); + + if (!WIFEXITED(status)) { + // bad + } + + return WEXITSTATUS(status);*/ +} + +error_t interpreter_startBin(char* file, args_t args) { + int pid = fork(); + if (pid < 0) + bailOut(EXIT_FAILURE, "interpreter_forkBin", "fork"); + if (pid == 0) { + if (execv(file, args.argv) < 0) + bailOut(EXIT_FAILURE, "interpreter_forkBin", "execv"); + } + + // parent + + int status; + if (waitpid(pid, &status, 0) < 0) + bailOut(EXIT_FAILURE, "interpreter_forkBin", "wait"); + + if (!WIFEXITED(status)) { + // bad + } + + return WEXITSTATUS(status); +} + +int interpreter_do(state_t* state, char* input) { + if (input == NULL) { + // this is: EOF + } + + args_t args = splitter_do(input); + + if (args.argc > 0) { + + program_handler_t handler = interpreter_searchBuiltIn(args.argv[0]); + + error_t result = 0; + + if (handler != NULL) { + result = interpreter_startBuiltIn(handler, args); + } else { + char* file = NULL; + int error = interpreter_searchBin(args.argv[0], &file); + if (error == 0) { + result = interpreter_startBin(file, args); + } else { + result = 127; // TODO export to constant + fprintf(stderr, "%s: command not found: %s\n", PROGRAM_NAME, args.argv[0]); + } + free(file); + } + + state->last = result; + } + + splitter_free(args); + + return -1; +} diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h new file mode 100644 index 0000000..660b7c5 --- /dev/null +++ b/src/interpreter/interpreter.h @@ -0,0 +1,28 @@ +#ifndef INTERPRETER_H +#define INTERPRETER_H + +#include "../global.h" + +typedef int error_t; + +typedef struct { + error_t last; +} state_t; + +typedef int (*program_handler_t)(int, char**); + +typedef struct { + const char* name; + program_handler_t handler; +} program_t; + +void interpreter_addBuiltIn(const char*, program_handler_t); + +void interpreter_init(settings_t*); +state_t interpreter_initState(); + +int interpreter_do(state_t*, char*); + +void builtin_add(); + +#endif diff --git a/src/interpreter/splitter.c b/src/interpreter/splitter.c new file mode 100644 index 0000000..f09a358 --- /dev/null +++ b/src/interpreter/splitter.c @@ -0,0 +1,52 @@ +#include +#include + +#include "splitter.h" +#include "../global.h" + +#define ARGS_ARRAY_RESIZE_STEP 5 + +args_t splitter_do(char* line) { + args_t args = (args_t) {0}; + + char* tmp = line; + char* token; + while ((token = strtok(tmp, " ")) != NULL) { + tmp = NULL; + + if (args.argc >= args.arraySize) { + void* tmp = realloc(args.argv, (args.arraySize + ARGS_ARRAY_RESIZE_STEP) * sizeof(char*)); + if (tmp == NULL) + bailOut(EXIT_FAILURE, "splitter_do", NULL); + args.argv = tmp; + args.arraySize += ARGS_ARRAY_RESIZE_STEP; + } + + void* tmp = malloc(strlen(token) + 1); + if (tmp == NULL) + bailOut(EXIT_FAILURE, "splitter_do", NULL); + + args.argv[args.argc] = tmp; + (void) strcpy(args.argv[args.argc], token); + args.argc++; + } + + if (args.argc >= args.arraySize) { + void* tmp = realloc(args.argv, (args.arraySize + ARGS_ARRAY_RESIZE_STEP) * sizeof(char*)); + if (tmp == NULL) + bailOut(EXIT_FAILURE, "splitter_do", NULL); + args.argv = tmp; + args.arraySize += ARGS_ARRAY_RESIZE_STEP; + } + + args.argv[args.argc] = NULL; // for execv + + return args; +} + +void splitter_free(args_t args) { + for (int i = 0; i < args.argc; i++) + free(args.argv[i]); + + free(args.argv); +} diff --git a/src/interpreter/splitter.h b/src/interpreter/splitter.h new file mode 100644 index 0000000..567897c --- /dev/null +++ b/src/interpreter/splitter.h @@ -0,0 +1,14 @@ +#ifndef SPLITTER_H +#define SPLITTER_H + +typedef struct { + int argc; + int arraySize; + char** argv; +} args_t; + +args_t splitter_do(char*); + +void splitter_free(args_t); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ba557bb --- /dev/null +++ b/src/main.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include +#include + +#include "global.h" +#include "cli.h" +#include "interpreter/interpreter.h" + +void version() { + printf("%s Version %s\n", PROGRAM_NAME, VERSION); + + cleanUp(); + exit(EXIT_SUCCESS); +} + +void parseArguments(int argc, char** argv, settings_t* settings) { + const char* options = "v"; + const struct option long_options[] = { + {"version", no_argument, 0, 'v'} + }; + + int opt; + while (true) { + int option_index = 0; + opt = getopt_long(argc, argv, options, long_options, &option_index); + + if (opt == -1) + break; + + switch(opt) { + case 0: // long option + if (option_index == 0) // version + version(); + break; + case 'v': + version(); + break; + default: + assert(false); + exit(EXIT_IMPOSSIBLE_STATE); + } + } + + if (optind < argc) { + // non-option arguments + } +} + +int main(int argc, char** argv) { + setup(); + + settings_t settings = {0}; + + parseArguments(argc, argv, &settings); + + cli_init(&settings); + interpreter_init(&settings); + + char* input; + int result; + state_t state = interpreter_initState(); + while (true) { + input = cli_getLine(&state); + result = interpreter_do(&state, input); + cli_afterLine(input); + + if (result >= 0 && result < 256) { + break; // result is exit code + } + } + + return result; +}