]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/service.c
core: only apply NonBlocking= to fds passed via socket activation
[thirdparty/systemd.git] / src / core / service.c
index 98edc437a2013f127b3858d70956ace8f5de91de..370a6316e7f8235f4d9a5b92c95fdfbd6738e30d 100644 (file)
@@ -33,7 +33,7 @@
 #include "exit-status.h"
 #include "fd-util.h"
 #include "fileio.h"
-#include "formats-util.h"
+#include "format-util.h"
 #include "fs-util.h"
 #include "load-dropin.h"
 #include "load-fragment.h"
 #include "service.h"
 #include "signal-util.h"
 #include "special.h"
+#include "stdio-util.h"
 #include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "unit-name.h"
-#include "unit-printf.h"
 #include "unit.h"
 #include "utf8.h"
 #include "util.h"
@@ -289,7 +289,17 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
         free(fs);
 }
 
-static void service_release_resources(Unit *u) {
+static void service_release_fd_store(Service *s) {
+        assert(s);
+
+        log_unit_debug(UNIT(s), "Releasing all stored fds");
+        while (s->fd_store)
+                service_fd_store_unlink(s->fd_store);
+
+        assert(s->n_fd_store == 0);
+}
+
+static void service_release_resources(Unit *u, bool inactive) {
         Service *s = SERVICE(u);
 
         assert(s);
@@ -297,16 +307,14 @@ static void service_release_resources(Unit *u) {
         if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
                 return;
 
-        log_unit_debug(u, "Releasing all resources.");
+        log_unit_debug(u, "Releasing resources.");
 
         s->stdin_fd = safe_close(s->stdin_fd);
         s->stdout_fd = safe_close(s->stdout_fd);
         s->stderr_fd = safe_close(s->stderr_fd);
 
-        while (s->fd_store)
-                service_fd_store_unlink(s->fd_store);
-
-        assert(s->n_fd_store == 0);
+        if (inactive)
+                service_release_fd_store(s);
 }
 
 static void service_done(Unit *u) {
@@ -350,7 +358,7 @@ static void service_done(Unit *u) {
 
         s->timer_event_source = sd_event_source_unref(s->timer_event_source);
 
-        service_release_resources(u);
+        service_release_resources(u, true);
 }
 
 static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
@@ -360,6 +368,10 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us
         assert(fs);
 
         /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */
+        log_unit_debug(UNIT(fs->service),
+                       "Received %s on stored fd %d (%s), closing.",
+                       revents & EPOLLERR ? "EPOLLERR" : "EPOLLHUP",
+                       fs->fd, strna(fs->fdname));
         service_fd_store_unlink(fs);
         return 0;
 }
@@ -368,20 +380,23 @@ static int service_add_fd_store(Service *s, int fd, const char *name) {
         ServiceFDStore *fs;
         int r;
 
+        /* fd is always consumed if we return >= 0 */
+
         assert(s);
         assert(fd >= 0);
 
         if (s->n_fd_store >= s->n_fd_store_max)
-                return 0;
+                return -EXFULL; /* Our store is full.
+                                 * Use this errno rather than E[NM]FILE to distinguish from
+                                 * the case where systemd itself hits the file limit. */
 
         LIST_FOREACH(fd_store, fs, s->fd_store) {
                 r = same_fd(fs->fd, fd);
                 if (r < 0)
                         return r;
                 if (r > 0) {
-                        /* Already included */
                         safe_close(fd);
-                        return 1;
+                        return 0; /* fd already included */
                 }
         }
 
@@ -409,7 +424,7 @@ static int service_add_fd_store(Service *s, int fd, const char *name) {
         LIST_PREPEND(fd_store, s->fd_store, fs);
         s->n_fd_store++;
 
-        return 1;
+        return 1; /* fd newly stored */
 }
 
 static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
@@ -417,10 +432,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
 
         assert(s);
 
-        if (fdset_size(fds) <= 0)
-                return 0;
-
-        while (s->n_fd_store < s->n_fd_store_max) {
+        while (fdset_size(fds) > 0) {
                 _cleanup_close_ int fd = -1;
 
                 fd = fdset_steal_first(fds);
@@ -428,17 +440,17 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
                         break;
 
                 r = service_add_fd_store(s, fd, name);
+                if (r == -EXFULL)
+                        return log_unit_warning_errno(UNIT(s), r,
+                                                      "Cannot store more fds than FileDescriptorStoreMax=%u, closing remaining.",
+                                                      s->n_fd_store_max);
                 if (r < 0)
-                        return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m");
-                if (r > 0) {
-                        log_unit_debug(UNIT(s), "Added fd to fd store.");
-                        fd = -1;
-                }
+                        return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m");
+                if (r > 0)
+                        log_unit_debug(UNIT(s), "Added fd %u (%s) to fd store.", fd, strna(name));
+                fd = -1;
         }
 
-        if (fdset_size(fds) > 0)
-                log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max);
-
         return 0;
 }
 
@@ -838,11 +850,8 @@ static int service_load_pid_file(Service *s, bool may_warn) {
                 return r;
 
         r = unit_watch_pid(UNIT(s), pid);
-        if (r < 0) {
-                /* FIXME: we need to do something here */
-                log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid);
-                return r;
-        }
+        if (r < 0) /* FIXME: we need to do something here */
+                return log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid);
 
         return 0;
 }
