From 52661efd21608dc7e0ac26b714a9254ed6180ddb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 Oct 2010 02:15:41 +0200 Subject: [PATCH] unit: add minimal condition checker for unit startup --- Makefile.am | 3 +- TODO | 4 +- man/systemd.unit.xml | 42 ++++++++++++ src/condition.c | 158 +++++++++++++++++++++++++++++++++++++++++++ src/condition.h | 57 ++++++++++++++++ src/execute.c | 1 - src/load-fragment.c | 65 ++++++++++++++++++ src/main.c | 4 +- src/unit.c | 10 +++ src/unit.h | 4 ++ 10 files changed, 341 insertions(+), 7 deletions(-) create mode 100644 src/condition.c create mode 100644 src/condition.h diff --git a/Makefile.am b/Makefile.am index c4d4d2773dc..a028553cd35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 4052945897b..6d905dc68c4 100644 --- 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 diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index e59c1a16c4d..e54cafaabcd 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -585,6 +585,48 @@ change. + + ConditionPathExists= + ConditionKernelCommandLine= + + Before starting a unit + verify that the specified condition is + true. With + ConditionPathExists= + 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 + ConditionPathExists= + is prefixed with an exclamation mark + (!), the test is negated, and the unit + only started if the path does not + exist. Similarly + ConditionKernelCommandLine= + 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). + Unit file may include a [Install] section, which diff --git a/src/condition.c b/src/condition.c new file mode 100644 index 00000000000..8c2db2d3e92 --- /dev/null +++ b/src/condition.c @@ -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 . +***/ + +#include +#include +#include +#include + +#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 index 00000000000..4e0d63cd515 --- /dev/null +++ b/src/condition.h @@ -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 . +***/ + +#include + +#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 diff --git a/src/execute.c b/src/execute.c index 9c7e0d6b708..b5afa681082 100644 --- a/src/execute.c +++ b/src/execute.c @@ -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) { diff --git a/src/load-fragment.c b/src/load-fragment.c index eb9861802b2..2b5c8e70dd2 100644 --- a/src/load-fragment.c +++ b/src/load-fragment.c @@ -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" }, diff --git a/src/main.c b/src/main.c index fa306d6aad3..15bd2e4d156 100644 --- a/src/main.c +++ b/src/main.c @@ -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)); diff --git a/src/unit.c b/src/unit.c index 9fc9be5c79a..07978134de9 100644 --- a/src/unit.c +++ b/src/unit.c @@ -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 diff --git a/src/unit.h b/src/unit.h index 605fa3774d8..fa869ece8fb 100644 --- a/src/unit.h +++ b/src/unit.h @@ -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;