#include #include #include #include #include #include #include #include #include "utils/env.h" #define BUFFER_SIZE 51 #define ERROR_CHECK(MSG) \ if (str[*i] == '\0') \ { \ string_free(input); \ return clean_exit(MSG, error); \ } #define DQUOTEESCAPED "$\\\n`\"" // Identifies double-quote escaped characters #define ISDQUOTEESCAPED(C) strchr(DQUOTEESCAPED, (int)C) #define SPECIAL "@*?$#" // Identifies special variable names #define ISSPECIAL(C) strchr(SPECIAL, (int)C) // error = 1 indicates a missing } // error = 0 and NULL returned indicates an unrecognized var name // error = 0 and anything else than NULL returned is the var name static struct string *get_var_name(char *str, int *error) { struct string *res = string_create(NULL); int i = 0; if (str[i] == '{') { while (str[i] && str[i] != '}') { if (str[i] == '\\' && str[i + 1] == '}') i++; string_pushc(res, str[i]); i++; } if (!str[i]) { string_free(res); *error = 1; return NULL; } *error = 0; return res; } else if (ISSPECIAL(str[i]) || isdigit(str[i])) { string_pushc(res, str[i]); *error = 0; return res; } else if (!isalpha(str[i])) { *error = 0; string_free(res); return NULL; } else { while (isalnum(str[i]) || str[i] == '_') string_pushc(res, str[i++]); return res; } } // Useful to automate the same exit process accross the few functions // that often do this static struct string *clean_exit(char *txt, int *error) { fprintf(stderr, "%s", txt); *error = 1; return NULL; } // Creates the fork() in order to make a subshell for the command expansion // (cf section 2.6.3 of the SCL) // Only called in expand_substitution() static struct string *fork_subshell(struct string *input, int j, char *str, int *error) { int fds[2]; if (pipe(fds) == -1) { return clean_exit("pipe() faild to create 2 fds\n", error); } struct string *output; pid_t child = fork(); if (child == -1) { // Fork not working return clean_exit("fork() faild to produce a children\n", error); } else if (child) { // Parent process close(fds[1]); output = string_create(NULL); char buff[BUFFER_SIZE]; buff[BUFFER_SIZE - 1] = 0; int r; int status; waitpid(child, &status, 0); // Check if the child terminated normally if (!WIFEXITED(status)) { close(fds[0]); string_free(output); return clean_exit("Child process failed miserably\n", error); } while ((r = read(fds[0], buff, 50))) { buff[r] = 0; if (!string_pushstr(output, buff)) { string_free(output); return clean_exit("Failed to transfer from pipe\n", error); } } close(fds[0]); return output; } else { // Child process str += j; close(fds[0]); if (dup2(fds[1], STDOUT_FILENO) == -1) { // We are forced to return NULL // how are we going to know if something wnet wrong ? exit(-1); } _process_input(input); close(fds[1]); exit(1); } } static int look_for_next(char *in, int i, char c) { int escaped = 0; while (in[i] && (in[i] != c || (escaped && in[i] == c))) { if (in[i] == '\\') escaped ^= 1; else escaped = 0; i++; } return i; } // Removes all the characters at the end of the string obtained by // the command substitution (Also section 2.6.3 of the SCL) static void trimming_newline(struct string *txt) { if (!txt->length) { return; } char *str = txt->data; size_t len = txt->length; size_t i = len - 1; while (str[i] == '\n') { str[i] = 0; len--; } // I am scared and I know this isn't useful but just in case txt->data = str; txt->length = len; } // Performs the substitution (forks and get back the stdout) struct string *expand_substitution(char *str, int *i, int *error, char delim) { int j = *i; struct string *input = string_create(NULL); if (input == NULL) { return clean_exit("Could not create string for input\n", error); } if (delim == '`') { *i = look_for_next(str, j, delim); ERROR_CHECK("Could not match `\n") str[*i] = '\0'; } // Sadly, there is no other way around this else { int escaped = 0; int par_count = 1; while (str[*i] != 0) { if (str[*i] == '\\') { escaped ^= 1; } else if (str[*i] == '\'') { (*i) += 1; while (str[*i] != '\0' && str[*i] != '\'') { (*i) += 1; } ERROR_CHECK("Missing matching '\n") } else if ((str[*i] == '\"' || str[*i] == '`') && !escaped) { (*i) += 1; *i = look_for_next(str, *i, str[(*i) - 1]); ERROR_CHECK("Missing matching `\n") } else if (str[*i] == '(' && !escaped) { par_count++; } else if (str[*i] == delim && !escaped) { par_count--; if (!par_count) { str[*i] = 0; break; } } else { escaped = 0; } (*i)++; } } string_pushstr(input, str + j); struct string *output = fork_subshell(input, j, str, error); string_free(input); trimming_newline(output); str[*i] = delim; return output; } static int expand_var(struct string *res, char *input, int i) { // Will only be called after a '$' was read int e = 0; struct string *name = get_var_name(input + i + 1, &e); if (e) { string_free(name); fprintf(stderr, "Missing } in variable expansion\n"); return -1; } else if (name == NULL) { string_pushc(res, input[i]); i++; } else { // Get the value associated to the name char *value = env_get(name->data); // Concatenate the strings if the variable has a value if (value) string_pushstr(res, value); if (input[++i] == '{') i += 2; i += name->length; string_free(name); } return i; } static int expand_dquotes(char *input, int i, struct string *res) { while (input[i] != '"') { if (input[i] == '$') { if ((i = i + expand_var(res, input, i)) == -1) { string_free(res); return -1; } continue; } if ((input[i] == '`' || (input[i] == '$' && input[i + 1] == '('))) { int e = 0; i += (input[i] == '$' ? 2 : 1); struct string *output = expand_substitution(input, &i, &e, input[i]); if (!e) { string_free(res); return -1; } // +1 for the last parenthesis/backquote i++; string_catenate(res, output); continue; } if (input[i] == '\\' && ISDQUOTEESCAPED(input[i + 1])) i++; string_pushc(res, input[i]); i++; } return i; } struct string *expand_word(struct string *word) { char *input = word->data; int escape = 0; struct string *res = string_create(NULL); for (int i = 0; input[i]; i++) { if (!escape && input[i] == '\'') { while (input[++i] != '\'') string_pushc(res, input[i]); } else if (!escape && input[i] == '"') { i++; if ((i = expand_dquotes(input, i, res)) == -1) return NULL; } else if (!escape && input[i] == '\\') escape ^= 1; else { // We don't care if we are after a backslash, we just include this // char if (input[i] == '$' && !escape) { if ((i = i + expand_var(res, input, i)) == -1) { string_free(res); return NULL; } continue; } string_pushc(res, input[i]); escape = 0; } } // string_free(word); return res; }