@@ -1059,14 +1068,16 @@ static int service_coldplug(Unit *u) {
         return 0;
 }
 
-static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
+static int service_collect_fds(Service *s, int **fds, char ***fd_names, unsigned *n_socket_fds) {
         _cleanup_strv_free_ char **rfd_names = NULL;
         _cleanup_free_ int *rfds = NULL;
-        int rn_fds = 0, r;
+        unsigned rn_socket_fds = 0;
+        int rn_fds = 0,  r;
 
         assert(s);
         assert(fds);
         assert(fd_names);
+        assert(n_socket_fds);
 
         if (s->socket_fd >= 0) {
 
@@ -1129,6 +1140,8 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
                 }
         }
 
+        rn_socket_fds = rn_fds;
+
         if (s->n_fd_store > 0) {
                 ServiceFDStore *fs;
                 char **nl;
@@ -1160,6 +1173,7 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
 
         *fds = rfds;
         *fd_names = rfd_names;
+        *n_socket_fds = rn_socket_fds;
 
         rfds = NULL;
         rfd_names = NULL;
@@ -1167,6 +1181,25 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
         return rn_fds;
 }
 
+static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) {
+        assert(s);
+
+        /* Notifications are accepted depending on the process and
+         * the access setting of the service:
+         *     process: \ access:  NONE  MAIN  EXEC   ALL
+         *     main                  no   yes   yes   yes
+         *     control               no    no   yes   yes
+         *     other (forked)        no    no    no   yes */
+
+        if (flags & EXEC_IS_CONTROL)
+                /* A control process */
+                return IN_SET(s->notify_access, NOTIFY_EXEC, NOTIFY_ALL);
+
+        /* We only spawn main processes and control processes, so any
+         * process that is not a control process is a main process */
+        return s->notify_access != NOTIFY_NONE;
+}
+
 static int service_spawn(
                 Service *s,
                 ExecCommand *c,
@@ -1174,9 +1207,9 @@ static int service_spawn(
                 ExecFlags flags,
                 pid_t *_pid) {
 
-        _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL;
+        _cleanup_strv_free_ char **final_env = NULL, **our_env = NULL, **fd_names = NULL;
         _cleanup_free_ int *fds = NULL;
-        unsigned n_fds = 0, n_env = 0;
+        unsigned n_fds = 0, n_socket_fds = 0, n_env = 0;
         const char *path;
         pid_t pid;
 
@@ -1220,26 +1253,23 @@ static int service_spawn(
             s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
             s->exec_context.std_error == EXEC_OUTPUT_SOCKET) {
 
-                r = service_collect_fds(s, &fds, &fd_names);
+                r = service_collect_fds(s, &fds, &fd_names, &n_socket_fds);
                 if (r < 0)
                         return r;
 
                 n_fds = r;
+                log_unit_debug(UNIT(s), "Passing %i fds to service", n_fds);
         }
 
         r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout));
         if (r < 0)
                 return r;
 
-        r = unit_full_printf_strv(UNIT(s), c->argv, &argv);
-        if (r < 0)
-                return r;
-
         our_env = new0(char*, 9);
         if (!our_env)
                 return -ENOMEM;
 
-        if ((flags & EXEC_IS_CONTROL) ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE)
+        if (service_exec_needs_notify_socket(s, flags))
                 if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0)
                         return -ENOMEM;
 
