/* Environment modification functions for GNU Pies. Copyright (C) 2019-2023 Sergey Poznyakoff GNU Pies is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Pies is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Pies. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "envop.h" #include "wordsplit.h" environ_t * environ_create (char **def) { size_t i; environ_t *env = malloc (sizeof (*env)); if (!env) return NULL; if (!def) { static char *nullenv[] = { NULL }; def = nullenv; } for (i = 0; def[i]; i++) ; env->env_count = 0; env->env_max = i + 1; env->env_base = calloc (env->env_max, sizeof (env->env_base[0])); if (!env->env_base) { free (env); return NULL; } for (i = 0; def[i]; i++) { if (!(env->env_base[i] = strdup (def[i]))) { environ_free (env); return NULL; } env->env_count++; } env->env_base[i] = NULL; return env; } void environ_free (environ_t *env) { size_t i; for (i = 0; i < env->env_count; i++) free (env->env_base[i]); free (env->env_base); free (env); } static ssize_t getenvind (environ_t *env, char const *name, char **pval) { size_t i; for (i = 0; i < env->env_count; i++) { char const *p; char *q; for (p = name, q = env->env_base[i]; *p == *q; p++, q++) if (*p == '=') break; if ((*p == 0 || *p == '=') && *q == '=') { if (pval) *pval = q + 1; return i; } } return -1; } static ssize_t environ_alloc (environ_t *env) { size_t n; if (env->env_count + 1 >= env->env_max) { char **p; if (env->env_base == NULL) { n = 64; p = calloc (n, sizeof (p[0])); if (!p) return -1; } else { n = env->env_max; if ((size_t) -1 / 3 * 2 / sizeof (p[0]) <= n) { errno = ENOMEM; return -1; } n += (n + 1) / 2; p = realloc (env->env_base, n * sizeof (p[0])); if (!p) return -1; } env->env_base = p; env->env_max = n; } n = env->env_count++; env->env_base[env->env_count] = NULL; return n; } static int environ_add_alloced (environ_t *env, char *def) { ssize_t n; n = getenvind (env, def, NULL); if (n == -1) { n = environ_alloc (env); if (n == -1) return -1; } free (env->env_base[n]); env->env_base[n] = def; return 0; } int environ_add (environ_t *env, char const *def) { char *defcp = strdup (def); if (!defcp) return -1; if (environ_add_alloced (env, defcp)) { free (defcp); return -1; } return 0; } int environ_set (environ_t *env, char const *name, char const *value) { size_t len; char *def; struct wordsplit ws; if (!value) { if (!name) { errno = EINVAL; return -1; } return environ_unset (env, name, value); } ws.ws_env = (char const **) env->env_base; if (wordsplit (value, &ws, WRDSF_NOSPLIT | WRDSF_QUOTE | WRDSF_NOCMD /* FIXME */ | WRDSF_SQUEEZE_DELIMS | WRDSF_CESCAPES | WRDSF_ENV | WRDSF_PATHEXPAND)) { int ec = errno; wordsplit_free (&ws); errno = ec; return -1; } if (ws.ws_envbuf) { free (env->env_base); env->env_base = ws.ws_envbuf; env->env_count = ws.ws_envidx; env->env_max = ws.ws_envsiz; ws.ws_envbuf = NULL; ws.ws_envidx = 0; ws.ws_envsiz = 0; } if (name == NULL || strcmp (name, ":") == 0) { wordsplit_free (&ws); return 0; } len = strlen (name) + strlen (ws.ws_wordv[0]) + 2; def = malloc (len); if (!def) { int ec = errno; wordsplit_free (&ws); errno = ec; return -1; } strcat (strcat (strcpy (def, name), "="), ws.ws_wordv[0]); wordsplit_free (&ws); if (environ_add_alloced (env, def)) { free (def); return -1; } return 0; } int environ_unset (environ_t *env, char const *name, char const *refval) { ssize_t n; char *val; if (!env || !name) { errno = EINVAL; return -1; } n = getenvind (env, name, &val); if (n == -1) return ENOENT; if (refval && strcmp (val, refval)) return ENOENT; free (env->env_base[n]); memmove (env->env_base + n, env->env_base + n + 1, (env->env_count - n) * sizeof (env->env_base[0])); env->env_count--; return 0; } int environ_unset_glob (environ_t *env, const char *pattern) { size_t i; if (!env || !pattern) { errno = EINVAL; return -1; } for (i = 0; i < env->env_count; ) { size_t len = strcspn (env->env_base[i], "="); if (wildmatch (pattern, env->env_base[i], len) == 0) { free (env->env_base[i]); memmove (env->env_base + i, env->env_base + i + 1, (env->env_count - i) * sizeof (env->env_base[0])); env->env_count--; } else i++; } return 0; } static void envop_entry_insert (struct envop_entry **phead, struct envop_entry *op) { struct envop_entry *head = *phead; if (!head) { *phead = op; return; } switch (op->code) { case envop_clear: if (head->code == envop_clear) free (op); else { op->next = head; *phead = op; } break; case envop_keep: { struct envop_entry *prev = NULL; while (head && head->code <= op->code) { prev = head; head = prev->next; } op->next = head; if (prev) prev->next = op; else *phead = op; } break; default: while (head && head->next) head = head->next; head->next = op; } } static int valid_envar_name (char const *name) { if (!name) return 0; if (!(isalpha (*name) || *name == '_')) return 0; while (*++name) { if (!(isalnum (*name) || *name == '_')) return 0; } return 1; } int envop_entry_add (struct envop_entry **head, enum envop_code code, char const *name, char const *value) { struct envop_entry *op; size_t s; char *p; switch (code) { case envop_clear: break; case envop_set: if (name && !(*name == ':' || valid_envar_name (name))) { errno = EINVAL; return -1; } break; case envop_keep: case envop_unset: break; default: errno = EINVAL; return -1; } s = sizeof (op[0]); if (name) s += strlen (name) + 1; if (value) s += strlen (value) + 1; op = malloc (s); if (!op) return -1; op->next = NULL; op->code = code; op->name = NULL; op->value = NULL; p = (char*)(op + 1); if (name) { op->name = p; strcpy (op->name, name); p += strlen (name) + 1; } if (value) { op->value = p; strcpy (op->value, value); } envop_entry_insert (head, op); return 0; } static int envopmatch (struct envop_entry *op, char const *var, int len) { if (op->value) { if (strncmp (op->name, var, len) == 0) return strcmp (var + len + 1, op->value); } return wildmatch (op->name, var, len); } static int keep_env (char const *var, struct envop_entry *keep) { int len = strcspn (var, "="); for (; keep && keep->code == envop_keep; keep = keep->next) { if (envopmatch (keep, var, len) == 0) return 1; } return 0; } int envop_exec (struct envop_entry *op, environ_t *env) { size_t i; if (op && op->code == envop_clear) { op = op->next; if (op && op->code == envop_keep) { size_t keep_count = 0; for (i = 0; i < env->env_count; i++) { if (keep_env (env->env_base[i], op)) { if (i > keep_count) { env->env_base[keep_count] = env->env_base[i]; env->env_base[i] = NULL; } keep_count++; } else { free (env->env_base[i]); env->env_base[i] = NULL; } } env->env_count = keep_count; } else { size_t i; for (i = 0; i < env->env_count; i++) free (env->env_base[i]); env->env_base[0] = 0; env->env_count = 0; } } /* Process eventual set and unset statements */ for (; op; op = op->next) { switch (op->code) { case envop_set: if (environ_set (env, op->name, op->value)) return -1; break; case envop_unset: if (op->value) environ_unset (env, op->name, op->value); else environ_unset_glob (env, op->name); break; case envop_keep: break; default: abort (); } } return 0; } void envop_free (struct envop_entry *op) { while (op) { struct envop_entry *next = op->next; free (op); op = next; } } int envop_cmp (struct envop_entry *a, struct envop_entry *b) { while (a) { if (!b) return 1; if (a->code != b->code) return 1; switch (a->code) { case envop_clear: break; case envop_keep: case envop_set: case envop_unset: if (!a->name) { if (b->name) return 1; } else if (!b->name || strcmp (a->name, b->name)) return 1; if (!a->value) { if (b->value) return 1; } else if (!b->value || strcmp (a->value, b->value)) return 1; break; } a = a->next; b = b->next; } if (b) return 1; return 0; }