diff options
author | Joseph Hunkeler <jhunkeler@gmail.com> | 2020-01-14 13:58:28 -0500 |
---|---|---|
committer | Joseph Hunkeler <jhunkeler@gmail.com> | 2020-01-14 13:58:28 -0500 |
commit | 77a35c4a098a11bcb8d93a8748f21a930b88b0d5 (patch) | |
tree | b3fa339759627078fc57d817f270ffb26d1191d3 /src | |
parent | fafbb0d61aa5645dfb47058079f3173d420dc13e (diff) | |
download | spmc-77a35c4a098a11bcb8d93a8748f21a930b88b0d5.tar.gz |
Add runtime environment manipulation routines
Diffstat (limited to 'src')
-rw-r--r-- | src/environment.c | 361 | ||||
-rw-r--r-- | src/spm.c | 45 |
2 files changed, 385 insertions, 21 deletions
diff --git a/src/environment.c b/src/environment.c index d338e4a..4c8fb14 100644 --- a/src/environment.c +++ b/src/environment.c @@ -4,10 +4,56 @@ #include "spm.h" /** + * Print a shell-specific listing of environment variables to `stdout` * - * @param env + * Example: + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = runtime_copy(arge); + * runtime_export(rt, NULL); + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * Usage: + * ~~~{.sh} + * $ gcc program.c + * $ ./a.out + * PATH="/thing/stuff/bin:/example/please/bin" + * SHELL="/your/shell" + * CC="/your/compiler" + * ...=... + * + * # You can also use this to modify the shell environment + * # (use `runtime_set` to manipulate the output) + * $ source $(./a.out) + * ~~~ + * + * Example of exporting specific keys from the environment: + * + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = runtime_copy(arge); + * + * // inline declaration + * runtime_export(rt, (char *[]) {"PATH", "LS_COLORS", NULL}); + * + * // standard declaration + * char *keys_to_export[] = { + * "PATH", "LS_COLORS", NULL + * } + * runtime_export(rt, keys_to_export); + * + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * @param env `RuntimeEnv` structure + * @param keys Array of keys to export. A value of `NULL` exports all environment keys */ -void runtime_export(char **env) { +void runtime_export(RuntimeEnv *env, char **keys) { char *borne[] = { "bash", "dash", @@ -42,36 +88,319 @@ void runtime_export(char **env) { } } - for (size_t i = 0; env[i] != NULL; i++) { - char **pair = split(env[i], "="); - sprintf(output, "%s %s=\"%s\"", export_command, pair[0], pair[1] ? pair[1] : ""); - puts(output); + for (size_t i = 0; i < env->num_inuse; i++) { + char **pair = split(env->env[i], "="); + if (keys != NULL) { + for (size_t j = 0; keys[j] != NULL; j++) { + if (strcmp(keys[j], pair[0]) == 0) { + sprintf(output, "%s %s=\"%s\"", export_command, pair[0], pair[1] ? pair[1] : ""); + puts(output); + } + } + } + else { + sprintf(output, "%s %s=\"%s\"", export_command, pair[0], pair[1] ? pair[1] : ""); + puts(output); + } split_free(pair); } } /** + * Populate a `RuntimeEnv` structure + * + * Example: + * + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = NULL; + * // Example 1: Copy the shell environment + * rt = runtime_copy(arge); + * // Example 2: Create your own environment + * rt = runtime_copy((char *[]) {"SHELL=/bin/bash", "PATH=/opt/secure:/bin:/usr/bin"}) * - * @param env - * @return + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * @param env Array of strings in `var=value` format + * @return `RuntimeEnv` structure */ -char **runtime_copy(char **env) { +RuntimeEnv *runtime_copy(char **env) { + RuntimeEnv *rt = NULL; char **envp = NULL; size_t env_count; for (env_count = 0; env[env_count] != NULL; env_count++); - envp = (char **)calloc(env_count + 1, sizeof(char *)); - for (size_t i = 0; i < env_count; i++) { + rt = (RuntimeEnv *)calloc(1, sizeof(RuntimeEnv)); + rt->num_alloc = env_count + 1; + rt->num_inuse = env_count; + if (rt->num_inuse > 1) { + rt->num_inuse--; + } + + envp = (char **)calloc(rt->num_alloc, sizeof(char *)); + for (size_t i = 0; i < rt->num_inuse; i++) { size_t len = strlen(env[i]); - envp[i] = (char *)calloc(len + 1, sizeof(char)); + envp[i] = (char *) calloc(len + 1, sizeof(char)); memcpy(envp[i], env[i], len); } - return envp; + rt->env = envp; + return rt; +} + +/** + * Determine whether or not a key exists in the runtime environment + * + * Example: + * + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = runtime_copy(arge); + * if (runtime_contains(rt, "PATH") { + * // $PATH is present + * } + * else { + * // $PATH is NOT present + * } + * + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * @param env `RuntimeEnv` structure + * @param key Environment variable string + * @return -1=no, positive_value=yes + */ +ssize_t runtime_contains(RuntimeEnv *env, const char *key) { + int result = -1; + for (size_t i = 0; i < env->num_inuse; i++) { + char **pair = split(env->env[i], "="); + if (pair == NULL) { + break; + } + if (strcmp(pair[0], key) == 0) { + result = i; + split_free(pair); + break; + } + split_free(pair); + } + return result; +} + +/** + * Retrieve the value of a runtime environment variable + * + * Example: + * + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = runtime_copy(arge); + * char *path = runtime_get("PATH"); + * if (path == NULL) { + * // handle error + * } + * + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * @param env `RuntimeEnv` structure + * @param key Environment variable string + * @return success=string, failure=`NULL` + */ +char *runtime_get(RuntimeEnv *env, const char *key) { + char *result = NULL; + ssize_t key_offset = runtime_contains(env, key); + if (key_offset != -1) { + char **pair = split(env->env[key_offset], "="); + result = join(&pair[1], "="); + split_free(pair); + } + return result; +} + +/** + * Parse an input string and expand any environment variable(s) found + * + * Example: + * + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = runtime_copy(arge); + * char *secure_path = runtime_expand_var(rt, "/opt/secure:$PATH:/aux/bin"); + * if (secure_path == NULL) { + * // handle error + * } + * // secure_path = "/opt/secure:/your/original/path/here:/aux/bin"); + * + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * @param env `RuntimeEnv` structure + * @param input String to parse + * @return success=expanded string, failure=`NULL` + */ +char *runtime_expand_var(RuntimeEnv *env, const char *input) { + const char delim = '$'; + const char *delim_literal = "$$"; + const char *escape = "\\"; + char *expanded = calloc(BUFSIZ, sizeof(char)); + + // If there's no environment variables to process return a copy of the input string + if (strchr(input, delim) == NULL) { + return strdup(input); + } + + // Parse the input string + for (size_t i = 0; i < strlen(input); i++) { + char var[MAXNAMLEN]; // environment variable name + memset(var, '\0', MAXNAMLEN); // zero out name + + // Ignore closing brace + if (input[i] == '}') { + i++; + } + + // Handle literal statement "$$var" + // Value becomes "\$var" + if (strncmp(&input[i], delim_literal, strlen(delim_literal)) == 0) { + strncat(expanded, escape, strlen(escape)); + strncat(expanded, &delim, 1); + i += strlen(delim_literal); + // Ignore opening brace + if (input[i] == '{') i++; + } + + // Handle variable when encountering a single $ + // Value expands from "$var" to "environment value of var" + if (input[i] == delim) { + // Ignore opening brace + if (input[i] == '{') i++; + char *tmp = NULL; + i++; + + // Construct environment variable name from input + // "$ var" == no + // "$-*)!@ == no + // "$var" == yes + for (size_t c = 0; isalnum(input[i]) || input[i] == '_'; c++, i++) { + var[c] = input[i]; + } + + tmp = runtime_get(env, var); + if (tmp == NULL) { + // This mimics shell behavior in general. + // Prevent appending whitespace when an environment variable does not exist + if (i > 0) { + i--; + } + continue; + } + // Append expanded environment variable to output + strncat(expanded, tmp, strlen(tmp)); + free(tmp); + } + // Nothing to do so append input to output + strncat(expanded, &input[i], 1); + } + + return expanded; } -void runtime_free(char **env) { - for (size_t i = 0; env[i] != NULL; i++) { - free(env[i]); +/** + * Set a runtime environment variable. + * + * + * Note: `_value` is passed through `runtime_expand_var` to provide shell expansion + * + * + * Example: + * + * ~~~{.c} + * int main(int argc, char *argv[], char *arge[]) { + * RuntimeEnv *rt = runtime_copy(arge); + * + * runtime_set(rt, "new_var", "1"); + * char *new_var = runtime_get("new_var"); + * // new_var = 1; + * + * char *path = runtime_get("PATH"); + * // path = /your/path:/here + * + * runtime_set(rt, "PATH", "/opt/secure:$PATH"); + * char *secure_path = runtime_get("PATH"); + * // secure_path = /opt/secure:/your/path:/here + * // NOTE: path and secure_path are COPIES, unlike `getenv()` and `setenv()` that reuse their pointers in `environ` + * + * runtime_free(rt); + * return 0; + * } + * ~~~ + * + * + * @param env `RuntimeEnv` structure + * @param _key Environment variable to set + * @param _value New environment variable value + */ +void runtime_set(RuntimeEnv *env, const char *_key, const char *_value) { + if (_key == NULL) { + return; + } + char *key = strdup(_key); + ssize_t key_offset = runtime_contains(env, key); + char *value = runtime_expand_var(env, _value); + char *arr[] = { + key, value, NULL + }; + char *now = join(arr, "="); + + if (key_offset != -1) { + free(env->env[key_offset]); + env->env[key_offset] = now; + } + else { + env->num_alloc++; + env->env = reallocarray(env->env, sizeof(char *), env->num_alloc); + env->env[env->num_inuse] = (char *)calloc(strlen(now) + 1, sizeof(char)); + env->env[env->num_inuse] = now; + env->num_inuse++; + } + free(key); + free(value); +} + +/** + * Update the global `environ` array with data from `RuntimeEnv` + * @param env `RuntimeEnv` structure + */ +void runtime_apply(RuntimeEnv *env) { + for (size_t i = 0; i < env->num_inuse; i++) { + char **pair = split(env->env[i], "="); + setenv(pair[0], pair[1], 1); + split_free(pair); + } +} + +/** + * Free `RuntimeEnv` allocated by `runtime_copy` + * @param env `RuntimeEnv` structure + */ +void runtime_free(RuntimeEnv *env) { + if (env == NULL) { + return; + } + + env->num_alloc = 0; + env->num_inuse = 0; + for (size_t i = 0; i < env->num_inuse; i++) { + free(env->env[i]); } free(env); }
\ No newline at end of file @@ -143,6 +143,38 @@ int main(int argc, char *argv[], char *arge[]) { exit(1); } + if (isempty(root)) { + sprintf(root, "%s%c%s", getenv("HOME"), DIRSEP, "spm_root"); + } + + // Construct installation runtime environment + RuntimeEnv *rt = runtime_copy(arge); + SPM_Hierarchy *root_hierarchy = NULL; + // TODO: Move environment allocation out of (above) this loop if possible + // TODO: replace variables below with SPM_Hierarchy, and write some control functions + + char *spm_binpath = join((char *[]) {root, "bin"}, DIRSEPS); + char *spm_includepath = join((char *[]) {root, "include"}, DIRSEPS); + char *spm_libpath = join((char *[]) {root, "lib"}, DIRSEPS); + char *spm_datapath = join((char *[]) {root, "share"}, DIRSEPS); + char *spm_manpath = join((char *[]) {spm_datapath, "man"}, DIRSEPS); + + runtime_set(rt, "SPM_BIN", spm_binpath); + runtime_set(rt, "SPM_INCLUDE", spm_includepath); + runtime_set(rt, "SPM_LIB", spm_libpath); + runtime_set(rt, "SPM_DATA", spm_datapath); + runtime_set(rt, "SPM_MAN", spm_manpath); + runtime_set(rt, "PATH", "$SPM_BIN:$PATH"); + runtime_set(rt, "MANPATH", "$SPM_MAN:$MANPATH"); + + if (exists(join((char *[]) {spm_binpath, "gcc"}, DIRSEPS)) == 0) { + runtime_set(rt, "CC", "$SPM_BIN/gcc"); + } + + runtime_set(rt, "CFLAGS", "-I$SPM_INCLUDE $CFLAGS"); + runtime_set(rt, "LDFLAGS", "-Wl,-rpath $SPM_LIB:$${ORIGIN}/lib -L$SPM_LIB $LDFLAGS"); + runtime_apply(rt); + if (RUNTIME_INSTALL) { Dependencies *deps = NULL; dep_init(&deps); @@ -151,15 +183,11 @@ int main(int argc, char *argv[], char *arge[]) { Manifest *manifest = manifest_read(); if (!manifest) { fprintf(stderr, "Package manifest is missing or corrupt\n"); + runtime_free(rt); exit(1); } printf("done\n"); - if (isempty(root)) { - printf("Using default installation root\n"); - sprintf(root, "%s%c%s", getenv("HOME"), DIRSEP, "spm_root"); - } - printf("Installation root: %s\n", root); printf("Requested packages:\n"); @@ -194,6 +222,7 @@ int main(int argc, char *argv[], char *arge[]) { if (dep_all(&deps, package->archive) < 0) { dep_free(&deps); free_global_config(); + runtime_free(rt); exit(1); } } @@ -209,6 +238,7 @@ int main(int argc, char *argv[], char *arge[]) { printf(" -> %s\n", deps->list[i]); if (install(root, deps->list[i]) < 0) { fprintf(SYSERROR); + runtime_free(rt); exit(errno); } } @@ -225,11 +255,13 @@ int main(int argc, char *argv[], char *arge[]) { if ((match = find_package(packages[i])) == NULL) { fprintf(SYSERROR); + runtime_free(rt); exit(1); } if ((package = basename(match)) == NULL) { fprintf(stderr, "Unable to derive package name from package path:\n\t-> %s\n", match); + runtime_free(rt); exit(1); } @@ -241,6 +273,7 @@ int main(int argc, char *argv[], char *arge[]) { printf(" -> %s\n", package); if (install(root, packages[i]) < 0) { fprintf(SYSERROR); + runtime_free(rt); exit(errno); } free(match); @@ -313,8 +346,10 @@ int main(int argc, char *argv[], char *arge[]) { } } + runtime_free(rt); exit(0); } + free_global_config(); return 0; } |