@@ -1265,10 +1295,10 @@ static int service_spawn(
                                 return r;
                 }
 
-                if (r == 0 && IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) {
+                if (r == 0 && IN_SET(sa.sa.sa_family, AF_INET, AF_INET6, AF_VSOCK)) {
                         _cleanup_free_ char *addr = NULL;
                         char *t;
-                        int port;
+                        unsigned port;
 
                         r = sockaddr_pretty(&sa.sa, salen, true, false, &addr);
                         if (r < 0)
@@ -1279,9 +1309,9 @@ static int service_spawn(
                                 return -ENOMEM;
                         our_env[n_env++] = t;
 
-                        port = sockaddr_port(&sa.sa);
-                        if (port < 0)
-                                return port;
+                        r = sockaddr_port(&sa.sa, &port);
+                        if (r < 0)
+                                return r;
 
                         if (asprintf(&t, "REMOTE_PORT=%u", port) < 0)
                                 return -ENOMEM;
@@ -1317,12 +1347,14 @@ static int service_spawn(
         } else
                 path = UNIT(s)->cgroup_path;
 
-        exec_params.argv = argv;
+        exec_params.flags |= MANAGER_IS_SYSTEM(UNIT(s)->manager) ? EXEC_NEW_KEYRING : 0;
+        exec_params.argv = c->argv;
         exec_params.environment = final_env;
         exec_params.fds = fds;
         exec_params.fd_names = fd_names;
         exec_params.n_fds = n_fds;
-        exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0;
+        exec_params.n_socket_fds = n_socket_fds;
+        exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager);
         exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported;
         exec_params.cgroup_path = path;
         exec_params.cgroup_delegate = s->cgroup_context.delegate;
@@ -1346,8 +1378,7 @@ static int service_spawn(
                 return r;
 
         r = unit_watch_pid(UNIT(s), pid);
-        if (r < 0)
-                /* FIXME: we need to do something here */
+        if (r < 0) /* FIXME: we need to do something here */
                 return r;
 
         *_pid = pid;
@@ -1455,7 +1486,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
 
         if (s->result != SERVICE_SUCCESS) {
                 log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result));
-                failure_action(UNIT(s)->manager, s->failure_action, UNIT(s)->reboot_arg);
+                emergency_action(UNIT(s)->manager, s->emergency_action, UNIT(s)->reboot_arg, "service failed");
         }
 
         if (allow_restart && service_shall_restart(s)) {
@@ -1681,7 +1712,9 @@ static void service_enter_running(Service *s, ServiceResult f) {
                         service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec));
                 }
 
-        } else if (s->remain_after_exit)
+        } else if (f != SERVICE_SUCCESS)
+                service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
+        else if (s->remain_after_exit)
                 service_set_state(s, SERVICE_EXITED);
         else
                 service_enter_stop(s, SERVICE_SUCCESS);
@@ -1756,7 +1789,15 @@ static void service_enter_start(Service *s) {
         }
 
         if (!c) {
-                assert(s->type == SERVICE_ONESHOT);
+                if (s->type != SERVICE_ONESHOT) {
+                        /* There's no command line configured for the main command? Hmm, that is strange. This can only
+                         * happen if the configuration changes at runtime. In this case, let's enter a failure
+                         * state. */
+                        log_unit_error(UNIT(s), "There's no 'start' task anymore we could start: %m");
+                        r = -ENXIO;
+                        goto fail;
+                }
+
                 service_enter_start_post(s);
                 return;
         }
@@ -1809,7 +1850,7 @@ static void service_enter_start(Service *s) {
 
 fail:
         log_unit_warning_errno(UNIT(s), r, "Failed to run 'start' task: %m");
-        service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+        service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
 }
 
 static void service_enter_start_pre(Service *s) {
@@ -1955,9 +1996,7 @@ static void service_run_next_control(Service *s) {
 fail:
         log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m");
 
-        if (s->state == SERVICE_START_PRE)
-                service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
-        else if (s->state == SERVICE_STOP)
+        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_STOP))
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
         else if (s->state == SERVICE_STOP_POST)
                 service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
@@ -2033,6 +2072,10 @@ static int service_start(Unit *u) {
                 return r;
         }
 
+        r = unit_acquire_invocation_id(u);
+        if (r < 0)
+                return r;
+
         s->result = SERVICE_SUCCESS;
         s->reload_result = SERVICE_SUCCESS;
         s->main_pid_known = false;
