]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #9624 from poettering/service-state-flush
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 2 Aug 2018 07:50:39 +0000 (09:50 +0200)
committerGitHub <noreply@github.com>
Thu, 2 Aug 2018 07:50:39 +0000 (09:50 +0200)
flush out ExecStatus structures when a new service cycle begins

1  2 
src/core/execute.c
src/core/execute.h
src/core/mount.c
src/core/service.c
src/core/socket.c
src/core/swap.c
src/systemctl/systemctl.c

diff --combined src/core/execute.c
index ed3e1459df9bc250213d11824ac2bfc34584a2fd,ccfe3e097d325575ade6bd425e998537ba874167..a35dbac9ef12319a4bc3f0d20f87dd57f145896b
@@@ -148,11 -148,11 +148,11 @@@ static int shift_fds(int fds[], size_t 
          return 0;
  }
  
 -static int flags_fds(const int fds[], size_t n_storage_fds, size_t n_socket_fds, bool nonblock) {
 +static int flags_fds(const int fds[], size_t n_socket_fds, size_t n_storage_fds, bool nonblock) {
          size_t i, n_fds;
          int r;
  
 -        n_fds = n_storage_fds + n_socket_fds;
 +        n_fds = n_socket_fds + n_storage_fds;
          if (n_fds <= 0)
                  return 0;
  
@@@ -2573,7 -2573,6 +2573,7 @@@ static int close_remaining_fds
                  const DynamicCreds *dcreds,
                  int user_lookup_fd,
                  int socket_fd,
 +                int exec_fd,
                  int *fds, size_t n_fds) {
  
          size_t n_dont_close = 0;
  
          if (socket_fd >= 0)
                  dont_close[n_dont_close++] = socket_fd;
 +        if (exec_fd >= 0)
 +                dont_close[n_dont_close++] = exec_fd;
          if (n_fds > 0) {
                  memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds);
                  n_dont_close += n_fds;
@@@ -2724,21 -2721,19 +2724,20 @@@ static int exec_child
                  const ExecParameters *params,
                  ExecRuntime *runtime,
                  DynamicCreds *dcreds,
-                 char **argv,
                  int socket_fd,
                  int named_iofds[3],
                  int *fds,
 -                size_t n_storage_fds,
                  size_t n_socket_fds,
 +                size_t n_storage_fds,
                  char **files_env,
                  int user_lookup_fd,
                  int *exit_status) {
  
          _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL;
 -        _cleanup_free_ char *home_buffer = NULL;
 +        int *fds_with_exec_fd, n_fds_with_exec_fd, r, ngids = 0, exec_fd = -1;
          _cleanup_free_ gid_t *supplementary_gids = NULL;
          const char *username = NULL, *groupname = NULL;
 +        _cleanup_free_ char *home_buffer = NULL;
          const char *home = NULL, *shell = NULL;
          dev_t journal_stream_dev = 0;
          ino_t journal_stream_ino = 0;
  #endif
          uid_t uid = UID_INVALID;
          gid_t gid = GID_INVALID;
 -        int r, ngids = 0;
          size_t n_fds;
          ExecDirectoryType dt;
          int secure_bits;
          /* In case anything used libc syslog(), close this here, too */
          closelog();
  
 -        n_fds = n_storage_fds + n_socket_fds;
 -        r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds);
 +        n_fds = n_socket_fds + n_storage_fds;
 +        r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, params->exec_fd, fds, n_fds);
          if (r < 0) {
                  *exit_status = EXIT_FDS;
                  return log_unit_error_errno(unit, r, "Failed to close unwanted file descriptors: %m");
                  const char *vc = params->confirm_spawn;
                  _cleanup_free_ char *cmdline = NULL;
  
-                 cmdline = exec_command_line(argv);
+                 cmdline = exec_command_line(command->argv);
                  if (!cmdline) {
                          *exit_status = EXIT_MEMORY;
                          return log_oom();
                  }
          }
  
 +        /* We are about to invoke NSS and PAM modules. Let's tell them what we are doing here, maybe they care. This is
 +         * used by nss-resolve to disable itself when we are about to start systemd-resolved, to avoid deadlocks. Note
 +         * that these env vars do not survive the execve(), which means they really only apply to the PAM and NSS
 +         * invocations themselves. Also note that while we'll only invoke NSS modules involved in user management they
 +         * might internally call into other NSS modules that are involved in hostname resolution, we never know. */
 +        if (setenv("SYSTEMD_ACTIVATION_UNIT", unit->id, true) != 0 ||
 +            setenv("SYSTEMD_ACTIVATION_SCOPE", MANAGER_IS_SYSTEM(unit->manager) ? "system" : "user", true) != 0) {
 +                *exit_status = EXIT_MEMORY;
 +                return log_unit_error_errno(unit, errno, "Failed to update environment: %m");
 +        }
 +
          if (context->dynamic_user && dcreds) {
                  _cleanup_strv_free_ char **suggested_paths = NULL;
  
 -                /* Make sure we bypass our own NSS module for any NSS checks */
 +                /* On top of that, make sure we bypass our own NSS module nss-systemd comprehensively for any NSS
 +                 * checks, if DynamicUser=1 is used, as we shouldn't create a feedback loop with ourselves here.*/
                  if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) {
                          *exit_status = EXIT_USER;
                          return log_unit_error_errno(unit, errno, "Failed to update environment: %m");
          }
  
          /* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that we are
 -         * more aggressive this time since socket_fd and the netns fds we don't need anymore. The custom endpoint fd
 -         * was needed to upload the policy and can now be closed as well. */
 -        r = close_all_fds(fds, n_fds);
 +         * more aggressive this time since socket_fd and the netns fds we don't need anymore. We do keep the exec_fd
 +         * however if we have it as we want to keep it open until the final execve(). */
 +
 +        if (params->exec_fd >= 0) {
 +                exec_fd = params->exec_fd;
 +
 +                if (exec_fd < 3 + (int) n_fds) {
 +                        int moved_fd;
 +
 +                        /* Let's move the exec fd far up, so that it's outside of the fd range we want to pass to the
 +                         * process we are about to execute. */
 +
 +                        moved_fd = fcntl(exec_fd, F_DUPFD_CLOEXEC, 3 + (int) n_fds);
 +                        if (moved_fd < 0) {
 +                                *exit_status = EXIT_FDS;
 +                                return log_unit_error_errno(unit, errno, "Couldn't move exec fd up: %m");
 +                        }
 +
 +                        safe_close(exec_fd);
 +                        exec_fd = moved_fd;
 +                } else {
 +                        /* This fd should be FD_CLOEXEC already, but let's make sure. */
 +                        r = fd_cloexec(exec_fd, true);
 +                        if (r < 0) {
 +                                *exit_status = EXIT_FDS;
 +                                return log_unit_error_errno(unit, r, "Failed to make exec fd FD_CLOEXEC: %m");
 +                        }
 +                }
 +
 +                fds_with_exec_fd = newa(int, n_fds + 1);
 +                memcpy(fds_with_exec_fd, fds, n_fds * sizeof(int));
 +                fds_with_exec_fd[n_fds] = exec_fd;
 +                n_fds_with_exec_fd = n_fds + 1;
 +        } else {
 +                fds_with_exec_fd = fds;
 +                n_fds_with_exec_fd = n_fds;
 +        }
 +
 +        r = close_all_fds(fds_with_exec_fd, n_fds_with_exec_fd);
          if (r >= 0)
                  r = shift_fds(fds, n_fds);
          if (r >= 0)
 -                r = flags_fds(fds, n_storage_fds, n_socket_fds, context->non_blocking);
 +                r = flags_fds(fds, n_socket_fds, n_storage_fds, context->non_blocking);
          if (r < 0) {
                  *exit_status = EXIT_FDS;
                  return log_unit_error_errno(unit, r, "Failed to adjust passed file descriptors: %m");
          }
  
 +        /* At this point, the fds we want to pass to the program are all ready and set up, with O_CLOEXEC turned off
 +         * and at the right fd numbers. The are no other fds open, with one exception: the exec_fd if it is defined,
 +         * and it has O_CLOEXEC set, after all we want it to be closed by the execve(), so that our parent knows we
 +         * came this far. */
 +
          secure_bits = context->secure_bits;
  
          if (needs_sandboxing) {
                  strv_free_and_replace(accum_env, ee);
          }
  
