summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org>2019-06-02 04:40:11 (GMT)
committer Sergey Poznyakoff <gray@gnu.org>2019-06-02 05:03:16 (GMT)
commitcc298a11a131b162f291d6ee27ba1d7598a1b6c4 (patch) (side-by-side diff)
tree4d82a9a5da05fbfdce0c9bf03e605ecf00baa07a
parent1f28935841a69aa8be7eb2e88f580df9c95206d2 (diff)
downloadpies-cc298a11a131b162f291d6ee27ba1d7598a1b6c4.tar.gz
pies-cc298a11a131b162f291d6ee27ba1d7598a1b6c4.tar.bz2
New configuration statement for manipulating the environment.
The "env" statement is now a compound statement. It can contain four substatements: "clear" to clear the environment, "keep" to retain certain variable while clearing it, "set" to set a variable, and "unset" to unset a variable or variables. Both "keep" and "unset" can take globbing pattern as their argument, in which case they affect all variable matching that pattern. The value part in the "set" statement is subject to variable expansion, e.g. set "MANPATH=$MANPATH${MANPATH:+:}/usr/local/man" The support for the old one-line syntax of "env" is retained for backward compatibility. This commit also fixes redirection to a file: new data are appended to the file, instead of overwriting it. * lib/Makefile.am: Add new files. * lib/envop.c: New file. * lib/envop.h: New file. * lib/wildmatch.c: New file. * src/comp.c (component_free): Update. * src/pies.c (argv_free): New function. (parse_legacy_env): New function. (_cb_env): Remove. (cb_env_section_parser): New function. (cb_env_keywords): New keywords for the "env" block statement: clear, keep, set, unset. (component_keywords): New compount statement: env. Old usage retained for backward compatibility. * src/pies.h: Include "envop.h" (component)<env>: Remove. <envop>: New field. * src/prog.h (prog)<env>: New field. * src/progman.c (redirect_to_file): Position to the end of file. (DEBUG_ENVIRON): Remove macro. (debug_environ): New function. (environ_setup): Remove. (prog_sockenv): Use environ_set to modify local copy of environment. (prog_start_prologue): Use environ_create + envop_exec to create and modify the environment. (prog_execute): Set environment to prog-local copy. * tests/.gitignore: Build envtest * tests/Makefile.am: Add new tests. * tests/testsuite.at: Add environment tests. * tests/envop.at: New file. * tests/envtest.c: New file. * tests/env.at: New file. * tests/redirect.at: Check first two lines of the output file.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--lib/Makefile.am5
-rw-r--r--lib/envop.c484
-rw-r--r--lib/envop.h68
-rw-r--r--lib/wildmatch.c140
-rw-r--r--src/comp.c16
-rw-r--r--src/pies.c278
-rw-r--r--src/pies.h5
-rw-r--r--src/prog.h1
-rw-r--r--src/progman.c310
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/env.at65
-rw-r--r--tests/envop.at101
-rw-r--r--tests/envtest.c209
-rw-r--r--tests/redirect.at2
-rw-r--r--tests/testsuite.at3
16 files changed, 1410 insertions, 289 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 4aa9d3e..7b4eb42 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -21,6 +21,8 @@ noinst_HEADERS = libpies.h grecsasrt.h
libpies_a_SOURCES=\
addrfmt.c\
arraymember.c\
+ envop.c\
+ envop.h\
grecsasrt.c\
mkfilename.c\
netrc.c\
@@ -30,7 +32,8 @@ libpies_a_SOURCES=\
safe_strcmp.c\
split3.c\
strtotok.c\
- url.c
+ url.c\
+ wildmatch.c
libpies_a_LIBADD=\
$(LIBOBJS)\
diff --git a/lib/envop.c b/lib/envop.c
new file mode 100644
index 0000000..79083f7
--- a/dev/null
+++ b/lib/envop.c
@@ -0,0 +1,484 @@
+/* Environment modification functions for GNU Pies.
+ Copyright (C) 2019 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#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);
+}
+
+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 (!name)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!value)
+ return environ_unset (env, name);
+
+ 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;
+ if (ws.ws_errno != WRDSE_USAGE) /* FIXME */
+ wordsplit_free (&ws);
+ errno = ec;
+ return -1;
+ }
+
+ if (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)
+{
+ ssize_t n;
+
+ if (!env || !name)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ n = getenvind (env, name, NULL);
+ if (n == -1)
+ 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;
+
+ 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;
+ if (name)
+ {
+ op->name = (char*)(op + 1);
+ strcpy (op->name, name);
+ if (value)
+ {
+ op->value = op->name + strlen (name) + 1;
+ 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:
+ 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;
+ }
+}
+
diff --git a/lib/envop.h b/lib/envop.h
new file mode 100644
index 0000000..054152e
--- a/dev/null
+++ b/lib/envop.h
@@ -0,0 +1,68 @@
+/* Environment and environment operation definitions for GNU Pies.
+ Copyright (C) 2019 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 <http://www.gnu.org/licenses/>. */
+
+/* Environment structure */
+struct environ
+{
+ char **env_base;
+ size_t env_count;
+ size_t env_max;
+};
+typedef struct environ environ_t;
+
+environ_t *environ_create (char **);
+void environ_free (environ_t *env);
+int environ_add (environ_t *env, char const *def);
+int environ_set (environ_t *env, char const *name, char const *val);
+int environ_unset (environ_t *env, char const *name);
+int environ_unset_glob (environ_t *env, const char *pattern);
+static inline char **
+environ_ptr (environ_t *env)
+{
+ return env->env_base;
+}
+
+/* Environment operation codes.
+ Elements in a oplist are sorted in that order. */
+enum envop_code
+ {
+ envop_clear, /* Clear environment */
+ envop_keep, /* Keep variable when clearing */
+ envop_set, /* Set variable */
+ envop_unset /* Unset variable */
+ };
+
+struct envop_entry /* Environment operation entry */
+{
+ struct envop_entry *next; /* Next entry in the list */
+ enum envop_code code; /* Operation code */
+ char *name; /* Variable name (or globbing pattern) */
+ char *value; /* Value of the variable */
+};
+
+typedef struct envop_entry envop_t;
+
+int wildmatch (char const *expr, char const *name, size_t len);
+
+int envop_entry_add (envop_t **head,
+ enum envop_code code,
+ char const *name, char const *value);
+
+int envop_exec (envop_t *op, environ_t *env);
+void envop_free (envop_t *op);
+
+
+
diff --git a/lib/wildmatch.c b/lib/wildmatch.c
new file mode 100644
index 0000000..2b4d7c8
--- a/dev/null
+++ b/lib/wildmatch.c
@@ -0,0 +1,140 @@
+/* Environment-specific globbing pattern matching for GNU Pies.
+ Copyright (C) 2019 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdlib.h>
+
+enum
+ {
+ WILD_FALSE = 0,
+ WILD_TRUE,
+ WILD_ABORT
+ };
+
+static int
+match_char_class (char const **pexpr, char c)
+{
+ int res;
+ int rc;
+ char const *expr = *pexpr;
+
+ expr++;
+ if (*expr == '^')
+ {
+ res = 0;
+ expr++;
+ }
+ else
+ res = 1;
+
+ if (*expr == '-' || *expr == ']')
+ rc = c == *expr++;
+ else
+ rc = !res;
+
+ for (; *expr && *expr != ']'; expr++)
+ {
+ if (rc == res)
+ {
+ if (*expr == '\\' && expr[1] == ']')
+ expr++;
+ }
+ else if (expr[1] == '-')
+ {
+ if (*expr == '\\')
+ rc = *++expr == c;
+ else
+ {
+ rc = *expr <= c && c <= expr[2];
+ expr += 2;
+ }
+ }
+ else if (*expr == '\\' && expr[1] == ']')
+ rc = *++expr == c;
+ else
+ rc = *expr == c;
+ }
+ *pexpr = *expr ? expr + 1 : expr;
+ return rc == res;
+}
+
+#define END_OF_NAME(s,l) ((l) == 0 || *(s) == 0)
+#define NEXT_CHAR(s,l) (s++, l--)
+
+int
+wilder_match (char const *expr, char const *name, size_t len)
+{
+ int c;
+
+ while (expr && *expr)
+ {
+ if (END_OF_NAME (name, len) && *expr != '*')
+ return WILD_ABORT;
+ switch (*expr)
+ {
+ case '*':
+ while (*++expr == '*')
+ ;
+ if (*expr == 0)
+ return WILD_TRUE;
+ while (!END_OF_NAME (name, len))
+ {
+ int res = wilder_match (expr, name, len);
+ if (res != WILD_FALSE)
+ return res;
+ NEXT_CHAR (name, len);
+ }
+ return WILD_ABORT;
+
+ case '?':
+ expr++;
+ NEXT_CHAR (name, len);
+ break;
+
+ case '[':
+ if (!match_char_class (&expr, *name))
+ return WILD_FALSE;
+ NEXT_CHAR (name, len);
+ break;
+
+ case '\\':
+ if (expr[1])
+ {
+ c = *++expr; expr++;
+ if (*name != wordsplit_c_unquote_char (c))
+ return WILD_FALSE;
+ NEXT_CHAR (name, len);
+ break;
+ }
+ /* fall through */
+ default:
+ if (*expr != *name)
+ return WILD_FALSE;
+ expr++;
+ NEXT_CHAR (name, len);
+ }
+ }
+ return END_OF_NAME (name, len) ? WILD_TRUE : WILD_FALSE;
+}
+
+/* Return 0 if first LEN bytes of NAME match globbing pattern EXPR. */
+int
+wildmatch (char const *expr, char const *name, size_t len)
+{
+ return wilder_match (expr, name, len) != WILD_TRUE;
+}
diff --git a/src/comp.c b/src/comp.c
index 499dfe5..d030979 100644
--- a/src/comp.c
+++ b/src/comp.c
@@ -166,24 +166,12 @@ component_create (const char *name)
void
component_free (struct component *comp)
{
- size_t i;
-
component_unlink (comp);
free (comp->tag);
free (comp->program);
free (comp->command);
- if (comp->argv)
- {
- for (i = 0; i < comp->argc; i++)
- free (comp->argv[i]);
- free (comp->argv);
- }
- if (comp->env)
- {
- for (i = 0; comp->env[i]; i++)
- free (comp->env[i]);
- free (comp->env);
- }
+ argv_free (comp->argv);
+ envop_free (comp->envop);
free (comp->dir);
grecs_list_free (comp->prereq);
grecs_list_free (comp->depend);
diff --git a/src/pies.c b/src/pies.c
index 6105ae6..39467a2 100644
--- a/src/pies.c
+++ b/src/pies.c
@@ -544,16 +544,127 @@ _cb_umask (enum grecs_callback_command cmd,
*pmode = n;
return 0;
}
+
+void
+argv_free (char **argv)
+{
+ if (argv)
+ {
+ size_t i;
+ for (i = 0; argv[i]; i++)
+ free (argv[i]);
+ free (argv);
+ }
+}
static int
-_cb_env (enum grecs_callback_command cmd,
- grecs_locus_t *locus,
- void *varptr, grecs_value_t *value, void *cb_data)
+parse_legacy_env (char **argv, envop_t **envop)
+{
+ size_t i = 0;
+ int rc;
+ char *name;
+
+ if (strcmp (argv[0], "-") == 0)
+ {
+ rc = envop_entry_add (envop, envop_clear, NULL, NULL);
+ if (rc)
+ return rc;
+ i++;
+ }
+ for (; (name = argv[i]) != NULL; i++)
+ {
+ char *name = argv[i];
+ size_t len = strcspn (name, "=");
+ char *value;
+ char *mem = NULL;
+ size_t msize = 0;
+ enum envop_code code;
+
+ if (name[0] == '-')
+ {
+ /* Unset directive */
+ name++;
+ len--;
+
+ if (name[len])
+ {
+ name[len] = 0;
+ value = name + len + 1;
+ }
+ else
+ value = NULL;
+
+ code = envop_unset;
+ }
+ else if (name[len])
+ {
+ size_t vlen;
+
+ if (len == 0)
+ /* Skip erroneous entry */
+ continue;
+ value = name + len + 1;
+ vlen = strlen (value);
+ name[len] = 0;
+ if (name[len-1] == '+')
+ {
+ name[--len] = 0;
+ if (c_ispunct (value[0]))
+ {
+ msize = 2*len + 9 + vlen + 1;
+ mem = grecs_malloc (msize);
+ snprintf (mem, msize, "${%s:-}${%s+%c}%s",
+ name, name, value[0], value + 1);
+ }
+ else
+ {
+ msize = len + vlen + 6;
+ snprintf (mem, msize, "${%s:-}%s", name, value);
+ }
+ value = mem;
+ }
+ else if (value[0] == '+')
+ {
+ value++;
+ vlen--;
+
+ if (vlen > 0 && c_ispunct (value[vlen-1]))
+ {
+ int c = value[vlen-1];
+ value[--vlen] = 0;
+
+ msize = 2*len + 10 + vlen + 1;
+ mem = grecs_malloc (msize);
+ snprintf (mem, msize, "%s${%s+%c}${%s:-}",
+ value, name, c, name);
+ }
+ else
+ {
+ msize = len + vlen + 6;
+ snprintf (mem, msize, "%s${%s:-}", value, name);
+ }
+ value = mem;
+ }
+ code = envop_set;
+ }
+ else
+ {
+ value = NULL;
+ code = envop_keep;
+ }
+ rc = envop_entry_add (envop, code, name, value);
+ free (mem);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int
+_cb_env (envop_t **envop, grecs_value_t *value, grecs_locus_t *locus)
{
- size_t argc;
char **argv;
- char ***penv = varptr;
- struct wordsplit ws;
+ int rc;
switch (value->type)
{
@@ -572,11 +683,147 @@ _cb_env (enum grecs_callback_command cmd,
return 1;
}
- *penv = argv;
+ rc = parse_legacy_env (argv, envop);
+ argv_free (argv);
+ if (rc)
+ {
+ grecs_error (locus, errno, _("can't parse legacy env statement"));
+ return 1;
+ }
+ return 0;
+}
+
+static int
+cb_env_section_parser (enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr,
+ grecs_value_t *value, void *cb_data)
+{
+ struct component *comp = varptr;
+
+ switch (cmd)
+ {
+ case grecs_callback_section_begin:
+ //FIXME
+ *(struct component **) cb_data = comp;
+ break;
+
+ case grecs_callback_section_end:
+ //FIXME
+ break;
+
+ case grecs_callback_set_value:
+ return _cb_env (&comp->envop, value, locus);
+ }
+ return 0;
+}
+
+static int
+_cb_env_clear (enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr, grecs_value_t *value, void *cb_data)
+{
+ struct component *comp = varptr;
+ int clear;
+
+ if (assert_scalar_stmt (locus, cmd)
+ || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
+ return 1;
+ if (grecs_string_convert(&clear, grecs_type_bool, value->v.string, locus))
+ return 1;
+ if (clear)
+ {
+ if (envop_entry_add (&comp->envop, envop_clear, NULL, NULL))
+ grecs_error (locus, errno, "envop_entry_add");
+ }
return 0;
}
-
+static int
+_cb_env_keep (enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr, grecs_value_t *value, void *cb_data)
+{
+ struct component *comp = varptr;
+ char *p;
+
+ if (assert_scalar_stmt (locus, cmd)
+ || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
+ return 1;
+ p = strchr (value->v.string, '=');
+ if (p)
+ *p++ = 0;
+ if (envop_entry_add (&comp->envop, envop_clear, NULL, NULL))
+ grecs_error (locus, errno, "envop_entry_add");
+ if (envop_entry_add (&comp->envop, envop_keep, value->v.string, p))
+ grecs_error (locus, errno, "envop_entry_add");
+ return 0;
+}
+
+static int
+_cb_env_set (enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr, grecs_value_t *value, void *cb_data)
+{
+ struct component *comp = varptr;
+ char *p;
+
+ if (assert_scalar_stmt (locus, cmd)
+ || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
+ return 1;
+ p = strchr (value->v.string, '=');
+ if (p)
+ *p++ = 0;
+ if (envop_entry_add (&comp->envop, envop_set, value->v.string, p))
+ grecs_error (locus, errno, "envop_entry_add");
+ return 0;
+}
+
+static int
+_cb_env_unset (enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr, grecs_value_t *value, void *cb_data)
+{
+ struct component *comp = varptr;
+
+ if (assert_scalar_stmt (locus, cmd)
+ || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
+ return 1;
+ if (envop_entry_add (&comp->envop, envop_unset, value->v.string, NULL))
+ grecs_error (locus, errno, "envop_entry_add");
+ return 0;
+}
+
+struct grecs_keyword cb_env_keywords[] = {
+ { "clear",
+ N_("bool"),
+ N_("Clear environment."),
+ grecs_type_bool, GRECS_DFLT,
+ NULL, 0,
+ _cb_env_clear },
+ { "keep",
+ N_("name[=value]"),
+ N_("Keep this variable. Unless value is supplied, name can contain wildcards.\n"
+ "Implies \"clear yes\"."),
+ grecs_type_string, GRECS_DFLT,
+ NULL, 0,
+ _cb_env_keep },
+ { "set",
+ N_("name=value"),
+ N_("Set environment variable. Note, that argument must be quoted."),
+ grecs_type_string, GRECS_DFLT,
+ NULL, 0,
+ _cb_env_set },
+ { "unset",
+ N_("name"),
+ N_("Unset environment variable. Name can contain wildcards."),
+ grecs_type_string, GRECS_DFLT,
+ NULL, 0,
+ _cb_env_unset },
+ { NULL }
+};
+
+
int
string_to_syslog_priority (const char *key, int *pres)
{
@@ -1157,12 +1404,19 @@ struct grecs_keyword component_keywords[] = {
_cb_limits,
},
{"env",
+ NULL,
+ N_("Modify program environment."),
+ grecs_type_section, GRECS_DFLT,
+ NULL, 0,
+ cb_env_section_parser, NULL, cb_env_keywords
+ },
+ {"env",
N_("arg: list"),
- N_("Set program environment. Argument is a list of assignments "
- "separated by white space."),
+ N_("Modify program environment (legacy syntax).\n"
+ "Argument is a list of quoted assignments separated by white space."),
grecs_type_string, GRECS_DFLT,
- NULL, offsetof (struct component, env),
- _cb_env,
+ NULL, 0,
+ cb_env_section_parser, NULL, NULL
},
{"chdir",
N_("dir"),
diff --git a/src/pies.h b/src/pies.h
index 2e544e1..4d52ce4 100644
--- a/src/pies.h
+++ b/src/pies.h
@@ -56,6 +56,7 @@
#include "identity.h"
#include "acl.h"
#include "libpies.h"
+#include "envop.h"
#include "grecs/json.h"
#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
@@ -229,7 +230,7 @@ struct component
char *command; /* Full command line */
size_t argc; /* Number of command line arguments */
char **argv; /* Program command line */
- char **env; /* Program environment */
+ envop_t *envop; /* Environment modification program */
char *dir; /* Working directory */
struct grecs_list *prereq; /* Prerequisites */
struct grecs_list *depend; /* Dependency targets */
@@ -333,6 +334,8 @@ void free_redirector (struct redirector *rp);
void pies_schedule_action (int act);
void free_action (struct action *act);
+void argv_free (char **argv);
+
#define PIES_CHLD_NONE 0
#define PIES_CHLD_CLEANUP 0x01
#define PIES_CHLD_WAKEUP 0x02
diff --git a/src/prog.h b/src/prog.h
index 04cf3d0..4069112 100644
--- a/src/prog.h
+++ b/src/prog.h
@@ -58,6 +58,7 @@ struct prog
time_t timestamp; /* Time of last startup */
size_t failcount; /* Number of failed starts since timestamp */
enum prog_status status; /* Current component status */
+ environ_t *env; /* Execution environment (only in child) */
/* If status == status_listener: */
size_t num_instances; /* Number of running instances */
/* If comp->type == pies_comp_inetd && status == status_running */
diff --git a/src/progman.c b/src/progman.c
index 85670f2..b75a2db 100644
--- a/src/progman.c
+++ b/src/progman.c
@@ -390,6 +390,14 @@ redirect_to_file (struct prog *master, int stream)
master->v.p.comp->redir[stream].v.file,
strerror (errno));
}
+ /* Position to the end of file */
+ if (lseek (fd, 0, SEEK_END) == -1)
+ {
+ if (errno != ESPIPE)
+ logmsg (LOG_ERR, "lseek(%s): %s",
+ master->v.p.comp->redir[stream].v.file,
+ strerror (errno));
+ }
return fd;
}
@@ -604,10 +612,6 @@ conn_class_remove (struct conn_class *pcclass)
}
-extern char **environ; /* Environment */
-static size_t envsize; /* Size of environ. If it is not 0, then we
- have allocated space for the environ. */
-
#define ENV_PROTO "PROTO"
#define ENV_SOCKTYPE "SOCKTYPE"
#define ENV_LOCALIP "LOCALIP"
@@ -616,254 +620,30 @@ static size_t envsize; /* Size of environ. If it is not 0, then we
#define ENV_REMOTEIP "REMOTEIP"
#define ENV_REMOTEPORT "REMOTEPORT"
#define ENV_REMOTEHOST "REMOTEHOST"
-static char *sockenv_hint[] = {
- "-" ENV_PROTO,
- "-" ENV_SOCKTYPE,
- "-" ENV_LOCALIP,
- "-" ENV_LOCALPORT,
- "-" ENV_LOCALHOST,
- "-" ENV_REMOTEIP,
- "-" ENV_REMOTEPORT,
- "-" ENV_REMOTEHOST,
+static char *sockenv_var[] = {
+ ENV_PROTO,
+ ENV_SOCKTYPE,
+ ENV_LOCALIP,
+ ENV_LOCALPORT,
+ ENV_LOCALHOST,
+ ENV_REMOTEIP,
+ ENV_REMOTEPORT,
+ ENV_REMOTEHOST,
NULL
};
-#define DEBUG_ENVIRON(l) \
- do \
- if (debug_level >= (l)) \
- { \
- int i; \
- logmsg_printf (LOG_DEBUG, "environment: "); \
- for (i = 0; environ[i]; i++) \
- logmsg_printf (LOG_DEBUG, "%s ", environ[i]); \
- logmsg_printf (LOG_DEBUG, "\n"); \
- } \
- while (0)
-
-static void
-add_env (const char *name, const char *value)
-{
- size_t i;
- size_t namelen = strlen (name);
- char *p;
-
- for (i = 0; environ[i]; i++)
- {
- if (!strncmp (environ[i], name, namelen) && environ[i][namelen] == '=')
- break;
- }
-
- if (environ[i] == NULL)
- {
- if (envsize == 0)
- {
- char **new_env;
-
- envsize = i + 4;
- new_env = grecs_calloc (envsize, sizeof new_env[0]);
- for (i = 0; (new_env[i] = environ[i]); i++)
- ;
- environ = new_env;
- }
- else if (i == envsize)
- {
- envsize += 4;
- environ = grecs_realloc (environ,
- envsize * sizeof environ[0]);
- }
- environ[i+1] = NULL;
- }
-
- if (!value)
- value = "";
- p = grecs_malloc (namelen + 1 + strlen (value) + 1);
- strcpy (p, name);
- p[namelen] = '=';
- strcpy (p + namelen + 1, value);
- environ[i] = p;
-}
-
-/* Find variable NAME in environment ENV.
- On success, store the index of the ENV slot in *IDX,
- the offset of the value (position right past '=') in *VALOFF, and
- return 0 (IDX and/or VALOFF can be NULL, if that info is not needed).
- Return -1 if NAME was not found. */
-static int
-find_env_pos (char **env, char *name, size_t *idx, size_t *valoff)
+static inline void
+debug_environ (struct prog *prog, int l)
{
- size_t nlen = strcspn (name, "+=");
- size_t i;
-
- for (i = 0; env[i]; i++)
+ if (debug_level >= l)
{
- size_t elen = strcspn (env[i], "=");
- if (elen == nlen && memcmp (name, env[i], nlen) == 0)
- {
- if (idx)
- *idx = i;
- if (valoff)
- *valoff = elen + 1;
- return 0;
- }
- }
- return -1;
-}
-
-/* Find variable NAME in environment ENV.
- On success, return pointer to the variable assignment (if VAL is 0),
- or to the value (if VAL is 1).
- Return NULL if NAME is not present in ENV. */
-static char *
-find_env_ptr (char **env, char *name, int val)
-{
- size_t i, j;
- if (find_env_pos (env, name, &i, &j))
- return NULL;
- return val ? env[i] + j : env[i];
-}
-
-/* Return 1 if ENV contains a matching unset statement for variable NAME. */
-static int
-var_is_unset (char **env, const char *name)
-{
- int i;
- int nlen = strcspn (name, "=");
-
- for (i = 0; env[i]; i++)
- {
- if (env[i][0] == '-')
- {
- size_t elen = strcspn (env[i] + 1, "=");
- if (elen == nlen && memcmp (name, env[i] + 1, nlen) == 0)
- {
- if (env[i][nlen + 1])
- return strcmp (name + nlen, env[i] + 1 + nlen) == 0;
- else
- return 1;
- }
- }
- }
- return 0;
-}
-
-static char *
-env_concat (const char *name, size_t namelen, const char *a, const char *b)
-{
- char *res;
- size_t len;
-
- if (a && b)
- {
- res = grecs_malloc (namelen + 1 + strlen (a) + strlen (b) + 1);
- strcpy (res + namelen + 1, a);
- strcat (res + namelen + 1, b);
- }
- else if (a)
- {
- len = strlen (a);
- if (c_ispunct (a[len-1]))
- len--;
- res = grecs_malloc (namelen + 1 + len + 1);
- memcpy (res + namelen + 1, a, len);
- res[namelen + 1 + len] = 0;
- }
- else /* if (a == NULL) */
- {
- if (c_ispunct (b[0]))
- b++;
- len = strlen (b);
- res = grecs_malloc (namelen + 1 + len + 1);
- strcpy (res + namelen + 1, b);
- }
- memcpy (res, name, namelen);
- res[namelen] = '=';
- return res;
-}
-
-static void
-environ_setup (char **hint)
-{
- char **old_env = environ;
- char **new_env;
- size_t count, i, j, n;
-
- if (!hint)
- return;
-
- if (strcmp (hint[0], "-") == 0)
- {
- old_env = NULL;
- hint++;
- }
-
- /* Count new environment size */
- count = 0;
- if (old_env)
- for (i = 0; old_env[i]; i++)
- count++;
-
- for (i = 0; hint[i]; i++)
- count++;
-
- /* Allocate new environment. */
- new_env = grecs_calloc (count + 1, sizeof new_env[0]);
-
- /* Populate the environment. */
- n = 0;
-
- if (old_env)
- for (i = 0; old_env[i]; i++)
- {
- if (!var_is_unset (hint, old_env[i]))
- new_env[n++] = old_env[i];
- }
-
- for (i = 0; hint[i]; i++)
- {
- char *p;
-
- if (hint[i][0] == '-')
- {
- /* Skip unset directives. */
- continue;
- }
-
- /* Find the slot for the variable. Use next available
- slot if there's no such variable in new_env */
- if (find_env_pos (new_env, hint[i], &j, NULL))
- j = n;
-
- if ((p = strchr (hint[i], '=')))
- {
- if (p == hint[i])
- continue; /* Ignore erroneous entry */
- if (p[-1] == '+')
- new_env[j] = env_concat (hint[i], p - hint[i] - 1,
- find_env_ptr (environ, hint[i], 1),
- p + 1);
- else if (p[1] == '+')
- new_env[j] = env_concat (hint[i], p - hint[i],
- p + 2,
- find_env_ptr (environ, hint[i], 1));
- else
- new_env[j] = hint[i];
- }
- else if ((p = find_env_ptr (environ, hint[i], 0)))
- new_env[j] = p;
- else
- continue;
-
- /* Adjust environment size */
- if (j == n)
- ++n;
+ int i;
+ char **env = environ_ptr (prog->v.p.env);
+ logmsg_printf (LOG_DEBUG, "environment: ");
+ for (i = 0; env[i]; i++)
+ logmsg_printf (LOG_DEBUG, "%s ", env[i]);
+ logmsg_printf (LOG_DEBUG, "\n");
}
- new_env[n] = NULL;
-
- if (envsize)
- free (environ);
-
- environ = new_env;
- envsize = count + 1;
}
/* Pass socket information in environment variables. */
@@ -884,7 +664,7 @@ prog_sockenv (struct prog *prog)
if (socket_type_to_str (prog->v.p.comp->socket_type, &proto))
proto = umaxtostr (prog->v.p.comp->socket_type, buf);
- add_env (ENV_SOCKTYPE, proto);
+ environ_set (prog->v.p.env, ENV_SOCKTYPE, proto);
if (prog->v.p.comp->socket_url)
proto = prog->v.p.comp->socket_url->proto_s;
@@ -893,7 +673,7 @@ prog_sockenv (struct prog *prog)
else if (ISCF_TCPMUX (prog->v.p.comp->flags))
proto = "TCP";
- add_env (ENV_PROTO, proto);
+ environ_set (prog->v.p.env, ENV_PROTO, proto);
if (getsockname (prog->v.p.socket,
(struct sockaddr *) &sa_server, &len) < 0)
@@ -902,10 +682,10 @@ prog_sockenv (struct prog *prog)
{
p = inet_ntoa (sa_server.s_in.sin_addr);
if (p)
- add_env (ENV_LOCALIP, p);
+ environ_set (prog->v.p.env, ENV_LOCALIP, p);
p = umaxtostr (ntohs (sa_server.s_in.sin_port), buf);
- add_env (ENV_LOCALPORT, p);
+ environ_set (prog->v.p.env, ENV_LOCALPORT, p);
if (prog->v.p.comp->flags & CF_RESOLVE)
{
@@ -914,7 +694,7 @@ prog_sockenv (struct prog *prog)
AF_INET)) == NULL)
logmsg (LOG_WARNING, "gethostbyaddr: %s", strerror (errno));
else
- add_env (ENV_LOCALHOST, host->h_name);
+ environ_set (prog->v.p.env, ENV_LOCALHOST, host->h_name);
}
}
@@ -922,10 +702,10 @@ prog_sockenv (struct prog *prog)
{
p = inet_ntoa (sa_client->s_in.sin_addr);
if (p)
- add_env (ENV_REMOTEIP, p);
+ environ_set (prog->v.p.env, ENV_REMOTEIP, p);
p = umaxtostr (ntohs (sa_client->s_in.sin_port), buf);
- add_env (ENV_REMOTEPORT, p);
+ environ_set (prog->v.p.env, ENV_REMOTEPORT, p);
if (prog->v.p.comp->flags & CF_RESOLVE)
{
@@ -935,11 +715,11 @@ prog_sockenv (struct prog *prog)
logmsg (LOG_WARNING, "gethostbyaddr: %s",
strerror (errno));
else
- add_env (ENV_REMOTEHOST, host->h_name);
+ environ_set (prog->v.p.env, ENV_REMOTEHOST, host->h_name);
}
}
/* FIXME: $REMOTEINFO ? */
- DEBUG_ENVIRON (4);
+ debug_environ (prog, 4);
}
static int
@@ -1036,12 +816,21 @@ prog_start_prologue (struct prog *prog)
prog_tag (prog), prog->v.p.comp->dir, strerror (errno));
}
- environ_setup (prog->v.p.comp->env ?
- prog->v.p.comp->env :
- ((prog->v.p.comp->flags & CF_SOCKENV) ? sockenv_hint : NULL));
+ prog->v.p.env = environ_create (environ);
+ if (prog->v.p.comp->flags & CF_SOCKENV)
+ {
+ size_t i;
+ for (i = 0; sockenv_var[i]; i++)
+ environ_unset (prog->v.p.env, sockenv_var[i]);
+ }
+ envop_exec (prog->v.p.comp->envop, prog->v.p.env);
if (init_process)
- environ_setup (sysvinit_environ_hint);
- DEBUG_ENVIRON (4);
+ {
+ size_t i;
+ for (i = 0; sysvinit_environ_hint[i]; i++)
+ environ_add (prog->v.p.env, sysvinit_environ_hint[i]);
+ }
+ debug_environ (prog, 4);
pies_priv_setup (&prog->v.p.comp->privs);
if (prog->v.p.comp->umask)
@@ -1066,6 +855,7 @@ prog_start_prologue (struct prog *prog)
static void
prog_execute (struct prog *prog)
{
+ environ = environ_ptr (prog->v.p.env);
if (prog->v.p.comp->builtin)
{
mf_proctitle_format ("inetd %s",
diff --git a/tests/.gitignore b/tests/.gitignore
index 93f8f46..276645a 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,5 +1,6 @@
atconfig
atlocal
+envtest
package.m4
testsuite
testsuite.dir
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 39899c8..419818b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -48,6 +48,8 @@ TESTSUITE_AT = \
testsuite.at\
control.at\
cyclic.at\
+ env.at\
+ envop.at\
respawn.at\
redirect.at\
ret-exec.at\
@@ -60,6 +62,15 @@ TESTSUITE_AT = \
TESTSUITE = $(srcdir)/testsuite
M4=m4
+noinst_PROGRAMS = envtest
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/lib\
+ @GRECS_INCLUDES@
+
+LDADD = \
+ ../lib/libpies.a\
+ @GRECS_LDADD@
+
AUTOTEST = $(AUTOM4TE) --language=autotest
$(TESTSUITE): package.m4 $(TESTSUITE_AT)
$(AM_V_GEN)$(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
diff --git a/tests/env.at b/tests/env.at
new file mode 100644
index 0000000..26fe866
--- a/dev/null
+++ b/tests/env.at
@@ -0,0 +1,65 @@
+dnl ENVTEST(NAME,KW,ENV,OUT)
+m4_pushdef([ENVTEST],
+[AT_SETUP([$1])
+AT_KEYWORDS([env $2])
+AT_CHECK([
+PIES_XFAIL_CHECK
+PIES_CONTROL_INIT
+cat > envtest.conf <<_EOT
+component envtest {
+ env {
+ $3
+ }
+ command "$abs_builddir/envtest -clone";
+ chdir $PWD;
+ stdout file "$PWD/log";
+ return-code 0 {
+ action disable;
+ exec "piesctl --url unix:///$PWD/pies.ctl shutdown";
+ }
+}
+_EOT
+
+envtest -exec $abs_top_builddir/src/pies --foreground --stderr --config-file control.conf --config-file envtest.conf 2>errlog
+cat log
+],
+[0],
+[$4])
+AT_CLEANUP])
+
+dnl #############################
+dnl Start tests
+dnl #############################
+
+AT_BANNER([Environment statement])
+
+ENVTEST([clear],[clear],[clear yes;],[])
+
+ENVTEST([keep],[keep],[keep "LC_*";],
+[LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+])
+
+ENVTEST([set],[set],[set "FOO=bar";],
+[FOO="bar"
+HOME="/home/user"
+LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin"
+PIES_INSTANCE="pies"
+PWD="/home"
+USER="user"
+])
+
+ENVTEST([unset],[unset],[unset "LC_*"; unset PWD;],
+[HOME="/home/user"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin"
+PIES_INSTANCE="pies"
+USER="user"
+])
diff --git a/tests/envop.at b/tests/envop.at
new file mode 100644
index 0000000..6f634fb
--- a/dev/null
+++ b/tests/envop.at
@@ -0,0 +1,101 @@
+# ENVTEST(name,kw,args,output)
+m4_pushdef([ENVTEST],
+[AT_SETUP([$1])
+AT_KEYWORDS([envop $2])
+AT_CHECK([envtest $3],
+[0],
+[$4])
+AT_CLEANUP
+])
+dnl #############################
+dnl Start tests
+dnl #############################
+
+AT_BANNER([Environment modification framework])
+
+ENVTEST([default environment],[defenv],[],
+[HOME="/home/user"
+LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin"
+PWD="/home"
+USER="user"
+])
+
+ENVTEST([clear],[clear],[-clear])
+
+ENVTEST([keep name],[keep],[-clear -keep HOME USER],
+[HOME="/home/user"
+USER="user"
+])
+
+ENVTEST([keep name=value],[keep],[-clear -keep USER=user],
+[USER="user"
+])
+
+ENVTEST([keep name=value (mismatch)],[keep],[-clear -keep USER=gray])
+
+ENVTEST([keep wildcard],[keep],[-clear -keep 'LC_*'],
+[LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+])
+
+ENVTEST([keep wildcard (2)],[keep],[-clear -keep 'LC_*A*'],
+[LC_ALL="C"
+LC_MESSAGES="C"
+])
+
+ENVTEST([keep wildcard (mismatch)],[keep],[-clear -keep 'LC_*A*R'])
+
+ENVTEST([set],[set],[-set FOO=bar BAR=bar],
+[BAR="bar"
+FOO="bar"
+HOME="/home/user"
+LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin"
+PWD="/home"
+USER="user"
+])
+
+ENVTEST([set (variable expansion)],[set],[-set 'PATH=${PATH}${PATH:+:}$HOME'],
+[HOME="/home/user"
+LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin:/home/user"
+PWD="/home"
+USER="user"
+])
+
+ENVTEST([unset name],[unset],[-unset HOME],
+[LC_ALL="C"
+LC_CTYPE="C"
+LC_MESSAGES="C"
+LC_NUMERIC="C"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin"
+PWD="/home"
+USER="user"
+])
+
+ENVTEST([unset wildcard],[unset],[-unset 'LC_*'],
+[HOME="/home/user"
+LOGIN="user"
+PATH="/usr/local/bin:/usr/bin:/bin"
+PWD="/home"
+USER="user"
+])
+
+m4_popdef([ENVTEST])
+
diff --git a/tests/envtest.c b/tests/envtest.c
new file mode 100644
index 0000000..d6b5650
--- a/dev/null
+++ b/tests/envtest.c
@@ -0,0 +1,209 @@
+/* Environment test program for GNU Pies.
+ Copyright (C) 2019 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <envop.h>
+#include <wordsplit.h>
+
+static int
+envcmp (void const *a, void const *b)
+{
+ char const *as = *(char const **)a;
+ char const *bs = *(char const **)b;
+ int c;
+
+ while (*as && *bs)
+ {
+ c = *as - *bs;
+ if (c || *as == '=' || *bs == '=')
+ break;
+ as++;
+ bs++;
+ }
+ return c;
+}
+
+static void
+sortenv (char **env)
+{
+ size_t n;
+ for (n = 0; env[n]; n++)
+ ;
+ qsort (env, n, sizeof (env[0]), envcmp);
+}
+
+static void
+printenv (char **env)
+{
+ size_t i;
+ for (i = 0; env[i]; i++)
+ {
+ char *p = env[i];
+ while (*p)
+ {
+ fputc (*p, stdout);
+ if (*p++ == '=')
+ break;
+ }
+ if (*p)
+ {
+ int c;
+ fputc ('"', stdout);
+ while ((c = *p++) != 0)
+ {
+ int c1;
+ if (isascii (c) && isprint (c) && c != '\\' && c != '"')
+ fputc (c, stdout);
+ else if ((c1 = wordsplit_c_quote_char (c)))
+ {
+ fputc ('\\', stdout);
+ fputc (c1, stdout);
+ }
+ }
+ fputc ('"', stdout);
+ }
+ fputc ('\n', stdout);
+ }
+}
+
+char *defenv[] = {
+ "PATH=/usr/local/bin:/usr/bin:/bin",
+ "HOME=/home/user",
+ "USER=user",
+ "LOGIN=user",
+ "PWD=/home",
+ "LC_ALL=C",
+ "LC_CTYPE=C",
+ "LC_MESSAGES=C",
+ "LC_NUMERIC=C",
+ NULL
+};
+
+extern char **environ;
+
+int
+main (int argc, char **argv)
+{
+ envop_t *envop = NULL;
+ int opcode = envop_set;
+ environ_t *env = NULL;
+
+ if (argc > 1)
+ {
+ if (strcmp (argv[1], "-clone") == 0)
+ {
+ env = environ_create (environ);
+ argc--;
+ argv++;
+ }
+ else if (strcmp (argv[1], "-null") == 0)
+ {
+ env = environ_create (NULL);
+ argc--;
+ argv++;
+ }
+ }
+
+ if (!env)
+ env = environ_create (defenv);
+
+ if (!env)
+ {
+ perror ("environ_create");
+ abort ();
+ }
+
+ while (--argc)
+ {
+ char *a = *++argv;
+
+ if (strcmp (a, "-set") == 0)
+ opcode = envop_set;
+ else if (strcmp (a, "-unset") == 0)
+ opcode = envop_unset;
+ else if (strcmp (a, "-keep") == 0)
+ opcode = envop_keep;
+ else if (strcmp (a, "-clear") == 0)
+ {
+ int rc = envop_entry_add (&envop, envop_clear, NULL, NULL);
+ if (rc)
+ {
+ perror ("envop_entry_add");
+ return 1;
+ }
+ }
+ else if (strcmp (a, "-exec") == 0)
+ {
+ --argc;
+ ++argv;
+ if (argc == 0)
+ {
+ fprintf (stderr, "program name required after -exec\n");
+ return 1;
+ }
+ break;
+ }
+ else if (a[0] == '-')
+ {
+ fprintf (stderr, "unrecognized option: %s\n", a);
+ return 1;
+ }
+ else
+ {
+ int rc;
+ char *p = strchr (a, '=');
+
+ if (p)
+ *p++ = 0;
+ rc = envop_entry_add (&envop, opcode, a, p);
+
+ if (rc)
+ {
+ perror ("envop_entry_add");
+ return 1;
+ }
+ }
+ }
+
+ if (envop_exec (envop, env))
+ {
+ perror ("envop_exec");
+ return 1;
+ }
+
+ if (argc)
+ {
+ environ = environ_ptr (env);
+ execvp (argv[0], argv);
+ perror ("execvp");
+ abort ();
+ }
+
+ sortenv (env->env_base);
+ printenv (env->env_base);
+ return 0;
+}
+
+
+
diff --git a/tests/redirect.at b/tests/redirect.at
index 3a8cca7..7860736 100644
--- a/tests/redirect.at
+++ b/tests/redirect.at
@@ -50,7 +50,7 @@ done
PIES_STOP
-cat $outfile
+head -2 $outfile
],
[0],
[respawn: start
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 28d1f9d..e340a2b 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -68,3 +68,6 @@ m4_include([ret-notify.at])
m4_include([startup.at])
m4_include([shutdown.at])
m4_include([shell.at])
+
+m4_include([envop.at])
+m4_include([env.at]) \ No newline at end of file

Return to:

Send suggestions and report system problems to the System administrator.