@@ -2104,6 +2147,79 @@ _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_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;
@@ -2131,11 +2247,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
         if (r < 0)
                 return r;
 
-        /* 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);
 
         r = unit_serialize_item_fd(u, f, fds, "stdin-fd", s->stdin_fd);
         if (r < 0)
@@ -2191,6 +2304,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 *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, "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;
@@ -2273,16 +2486,6 @@ 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;
-
-                id = service_exec_command_from_string(value);
-                if (id < 0)
-                        log_unit_debug(u, "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, "accept-socket")) {
                 Unit *socket;
 
@@ -2324,7 +2527,7 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
                         r = service_add_fd_store(s, fd, t);
                         if (r < 0)
                                 log_unit_error_errno(u, r, "Failed to add fd to store: %m");
-                        else if (r > 0)
+                        else
                                 fdset_remove(fds, fd);
                 }
 
@@ -2401,6 +2604,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
                         s->watchdog_override_enable = true;
                         s->watchdog_override_usec = watchdog_override_usec;
                 }
+        } 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, r, "Failed to parse serialized command \"%s\": %m", value);
         } else
                 log_unit_debug(u, "Unknown serialization key: %s", key);
 
@@ -2552,17 +2759,24 @@ static void service_notify_cgroup_empty_event(Unit *u) {
                  * SIGCHLD for. */
 
         case SERVICE_START:
+                if (s->type == SERVICE_NOTIFY) {
+                        /* No chance of getting a ready notification anymore */
+                        service_enter_stop_post(s, SERVICE_FAILURE_PROTOCOL);
+                        break;
+                }
+
+                /* Fall through */
+
         case SERVICE_START_POST:
-                /* If we were hoping for the daemon to write its PID file,
-                 * we can give up now. */
                 if (s->pid_file_pathspec) {
+                        /* Give up hoping for the daemon to write its PID file */
                         log_unit_warning(u, "Daemon never wrote its PID file. Failing.");
 
                         service_unwatch_pid_file(s);
                         if (s->state == SERVICE_START)
-                                service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+                                service_enter_stop_post(s, SERVICE_FAILURE_PROTOCOL);
                         else
-                                service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+                                service_enter_stop(s, SERVICE_FAILURE_PROTOCOL);
                 }
                 break;
 
@@ -2642,8 +2856,14 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                 f = SERVICE_SUCCESS;
                 }
 
-                log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE,
-                           LOG_UNIT_ID(u),
+                /* When this is a successful exit, let's log about the exit code on DEBUG level. If this is a failure
+                 * and the process exited on its own via exit(), then let's make this a NOTICE, under the assumption
+                 * that the service already logged the reason at a higher log level on its own. However, if the service
+                 * died due to a signal, then it most likely didn't say anything about any reason, hence let's raise
+                 * our log level to WARNING then. */
+
+                log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG :
+                           (code == CLD_EXITED ? LOG_NOTICE : LOG_WARNING),
                            LOG_UNIT_MESSAGE(u, "Main process exited, code=%s, status=%i/%s",
                                             sigchld_code_to_string(code), status,
                                             strna(code == CLD_EXITED
@@ -2651,6 +2871,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                                   : signal_to_string(status))),
                            "EXIT_CODE=%s", sigchld_code_to_string(code),
                            "EXIT_STATUS=%i", status,
+                           LOG_UNIT_ID(u),
                            NULL);
 
                 if (s->result == SERVICE_SUCCESS)
@@ -2687,7 +2908,17 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                         if (f == SERVICE_SUCCESS)
                                                 service_enter_start_post(s);
                                         else
-                                                service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+                                                service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
+                                        break;
+                                } else if (s->type == SERVICE_NOTIFY) {
+                                        /* Only enter running through a notification, so that the
+                                         * SERVICE_START state signifies that no ready notification
+                                         * has been received */
+                                        if (f != SERVICE_SUCCESS)
+                                                service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
+                                        else if (!s->remain_after_exit)
+                                                /* The service has never been active */
+                                                service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL);
                                         break;
                                 }
 
@@ -2767,7 +2998,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                 if (f == SERVICE_SUCCESS)
                                         service_enter_start(s);
                                 else
-                                        service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
                                 break;
 
                         case SERVICE_START:
