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.
304 lines
10 KiB
304 lines
10 KiB
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; |
|
|
|
|