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

#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;
}