-         final_argv = replace_env_argv(argv, accum_env);
+         final_argv = replace_env_argv(command->argv, accum_env);
          if (!final_argv) {
                  *exit_status = EXIT_MEMORY;
                  return log_oom();
                                     LOG_UNIT_INVOCATION_ID(unit));
          }
  
 +        if (exec_fd >= 0) {
 +                uint8_t hot = 1;
 +
 +                /* We have finished with all our initializations. Let's now let the manager know that. From this point
 +                 * on, if the manager sees POLLHUP on the exec_fd, then execve() was successful. */
 +
 +                if (write(exec_fd, &hot, sizeof(hot)) < 0) {
 +                        *exit_status = EXIT_EXEC;
 +                        return log_unit_error_errno(unit, errno, "Failed to enable exec_fd: %m");
 +                }
 +        }
 +
          execve(command->path, final_argv, accum_env);
 +        r = -errno;
  
 -        if (errno == ENOENT && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) {
 -                log_struct_errno(LOG_INFO, errno,
 +        if (exec_fd >= 0) {
 +                uint8_t hot = 0;
 +
 +                /* The execve() failed. This means the exec_fd is still open. Which means we need to tell the manager
 +                 * that POLLHUP on it no longer means execve() succeeded. */
 +
 +                if (write(exec_fd, &hot, sizeof(hot)) < 0) {
 +                        *exit_status = EXIT_EXEC;
 +                        return log_unit_error_errno(unit, errno, "Failed to disable exec_fd: %m");
 +                }
 +        }
 +
 +        if (r == -ENOENT && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) {
 +                log_struct_errno(LOG_INFO, r,
                                   "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR,
                                   LOG_UNIT_ID(unit),
                                   LOG_UNIT_INVOCATION_ID(unit),
          }
  
          *exit_status = EXIT_EXEC;
 -        return log_unit_error_errno(unit, errno, "Failed to execute command: %m");
 +        return log_unit_error_errno(unit, r, "Failed to execute command: %m");
  }
  
  static int exec_context_load_environment(const Unit *unit, const ExecContext *c, char ***l);
@@@ -3523,13 -3441,10 +3522,10 @@@ int exec_spawn(Unit *unit
                 DynamicCreds *dcreds,
                 pid_t *ret) {
  
+         int socket_fd, r, named_iofds[3] = { -1, -1, -1 }, *fds = NULL;
          _cleanup_strv_free_ char **files_env = NULL;
-         int *fds = NULL;
          size_t n_storage_fds = 0, n_socket_fds = 0;
          _cleanup_free_ char *line = NULL;
-         int socket_fd, r;
-         int named_iofds[3] = { -1, -1, -1 };
-         char **argv;
          pid_t pid;
  
          assert(unit);
          assert(context);
          assert(ret);
          assert(params);
 -        assert(params->fds || (params->n_storage_fds + params->n_socket_fds <= 0));
 +        assert(params->fds || (params->n_socket_fds + params->n_storage_fds <= 0));
  
          if (context->std_input == EXEC_INPUT_SOCKET ||
              context->std_output == EXEC_OUTPUT_SOCKET ||
          } else {
                  socket_fd = -1;
                  fds = params->fds;
 -                n_storage_fds = params->n_storage_fds;
                  n_socket_fds = params->n_socket_fds;
 +                n_storage_fds = params->n_storage_fds;
          }
  
          r = exec_context_named_iofds(context, params, named_iofds);
          if (r < 0)
                  return log_unit_error_errno(unit, r, "Failed to load environment files: %m");
  
-         argv = params->argv ?: command->argv;
-         line = exec_command_line(argv);
+         line = exec_command_line(command->argv);
          if (!line)
                  return log_oom();
  
                                 params,
                                 runtime,
                                 dcreds,
-                                argv,
                                 socket_fd,
                                 named_iofds,
                                 fds,
 -                               n_storage_fds,
                                 n_socket_fds,
 +                               n_storage_fds,
                                 files_env,
                                 unit->manager->user_lookup_fds[1],
                                 &exit_status);
@@@ -3743,7 -3656,6 +3737,6 @@@ static void exec_command_done(ExecComma
          assert(c);
  
          c->path = mfree(c->path);
          c->argv = strv_free(c->argv);
  }
  
@@@ -3773,6 -3685,24 +3766,24 @@@ void exec_command_free_array(ExecComman
                  c[i] = exec_command_free_list(c[i]);
  }
  
+ void exec_command_reset_status_array(ExecCommand *c, size_t n) {
+         size_t i;
+         for (i = 0; i < n; i++)
+                 exec_status_reset(&c[i].exec_status);
+ }
+ void exec_command_reset_status_list_array(ExecCommand **c, size_t n) {
+         size_t i;
+         for (i = 0; i < n; i++) {
+                 ExecCommand *z;
+                 LIST_FOREACH(command, z, c[i])
+                         exec_status_reset(&z->exec_status);
+         }
+ }
  typedef struct InvalidEnvInfo {
          const Unit *unit;
          const char *path;
@@@ -4423,18 -4353,22 +4434,22 @@@ void exec_context_free_log_extra_fields
  void exec_status_start(ExecStatus *s, pid_t pid) {
          assert(s);
  
-         zero(*s);
-         s->pid = pid;
+         *s = (ExecStatus) {
+                 .pid = pid,
+         };
          dual_timestamp_get(&s->start_timestamp);
  }
  
  void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status) {
          assert(s);
  
-         if (s->pid && s->pid != pid)
-                 zero(*s);
+         if (s->pid != pid) {
+                 *s = (ExecStatus) {
+                         .pid = pid,
+                 };
+         }
  
-         s->pid = pid;
          dual_timestamp_get(&s->exit_timestamp);
  
          s->code = code;
  
          if (context) {
                  if (context->utmp_id)
-                         utmp_put_dead_process(context->utmp_id, pid, code, status);
+                         (void) utmp_put_dead_process(context->utmp_id, pid, code, status);
  
                  exec_context_tty_reset(context, NULL);
          }
  }
  
+ void exec_status_reset(ExecStatus *s) {
+         assert(s);
+         *s = (ExecStatus) {};
+ }
  void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix) {
          char buf[FORMAT_TIMESTAMP_MAX];
  
diff --combined src/core/execute.h
index cd5165c2d1297e386e20428a4284aef1223c18a9,678ee5b1893aabbc2642239c776fcbb44f63b52e..2e8b01f7695f97f9899e195a0187689cb62533b4
@@@ -77,21 -77,23 +77,23 @@@ typedef enum ExecKeyringMode 
          _EXEC_KEYRING_MODE_INVALID = -1,
  } ExecKeyringMode;
  
+ /* Contains start and exit information about an executed command.  */
  struct ExecStatus {
+         pid_t pid;
          dual_timestamp start_timestamp;
          dual_timestamp exit_timestamp;
-         pid_t pid;
          int code;     /* as in siginfo_t::si_code */
          int status;   /* as in sigingo_t::si_status */
  };
  
  typedef enum ExecCommandFlags {
 -        EXEC_COMMAND_IGNORE_FAILURE = 1,
 -        EXEC_COMMAND_FULLY_PRIVILEGED = 2,
 -        EXEC_COMMAND_NO_SETUID = 4,
 -        EXEC_COMMAND_AMBIENT_MAGIC = 8,
 +        EXEC_COMMAND_IGNORE_FAILURE   = 1 << 0,
 +        EXEC_COMMAND_FULLY_PRIVILEGED = 1 << 1,
 +        EXEC_COMMAND_NO_SETUID        = 1 << 2,
 +        EXEC_COMMAND_AMBIENT_MAGIC    = 1 << 3,
  } ExecCommandFlags;
  
+ /* Stores information about commands we execute. Covers both configuration settings as well as runtime data. */
  struct ExecCommand {
          char *path;
          char **argv;
          LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */
  };
  
+ /* Encapsulates certain aspects of the runtime environment that is to be shared between multiple otherwise separate
+  * invocations of commands. Specifically, this allows sharing of /tmp and /var/tmp data as well as network namespaces
+  * between invocations of commands. This is a reference counted object, with one reference taken by each currently
+  * active command invocation that wants to share this runtime. */
  struct ExecRuntime {
          int n_ref;
  
          Manager *manager;
  
-         /* unit id of the owner */
-         char *id;
+         char *id; /* Unit id of the owner */
  
          char *tmp_dir;
          char *var_tmp_dir;
@@@ -131,6 -136,9 +136,9 @@@ typedef struct ExecDirectory 
          mode_t mode;
  } ExecDirectory;
  
+ /* Encodes configuration parameters applied to invoked commands. Does not carry runtime data, but only configuration
+  * changes sourced from unit files and suchlike. ExecContext objects are usually embedded into Unit objects, and do not
+  * change after being loaded. */
  struct ExecContext {
          char **environment;
          char **environment_files;
@@@ -291,14 -299,15 +299,15 @@@ typedef enum ExecFlags 
          EXEC_SET_WATCHDOG      = 1 << 11,
  } ExecFlags;
  
+ /* Parameters for a specific invocation of a command. This structure is put together right before a command is
+  * executed. */
  struct ExecParameters {
-         char **argv;
          char **environment;
  
          int *fds;
          char **fd_names;
 -        size_t n_storage_fds;
          size_t n_socket_fds;
 +        size_t n_storage_fds;
  
          ExecFlags flags;
          bool selinux_context_net:1;
          int stdin_fd;
          int stdout_fd;
          int stderr_fd;
 +
 +        /* An fd that is closed by the execve(), and thus will result in EOF when the execve() is done */
 +        int exec_fd;
  };
  
  #include "unit.h"
@@@ -334,10 -340,10 +343,10 @@@ int exec_spawn(Unit *unit
                 pid_t *ret);
  
  void exec_command_done_array(ExecCommand *c, size_t n);
  ExecCommand* exec_command_free_list(ExecCommand *c);
  void exec_command_free_array(ExecCommand **c, size_t n);
+ void exec_command_reset_status_array(ExecCommand *c, size_t n);
+ void exec_command_reset_status_list_array(ExecCommand **c, size_t n);
  void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix);
  void exec_command_append_list(ExecCommand **l, ExecCommand *e);
  int exec_command_set(ExecCommand *c, const char *path, ...);