@@ -2776,7 +3007,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                         break;
 
                                 if (f != SERVICE_SUCCESS) {
-                                        service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
                                         break;
                                 }
 
@@ -2793,7 +3024,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                         if (!has_start_post && r < 0) {
                                                 r = service_demand_pid_file(s);
                                                 if (r < 0 || !cgroup_good(s))
-                                                        service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+                                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL);
                                                 break;
                                         }
                                 } else
@@ -2815,7 +3046,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                         if (r < 0) {
                                                 r = service_demand_pid_file(s);
                                                 if (r < 0 || !cgroup_good(s))
-                                                        service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+                                                        service_enter_stop(s, SERVICE_FAILURE_PROTOCOL);
                                                 break;
                                         }
                                 } else
@@ -2874,7 +3105,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
 
         /* If the PID set is empty now, then let's finish this off
            (On unified we use proper notifications) */
-        if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) <= 0 && set_isempty(u->pids))
+        if (cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) == 0 && set_isempty(u->pids))
                 service_notify_cgroup_empty_event(u);
 }
 
@@ -2889,7 +3120,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
         case SERVICE_START_PRE:
         case SERVICE_START:
                 log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre");
-                service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT);
+                service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT);
                 break;
 
         case SERVICE_START_POST:
@@ -3010,7 +3241,18 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
                 if (s->main_pid != 0)
                         log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
                 else
-                        log_unit_debug(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid);
+                        log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid);
+                return;
+        } else if (s->notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
+                if (s->main_pid != 0 && s->control_pid != 0)
+                        log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT,
+                                         pid, s->main_pid, s->control_pid);
+                else if (s->main_pid != 0)
+                        log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
+                else if (s->control_pid != 0)
+                        log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid);
+                else
+                        log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid);
                 return;
         } else
                 log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc);
@@ -3020,6 +3262,10 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
         if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) {
                 if (parse_pid(e, &pid) < 0)
                         log_unit_warning(u, "Failed to parse MAINPID= field in notification message: %s", e);
+                else if (pid == s->control_pid)
+                        log_unit_warning(u, "A control process cannot also be the main process");
+                else if (pid == getpid() || pid == 1)
+                        log_unit_warning(u, "Service manager can't be main process, ignoring sd_notify() MAINPID= field");
                 else {
                         service_set_main_pid(s, pid);
                         unit_watch_pid(UNIT(s), pid);
@@ -3084,9 +3330,7 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
 
                 if (!streq_ptr(s->status_text, t)) {
 
-                        free(s->status_text);
-                        s->status_text = t;
-                        t = NULL;
+                        free_and_replace(s->status_text, t);
 
                         notify_dbus = true;
                 }
@@ -3211,7 +3455,7 @@ static void service_bus_name_owner_change(
                 if (r >= 0)
                         r = sd_bus_creds_get_pid(creds, &pid);
                 if (r >= 0) {
-                        log_unit_debug(u, "D-Bus name %s is now owned by process %u", name, (unsigned) pid);
+                        log_unit_debug(u, "D-Bus name %s is now owned by process " PID_FMT, name, pid);
 
                         service_set_main_pid(s, pid);
                         unit_watch_pid(UNIT(s), pid);
@@ -3243,7 +3487,7 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context
                 if (UNIT(s)->description) {
                         _cleanup_free_ char *a;
 
-                        a = strjoin(UNIT(s)->description, " (", peer, ")", NULL);
+                        a = strjoin(UNIT(s)->description, " (", peer, ")");
                         if (!a)
                                 return -ENOMEM;
 
@@ -3337,6 +3581,7 @@ DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand);
 static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = {
         [NOTIFY_NONE] = "none",
         [NOTIFY_MAIN] = "main",
+        [NOTIFY_EXEC] = "exec",
         [NOTIFY_ALL] = "all"
 };
 
@@ -3354,6 +3599,7 @@ DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState);
 static const char* const service_result_table[_SERVICE_RESULT_MAX] = {
         [SERVICE_SUCCESS] = "success",
         [SERVICE_FAILURE_RESOURCES] = "resources",
+        [SERVICE_FAILURE_PROTOCOL] = "protocol",
         [SERVICE_FAILURE_TIMEOUT] = "timeout",
         [SERVICE_FAILURE_EXIT_CODE] = "exit-code",
         [SERVICE_FAILURE_SIGNAL] = "signal",