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