first working version

This commit is contained in:
overflowerror 2017-12-14 00:41:41 +01:00
parent cc30f18a3f
commit 44a51013ed
12 changed files with 595 additions and 0 deletions

71
src/cli.c Normal file
View file

@ -0,0 +1,71 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <editline/readline.h>
#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);
}

13
src/cli.h Normal file
View file

@ -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

88
src/global.c Normal file
View file

@ -0,0 +1,88 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#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);
}

34
src/global.h Normal file
View file

@ -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 <stdbool.h>
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

View file

@ -0,0 +1,6 @@
#ifndef BUILTIN_H
#define BUILTIN_H
int cd(int, char**);
#endif

View file

@ -0,0 +1,25 @@
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#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;
}

16
src/interpreter/builtin.c Normal file
View file

@ -0,0 +1,16 @@
#include <stdio.h>
#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);
}

View file

@ -0,0 +1,171 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#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;
}

View file

@ -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

View file

@ -0,0 +1,52 @@
#include <stdlib.h>
#include <string.h>
#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);
}

View file

@ -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

77
src/main.c Normal file
View file

@ -0,0 +1,77 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <getopt.h>
#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;
}