@@@ -361,6 -367,7 +370,7 @@@ void exec_context_free_log_extra_fields
  void exec_status_start(ExecStatus *s, pid_t pid);
  void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status);
  void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix);
+ void exec_status_reset(ExecStatus *s);
  
  int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *name, bool create, ExecRuntime **ret);
  ExecRuntime *exec_runtime_unref(ExecRuntime *r, bool destroy);
diff --combined src/core/mount.c
index 16229d4af1d838b14f6b2c25f96ca459722020d4,75e1b6ac53017059003085f49a5cf25a4dd9599b..55d851d8c0ba3aec2cae0538e7e5c7df30010e9d
@@@ -747,11 -747,10 +747,11 @@@ static void mount_dump(Unit *u, FILE *f
  static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
  
          ExecParameters exec_params = {
 -                .flags      = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN,
 -                .stdin_fd   = -1,
 -                .stdout_fd  = -1,
 -                .stderr_fd  = -1,
 +                .flags     = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN,
 +                .stdin_fd  = -1,
 +                .stdout_fd = -1,
 +                .stderr_fd = -1,
 +                .exec_fd   = -1,
          };
          pid_t pid;
          int r;
@@@ -1075,6 -1074,7 +1075,7 @@@ static int mount_start(Unit *u) 
  
          m->result = MOUNT_SUCCESS;
          m->reload_result = MOUNT_SUCCESS;
+         exec_command_reset_status_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX);
  
          u->reset_accounting = true;
  
