chiark / gitweb /
Prep v233: Add missing files from upstream and rename formats-util.[hc]
authorSven Eden <yamakuzure@gmx.net>
Mon, 17 Jul 2017 13:03:10 +0000 (15:03 +0200)
committerSven Eden <yamakuzure@gmx.net>
Mon, 17 Jul 2017 15:58:43 +0000 (17:58 +0200)
Some functionality has been exported to the following files:

 - src/basic/env-util.[hc]
 - src/basic/exec-util.[hc]
 - src/shared/nsflags.[hc]

The content of these files is now needed in elogind, and the files have been
added as-is. Cleanup is done later.

Further the header
 src/basic/formats-util.h
has been renamed to
 src/basic/format-util.h

Makefile.am
cb/elogind.cbp
src/basic/env-util.c [new file with mode: 0644]
src/basic/env-util.h [new file with mode: 0644]
src/basic/exec-util.c [new file with mode: 0644]
src/basic/exec-util.h [new file with mode: 0644]
src/basic/format-util.h [moved from src/basic/formats-util.h with 100% similarity]
src/shared/nsflags.c [new file with mode: 0644]
src/shared/nsflags.h [new file with mode: 0644]

index 0117401..9340eaf 100644 (file)
@@ -133,6 +133,7 @@ AM_CPPFLAGS = \
        -DPKGSYSCONFDIR=\"$(pkgsysconfdir)\" \
        -DSYSTEMD_CGROUP_CONTROLLER=\"_$(CGROUP_CONTROLLER)\" \
        -DSYSTEMD_CGROUP_CONTROLLER_LEGACY=\"name=$(CGROUP_CONTROLLER)\" \
+       -DSYSTEMD_CGROUP_CONTROLLER_HYBRID=\"name=$(CGROUP_CONTROLLER)\" \
        -DSYSTEMD_CGROUP_AGENT_PATH=\"$(rootlibexecdir)/elogind-cgroups-agent\" \
        -DUDEVLIBEXECDIR=\"$(udevlibexecdir)\" \
        -DPOLKIT_AGENT_BINARY_PATH=\"$(PKTTYAGENT)\" \
@@ -363,10 +364,14 @@ libbasic_la_SOURCES = \
        src/basic/prioq.h \
        src/basic/strv.c \
        src/basic/strv.h \
+       src/basic/env-util.c \
+       src/basic/env-util.h \
        src/basic/log.c \
        src/basic/log.h \
        src/basic/bus-label.c \
        src/basic/bus-label.h \
+       src/basic/exec-util.c \
+       src/basic/exec-util.h \
        src/basic/virt.c \
        src/basic/virt.h \
        src/basic/smack-util.c \
@@ -411,7 +416,10 @@ libbasic_la_SOURCES = \
        src/basic/copy.c \
        src/basic/copy.h \
        src/basic/alloc-util.h \
-       src/basic/alloc-util.c
+       src/basic/alloc-util.c \
+       src/basic/format-util.h \
+       src/basic/khash.h \
+       src/basic/khash.c
 
 nodist_libbasic_la_SOURCES = \
        src/basic/errno-from-name.h \
@@ -450,7 +458,9 @@ libshared_la_SOURCES = \
        src/shared/sleep-config.c \
        src/shared/sleep-config.h \
        src/shared/spawn-polkit-agent.c \
-       src/shared/spawn-polkit-agent.h
+       src/shared/spawn-polkit-agent.h \
+       src/shared/nsflags.h \
+       src/shared/nsflags.c
 
 if HAVE_ACL
 libshared_la_SOURCES += \
index 90317ed..017254a 100644 (file)
                        <Option compilerVar="CC" />
                </Unit>
                <Unit filename="../src/basic/dirent-util.h" />
+               <Unit filename="../src/basic/env-util.c">
+                       <Option compilerVar="CC" />
+               </Unit>
+               <Unit filename="../src/basic/env-util.h" />
                <Unit filename="../src/basic/errno-list.c">
                        <Option compilerVar="CC" />
                </Unit>
                        <Option compilerVar="CC" />
                </Unit>
                <Unit filename="../src/basic/escape.h" />
+               <Unit filename="../src/basic/exec-util.c">
+                       <Option compilerVar="CC" />
+               </Unit>
+               <Unit filename="../src/basic/exec-util.h" />
                <Unit filename="../src/basic/extract-word.c">
                        <Option compilerVar="CC" />
                </Unit>
                        <Option compilerVar="CC" />
                </Unit>
                <Unit filename="../src/basic/fileio.h" />
