summaryrefslogtreecommitdiffabout
Side-by-side diff
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.