diff --combined src/core/service.c
index 6abf87cb3944ea0da64e8e7ef0c7c905e429dcb2,7476ee533395071c979681013716fcaeb68c9043..722a9e9ae7792e103fe1af6bfd4501573f2aaf7a
@@@ -79,10 -79,9 +79,10 @@@ static const UnitActiveState state_tran
          [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
  };
  
 -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
 +static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
  static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata);
  static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata);
 +static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
  
  static void service_enter_signal(Service *s, ServiceState state, ServiceResult f);
  static void service_enter_reload_by_notify(Service *s);
@@@ -390,7 -389,6 +390,7 @@@ static void service_done(Unit *u) 
          service_stop_watchdog(s);
  
          s->timer_event_source = sd_event_source_unref(s->timer_event_source);
 +        s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source);
  
          service_release_resources(u);
  }
@@@ -1068,9 -1066,6 +1068,9 @@@ static void service_set_state(Service *
              !(state == SERVICE_DEAD && UNIT(s)->job))
                  service_close_socket_fd(s);
  
 +        if (state != SERVICE_START)
 +                s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source);
 +
          if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
                  service_stop_watchdog(s);
  
@@@ -1183,23 -1178,21 +1183,23 @@@ static int service_coldplug(Unit *u) 
          return 0;
  }
  
 -static int service_collect_fds(Service *s,
 -                               int **fds,
 -                               char ***fd_names,
 -                               unsigned *n_storage_fds,
 -                               unsigned *n_socket_fds) {
 +static int service_collect_fds(
 +                Service *s,
 +                int **fds,
 +                char ***fd_names,
 +                size_t *n_socket_fds,
 +                size_t *n_storage_fds) {
  
          _cleanup_strv_free_ char **rfd_names = NULL;
          _cleanup_free_ int *rfds = NULL;
 -        unsigned rn_socket_fds = 0, rn_storage_fds = 0;
 +        size_t rn_socket_fds = 0, rn_storage_fds = 0;
          int r;
  
          assert(s);
          assert(fds);
          assert(fd_names);
          assert(n_socket_fds);
 +        assert(n_storage_fds);
  
          if (s->socket_fd >= 0) {
  
  
          if (s->n_fd_store > 0) {
                  ServiceFDStore *fs;
 -                unsigned n_fds;
 +                size_t n_fds;
                  char **nl;
                  int *t;
  
          return 0;
  }
  
 +static int service_allocate_exec_fd_event_source(
 +                Service *s,
 +                int fd,
 +                sd_event_source **ret_event_source) {
 +
 +        _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL;
 +        int r;
 +
 +        assert(s);
 +        assert(fd >= 0);
 +        assert(ret_event_source);
 +
 +        r = sd_event_add_io(UNIT(s)->manager->event, &source, fd, 0, service_dispatch_exec_io, s);
 +        if (r < 0)
 +                return log_unit_error_errno(UNIT(s), r, "Failed to allocate exec_fd event source: %m");
 +
 +        /* This is a bit lower priority than SIGCHLD, as that carries a lot more interesting failure information */
 +
 +        r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_NORMAL-3);
 +        if (r < 0)
 +                return log_unit_error_errno(UNIT(s), r, "Failed to adjust priority of exec_fd event source: %m");
 +
 +        (void) sd_event_source_set_description(source, "service event_fd");
 +
 +        r = sd_event_source_set_io_fd_own(source, true);
 +        if (r < 0)
 +                return log_unit_error_errno(UNIT(s), r, "Failed to pass ownership of fd to event source: %m");
 +
 +        *ret_event_source = TAKE_PTR(source);
 +        return 0;
 +}
 +
 +static int service_allocate_exec_fd(
 +                Service *s,
 +                sd_event_source **ret_event_source,
 +                int* ret_exec_fd) {
 +
 +        _cleanup_close_pair_ int p[2] = { -1, -1 };
 +        int r;
 +
 +        assert(s);
 +        assert(ret_event_source);
 +        assert(ret_exec_fd);
 +
 +        if (pipe2(p, O_CLOEXEC|O_NONBLOCK) < 0)
 +                return log_unit_error_errno(UNIT(s), errno, "Failed to allocate exec_fd pipe: %m");
 +
 +        r = service_allocate_exec_fd_event_source(s, p[0], ret_event_source);
 +        if (r < 0)
 +                return r;
 +
 +        p[0] = -1;
 +        *ret_exec_fd = TAKE_FD(p[1]);
 +
 +        return 0;
 +}
 +
  static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) {
          assert(s);
  
@@@ -1389,12 -1325,9 +1389,12 @@@ static int service_spawn
                  .stdin_fd   = -1,
                  .stdout_fd  = -1,
                  .stderr_fd  = -1,
 +                .exec_fd    = -1,
          };
          _cleanup_strv_free_ char **final_env = NULL, **our_env = NULL, **fd_names = NULL;
 -        unsigned n_storage_fds = 0, n_socket_fds = 0, n_env = 0;
 +        _cleanup_(sd_event_source_unrefp) sd_event_source *exec_fd_source = NULL;
 +        size_t n_socket_fds = 0, n_storage_fds = 0, n_env = 0;
 +        _cleanup_close_ int exec_fd = -1;
          _cleanup_free_ int *fds = NULL;
          pid_t pid;
          int r;
              s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
              s->exec_context.std_error == EXEC_OUTPUT_SOCKET) {
  
 -                r = service_collect_fds(s, &fds, &fd_names, &n_storage_fds, &n_socket_fds);
 +                r = service_collect_fds(s, &fds, &fd_names, &n_socket_fds, &n_storage_fds);
                  if (r < 0)
                          return r;
  
 -                log_unit_debug(UNIT(s), "Passing %i fds to service", n_storage_fds + n_socket_fds);
 +                log_unit_debug(UNIT(s), "Passing %zu fds to service", n_socket_fds + n_storage_fds);
 +        }
 +
 +        if (!FLAGS_SET(flags, EXEC_IS_CONTROL) && s->type == SERVICE_EXEC) {
 +                assert(!s->exec_fd_event_source);
 +
 +                r = service_allocate_exec_fd(s, &exec_fd_source, &exec_fd);
 +                if (r < 0)
 +                        return r;
          }
  
          r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout));
          SET_FLAG(exec_params.flags, EXEC_NSS_BYPASS_BUS,
                   MANAGER_IS_SYSTEM(UNIT(s)->manager) && unit_has_name(UNIT(s), SPECIAL_DBUS_SERVICE));
  
