]> err.no Git - systemd/commitdiff
unit: add minimal condition checker for unit startup
authorLennart Poettering <lennart@poettering.net>
Wed, 13 Oct 2010 00:15:41 +0000 (02:15 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 13 Oct 2010 00:15:41 +0000 (02:15 +0200)
Makefile.am
TODO
man/systemd.unit.xml
src/condition.c [new file with mode: 0644]
src/condition.h [new file with mode: 0644]
src/execute.c
src/load-fragment.c
src/main.c
src/unit.c
src/unit.h

index c4d4d2773dc1157998b8fa0745e28b3836a9e8d4..a028553cd3589b7c3d049d4bbe9496a18e148761 100644 (file)
@@ -402,7 +402,8 @@ libsystemd_core_la_SOURCES = \
        src/fdset.c \
        src/namespace.c \
        src/tcpwrap.c \
-       src/cgroup-util.c
+       src/cgroup-util.c \
+       src/condition.c
 
 libsystemd_core_la_CFLAGS = \
        $(AM_CFLAGS) \
diff --git a/TODO b/TODO
index 4052945897b5aa651c4824a4ebf38f3fa6c5d763..6d905dc68c44f10bd8fdd59ef3c25611f23e7da9 100644 (file)
--- a/TODO
+++ b/TODO
@@ -42,8 +42,6 @@
 
 * systemctl list-jobs - show dependencies
 
-* ConditionFileExists=, ConditionKernelCommandLine=, ConditionEnvironment= with !
-
 * accountsservice is borked
 
 * auditd service files
@@ -84,6 +82,8 @@
 
 * fix plymouth socket, when plymouth started to use a clean one
 
+* parse early boot time env var from dracut
+
 External:
 
 * patch kernel to add /proc/swaps change notifications
index e59c1a16c4d828d4530b50bcd8322509c168a012..e54cafaabcd2f0b854b7d9e1aa7126c83170d5a5 100644 (file)
                                 change.</para></listitem>
                         </varlistentry>
 
+                        <varlistentry>
+                                <term><varname>ConditionPathExists=</varname></term>
+                                <term><varname>ConditionKernelCommandLine=</varname></term>
+
+                                <listitem><para>Before starting a unit
+                                verify that the specified condition is
+                                true. With
+                                <varname>ConditionPathExists=</varname>
+                                a file existance condition can be
+                                checked before a unit is started. If
+                                the specified absolute path name does
+                                not exist startup of a unit will not
+                                actually happen, however the unit is
+                                still useful for ordering purposes in
+                                this case. The condition is checked at
+                                the time the queued start job is to be
+                                executed. If the absolute path name
+                                passed to
+                                <varname>ConditionPathExists=</varname>
+                                is prefixed with an exclamation mark
+                                (!), the test is negated, and the unit
+                                only started if the path does not
+                                exist. Similarly
+                                <varname>ConditionKernelCommandLine=</varname>
+                                may be used to check whether a
+                                specific kernel command line option is
+                                set (or if prefixed with the
+                                exclamation mark unset). The argument
+                                must either be a single word, or an
+                                assignment (i.e. two words, seperated
+                                by the equality sign). In the former
+                                case the kernel command line is search
+                                for the word appearing as is, or as
+                                left hand side of an assignment. In
+                                the latter case the exact assignment
+                                is looked for with right and left hand
+                                side matching. If multiple conditions
+                                are specified the unit will be
+                                executed iff at least one of them
+                                apply (i.e. a logical OR is
+                                applied).</para></listitem>
+                        </varlistentry>
                 </variablelist>
 
                 <para>Unit file may include a [Install] section, which
diff --git a/src/condition.c b/src/condition.c
new file mode 100644 (file)
index 0000000..8c2db2d
--- /dev/null
@@ -0,0 +1,158 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 ProFUSION embedded systems
+
+  systemd 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 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "condition.h"
+
+Condition* condition_new(ConditionType type, const char *parameter, bool negate) {
+        Condition *c;
+
+        c = new0(Condition, 1);
+        c->type = type;
+        c->negate = negate;
+
+        if (!(c->parameter = strdup(parameter))) {
+                free(c);
+                return NULL;
+        }
+
+        return c;
+}
+
+void condition_free(Condition *c) {
+        assert(c);
+
+        free(c->parameter);
+        free(c);
+}
+
+void condition_free_list(Condition *first) {
+        Condition *c, *n;
+
+        LIST_FOREACH_SAFE(conditions, c, n, first)
+                condition_free(c);
+}
+
+static bool test_kernel_command_line(const char *parameter) {
+        char *line, *w, *state, *word = NULL;
+        bool equal;
+        int r;
+        size_t l, pl;
+        bool found = false;
+
+        if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
+                log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+                return false;
+        }
+
+        equal = !!strchr(parameter, '=');
+        pl = strlen(parameter);
+
+        FOREACH_WORD_QUOTED(w, l, line, state) {
+
+                free(word);
+                if (!(word = strndup(w, l)))
+                        break;
+
+                if (equal) {
+                        if (streq(word, parameter)) {
+                                found = true;
+                                break;
+                        }
+                } else {
+                        if (startswith(word, parameter) && (word[pl] == '=' || word[pl] == 0)) {
+                                found = true;
+                                break;
+                        }
+                }
+
+        }
+
+        free(word);
+        free(line);
+
+        return found;
+}
+
+bool condition_test(Condition *c) {
+        assert(c);
+
+        switch(c->type) {
+
+        case CONDITION_PATH_EXISTS:
+                return (access(c->parameter, F_OK) >= 0) == !c->negate;
+
+        case CONDITION_KERNEL_COMMAND_LINE:
+                return !!test_kernel_command_line(c->parameter) == !c->negate;
+
+        default:
+                assert_not_reached("Invalid condition type.");
+        }
+}
+
+bool condition_test_list(Condition *first) {
+        Condition *c;
+
+        /* If the condition list is empty, then it is true */
+        if (!first)
+                return true;
+
+        /* Otherwise, if any of the conditions apply we return true */
+        LIST_FOREACH(conditions, c, first)
+                if (condition_test(c))
+                        return true;
+
+        return false;
+}
+
+void condition_dump(Condition *c, FILE *f, const char *prefix) {
+        assert(c);
+        assert(f);
+
+        if (!prefix)
+                prefix = "";
+
+        fprintf(f,
+                "%s%s: %s%s\n",
+                prefix,
+                condition_type_to_string(c->type),
+                c->negate ? "!" : "",
+                c->parameter);
+}
+
+void condition_dump_list(Condition *first, FILE *f, const char *prefix) {
+        Condition *c;
+
+        LIST_FOREACH(conditions, c, first)
+                condition_dump(c, f, prefix);
+}
+
+static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
+        [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
+        [CONDITION_PATH_EXISTS] = "ConditionPathExists"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
diff --git a/src/condition.h b/src/condition.h
new file mode 100644 (file)
index 0000000..4e0d63c
--- /dev/null
@@ -0,0 +1,57 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooconditionhfoo
+#define fooconditionhfoo
+
+/***
+  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 General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "list.h"
+
+typedef enum ConditionType {
+        CONDITION_PATH_EXISTS,
+        CONDITION_KERNEL_COMMAND_LINE,
+        _CONDITION_TYPE_MAX,
+        _CONDITION_TYPE_INVALID = -1
+} ConditionType;
+
+typedef struct Condition {
+        ConditionType type;
+        char *parameter;
+        bool negate;
+
+        LIST_FIELDS(struct Condition, conditions);
+} Condition;
+
+Condition* condition_new(ConditionType type, const char *parameter, bool negate);
+void condition_free(Condition *c);
+void condition_free_list(Condition *c);
+
+bool condition_test(Condition *c);
+bool condition_test_list(Condition *c);
+
+void condition_dump(Condition *c, FILE *f, const char *prefix);
+void condition_dump_list(Condition *c, FILE *f, const char *prefix);
+
+const char* condition_type_to_string(ConditionType t);
+int condition_type_from_string(const char *s);
+
+#endif
index 9c7e0d6b708e7d618957fb5b5fbc190a48f8ff61..b5afa681082c7cfa2d07bc6dfd95b71a6162693d 100644 (file)
@@ -1613,7 +1613,6 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
                 fprintf(f,
                         "%sUtmpIdentifier: %s\n",
                         prefix, c->utmp_id);
-
 }
 
 void exec_status_start(ExecStatus *s, pid_t pid) {
index eb9861802b21da0ca226f0549d3efa3d509e11b5..2b5c8e70dd29bf60699f3a5719b9c6653655a426 100644 (file)
@@ -1400,6 +1400,67 @@ static int config_parse_ip_tos(
         return 0;
 }
 
+static int config_parse_condition_path(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Unit *u = data;
+        bool negate;
+        Condition *c;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((negate = rvalue[0] == '!'))
+                rvalue++;
+
+        if (!path_is_absolute(rvalue)) {
+                log_error("[%s:%u] Path in condition not absolute: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        if (!(c = condition_new(CONDITION_PATH_EXISTS, rvalue, negate)))
+                return -ENOMEM;
+
+        LIST_PREPEND(Condition, conditions, u->meta.conditions, c);
+        return 0;
+}
+
+static int config_parse_condition_kernel(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Unit *u = data;
+        bool negate;
+        Condition *c;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((negate = rvalue[0] == '!'))
+                rvalue++;
+
+        if (!(c = condition_new(CONDITION_KERNEL_COMMAND_LINE, rvalue, negate)))
+                return -ENOMEM;
+
+        LIST_PREPEND(Condition, conditions, u->meta.conditions, c);
+        return 0;
+}
+
 static DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
 
 #define FOLLOW_MAX 8
@@ -1571,6 +1632,8 @@ static void dump_items(FILE *f, const ConfigItem *items) {
                 { config_parse_path_unit,        "UNIT" },
                 { config_parse_notify_access,    "ACCESS" },
                 { config_parse_ip_tos,           "TOS" },
+                { config_parse_condition_path,   "CONDITION" },
+                { config_parse_condition_kernel, "CONDITION" },
         };
 
         assert(f);
@@ -1692,6 +1755,8 @@ static int load_from_path(Unit *u, const char *path) {
                 { "DefaultDependencies",    config_parse_bool,            &u->meta.default_dependencies,                   "Unit"    },
                 { "IgnoreDependencyFailure",config_parse_bool,            &u->meta.ignore_dependency_failure,              "Unit"    },
                 { "JobTimeoutSec",          config_parse_usec,            &u->meta.job_timeout,                            "Unit"    },
+                { "ConditionPathExists",    config_parse_condition_path,  u,                                               "Unit"    },
+                { "ConditionKernelCommandLine", config_parse_condition_kernel, u,                                          "Unit"    },
 
                 { "PIDFile",                config_parse_path,            &u->service.pid_file,                            "Service" },
                 { "ExecStartPre",           config_parse_exec,            u->service.exec_command+SERVICE_EXEC_START_PRE,  "Service" },
index fa306d6aad3bf18a1b07162e06d77993edf04fb5..15bd2e4d1562bf14ad2089811e4845ae3ab05b41 100644 (file)
@@ -545,11 +545,9 @@ static int parse_config_file(void) {
 }
 
 static int parse_proc_cmdline(void) {
-        char *line;
+        char *line, *w, *state;
         int r;
-        char *w;
         size_t l;
-        char *state;
 
         if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
index 9fc9be5c79a1810dc940cb3f3147f4ac10464589..07978134de959889f213740bd55aaeffd80bb939 100644 (file)
@@ -375,6 +375,8 @@ void unit_free(Unit *u) {
 
         set_free_free(u->meta.names);
 
+        condition_free_list(u->meta.conditions);
+
         free(u->meta.instance);
         free(u);
 }
@@ -639,6 +641,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
         if (u->meta.job_timeout > 0)
                 fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->meta.job_timeout));
 
+        condition_dump_list(u->meta.conditions, f, prefix);
+
         for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) {
                 Unit *other;
 
@@ -840,6 +844,12 @@ int unit_start(Unit *u) {
         if (!UNIT_VTABLE(u)->start)
                 return -EBADR;
 
+        /* If the conditions failed, don't do anything at all */
+        if (!condition_test_list(u->meta.conditions)) {
+                log_debug("Starting of %s requested but condition failed. Ignoring.", u->meta.id);
+                return -EALREADY;
+        }
+
         /* We don't suppress calls to ->start() here when we are
          * already starting, to allow this request to be used as a
          * "hurry up" call, for example when the unit is in some "auto
index 605fa3774d8944ec4c27a0d5514dcc408efecfa7..fa869ece8fb7859dbea2def6c055dd1b68632796 100644 (file)
@@ -38,6 +38,7 @@ typedef enum UnitDependency UnitDependency;
 #include "list.h"
 #include "socket-util.h"
 #include "execute.h"
+#include "condition.h"
 
 #define DEFAULT_TIMEOUT_USEC (60*USEC_PER_SEC)
 #define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
@@ -154,6 +155,9 @@ struct Meta {
 
         usec_t job_timeout;
 
+        /* Conditions to check */
+        LIST_HEAD(Condition, conditions);
+
         dual_timestamp inactive_exit_timestamp;
         dual_timestamp active_enter_timestamp;
         dual_timestamp active_exit_timestamp;