mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 18:23:32 +08:00
unit: add minimal condition checker for unit startup
This commit is contained in:
parent
e04aad61bb
commit
52661efd21
@ -402,7 +402,8 @@ libsystemd_core_la_SOURCES = \
|
|||||||
src/fdset.c \
|
src/fdset.c \
|
||||||
src/namespace.c \
|
src/namespace.c \
|
||||||
src/tcpwrap.c \
|
src/tcpwrap.c \
|
||||||
src/cgroup-util.c
|
src/cgroup-util.c \
|
||||||
|
src/condition.c
|
||||||
|
|
||||||
libsystemd_core_la_CFLAGS = \
|
libsystemd_core_la_CFLAGS = \
|
||||||
$(AM_CFLAGS) \
|
$(AM_CFLAGS) \
|
||||||
|
4
TODO
4
TODO
@ -42,8 +42,6 @@
|
|||||||
|
|
||||||
* systemctl list-jobs - show dependencies
|
* systemctl list-jobs - show dependencies
|
||||||
|
|
||||||
* ConditionFileExists=, ConditionKernelCommandLine=, ConditionEnvironment= with !
|
|
||||||
|
|
||||||
* accountsservice is borked
|
* accountsservice is borked
|
||||||
|
|
||||||
* auditd service files
|
* auditd service files
|
||||||
@ -84,6 +82,8 @@
|
|||||||
|
|
||||||
* fix plymouth socket, when plymouth started to use a clean one
|
* fix plymouth socket, when plymouth started to use a clean one
|
||||||
|
|
||||||
|
* parse early boot time env var from dracut
|
||||||
|
|
||||||
External:
|
External:
|
||||||
|
|
||||||
* patch kernel to add /proc/swaps change notifications
|
* patch kernel to add /proc/swaps change notifications
|
||||||
|
@ -585,6 +585,48 @@
|
|||||||
change.</para></listitem>
|
change.</para></listitem>
|
||||||
</varlistentry>
|
</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>
|
</variablelist>
|
||||||
|
|
||||||
<para>Unit file may include a [Install] section, which
|
<para>Unit file may include a [Install] section, which
|
||||||
|
158
src/condition.c
Normal file
158
src/condition.c
Normal 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
57
src/condition.h
Normal 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
|
@ -1613,7 +1613,6 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
|
|||||||
fprintf(f,
|
fprintf(f,
|
||||||
"%sUtmpIdentifier: %s\n",
|
"%sUtmpIdentifier: %s\n",
|
||||||
prefix, c->utmp_id);
|
prefix, c->utmp_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void exec_status_start(ExecStatus *s, pid_t pid) {
|
void exec_status_start(ExecStatus *s, pid_t pid) {
|
||||||
|
@ -1400,6 +1400,67 @@ static int config_parse_ip_tos(
|
|||||||
return 0;
|
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");
|
static DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
|
||||||
|
|
||||||
#define FOLLOW_MAX 8
|
#define FOLLOW_MAX 8
|
||||||
@ -1571,6 +1632,8 @@ static void dump_items(FILE *f, const ConfigItem *items) {
|
|||||||
{ config_parse_path_unit, "UNIT" },
|
{ config_parse_path_unit, "UNIT" },
|
||||||
{ config_parse_notify_access, "ACCESS" },
|
{ config_parse_notify_access, "ACCESS" },
|
||||||
{ config_parse_ip_tos, "TOS" },
|
{ config_parse_ip_tos, "TOS" },
|
||||||
|
{ config_parse_condition_path, "CONDITION" },
|
||||||
|
{ config_parse_condition_kernel, "CONDITION" },
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(f);
|
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" },
|
{ "DefaultDependencies", config_parse_bool, &u->meta.default_dependencies, "Unit" },
|
||||||
{ "IgnoreDependencyFailure",config_parse_bool, &u->meta.ignore_dependency_failure, "Unit" },
|
{ "IgnoreDependencyFailure",config_parse_bool, &u->meta.ignore_dependency_failure, "Unit" },
|
||||||
{ "JobTimeoutSec", config_parse_usec, &u->meta.job_timeout, "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" },
|
{ "PIDFile", config_parse_path, &u->service.pid_file, "Service" },
|
||||||
{ "ExecStartPre", config_parse_exec, u->service.exec_command+SERVICE_EXEC_START_PRE, "Service" },
|
{ "ExecStartPre", config_parse_exec, u->service.exec_command+SERVICE_EXEC_START_PRE, "Service" },
|
||||||
|
@ -545,11 +545,9 @@ static int parse_config_file(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int parse_proc_cmdline(void) {
|
static int parse_proc_cmdline(void) {
|
||||||
char *line;
|
char *line, *w, *state;
|
||||||
int r;
|
int r;
|
||||||
char *w;
|
|
||||||
size_t l;
|
size_t l;
|
||||||
char *state;
|
|
||||||
|
|
||||||
if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
|
if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
|
||||||
log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
|
log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
|
||||||
|
10
src/unit.c
10
src/unit.c
@ -375,6 +375,8 @@ void unit_free(Unit *u) {
|
|||||||
|
|
||||||
set_free_free(u->meta.names);
|
set_free_free(u->meta.names);
|
||||||
|
|
||||||
|
condition_free_list(u->meta.conditions);
|
||||||
|
|
||||||
free(u->meta.instance);
|
free(u->meta.instance);
|
||||||
free(u);
|
free(u);
|
||||||
}
|
}
|
||||||
@ -639,6 +641,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
|
|||||||
if (u->meta.job_timeout > 0)
|
if (u->meta.job_timeout > 0)
|
||||||
fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->meta.job_timeout));
|
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++) {
|
for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) {
|
||||||
Unit *other;
|
Unit *other;
|
||||||
|
|
||||||
@ -840,6 +844,12 @@ int unit_start(Unit *u) {
|
|||||||
if (!UNIT_VTABLE(u)->start)
|
if (!UNIT_VTABLE(u)->start)
|
||||||
return -EBADR;
|
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
|
/* We don't suppress calls to ->start() here when we are
|
||||||
* already starting, to allow this request to be used as a
|
* already starting, to allow this request to be used as a
|
||||||
* "hurry up" call, for example when the unit is in some "auto
|
* "hurry up" call, for example when the unit is in some "auto
|
||||||
|
@ -38,6 +38,7 @@ typedef enum UnitDependency UnitDependency;
|
|||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "socket-util.h"
|
#include "socket-util.h"
|
||||||
#include "execute.h"
|
#include "execute.h"
|
||||||
|
#include "condition.h"
|
||||||
|
|
||||||
#define DEFAULT_TIMEOUT_USEC (60*USEC_PER_SEC)
|
#define DEFAULT_TIMEOUT_USEC (60*USEC_PER_SEC)
|
||||||
#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
|
#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
|
||||||
@ -154,6 +155,9 @@ struct Meta {
|
|||||||
|
|
||||||
usec_t job_timeout;
|
usec_t job_timeout;
|
||||||
|
|
||||||
|
/* Conditions to check */
|
||||||
|
LIST_HEAD(Condition, conditions);
|
||||||
|
|
||||||
dual_timestamp inactive_exit_timestamp;
|
dual_timestamp inactive_exit_timestamp;
|
||||||
dual_timestamp active_enter_timestamp;
|
dual_timestamp active_enter_timestamp;
|
||||||
dual_timestamp active_exit_timestamp;
|
dual_timestamp active_exit_timestamp;
|
||||||
|
Loading…
Reference in New Issue
Block a user