-         exec_params.argv = c->argv;
          exec_params.environment = final_env;
          exec_params.fds = fds;
          exec_params.fd_names = fd_names;
 -        exec_params.n_storage_fds = n_storage_fds;
          exec_params.n_socket_fds = n_socket_fds;
 +        exec_params.n_storage_fds = n_storage_fds;
          exec_params.watchdog_usec = s->watchdog_usec;
          exec_params.selinux_context_net = s->socket_fd_selinux_context_net;
          if (s->type == SERVICE_IDLE)
          exec_params.stdin_fd = s->stdin_fd;
          exec_params.stdout_fd = s->stdout_fd;
          exec_params.stderr_fd = s->stderr_fd;
 +        exec_params.exec_fd = exec_fd;
  
          r = exec_spawn(UNIT(s),
                         c,
          if (r < 0)
                  return r;
  
 +        s->exec_fd_event_source = TAKE_PTR(exec_fd_source);
 +        s->exec_fd_hot = false;
 +
          r = unit_watch_pid(UNIT(s), pid);
          if (r < 0) /* FIXME: we need to do something here */
                  return r;
@@@ -1753,7 -1673,6 +1752,6 @@@ static void service_enter_stop_post(Ser
                  s->result = f;
  
          service_unwatch_control_pid(s);
          (void) unit_enqueue_rewatch_pids(UNIT(s));
  
          s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST];
@@@ -2060,12 -1979,14 +2058,12 @@@ static void service_enter_start(Servic
                  s->control_pid = pid;
                  service_set_state(s, SERVICE_START);
  
 -        } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) {
 +        } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_EXEC)) {
  
 -                /* For oneshot services we wait until the start
 -                 * process exited, too, but it is our main process. */
 +                /* For oneshot services we wait until the start process exited, too, but it is our main process. */
  
 -                /* For D-Bus services we know the main pid right away,
 -                 * but wait for the bus name to appear on the
 -                 * bus. Notify services are similar. */
 +                /* For D-Bus services we know the main pid right away, but wait for the bus name to appear on the
 +                 * bus. 'notify' and 'exec' services are similar. */
  
                  service_set_main_pid(s, pid);
                  service_set_state(s, SERVICE_START);
@@@ -2330,8 -2251,6 +2328,6 @@@ static int service_start(Unit *u) 
          s->main_pid_alien = false;
          s->forbid_restart = false;
  
-         u->reset_accounting = true;
          s->status_text = mfree(s->status_text);
          s->status_errno = 0;
  
          s->watchdog_override_enable = false;
          s->watchdog_override_usec = 0;
  
+         exec_command_reset_status_list_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
+         exec_status_reset(&s->main_exec_status);
          /* This is not an automatic restart? Flush the restart counter then */
          if (s->flush_n_restarts) {
                  s->n_restarts = 0;
                  s->flush_n_restarts = false;
          }
  
+         u->reset_accounting = true;
          service_enter_start_pre(s);
          return 1;
  }
