diff options
Diffstat (limited to 'envlist.c')
-rw-r--r-- | envlist.c | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/envlist.c b/envlist.c new file mode 100644 index 0000000000..e13c2d3e14 --- /dev/null +++ b/envlist.c @@ -0,0 +1,247 @@ +#include <sys/queue.h> + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "envlist.h" + +struct envlist_entry { + const char *ev_var; /* actual env value */ + LIST_ENTRY(envlist_entry) ev_link; +}; + +struct envlist { + LIST_HEAD(, envlist_entry) el_entries; /* actual entries */ + size_t el_count; /* number of entries */ +}; + +static int envlist_parse(envlist_t *envlist, + const char *env, int (*)(envlist_t *, const char *)); + +/* + * Allocates new envlist and returns pointer to that or + * NULL in case of error. + */ +envlist_t * +envlist_create(void) +{ + envlist_t *envlist; + + if ((envlist = malloc(sizeof (*envlist))) == NULL) + return (NULL); + + LIST_INIT(&envlist->el_entries); + envlist->el_count = 0; + + return (envlist); +} + +/* + * Releases given envlist and its entries. + */ +void +envlist_free(envlist_t *envlist) +{ + struct envlist_entry *entry; + + assert(envlist != NULL); + + while (envlist->el_entries.lh_first != NULL) { + entry = envlist->el_entries.lh_first; + LIST_REMOVE(entry, ev_link); + + free((char *)entry->ev_var); + free(entry); + } + free(envlist); +} + +/* + * Parses comma separated list of set/modify environment + * variable entries and updates given enlist accordingly. + * + * For example: + * envlist_parse(el, "HOME=foo,SHELL=/bin/sh"); + * + * inserts/sets environment variables HOME and SHELL. + * + * Returns 0 on success, errno otherwise. + */ +int +envlist_parse_set(envlist_t *envlist, const char *env) +{ + return (envlist_parse(envlist, env, &envlist_setenv)); +} + +/* + * Parses comma separated list of unset environment variable + * entries and removes given variables from given envlist. + * + * Returns 0 on success, errno otherwise. + */ +int +envlist_parse_unset(envlist_t *envlist, const char *env) +{ + return (envlist_parse(envlist, env, &envlist_unsetenv)); +} + +/* + * Parses comma separated list of set, modify or unset entries + * and calls given callback for each entry. + * + * Returns 0 in case of success, errno otherwise. + */ +static int +envlist_parse(envlist_t *envlist, const char *env, + int (*callback)(envlist_t *, const char *)) +{ + char *tmpenv, *envvar; + char *envsave = NULL; + + assert(callback != NULL); + + if ((envlist == NULL) || (env == NULL)) + return (EINVAL); + + /* + * We need to make temporary copy of the env string + * as strtok_r(3) modifies it while it tokenizes. + */ + if ((tmpenv = strdup(env)) == NULL) + return (errno); + + envvar = strtok_r(tmpenv, ",", &envsave); + while (envvar != NULL) { + if ((*callback)(envlist, envvar) != 0) { + free(tmpenv); + return (errno); + } + envvar = strtok_r(NULL, ",", &envsave); + } + + free(tmpenv); + return (0); +} + +/* + * Sets environment value to envlist in similar manner + * than putenv(3). + * + * Returns 0 in success, errno otherwise. + */ +int +envlist_setenv(envlist_t *envlist, const char *env) +{ + struct envlist_entry *entry = NULL; + const char *eq_sign; + size_t envname_len; + + if ((envlist == NULL) || (env == NULL)) + return (EINVAL); + + /* find out first equals sign in given env */ + if ((eq_sign = strchr(env, '=')) == NULL) + return (EINVAL); + envname_len = eq_sign - env + 1; + + /* + * If there already exists variable with given name + * we remove and release it before allocating a whole + * new entry. + */ + for (entry = envlist->el_entries.lh_first; entry != NULL; + entry = entry->ev_link.le_next) { + if (strncmp(entry->ev_var, env, envname_len) == 0) + break; + } + + if (entry != NULL) { + LIST_REMOVE(entry, ev_link); + free((char *)entry->ev_var); + free(entry); + } else { + envlist->el_count++; + } + + if ((entry = malloc(sizeof (*entry))) == NULL) + return (errno); + if ((entry->ev_var = strdup(env)) == NULL) { + free(entry); + return (errno); + } + LIST_INSERT_HEAD(&envlist->el_entries, entry, ev_link); + + return (0); +} + +/* + * Removes given env value from envlist in similar manner + * than unsetenv(3). Returns 0 in success, errno otherwise. + */ +int +envlist_unsetenv(envlist_t *envlist, const char *env) +{ + struct envlist_entry *entry; + size_t envname_len; + + if ((envlist == NULL) || (env == NULL)) + return (EINVAL); + + /* env is not allowed to contain '=' */ + if (strchr(env, '=') != NULL) + return (EINVAL); + + /* + * Find out the requested entry and remove + * it from the list. + */ + envname_len = strlen(env); + for (entry = envlist->el_entries.lh_first; entry != NULL; + entry = entry->ev_link.le_next) { + if (strncmp(entry->ev_var, env, envname_len) == 0) + break; + } + if (entry != NULL) { + LIST_REMOVE(entry, ev_link); + free((char *)entry->ev_var); + free(entry); + + envlist->el_count--; + } + return (0); +} + +/* + * Returns given envlist as array of strings (in same form that + * global variable environ is). Caller must free returned memory + * by calling free(3) for each element and for the array. Returned + * array and given envlist are not related (no common references). + * + * If caller provides count pointer, number of items in array is + * stored there. In case of error, NULL is returned and no memory + * is allocated. + */ +char ** +envlist_to_environ(const envlist_t *envlist, size_t *count) +{ + struct envlist_entry *entry; + char **env, **penv; + + penv = env = malloc((envlist->el_count + 1) * sizeof (char *)); + if (env == NULL) + return (NULL); + + for (entry = envlist->el_entries.lh_first; entry != NULL; + entry = entry->ev_link.le_next) { + *(penv++) = strdup(entry->ev_var); + } + *penv = NULL; /* NULL terminate the list */ + + if (count != NULL) + *count = envlist->el_count; + + return (env); +} |