dcavalca / rpms / systemd

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