@@@ -2518,13 -2442,6 +2519,13 @@@ static int service_serialize(Unit *u, F
          if (r < 0)
                  return r;
  
 +        if (s->exec_fd_event_source) {
 +                r = unit_serialize_item_fd(u, f, fds, "exec-fd", sd_event_source_get_io_fd(s->exec_fd_event_source));
 +                if (r < 0)
 +                        return r;
 +                unit_serialize_item(u, f, "exec-fd-hot", yes_no(s->exec_fd_hot));
 +        }
 +
          if (UNIT_ISSET(s->accept_socket)) {
                  r = unit_serialize_item(u, f, "accept-socket", UNIT_DEREF(s->accept_socket)->id);
                  if (r < 0)
@@@ -2858,18 -2775,6 +2859,18 @@@ static int service_deserialize_item(Uni
                          s->stderr_fd = fdset_remove(fds, fd);
                          s->exec_context.stdio_as_fds = true;
                  }
 +        } else if (streq(key, "exec-fd")) {
 +                int fd;
 +
 +                if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
 +                        log_unit_debug(u, "Failed to parse exec-fd value: %s", value);
 +                else {
 +                        s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source);
 +
 +                        fd = fdset_remove(fds, fd);
 +                        if (service_allocate_exec_fd_event_source(s, fd, &s->exec_fd_event_source) < 0)
 +                                safe_close(fd);
 +                }
          } else if (streq(key, "watchdog-override-usec")) {
                  usec_t watchdog_override_usec;
                  if (timestamp_deserialize(value, &watchdog_override_usec) < 0)
@@@ -2953,7 -2858,7 +2954,7 @@@ static int service_watch_pid_file(Servi
  
          log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path);
  
 -        r = path_spec_watch(s->pid_file_pathspec, service_dispatch_io);
 +        r = path_spec_watch(s->pid_file_pathspec, service_dispatch_inotify_io);
          if (r < 0)
                  goto fail;
  
@@@ -2997,7 -2902,7 +2998,7 @@@ static int service_demand_pid_file(Serv
          return service_watch_pid_file(s);
  }
  
 -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata) {
 +static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata) {
          PathSpec *p = userdata;
          Service *s;
  
@@@ -3030,59 -2935,6 +3031,59 @@@ fail
          return 0;
  }
  
 +static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata) {
 +        Service *s = SERVICE(userdata);
 +
 +        assert(s);
 +
 +        log_unit_debug(UNIT(s), "got exec-fd event");
 +
 +        /* If Type=exec is set, we'll consider a service started successfully the instant we invoked execve()
 +         * successfully for it. We implement this through a pipe() towards the child, which the kernel automatically
 +         * closes for us due to O_CLOEXEC on execve() in the child, which then triggers EOF on the pipe in the
 +         * parent. We need to be careful however, as there are other reasons that we might cause the child's side of
 +         * the pipe to be closed (for example, a simple exit()). To deal with that we'll ignore EOFs on the pipe unless
 +         * the child signalled us first that it is about to call the execve(). It does so by sending us a simple
 +         * non-zero byte via the pipe. We also provide the child with a way to inform us in case execve() failed: if it
 +         * sends a zero byte we'll ignore POLLHUP on the fd again. */
 +
 +        for (;;) {
 +                uint8_t x;
 +                ssize_t n;
 +
 +                n = read(fd, &x, sizeof(x));
 +                if (n < 0) {
 +                        if (errno == EAGAIN) /* O_NONBLOCK in effect → everything queued has now been processed. */
 +                                return 0;
 +
 +                        return log_unit_error_errno(UNIT(s), errno, "Failed to read from exec_fd: %m");
 +                }
 +                if (n == 0) { /* EOF → the event we are waiting for */
 +
 +                        s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source);
 +
 +                        if (s->exec_fd_hot) { /* Did the child tell us to expect EOF now? */
 +                                log_unit_debug(UNIT(s), "Got EOF on exec-fd");
 +
 +                                s->exec_fd_hot = false;
 +
 +                                /* Nice! This is what we have been waiting for. Transition to next state. */
 +                                if (s->type == SERVICE_EXEC && s->state == SERVICE_START)
 +                                        service_enter_start_post(s);
 +                        } else
 +                                log_unit_debug(UNIT(s), "Got EOF on exec-fd while it was disabled, ignoring.");
 +
 +                        return 0;
 +                }
 +
 +                /* A byte was read → this turns on/off the exec fd logic */
 +                assert(n == sizeof(x));
 +                s->exec_fd_hot = x;
 +        }
 +
 +        return 0;
 +}
 +
  static void service_notify_cgroup_empty_event(Unit *u) {
          Service *s = SERVICE(u);
  
@@@ -3992,8 -3844,7 +3993,8 @@@ static const char* const service_type_t
          [SERVICE_ONESHOT] = "oneshot",
          [SERVICE_DBUS] = "dbus",
          [SERVICE_NOTIFY] = "notify",
 -        [SERVICE_IDLE] = "idle"
 +        [SERVICE_IDLE] = "idle",
 +        [SERVICE_EXEC] = "exec",
  };
  
  DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