-               <Unit filename="../src/basic/formats-util.h" />
+               <Unit filename="../src/basic/format-util.h" />
                <Unit filename="../src/basic/fs-util.c">
                        <Option compilerVar="CC" />
                </Unit>
                        <Option compilerVar="CC" />
                </Unit>
                <Unit filename="../src/basic/io-util.h" />
+               <Unit filename="../src/basic/khash.c">
+                       <Option compilerVar="CC" />
+               </Unit>
+               <Unit filename="../src/basic/khash.h" />
                <Unit filename="../src/basic/label.c">
                        <Option compilerVar="CC" />
                </Unit>
                        <Option compilerVar="CC" />
                </Unit>
                <Unit filename="../src/shared/musl_missing.h" />
+               <Unit filename="../src/shared/nsflags.c">
+                       <Option compilerVar="CC" />
+               </Unit>
+               <Unit filename="../src/shared/nsflags.h" />
                <Unit filename="../src/shared/pager.c">
                        <Option compilerVar="CC" />
                </Unit>
diff --git a/src/basic/env-util.c b/src/basic/env-util.c
new file mode 100644 (file)
index 0000000..1ec574e
--- /dev/null
@@ -0,0 +1,806 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "env-util.h"
+#include "escape.h"
+#include "extract-word.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+#define VALID_CHARS_ENV_NAME                    \
+        DIGITS LETTERS                          \
+        "_"
+
+#ifndef ARG_MAX
+#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX))
+#endif
+
+static bool env_name_is_valid_n(const char *e, size_t n) {
+        const char *p;
+
+        if (!e)
+                return false;
+
+        if (n <= 0)
+                return false;
+
+        if (e[0] >= '0' && e[0] <= '9')
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, an individual assignment hence cannot be
+         * either. Discounting the equal sign and trailing NUL this
+         * hence leaves ARG_MAX-2 as longest possible variable
+         * name. */
+        if (n > ARG_MAX - 2)
+                return false;
+
+        for (p = e; p < e + n; p++)
+                if (!strchr(VALID_CHARS_ENV_NAME, *p))
+                        return false;
+
+        return true;
+}
+
+bool env_name_is_valid(const char *e) {
+        if (!e)
+                return false;
+
+        return env_name_is_valid_n(e, strlen(e));
+}
+
+bool env_value_is_valid(const char *e) {
+        if (!e)
+                return false;
+
+        if (!utf8_is_valid(e))
+                return false;
+
+        /* bash allows tabs in environment variables, and so should
+         * we */
+        if (string_has_cc(e, "\t"))
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, an individual assignment hence cannot be
+         * either. Discounting the shortest possible variable name of
+         * length 1, the equal sign and trailing NUL this hence leaves
+         * ARG_MAX-3 as longest possible variable value. */
+        if (strlen(e) > ARG_MAX - 3)
+                return false;
+
+        return true;
+}
+
+bool env_assignment_is_valid(const char *e) {
+        const char *eq;
+
+        eq = strchr(e, '=');
+        if (!eq)
+                return false;
+
+        if (!env_name_is_valid_n(e, eq - e))
+                return false;
+
+        if (!env_value_is_valid(eq + 1))
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, hence the individual variable assignments
+         * cannot be either, but let's leave room for one trailing NUL
+         * byte. */
+        if (strlen(e) > ARG_MAX - 1)
+                return false;
+
+        return true;
+}
+
+bool strv_env_is_valid(char **e) {
+        char **p, **q;
+
+        STRV_FOREACH(p, e) {
+                size_t k;
+
+                if (!env_assignment_is_valid(*p))
+                        return false;
+
+                /* Check if there are duplicate assginments */
+                k = strcspn(*p, "=");
+                STRV_FOREACH(q, p + 1)
+                        if (strneq(*p, *q, k) && (*q)[k] == '=')
+                                return false;
+        }
+
+        return true;
+}
+
+bool strv_env_name_is_valid(char **l) {
+        char **p, **q;
+
+        STRV_FOREACH(p, l) {
+                if (!env_name_is_valid(*p))
+                        return false;
+
+                STRV_FOREACH(q, p + 1)
+                        if (streq(*p, *q))
+                                return false;
+        }
+
+        return true;
+}
+
+bool strv_env_name_or_assignment_is_valid(char **l) {
+        char **p, **q;
+
+        STRV_FOREACH(p, l) {
+                if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
+                        return false;
+
+                STRV_FOREACH(q, p + 1)
+                        if (streq(*p, *q))
+                                return false;
+        }
+
+        return true;
+}
+
+static int env_append(char **r, char ***k, char **a) {
+        assert(r);
+        assert(k);
+
+        if (!a)
+                return 0;
+
+        /* Add the entries of a to *k unless they already exist in *r
+         * in which case they are overridden instead. This assumes
+         * there is enough space in the r array. */
+
+        for (; *a; a++) {
+                char **j;
+                size_t n;
+
+                n = strcspn(*a, "=");
+
+                if ((*a)[n] == '=')
+                        n++;
+
+                for (j = r; j < *k; j++)
+                        if (strneq(*j, *a, n))
+                                break;
+
+                if (j >= *k)
+                        (*k)++;
+                else
+                        free(*j);
+
+                *j = strdup(*a);
+                if (!*j)
+                        return -ENOMEM;
+        }
+
+        return 0;
+}
+
+char **strv_env_merge(unsigned n_lists, ...) {
+        size_t n = 0;
+        char **l, **k, **r;
+        va_list ap;
+        unsigned i;
+
+        /* Merges an arbitrary number of environment sets */
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                n += strv_length(l);
+        }
+        va_end(ap);
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        k = r;
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                if (env_append(r, &k, l) < 0)
+                        goto fail;
+        }
+        va_end(ap);
+
+        *k = NULL;
+
+        return r;
+
+fail:
+        va_end(ap);
+        strv_free(r);
+
+        return NULL;
+}
+
+static bool env_match(const char *t, const char *pattern) {
+        assert(t);
+        assert(pattern);
+
+        /* pattern a matches string a
+         *         a matches a=
+         *         a matches a=b
+         *         a= matches a=
+         *         a=b matches a=b
+         *         a= does not match a
+         *         a=b does not match a=
+         *         a=b does not match a
+         *         a=b does not match a=c */
+
+        if (streq(t, pattern))
+                return true;
+
+        if (!strchr(pattern, '=')) {
+                size_t l = strlen(pattern);
+
+                return strneq(t, pattern, l) && t[l] == '=';
+        }
+
+        return false;
+}
+
+static bool env_entry_has_name(const char *entry, const char *name) {
+        const char *t;
+
+        assert(entry);
+        assert(name);
+
+        t = startswith(entry, name);
+        if (!t)
+                return false;
+
+        return *t == '=';
+}
+
+char **strv_env_delete(char **x, unsigned n_lists, ...) {
+        size_t n, i = 0;
+        char **k, **r;
+        va_list ap;
+
+        /* Deletes every entry from x that is mentioned in the other
+         * string lists */
+
+        n = strv_length(x);
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        STRV_FOREACH(k, x) {
+                unsigned v;
+
+                va_start(ap, n_lists);
+                for (v = 0; v < n_lists; v++) {
+                        char **l, **j;
+
+                        l = va_arg(ap, char**);
+                        STRV_FOREACH(j, l)
+                                if (env_match(*k, *j))
+                                        goto skip;
+                }
+                va_end(ap);
+
+                r[i] = strdup(*k);
+                if (!r[i]) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+                i++;
+                continue;
+
+        skip:
+                va_end(ap);
+        }
+
+        r[i] = NULL;
+
+        assert(i <= n);
+
+        return r;
+}
+
+char **strv_env_unset(char **l, const char *p) {
+
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        assert(p);
+
+        /* Drops every occurrence of the env var setting p in the
+         * string list. Edits in-place. */
+
+        for (f = t = l; *f; f++) {
+
+                if (env_match(*f, p)) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+char **strv_env_unset_many(char **l, ...) {
+
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        /* Like strv_env_unset() but applies many at once. Edits in-place. */
+
+        for (f = t = l; *f; f++) {
+                bool found = false;
+                const char *p;
+                va_list ap;
+
+                va_start(ap, l);
+
+                while ((p = va_arg(ap, const char*))) {
+                        if (env_match(*f, p)) {
+                                found = true;
+                                break;
+                        }
+                }
+
+                va_end(ap);
+
+                if (found) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+int strv_env_replace(char ***l, char *p) {
+        char **f;
+        const char *t, *name;
+
+        assert(p);
+
+        /* Replace first occurrence of the env var or add a new one in the
+         * string list. Drop other occurences. Edits in-place. Does not copy p.
+         * p must be a valid key=value assignment.
+         */
+
+        t = strchr(p, '=');
+        assert(t);
+
+        name = strndupa(p, t - p);
+
+        for (f = *l; f && *f; f++)
+                if (env_entry_has_name(*f, name)) {
+                        free_and_replace(*f, p);
+                        strv_env_unset(f + 1, *f);
+                        return 0;
+                }
+
+        /* We didn't find a match, we need to append p or create a new strv */
+        if (strv_push(l, p) < 0)
+                return -ENOMEM;
+        return 1;
+}
+
+char **strv_env_set(char **x, const char *p) {
+
+        char **k, **r;
+        char* m[2] = { (char*) p, NULL };
+
+        /* Overrides the env var setting of p, returns a new copy */
+
+        r = new(char*, strv_length(x)+2);
+        if (!r)
+                return NULL;
+
+        k = r;
+        if (env_append(r, &k, x) < 0)
+                goto fail;
+
+        if (env_append(r, &k, m) < 0)
+                goto fail;
+
+        *k = NULL;
+
+        return r;
+
+fail:
+        strv_free(r);
+        return NULL;
+}
+
+char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
+        char **i;
+
+        assert(name);
+
+        if (k <= 0)
+                return NULL;
+
+        STRV_FOREACH_BACKWARDS(i, l)
+                if (strneq(*i, name, k) &&
+                    (*i)[k] == '=')
+                        return *i + k + 1;
+
+        if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
+                const char *t;
+
+                t = strndupa(name, k);
+                return getenv(t);
+        };
+
+        return NULL;
+}
+
+char *strv_env_get(char **l, const char *name) {
+        assert(name);
+
+        return strv_env_get_n(l, name, strlen(name), 0);
+}
+
+char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
+        char **p, **q;
+        int k = 0;
+
+        STRV_FOREACH(p, e) {
+                size_t n;
+                bool duplicate = false;
+
+                if (!env_assignment_is_valid(*p)) {
+                        if (invalid_callback)
+                                invalid_callback(*p, userdata);
+                        free(*p);
+                        continue;
+                }
+
+                n = strcspn(*p, "=");
+                STRV_FOREACH(q, p + 1)
+                        if (strneq(*p, *q, n) && (*q)[n] == '=') {
+                                duplicate = true;
+                                break;
+                        }
+
+                if (duplicate) {
+                        free(*p);
+                        continue;
+                }
+
+                e[k++] = *p;
+        }
+
+        if (e)
+                e[k] = NULL;
+
+        return e;
+}
+
+char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
+        enum {
+                WORD,
+                CURLY,
+                VARIABLE,
+                VARIABLE_RAW,
+                TEST,
+                DEFAULT_VALUE,
+                ALTERNATE_VALUE,
+        } state = WORD;
+
+        const char *e, *word = format, *test_value;
+        char *k;
+        _cleanup_free_ char *r = NULL;
+        size_t i, len;
+        int nest = 0;
+
+        assert(format);
+
+        for (e = format, i = 0; *e && i < n; e ++, i ++) {
+
+                switch (state) {
+
+                case WORD:
+                        if (*e == '$')
+                                state = CURLY;
+                        break;
+
+                case CURLY:
+                        if (*e == '{') {
+                                k = strnappend(r, word, e-word-1);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
+                                word = e-1;
+                                state = VARIABLE;
+                                nest++;
+                        } else if (*e == '$') {
+                                k = strnappend(r, word, e-word);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
+                                word = e+1;
+                                state = WORD;
+
+                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) {
+                                k = strnappend(r, word, e-word-1);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
+                                word = e-1;
+                                state = VARIABLE_RAW;
+
+                        } else
+                                state = WORD;
+                        break;
+
+                case VARIABLE:
+                        if (*e == '}') {
+                                const char *t;
+
+                                t = strv_env_get_n(env, word+2, e-word-2, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
+                                word = e+1;
+                                state = WORD;
+                        } else if (*e == ':') {
+                                if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
+                                        /* Treat this as unsupported syntax, i.e. do no replacement */
+                                        state = WORD;
+                                else {
+                                        len = e-word-2;
+                                        state = TEST;
+                                }
+                        }
+                        break;
+
+                case TEST:
+                        if (*e == '-')
+                                state = DEFAULT_VALUE;
+                        else if (*e == '+')
+                                state = ALTERNATE_VALUE;
+                        else {
+                                state = WORD;
+                                break;
+                        }
+
+                        test_value = e+1;
+                        break;
+
+                case DEFAULT_VALUE: /* fall through */
+                case ALTERNATE_VALUE:
+                        assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
+
+                        if (*e == '{') {
+                                nest++;
+                                break;
+                        }
+
+                        if (*e != '}')
+                                break;
+
+                        nest--;
+                        if (nest == 0) {
+                                const char *t;
+                                _cleanup_free_ char *v = NULL;
+
+                                t = strv_env_get_n(env, word+2, len, flags);
+
+                                if (t && state == ALTERNATE_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+                                else if (!t && state == DEFAULT_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
+                                word = e+1;
+                                state = WORD;
+                        }
+                        break;
+
+                case VARIABLE_RAW:
+                        assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
+
+                        if (!strchr(VALID_CHARS_ENV_NAME, *e)) {
+                                const char *t;
+
+                                t = strv_env_get_n(env, word+1, e-word-1, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
+                                word = e--;
+                                i--;
+                                state = WORD;
+                        }
+                        break;
+                }
+        }
+
+        if (state == VARIABLE_RAW) {
+                const char *t;
+
+                assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
+
+                t = strv_env_get_n(env, word+1, e-word-1, flags);
+                return strappend(r, t);
+        } else
+                return strnappend(r, word, e-word);
+}
+
+char **replace_env_argv(char **argv, char **env) {
+        char **ret, **i;
+        unsigned k = 0, l = 0;
+
+        l = strv_length(argv);
+
+        ret = new(char*, l+1);
+        if (!ret)
+                return NULL;
+
+        STRV_FOREACH(i, argv) {
+
+                /* If $FOO appears as single word, replace it by the split up variable */
+                if ((*i)[0] == '$' && (*i)[1] != '{' && (*i)[1] != '$') {
+                        char *e;
+                        char **w, **m = NULL;
+                        unsigned q;
+
+                        e = strv_env_get(env, *i+1);
+                        if (e) {
+                                int r;
+
+                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES);
+                                if (r < 0) {
+                                        ret[k] = NULL;
+                                        strv_free(ret);
+                                        return NULL;
+                                }
+                        } else
+                                m = NULL;
+
+                        q = strv_length(m);
+                        l = l + q - 1;
+
+                        w = realloc(ret, sizeof(char*) * (l+1));
+                        if (!w) {
+                                ret[k] = NULL;
+                                strv_free(ret);
+                                strv_free(m);
+                                return NULL;
+                        }
+
+                        ret = w;
+                        if (m) {
+                                memcpy(ret + k, m, q * sizeof(char*));
+                                free(m);
+                        }
+
+                        k += q;
+                        continue;
+                }
+
+                /* If ${FOO} appears as part of a word, replace it by the variable as-is */
+                ret[k] = replace_env(*i, env, 0);
+                if (!ret[k]) {
+                        strv_free(ret);
+                        return NULL;
+                }
+                k++;
+        }
+
+        ret[k] = NULL;
+        return ret;
+}
+
+int getenv_bool(const char *p) {
+        const char *e;
+
+        e = getenv(p);
+        if (!e)
+                return -ENXIO;
+
+        return parse_boolean(e);
+}
+
+int serialize_environment(FILE *f, char **environment) {
+        char **e;
+
+        STRV_FOREACH(e, environment) {
+                _cleanup_free_ char *ce;
+
+                ce = cescape(*e);
+                if (!ce)
+                        return -ENOMEM;
+
+                fprintf(f, "env=%s\n", *e);
+        }
+
+        /* caller should call ferror() */
+
+        return 0;
+}
+
+int deserialize_environment(char ***environment, const char *line) {
+        char *uce = NULL;
+        int r;
+
+        assert(line);
+        assert(environment);
+
+        assert(startswith(line, "env="));
+        r = cunescape(line + 4, UNESCAPE_RELAX, &uce);
+        if (r < 0)
+                return r;
+
+        if (!env_assignment_is_valid(uce))
+                return -EINVAL;
+
+        return strv_env_replace(environment, uce);
+}
diff --git a/src/basic/env-util.h b/src/basic/env-util.h
new file mode 100644 (file)
index 0000000..e88fa6a
--- /dev/null
@@ -0,0 +1,66 @@
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "macro.h"
+
+bool env_name_is_valid(const char *e);
+bool env_value_is_valid(const char *e);
+bool env_assignment_is_valid(const char *e);
+
+enum {
+        REPLACE_ENV_USE_ENVIRONMENT = 1u,
+        REPLACE_ENV_ALLOW_BRACELESS = 2u,
+        REPLACE_ENV_ALLOW_EXTENDED  = 4u,
+};
+
+char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
+char **replace_env_argv(char **argv, char **env);
+
+static inline char *replace_env(const char *format, char **env, unsigned flags) {
+        return replace_env_n(format, strlen(format), env, flags);
+}
+
+bool strv_env_is_valid(char **e);
+#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL)
+char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata);
+
+bool strv_env_name_is_valid(char **l);
+bool strv_env_name_or_assignment_is_valid(char **l);
+
+char **strv_env_merge(unsigned n_lists, ...);
+char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */
+
+char **strv_env_set(char **x, const char *p); /* New copy ... */
+char **strv_env_unset(char **l, const char *p); /* In place ... */
+char **strv_env_unset_many(char **l, ...) _sentinel_;
+int strv_env_replace(char ***l, char *p); /* In place ... */
+
+char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
+char *strv_env_get(char **x, const char *n) _pure_;
+
+int getenv_bool(const char *p);
+
+int serialize_environment(FILE *f, char **environment);
+int deserialize_environment(char ***environment, const char *line);
diff --git a/src/basic/exec-util.c b/src/basic/exec-util.c
new file mode 100644 (file)
index 0000000..aced9e8
--- /dev/null
@@ -0,0 +1,360 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "env-util.h"
+#include "exec-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "process-util.h"
+#include "set.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "util.h"
+
+/* Put this test here for a lack of better place */
+assert_cc(EAGAIN == EWOULDBLOCK);
+
+static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
+
+        pid_t _pid;
+
+        if (null_or_empty_path(path)) {
+                log_debug("%s is empty (a mask).", path);
+                return 0;
+        }
+
+        _pid = fork();
+        if (_pid < 0)
+                return log_error_errno(errno, "Failed to fork: %m");
+        if (_pid == 0) {
+                char *_argv[2];
+
+                assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+                if (stdout_fd >= 0) {
+                        /* If the fd happens to be in the right place, go along with that */
+                        if (stdout_fd != STDOUT_FILENO &&
+                            dup2(stdout_fd, STDOUT_FILENO) < 0)
+                                return -errno;
+
+                        fd_cloexec(STDOUT_FILENO, false);
+                }
+
+                if (!argv) {
+                        _argv[0] = (char*) path;
+                        _argv[1] = NULL;
+                        argv = _argv;
+                } else
+                        argv[0] = (char*) path;
+
+                execv(path, argv);
+                log_error_errno(errno, "Failed to execute %s: %m", path);
+                _exit(EXIT_FAILURE);
+        }
+
+        log_debug("Spawned %s as " PID_FMT ".", path, _pid);
+        *pid = _pid;
+        return 1;
+}
+
+static int do_execute(
+                char **directories,
+                usec_t timeout,
+                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
+                void* const callback_args[_STDOUT_CONSUME_MAX],
+                int output_fd,
+                char *argv[]) {
+
+        _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
+        _cleanup_strv_free_ char **paths = NULL;
+        char **path;
+        int r;
+
+        /* We fork this all off from a child process so that we can somewhat cleanly make
+         * use of SIGALRM to set a time limit.
+         *
+         * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
+         */
+
+        (void) reset_all_signal_handlers();
+        (void) reset_signal_mask();
+
+        assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+        r = conf_files_list_strv(&paths, NULL, NULL, (const char* const*) directories);
+        if (r < 0)
+                return r;
+
+        if (!callbacks) {
+                pids = hashmap_new(NULL);
+                if (!pids)
+                        return log_oom();
+        }
+
+        /* Abort execution of this process after the timout. We simply rely on SIGALRM as
+         * default action terminating the process, and turn on alarm(). */
+
+        if (timeout != USEC_INFINITY)
+                alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
+
+        STRV_FOREACH(path, paths) {
+                _cleanup_free_ char *t = NULL;
+                _cleanup_close_ int fd = -1;
+                pid_t pid;
+
+                t = strdup(*path);
+                if (!t)
+                        return log_oom();
+
+                if (callbacks) {
+                        fd = open_serialization_fd(basename(*path));
+                        if (fd < 0)
+                                return log_error_errno(fd, "Failed to open serialization file: %m");
+                }
+
+                r = do_spawn(t, argv, fd, &pid);
+                if (r <= 0)
+                        continue;
+
+                if (pids) {
+                        r = hashmap_put(pids, PID_TO_PTR(pid), t);
+                        if (r < 0)
+                                return log_oom();
+                        t = NULL;
+                } else {
+                        r = wait_for_terminate_and_warn(t, pid, true);
+                        if (r < 0)
+                                continue;
+
+                        if (lseek(fd, 0, SEEK_SET) < 0)
+                                return log_error_errno(errno, "Failed to seek on serialization fd: %m");
+
+                        r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
+                        fd = -1;
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to process output from %s: %m", *path);
+                }
+        }
+
+        if (callbacks) {
+                r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
+                if (r < 0)
+                        return log_error_errno(r, "Callback two failed: %m");
+        }
+
+        while (!hashmap_isempty(pids)) {
+                _cleanup_free_ char *t = NULL;
+                pid_t pid;
+
+                pid = PTR_TO_PID(hashmap_first_key(pids));
+                assert(pid > 0);
+
+                t = hashmap_remove(pids, PID_TO_PTR(pid));
+                assert(t);
+
+                wait_for_terminate_and_warn(t, pid, true);
+        }
+
+        return 0;
+}
+
+int execute_directories(
+                const char* const* directories,
+                usec_t timeout,
+                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
+                void* const callback_args[_STDOUT_CONSUME_MAX],
+                char *argv[]) {
+
+        pid_t executor_pid;
+        char *name;
+        char **dirs = (char**) directories;
+        _cleanup_close_ int fd = -1;
+        int r;
+
+        assert(!strv_isempty(dirs));
+
+        name = basename(dirs[0]);
+        assert(!isempty(name));
+
+        if (callbacks) {
+                assert(callback_args);
+                assert(callbacks[STDOUT_GENERATE]);
+                assert(callbacks[STDOUT_COLLECT]);
+                assert(callbacks[STDOUT_CONSUME]);
+
+                fd = open_serialization_fd(name);
+                if (fd < 0)
+                        return log_error_errno(fd, "Failed to open serialization file: %m");
+        }
+
+        /* Executes all binaries in the directories serially or in parallel and waits for
+         * them to finish. Optionally a timeout is applied. If a file with the same name
+         * exists in more than one directory, the earliest one wins. */
+
+        executor_pid = fork();
+        if (executor_pid < 0)
+                return log_error_errno(errno, "Failed to fork: %m");
+
+        if (executor_pid == 0) {
+                r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv);
+                _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+        }
+
+        r = wait_for_terminate_and_warn(name, executor_pid, true);
+        if (r < 0)
+                return log_error_errno(r, "Execution failed: %m");
+        if (r > 0) {
+                /* non-zero return code from child */
+                log_error("Forker process failed.");
+                return -EREMOTEIO;
+        }
+
+        if (!callbacks)
+                return 0;
+
+        if (lseek(fd, 0, SEEK_SET) < 0)
+                return log_error_errno(errno, "Failed to rewind serialization fd: %m");
+
+        r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
+        fd = -1;
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse returned data: %m");
+        return 0;
+}
+
+static int gather_environment_generate(int fd, void *arg) {
+        char ***env = arg, **x, **y;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_strv_free_ char **new;
+        int r;
+
+        /* Read a series of VAR=value assignments from fd, use them to update the list of
+         * variables in env. Also update the exported environment.
+         *
+         * fd is always consumed, even on error.
+         */
+
+        assert(env);
+
+        f = fdopen(fd, "r");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        r = load_env_file_pairs(f, NULL, NULL, &new);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH_PAIR(x, y, new) {
+                char *p;
+
+                if (!env_name_is_valid(*x)) {
+                        log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
+                        continue;
+                }
+
+                p = strjoin(*x, "=", *y);
+                if (!p)
+                        return -ENOMEM;
+
+                r = strv_env_replace(env, p);
+                if (r < 0)
+                        return r;
+
+                if (setenv(*x, *y, true) < 0)
+                        return -errno;
+        }
+
+        return r;
+}
+
+static int gather_environment_collect(int fd, void *arg) {
+        char ***env = arg;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        /* Write out a series of env=cescape(VAR=value) assignments to fd. */
+
+        assert(env);
+
+        f = fdopen(fd, "w");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        r = serialize_environment(f, *env);
+        if (r < 0)
+                return r;
+
+        if (ferror(f))
+                return errno > 0 ? -errno : -EIO;
+
+        return 0;
+}
+
+static int gather_environment_consume(int fd, void *arg) {
+        char ***env = arg;
+        _cleanup_fclose_ FILE *f = NULL;
+        char line[LINE_MAX];
+        int r = 0, k;
+
+        /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
+
+        assert(env);
+
+        f = fdopen(fd, "r");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        FOREACH_LINE(line, f, return -EIO) {
+                truncate_nl(line);
+
+                k = deserialize_environment(env, line);
+                if (k < 0)
+                        log_error_errno(k, "Invalid line \"%s\": %m", line);
+                if (k < 0 && r == 0)
+                        r = k;
+        }
+
+        return r;
+}
+
+const gather_stdout_callback_t gather_environment[] = {
+        gather_environment_generate,
+        gather_environment_collect,
+        gather_environment_consume,
+};
diff --git a/src/basic/exec-util.h b/src/basic/exec-util.h
new file mode 100644 (file)
index 0000000..7200979
--- /dev/null
@@ -0,0 +1,40 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2017 Zbigniew JÄ™drzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "time-util.h"
+
+typedef int (*gather_stdout_callback_t) (int fd, void *arg);
+
+enum {
+        STDOUT_GENERATE,   /* from generators to helper process */
+        STDOUT_COLLECT,    /* from helper process to main process */
+        STDOUT_CONSUME,    /* process data in main process */
+        _STDOUT_CONSUME_MAX,
+};
+
+int execute_directories(
+                const char* const* directories,
+                usec_t timeout,
+                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
+                void* const callback_args[_STDOUT_CONSUME_MAX],
+                char *argv[]);
+
+extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX];
diff --git a/src/shared/nsflags.c b/src/shared/nsflags.c
new file mode 100644 (file)
index 0000000..aeb79b1
--- /dev/null
@@ -0,0 +1,120 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2016 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sched.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "nsflags.h"
+#include "string-util.h"
+
+const struct namespace_flag_map namespace_flag_map[] = {
+        { CLONE_NEWCGROUP, "cgroup" },
+        { CLONE_NEWIPC,    "ipc"    },
+        { CLONE_NEWNET,    "net"    },
+        /* So, the mount namespace flag is called CLONE_NEWNS for historical reasons. Let's expose it here under a more
+         * explanatory name: "mnt". This is in-line with how the kernel exposes namespaces in /proc/$PID/ns. */
+        { CLONE_NEWNS,     "mnt"    },
+        { CLONE_NEWPID,    "pid"    },
+        { CLONE_NEWUSER,   "user"   },
+        { CLONE_NEWUTS,    "uts"    },
+        {}
+};
+
+const char* namespace_flag_to_string(unsigned long flag) {
+        unsigned i;
+
+        flag &= NAMESPACE_FLAGS_ALL;
+
+        for (i = 0; namespace_flag_map[i].name; i++)
+                if (flag == namespace_flag_map[i].flag)
+                        return namespace_flag_map[i].name;
+
+        return NULL; /* either unknown namespace flag, or a combination of many. This call supports neither. */
+}
+
+unsigned long namespace_flag_from_string(const char *name) {
+        unsigned i;
+
+        if (isempty(name))
+                return 0;
+
+        for (i = 0; namespace_flag_map[i].name; i++)
+                if (streq(name, namespace_flag_map[i].name))
+                        return namespace_flag_map[i].flag;
+
+        return 0;
+}
+
+int namespace_flag_from_string_many(const char *name, unsigned long *ret) {
+        unsigned long flags = 0;
+        int r;
+
+        assert_se(ret);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                unsigned long f;
+
+                r = extract_first_word(&name, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                f = namespace_flag_from_string(word);
+                if (f == 0)
+                        return -EINVAL;
+
+                flags |= f;
+        }
+
+        *ret = flags;
+        return 0;
+}
+
+int namespace_flag_to_string_many(unsigned long flags, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        unsigned i;
+
+        for (i = 0; namespace_flag_map[i].name; i++) {
+                if ((flags & namespace_flag_map[i].flag) != namespace_flag_map[i].flag)
+                        continue;
+
+                if (!s) {
+                        s = strdup(namespace_flag_map[i].name);
+                        if (!s)
+                                return -ENOMEM;
+                } else {
+                        if (!strextend(&s, " ", namespace_flag_map[i].name, NULL))
+                                return -ENOMEM;
+                }
+        }
+
+        if (!s) {
+                s = strdup("");
+                if (!s)
+                        return -ENOMEM;
+        }
+
+        *ret = s;
+        s = NULL;
+
+        return 0;
+}
diff --git a/src/shared/nsflags.h b/src/shared/nsflags.h
new file mode 100644 (file)
index 0000000..152ab8b
--- /dev/null
@@ -0,0 +1,49 @@
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2016 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sched.h>
+
+#include "missing.h"
+
+/* The combination of all namespace flags defined by the kernel. The right type for this isn't clear. setns() and
+ * unshare() expect these flags to be passed as (signed) "int", while clone() wants them as "unsigned long". The latter
+ * is definitely more appropriate for a flags parameter, and also the larger type of the two, hence let's stick to that
+ * here. */
+#define NAMESPACE_FLAGS_ALL                                             \
+        ((unsigned long) (CLONE_NEWCGROUP|                              \
+                          CLONE_NEWIPC|                                 \
+                          CLONE_NEWNET|                                 \
+                          CLONE_NEWNS|                                  \
+                          CLONE_NEWPID|                                 \
+                          CLONE_NEWUSER|                                \
+                          CLONE_NEWUTS))
+
+const char* namespace_flag_to_string(unsigned long flag);
+unsigned long namespace_flag_from_string(const char *name);
+int namespace_flag_from_string_many(const char *name, unsigned long *ret);
+int namespace_flag_to_string_many(unsigned long flags, char **ret);
+
+struct namespace_flag_map {
+        unsigned long flag;
+        const char *name;
+};
+
+extern const struct namespace_flag_map namespace_flag_map[];