unit: add minimal condition checker for unit startup

This commit is contained in:
Lennart Poettering 2010-10-13 02:15:41 +02:00
parent e04aad61bb
commit 52661efd21
10 changed files with 341 additions and 7 deletions

View 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) \

4
TODO
View File

@ -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

View File

@ -585,6 +585,48 @@
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

158
src/condition.c Normal file
View File

@ -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);

57
src/condition.h Normal file
View File

@ -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

View 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) {

View 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" },

View 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));

View 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

View 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;