diff --combined src/core/socket.c
index d488c64e914e5792a525f085ebbed93a48594a96,a6127654b5229f479771c526872e2016c1c0badf..aedbf51a4cea2d057fa5b56a7fba80e1662243d4
@@@ -1867,11 -1867,10 +1867,11 @@@ static int socket_coldplug(Unit *u) 
  static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
  
          ExecParameters exec_params = {
 -                .flags      = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN,
 -                .stdin_fd   = -1,
 -                .stdout_fd  = -1,
 -                .stderr_fd  = -1,
 +                .flags     = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN,
 +                .stdin_fd  = -1,
 +                .stdout_fd = -1,
 +                .stderr_fd = -1,
 +                .exec_fd   = -1,
          };
          pid_t pid;
          int r;
  
          unit_set_exec_params(UNIT(s), &exec_params);
  
-         exec_params.argv = c->argv;
          r = exec_spawn(UNIT(s),
                         c,
                         &s->exec_context,
@@@ -2460,6 -2457,7 +2458,7 @@@ static int socket_start(Unit *u) 
                  return r;
  
          s->result = SOCKET_SUCCESS;
+         exec_command_reset_status_list_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX);
  
          u->reset_accounting = true;
  
diff --combined src/core/swap.c
index e01e61e56d51f29a969788a7ec2fa96a5ca1eb8b,f8d6a4dd22f5fb555f9352f3e28ced9383d07c1a..000c028e77361ac8b7241f3f2df70c734b36cf54
@@@ -606,7 -606,6 +606,7 @@@ static int swap_spawn(Swap *s, ExecComm
                  .stdin_fd  = -1,
                  .stdout_fd = -1,
                  .stderr_fd = -1,
 +                .exec_fd   = -1,
          };
          pid_t pid;
          int r;
