You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
17 KiB
640 lines
17 KiB
#include "error.h" |
|
#include "run.h" |
|
#include "main.h" |
|
#include "util.h" |
|
#include <err.h> |
|
|
|
#include <dirent.h> |
|
#include <string.h> |
|
|
|
#include <readline/readline.h> |
|
#include <readline/history.h> |
|
|
|
int get_alias(Alias *alias, State *state, char *name); |
|
int add_alias(State* state, char *name, char *substitution); |
|
int del_alias(State *state, char *name); |
|
int repl(State* state, FILE *input); |
|
int run_command(State *state, int argc, char *argv[]); |
|
int get_array_element(ArrayElement **ret, GrowingArray* array, char* key); |
|
int del_array_element(GrowingArray* array, char* key); |
|
int expand_command(State *state, int *argc, char** ret_argv[]); |
|
|
|
GrowingArray get_growing_array(size_t num_elements, size_t size_elem) { |
|
GrowingArray arr; |
|
arr.inner = calloc(num_elements, size_elem); |
|
arr.space = num_elements; |
|
arr.elements = 0; |
|
return arr; |
|
} |
|
|
|
int get_alias(Alias *alias, State *state, char *name) { |
|
for (int i = 0; i < state->num_aliases; i++) { |
|
if (!strcmp(name, state->aliases[i].name)) { |
|
*alias = state->aliases[i]; |
|
return ER_SUCCESS; |
|
} |
|
} |
|
return ER_FAILURE; |
|
} |
|
|
|
int get_array_element(ArrayElement **ret, GrowingArray* array, char* key) { |
|
for (int i = 0; i < array->elements; i++) { |
|
if (!strcmp(key, array->inner[i].key)) { |
|
*ret = array->inner + i; |
|
return ER_SUCCESS; |
|
} |
|
} |
|
return ER_NOT_EXISTS; |
|
} |
|
|
|
int check_array_space(GrowingArray* array) { |
|
int resize = 0; |
|
if (array->elements > MIN_ARRAY_SIZE) { |
|
if (array->elements >= array->space - 2) { |
|
resize = 2 * array->space; |
|
} else if (array->elements < (array->space / 3)) { |
|
resize = array->space / 2; |
|
} |
|
} |
|
|
|
if (resize) { |
|
void *new = reallocarray(array->inner, resize, |
|
sizeof(ArrayElement)); |
|
if (new) { |
|
array->space = resize; |
|
array->inner = new; |
|
return ER_SUCCESS; |
|
} else { |
|
return ER_FAILURE; |
|
} |
|
} else { |
|
return ER_SUCCESS; |
|
} |
|
} |
|
|
|
int set_array_element(GrowingArray* array, char* key, void* value, |
|
size_t valsize) { |
|
ArrayElement *arr; |
|
if (!get_array_element(&arr, array, key)) { |
|
free(arr->value); |
|
arr->value = dualloc(value, valsize); |
|
return ER_SUCCESS; |
|
} else { |
|
check_array_space(array); |
|
void *val = dualloc(value, valsize); |
|
void *nkey = dualloc(key, (strlen(key) + 1) * sizeof(char)); |
|
if (value && key) { |
|
array->inner[array->elements].value = val; |
|
array->inner[array->elements].key = nkey; |
|
array->elements++; |
|
return ER_SUCCESS; |
|
} |
|
} |
|
return ER_FAILURE; |
|
} |
|
|
|
int del_array_element(GrowingArray* array, char* key) { |
|
ArrayElement *elem; |
|
int err = get_array_element(&elem, array, key); |
|
if (err) { |
|
return err; |
|
} else { |
|
bool move = false; |
|
for (int i = 0; i < array->elements; i++) { |
|
if (move) { |
|
if (i == array->elements - 1) { |
|
memset(&array->inner[i], 0, sizeof(ArrayElement)); |
|
} else { |
|
array->inner[i] = array->inner[i+1]; |
|
} |
|
} |
|
if (!strcmp(array->inner[i].key, key)) { |
|
if (!move) { |
|
free(array->inner[i].key); |
|
free(array->inner[i].value); |
|
move = true; |
|
// move |
|
if (i == array->elements - 1) { |
|
memset(&array->inner[i], 0, sizeof(ArrayElement)); |
|
} else { |
|
array->inner[i] = array->inner[i+1]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
array->elements--; |
|
return ER_SUCCESS; |
|
} |
|
return ER_FAILURE; |
|
} |
|
|
|
int add_alias(State* state, char* name, char* substitution) { |
|
Alias alias; |
|
strip_char(name, ' '); |
|
alias.name = calloc(strlen(name) + 1, sizeof(char)); |
|
alias.substitution = calloc(strlen(substitution) + 1, sizeof(char)); |
|
|
|
strcpy(alias.name, name); |
|
strcpy(alias.substitution, substitution); |
|
Alias existing; |
|
|
|
if (get_alias(&existing, state, name)) { |
|
// add it if it doesnt exist |
|
|
|
state->aliases[state->num_aliases] = alias; |
|
state->num_aliases++; |
|
} else { |
|
del_alias(state, name); |
|
free(alias.name); |
|
free(alias.substitution); |
|
add_alias(state, name, substitution); |
|
} |
|
|
|
return ER_SUCCESS; |
|
} |
|
|
|
int del_alias(State *state, char *name) { |
|
// read the list until the alias is found, then |
|
// shuffle all the alias definitions down by one |
|
bool move = false; |
|
for (int i = 0; i < state->num_aliases; i++) { |
|
if (move) { |
|
if (i < state->num_aliases -1) { |
|
state->aliases[i] = state->aliases[i + 1]; |
|
} else { |
|
memset(&state->aliases[i], 0, sizeof(Alias)); |
|
} |
|
} else { |
|
if (!strcmp(name, state->aliases[i].name)) { |
|
move = true; |
|
} |
|
} |
|
} |
|
|
|
if (move) { |
|
state->num_aliases--; |
|
return ER_SUCCESS; |
|
} else { |
|
return ER_FAILURE; |
|
} |
|
} |
|
|
|
int list_aliases(State *state) { |
|
for (int i = 0; i < state->num_aliases; i++) { |
|
Alias alias = state->aliases[i]; |
|
fprintf(state->output, "alias %s=%s\n", alias.name, alias.substitution); |
|
} |
|
return ER_SUCCESS; |
|
} |
|
|
|
int list_variables(State *state) { |
|
for (int i = 0; i < state->variables.elements; i++) { |
|
char *key = state->variables.inner[i].key; |
|
char *value = state->variables.inner[i].value; |
|
fprintf(state->output, "%s=%s\n", key, value); |
|
} |
|
return ER_SUCCESS; |
|
} |
|
|
|
void shutdown(State *state) { |
|
for (int i = 0; i < state->num_aliases; i++) { |
|
free(state->aliases[i].name); |
|
free(state->aliases[i].substitution); |
|
} |
|
free(state->ps1); |
|
free(state); |
|
exit(0); |
|
} |
|
|
|
int run_pipes(State *state, int num_args, char *args []) { |
|
char *joined = 0; |
|
int err = join_string(&joined, num_args, args, " "); |
|
if (err) { |
|
errx(1, "no"); |
|
} |
|
char * separated_by_pipe; |
|
|
|
int pipe_count = 0; |
|
for (int i = 0; i < strlen(joined); i++) { |
|
if (joined[i] == '|') { |
|
pipe_count++; |
|
} |
|
} |
|
|
|
int num_commands = pipe_count + 1; |
|
|
|
char **sep_commands = calloc(pipe_count + 1, sizeof(char *)); |
|
|
|
int i = 0; |
|
|
|
char *piece = strtok(joined, "|"); |
|
do { |
|
sep_commands[i] = calloc(strlen(piece) + 1, sizeof(char)); |
|
strcpy(sep_commands[i], piece); |
|
i++; |
|
} while ((piece = (char *)strtok(NULL, "|")) != 0); |
|
|
|
if (num_commands == 1) { |
|
for (int i = 0; i < num_commands; i++) { |
|
free(sep_commands[i]); |
|
} |
|
free(sep_commands); |
|
free(joined); |
|
// free everything |
|
return ER_NOT_EXISTS; |
|
} |
|
|
|
int *pipes = calloc(pipe_count * 2, sizeof(int)); |
|
|
|
for (int i = 0; i < pipe_count; i++) { |
|
pipe(pipes + (2 * i)); |
|
} |
|
|
|
pid_t child; |
|
|
|
int in; |
|
int out; |
|
|
|
for (int i = 0; i < num_commands; i++) { |
|
char **command; |
|
int words; |
|
|
|
int err = split_string(&command, &words, sep_commands[i], ' '); |
|
|
|
if (i == 0) { |
|
in = 0; |
|
out = pipes[2*i + 1]; |
|
} else if (i == num_commands - 1) { |
|
in = pipes[2*(i-1)]; |
|
out = 1; |
|
} else { |
|
in = pipes[2*(i-1)]; |
|
out = pipes[2*i + 1]; |
|
} |
|
|
|
int command_len = 0; |
|
for (int i = 0; command[i] != 0; i++) { |
|
command_len++; |
|
} |
|
//free(command[i]); // free the null pointer on the end |
|
expand_command(state, &command_len, &command); |
|
for (int i = 0; i < command_len; i++) { |
|
printf("%s ", command[i]); |
|
} |
|
printf("\n"); |
|
|
|
command = reallocarray(command, command_len+1, sizeof(char*)); |
|
command[command_len] = 0; |
|
|
|
execute(in, out, command, &child); |
|
|
|
for (int i = 0; i < command_len; i++) { |
|
free(command[i]); |
|
} |
|
free(command); |
|
} |
|
|
|
int status; |
|
while (!wait(&status)); |
|
|
|
free(pipes); |
|
for (int i = 0; i < num_commands; i++) { |
|
free(sep_commands[i]); |
|
} |
|
free(sep_commands); |
|
free(joined); |
|
|
|
return ER_SUCCESS; |
|
} |
|
|
|
int check_builtins(State *state, int num_args, char *args[]) { |
|
char *builtins[NUM_BUILTINS] = { |
|
"cd", |
|
"alias", |
|
"unalias" |
|
}; |
|
if (!strcmp(args[0], "cd")) { |
|
if (num_args == 1) { |
|
chdir(getenv("HOME")); |
|
return ER_SUCCESS; |
|
} |
|
if (num_args != 2) { |
|
return ER_FAILURE; |
|
} |
|
chdir(args[1]); |
|
} |
|
|
|
if (!strcmp(args[0], "set")) { |
|
if (num_args >= 2) { |
|
char *joined; |
|
int err = join_string(&joined, num_args -1, args + 1, " "); |
|
char **split; |
|
int num; |
|
err = split_string(&split, &num, joined, '='); |
|
printf("%s\n", joined); |
|
if (num == 2) { |
|
char * key = split[0]; |
|
char * value = split[1]; |
|
set_array_element(&state->variables, key, value, |
|
(strlen(value) + 1) * sizeof(char)); |
|
return ER_SUCCESS; |
|
} else { |
|
return ER_FAILURE; |
|
} |
|
} |
|
} |
|
|
|
if (!strcmp(args[0], "listvars")) { |
|
list_variables(state); |
|
return ER_SUCCESS; |
|
} |
|
|
|
if (!strcmp(args[0], "drop")) { |
|
if (num_args == 2) { |
|
char *key = args[1]; |
|
int err = del_array_element(&state->variables, key); |
|
if (err) { |
|
return ER_FAILURE; |
|
} else { |
|
return ER_SUCCESS; |
|
} |
|
} |
|
return ER_FAILURE; |
|
} |
|
|
|
if (!strcmp(args[0], "alias")) { |
|
Alias alias; |
|
// list aliases |
|
if (args[1] == 0) { |
|
list_aliases(state); |
|
return ER_SUCCESS; |
|
} |
|
// add new alias |
|
if (num_args >= 2) { |
|
if (num_args == 2) { |
|
if (!get_alias(&alias, state, args[1])) { |
|
fprintf(state->output, "alias %s=%s\n", alias.name, |
|
alias.substitution); |
|
return ER_SUCCESS; |
|
} |
|
} |
|
if (num_args >= 2) { |
|
char *substitution; |
|
char **new_split; |
|
int err = join_string(&substitution, num_args - 1, |
|
args + 1, " "); |
|
if (err) { |
|
free(substitution); |
|
return err; |
|
} |
|
int eqcount = 0; |
|
int num_parts; |
|
err = split_string(&new_split, &num_parts, substitution, '='); |
|
|
|
// exit if alias contains an = |
|
if (num_parts != 2 || err) { |
|
free(substitution); |
|
for (int i =0; i < num_parts; i++) { |
|
free(new_split[i]); |
|
} |
|
free(new_split); |
|
return ER_FAILURE; |
|
} |
|
|
|
err = add_alias(state, new_split[0], new_split[1]); |
|
|
|
free(substitution); |
|
for (int i =0; i < num_parts; i++) { |
|
free(new_split[i]); |
|
} |
|
free(new_split); |
|
|
|
return err; |
|
} |
|
} |
|
} |
|
|
|
if (!strcmp(args[0], "unalias")) { |
|
Alias alias; |
|
if (num_args == 2) { |
|
if (!del_alias(state, args[1])) { |
|
return ER_SUCCESS; |
|
} |
|
} |
|
} |
|
|
|
if (!strcmp(args[0], "run")) { |
|
if (num_args == 2) { |
|
FILE *file = fopen(args[1], "r"); |
|
if (!file) { |
|
return ER_FAILURE; |
|
} |
|
bool temp_repl_state = state->interactive; |
|
state->interactive = false; |
|
int err; |
|
if ((err = repl(state, file))) { |
|
perror("Config file error"); |
|
return err; |
|
} |
|
state->interactive = temp_repl_state; |
|
fclose(file); |
|
|
|
return ER_SUCCESS; |
|
} |
|
} |
|
|
|
if (!strcmp(args[0], "exit")) { |
|
if (num_args == 1) { |
|
shutdown(state); |
|
return ER_FAILURE; |
|
} |
|
} |
|
return ER_NOT_EXISTS; |
|
} |
|
|
|
int check_aliase(State *state, char **substitution, char *name) { |
|
Alias alias; |
|
for (int i = 0; i < state->num_aliases; i++) { |
|
if (!get_alias(&alias, state, name)) { |
|
*substitution = alias.substitution; |
|
return ER_SUCCESS; |
|
} |
|
} |
|
return ER_FAILURE; |
|
} |
|
|
|
int expand_command(State *state, int* argc, char** ret_argv[]) { |
|
if (*argc == 0) { |
|
return ER_FAILURE; |
|
} |
|
|
|
char **argv = *ret_argv; |
|
|
|
char *substitution; |
|
for (int i = 0; i < *argc; i++) { |
|
if (!check_aliase(state, &substitution, argv[i])) { |
|
|
|
strip_char(argv[i], ' '); |
|
|
|
// copy substitution into argv array |
|
*ret_argv[i] = reallocarray(*ret_argv[i], strlen(substitution) + 1, sizeof(char)); |
|
strcpy(*ret_argv[0], substitution); |
|
|
|
// expand substituted piece into the erray splitting over spaces |
|
char *joined; |
|
char **new_argv; |
|
join_string(&joined, *argc, *ret_argv, " "); |
|
split_string(&new_argv, argc, joined, ' '); |
|
|
|
for (int j = 0 ; j < *argc;j ++) { |
|
free(*ret_argv[j]); |
|
} |
|
free(*ret_argv); |
|
|
|
*ret_argv = new_argv; |
|
/***************************************/ |
|
|
|
} |
|
} |
|
|
|
return ER_SUCCESS; |
|
} |
|
|
|
int run_command(State *state, int argc, char *argv[]) { |
|
pid_t child; |
|
if (check_builtins(state, argc, argv) == ER_NOT_EXISTS) { |
|
if (!execute(0,1, argv, &child)) |
|
waitpid(child, NULL, 0); |
|
} |
|
return ER_SUCCESS; |
|
} |
|
|
|
/* read eval print loop until SIGTERM or end of non-interact file*/ |
|
int repl(State* state, FILE *input) { |
|
while (true) { |
|
char *line; |
|
|
|
if (state->interactive) { |
|
|
|
char ** ps1_sep; |
|
int num; |
|
split_string(&ps1_sep, &num, state->ps1, ' '); |
|
run_command(state, num, ps1_sep); |
|
for (int i = 0; i < num; i ++) { |
|
free(ps1_sep[i]); |
|
} |
|
free(ps1_sep); |
|
|
|
line = readline("$ "); |
|
if (!line) { |
|
printf("EOF\n"); |
|
return ER_SUCCESS; |
|
} |
|
|
|
add_history(line); |
|
} else { |
|
if (get_line(input, &line)) { |
|
if (line) |
|
free(line); |
|
return ER_SUCCESS; |
|
} |
|
} |
|
|
|
char **sep_string; |
|
int num_words; |
|
|
|
split_string(&sep_string, &num_words, line, ' '); |
|
free(line); |
|
|
|
if (num_words == 0 // line is empty |
|
|| sep_string[0][0] == '#') { // line is comment |
|
for (int i = 0; i <= num_words; i ++) |
|
free(sep_string[i]); |
|
free(sep_string); |
|
continue; // skip execution |
|
} |
|
|
|
int err = run_pipes(state, num_words, sep_string); |
|
|
|
if (err == ER_NOT_EXISTS) { |
|
expand_command(state, &num_words, &sep_string); |
|
run_command(state, num_words, sep_string); |
|
} |
|
|
|
for (int i = 0; i < num_words; i ++) { |
|
free(sep_string[i]); |
|
} |
|
free(sep_string); |
|
} |
|
} |
|
|
|
int startup(State *state, int argc, char **argv) { |
|
state->output = stdout; |
|
|
|
state->ps1 = calloc(3, sizeof(char)); |
|
strcpy(state->ps1, "pwd"); |
|
|
|
state->variables = get_growing_array(INITIAL_NUM_VARIABLES, sizeof(char*)); |
|
|
|
char *config_home; |
|
char *config = calloc(500, sizeof(char)); |
|
|
|
if ((config_home = getenv("XDG_CONFIG_HOME"))) { |
|
strcpy(config, config_home); |
|
int len = strlen(config_home); |
|
strcpy(config + len, "/chicken/chickenrc"); |
|
} else { |
|
config_home = getenv("HOME"); |
|
strcpy(config, config_home); |
|
int len = strlen(config_home); |
|
strcpy(config + len, "/.chickenrc"); |
|
} |
|
|
|
int err; |
|
FILE* rc = fopen(config, "r"); |
|
free(config); |
|
if (rc) { |
|
state->interactive = false; |
|
state->input = rc; |
|
if ((err = repl(state, rc))) { |
|
perror("Config file error"); |
|
return err; |
|
} |
|
fclose(rc); |
|
} |
|
|
|
if (argc == 2) { |
|
FILE *script_text = fopen(argv[1], "r"); |
|
if (script_text) { |
|
state->code = script_text; |
|
} else { |
|
perror("Failed to load script"); |
|
return ER_FAILURE; |
|
} |
|
} |
|
|
|
return ER_SUCCESS; |
|
} |
|
|
|
int main(int argc, char** argv) { |
|
State *state = calloc(1, sizeof(State)); |
|
state->aliases = calloc(INITIAL_NUM_ALIASES, sizeof(struct Alias)); |
|
state->num_aliases = 0; |
|
|
|
int err = startup(state, argc, argv); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
if (state->code) { |
|
state->interactive = false; |
|
repl(state, state->code); |
|
} else { |
|
state->interactive = true; |
|
print_chicken(state); |
|
repl(state, stdin); |
|
} |
|
|
|
return ER_SUCCESS; |
|
}
|
|
|