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