@@@ -854,6 -853,7 +854,7 @@@ static int swap_start(Unit *u) 
                  return r;
  
          s->result = SWAP_SUCCESS;
+         exec_command_reset_status_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX);
  
          u->reset_accounting = true;
  
index 64584e4a8623d8938f7a67793b2a69d94a8456cb,93c1979d19abfcc32482f4e353e9a5a76f1a7e3b..abd4b92a24d9850c7212b7784f7be306e32a1ea8
@@@ -4548,6 -4548,7 +4548,7 @@@ static int map_asserts(sd_bus *bus, con
  
  static int map_exec(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
          _cleanup_free_ ExecStatusInfo *info = NULL;
+         ExecStatusInfo *last;
          UnitStatusInfo *i = userdata;
          int r;
  
          if (!info)
                  return -ENOMEM;
  
+         LIST_FIND_TAIL(exec, i->exec, last);
          while ((r = exec_status_info_deserialize(m, info)) > 0) {
  
                  info->name = strdup(member);
                  if (!info->name)
                          return -ENOMEM;
  
-                 LIST_PREPEND(exec, i->exec, info);
+                 LIST_INSERT_AFTER(exec, i->exec, last, info);
+                 last = info;
  
                  info = new0(ExecStatusInfo, 1);
                  if (!info)
@@@ -4927,7 -4931,7 +4931,7 @@@ typedef enum SystemctlShowMode
          _SYSTEMCTL_SHOW_MODE_INVALID = -1,
  } SystemctlShowMode;
  
 -static const char* const systemctl_show_mode_table[] = {
 +static const char* const systemctl_show_mode_table[_SYSTEMCTL_SHOW_MODE_MAX] = {
          [SYSTEMCTL_SHOW_PROPERTIES] = "show",
          [SYSTEMCTL_SHOW_STATUS] = "status",
          [SYSTEMCTL_SHOW_HELP] = "help",