You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
10 KiB
305 lines
10 KiB
7 years ago
|
From 5aafbcced90ae2a3b418d6fe26c67e820daa8bad Mon Sep 17 00:00:00 2001
|
||
|
From: Michal Sekletar <msekleta@redhat.com>
|
||
|
Date: Mon, 23 Jan 2017 17:12:35 +0100
|
||
|
Subject: [PATCH] service: serialize information about currently executing
|
||
|
command
|
||
|
|
||
|
Stored information will help us to resume execution after the
|
||
|
daemon-reload.
|
||
|
|
||
|
This commit implements following scheme,
|
||
|
|
||
|
* On serialization:
|
||
|
- we count rank of the currently executing command
|
||
|
- we store command type, its rank and command line arguments
|
||
|
|
||
|
* On deserialization:
|
||
|
- configuration is parsed and loaded
|
||
|
- we deserialize stored data, command type, rank and arguments
|
||
|
- we look at the given rank in the list and if command there has same
|
||
|
arguments then we restore execution at that point
|
||
|
- otherwise we search respective command list and we look for command
|
||
|
that has the same arguments
|
||
|
- if both methods fail we do not do not resume execution at all
|
||
|
|
||
|
To better illustrate how does above scheme works, please consider
|
||
|
following cases (<<< denotes position where we resume execution after reload)
|
||
|
|
||
|
; Original unit file
|
||
|
[Service]
|
||
|
ExecStart=/bin/true <<<
|
||
|
ExecStart=/bin/false
|
||
|
|
||
|
; Swapped commands
|
||
|
; Second command is not going to be executed
|
||
|
[Service]
|
||
|
ExecStart=/bin/false
|
||
|
ExecStart=/bin/true <<<
|
||
|
|
||
|
; Commands added before
|
||
|
; Same commands are problematic and execution could be restarted at wrong place
|
||
|
[Service]
|
||
|
ExecStart=/bin/foo
|
||
|
ExecStart=/bin/bar
|
||
|
ExecStart=/bin/true <<<
|
||
|
ExecStart=/bin/false
|
||
|
|
||
|
; Commands added after
|
||
|
; Same commands are not an issue in this case
|
||
|
[Service]
|
||
|
ExecStart=/bin/true <<<
|
||
|
ExecStart=/bin/false
|
||
|
ExecStart=/bin/foo
|
||
|
ExecStart=/bin/bar
|
||
|
|
||
|
; New commands interleaved with old commands
|
||
|
; Some new commands will be executed while others won't
|
||
|
ExecStart=/bin/foo
|
||
|
ExecStart=/bin/true <<<
|
||
|
ExecStart=/bin/bar
|
||
|
ExecStart=/bin/false
|
||
|
|
||
|
As you can see, above scheme has some drawbacks. However, in most
|
||
|
cases (we assume that in most common case unit file command list is not
|
||
|
changed while some other command is running for the same unit) it
|
||
|
should cause that systemd does the right thing, which is restoring
|
||
|
execution exactly at the point we were before daemon-reload.
|
||
|
|
||
|
Fixes #518
|
||
|
|
||
|
(cherry picked from commit e266c068b5597e18b2299f9c9d3ee6cf04198c41)
|
||
|
|
||
|
Resolves: #1404657,#1471230
|
||
|
---
|
||
|
src/core/service.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++-----
|
||
|
1 file changed, 180 insertions(+), 15 deletions(-)
|
||
|
|
||
|
diff --git a/src/core/service.c b/src/core/service.c
|
||
|
index 3bd6c3338..9ad3a0eb0 100644
|
||
|
--- a/src/core/service.c
|
||
|
+++ b/src/core/service.c
|
||
|
@@ -1950,6 +1950,80 @@ _pure_ static bool service_can_reload(Unit *u) {
|
||
|
return !!s->exec_command[SERVICE_EXEC_RELOAD];
|
||
|
}
|
||
|
|
||
|
+static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, ExecCommand *current) {
|
||
|
+ Service *s = SERVICE(u);
|
||
|
+ unsigned idx = 0;
|
||
|
+ ExecCommand *first, *c;
|
||
|
+
|
||
|
+ assert(s);
|
||
|
+
|
||
|
+ first = s->exec_command[id];
|
||
|
+
|
||
|
+ /* Figure out where we are in the list by walking back to the beginning */
|
||
|
+ for (c = current; c != first; c = c->command_prev)
|
||
|
+ idx++;
|
||
|
+
|
||
|
+ return idx;
|
||
|
+}
|
||
|
+
|
||
|
+static int service_serialize_exec_command(Unit *u, FILE *f, ExecCommand *command) {
|
||
|
+ Service *s = SERVICE(u);
|
||
|
+ ServiceExecCommand id;
|
||
|
+ unsigned idx;
|
||
|
+ const char *type;
|
||
|
+ char **arg;
|
||
|
+ _cleanup_strv_free_ char **escaped_args = NULL;
|
||
|
+ _cleanup_free_ char *args = NULL, *p = NULL;
|
||
|
+ size_t allocated = 0, length = 0;
|
||
|
+
|
||
|
+ assert(s);
|
||
|
+ assert(f);
|
||
|
+
|
||
|
+ if (!command)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ if (command == s->control_command) {
|
||
|
+ type = "control";
|
||
|
+ id = s->control_command_id;
|
||
|
+ } else {
|
||
|
+ type = "main";
|
||
|
+ id = SERVICE_EXEC_START;
|
||
|
+ }
|
||
|
+
|
||
|
+ idx = service_exec_command_index(u, id, command);
|
||
|
+
|
||
|
+ STRV_FOREACH(arg, command->argv) {
|
||
|
+ size_t n;
|
||
|
+ _cleanup_free_ char *e = NULL;
|
||
|
+
|
||
|
+ e = xescape(*arg, WHITESPACE);
|
||
|
+ if (!e)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ n = strlen(e);
|
||
|
+ if (!GREEDY_REALLOC(args, allocated, length + 1 + n + 1))
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ if (length > 0)
|
||
|
+ args[length++] = ' ';
|
||
|
+
|
||
|
+ memcpy(args + length, e, n);
|
||
|
+ length += n;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!GREEDY_REALLOC(args, allocated, length + 1))
|
||
|
+ return -ENOMEM;
|
||
|
+ args[length++] = 0;
|
||
|
+
|
||
|
+ p = xescape(command->path, WHITESPACE);
|
||
|
+ if (!p)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ fprintf(f, "%s-command=%s %u %s %s\n", type, service_exec_command_to_string(id), idx, p, args);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
|
||
|
Service *s = SERVICE(u);
|
||
|
ServiceFDStore *fs;
|
||
|
@@ -1974,12 +2048,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
|
||
|
if (s->status_text)
|
||
|
unit_serialize_item(u, f, "status-text", s->status_text);
|
||
|
|
||
|
- /* FIXME: There's a minor uncleanliness here: if there are
|
||
|
- * multiple commands attached here, we will start from the
|
||
|
- * first one again */
|
||
|
- if (s->control_command_id >= 0)
|
||
|
- unit_serialize_item(u, f, "control-command",
|
||
|
- service_exec_command_to_string(s->control_command_id));
|
||
|
+ service_serialize_exec_command(u, f, s->control_command);
|
||
|
+ service_serialize_exec_command(u, f, s->main_command);
|
||
|
|
||
|
if (s->socket_fd >= 0) {
|
||
|
int copy;
|
||
|
@@ -2035,6 +2105,106 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
+static int service_deserialize_exec_command(Unit *u, const char *key, const char *value) {
|
||
|
+ Service *s = SERVICE(u);
|
||
|
+ int r;
|
||
|
+ unsigned idx = 0, i;
|
||
|
+ bool control, found = false;
|
||
|
+ ServiceExecCommand id = _SERVICE_EXEC_COMMAND_INVALID;
|
||
|
+ ExecCommand *command = NULL;
|
||
|
+ _cleanup_free_ char *args = NULL, *path = NULL;
|
||
|
+ _cleanup_strv_free_ char **argv = NULL;
|
||
|
+
|
||
|
+ enum ExecCommandState {
|
||
|
+ STATE_EXEC_COMMAND_TYPE,
|
||
|
+ STATE_EXEC_COMMAND_INDEX,
|
||
|
+ STATE_EXEC_COMMAND_PATH,
|
||
|
+ STATE_EXEC_COMMAND_ARGS,
|
||
|
+ _STATE_EXEC_COMMAND_MAX,
|
||
|
+ _STATE_EXEC_COMMAND_INVALID = -1,
|
||
|
+ } state;
|
||
|
+
|
||
|
+ assert(s);
|
||
|
+ assert(key);
|
||
|
+ assert(value);
|
||
|
+
|
||
|
+ control = streq(key, "control-command");
|
||
|
+
|
||
|
+ state = STATE_EXEC_COMMAND_TYPE;
|
||
|
+
|
||
|
+ for (;;) {
|
||
|
+ _cleanup_free_ char *arg = NULL;
|
||
|
+
|
||
|
+ r = extract_first_word(&value, &arg, NULL, EXTRACT_CUNESCAPE);
|
||
|
+ if (r == 0)
|
||
|
+ break;
|
||
|
+ else if (r < 0)
|
||
|
+ return r;
|
||
|
+
|
||
|
+ switch (state) {
|
||
|
+ case STATE_EXEC_COMMAND_TYPE:
|
||
|
+ id = service_exec_command_from_string(arg);
|
||
|
+ if (id < 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ state = STATE_EXEC_COMMAND_INDEX;
|
||
|
+ break;
|
||
|
+ case STATE_EXEC_COMMAND_INDEX:
|
||
|
+ r = safe_atou(arg, &idx);
|
||
|
+ if (r < 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ state = STATE_EXEC_COMMAND_PATH;
|
||
|
+ break;
|
||
|
+ case STATE_EXEC_COMMAND_PATH:
|
||
|
+ path = arg;
|
||
|
+ arg = NULL;
|
||
|
+ state = STATE_EXEC_COMMAND_ARGS;
|
||
|
+
|
||
|
+ if (!path_is_absolute(path))
|
||
|
+ return -EINVAL;
|
||
|
+ break;
|
||
|
+ case STATE_EXEC_COMMAND_ARGS:
|
||
|
+ r = strv_extend(&argv, arg);
|
||
|
+ if (r < 0)
|
||
|
+ return -ENOMEM;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ assert_not_reached("Unknown error at deserialization of exec command");
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (state != STATE_EXEC_COMMAND_ARGS)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ /* Let's check whether exec command on given offset matches data that we just deserialized */
|
||
|
+ for (command = s->exec_command[id], i = 0; command; command = command->command_next, i++) {
|
||
|
+ if (i != idx)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ found = strv_equal(argv, command->argv) && streq(command->path, path);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!found) {
|
||
|
+ /* Command at the index we serialized is different, let's look for command that exactly
|
||
|
+ * matches but is on different index. If there is no such command we will not resume execution. */
|
||
|
+ for (command = s->exec_command[id]; command; command = command->command_next)
|
||
|
+ if (strv_equal(command->argv, argv) && streq(command->path, path))
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (command && control)
|
||
|
+ s->control_command = command;
|
||
|
+ else if (command)
|
||
|
+ s->main_command = command;
|
||
|
+ else
|
||
|
+ log_unit_warning(u->id, "Current command vanished from the unit file, execution of the command list won't be resumed.");
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
|
||
|
Service *s = SERVICE(u);
|
||
|
int r;
|
||
|
@@ -2105,16 +2275,11 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
|
||
|
s->status_text = t;
|
||
|
}
|
||
|
|
||
|
- } else if (streq(key, "control-command")) {
|
||
|
- ServiceExecCommand id;
|
||
|
+ } else if (STR_IN_SET(key, "main-command", "control-command")) {
|
||
|
+ r = service_deserialize_exec_command(u, key, value);
|
||
|
+ if (r < 0)
|
||
|
+ log_unit_debug_errno(u->id, r, "Failed to parse serialized command \"%s\": %m", value);
|
||
|
|
||
|
- id = service_exec_command_from_string(value);
|
||
|
- if (id < 0)
|
||
|
- log_unit_debug(u->id, "Failed to parse exec-command value %s", value);
|
||
|
- else {
|
||
|
- s->control_command_id = id;
|
||
|
- s->control_command = s->exec_command[id];
|
||
|
- }
|
||
|
} else if (streq(key, "socket-fd")) {
|
||
|
int fd;
|
||
|
|