chiark / gitweb /
rework tty handling
authorLennart Poettering <lennart@poettering.net>
Tue, 13 Apr 2010 00:06:27 +0000 (02:06 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 13 Apr 2010 00:06:27 +0000 (02:06 +0200)
We now make sure to run all services in their own session, possibly with
a controlling terminal.

This also extends the service and socket state machines a little.

16 files changed:
execute.c
execute.h
load-fragment.c
main.c
manager.c
manager.h
mount.c
service.c
service.h
socket.c
socket.h
test-engine.c
unit.c
unit.h
util.c
util.h

index 3d2e511..065296b 100644 (file)
--- a/execute.c
+++ b/execute.c
@@ -115,130 +115,320 @@ static int flags_fds(int fds[], unsigned n_fds, bool nonblock) {
         return 0;
 }
 
-static int replace_null_fd(int fd, int flags) {
-        int nfd;
-        assert(fd >= 0);
+static const char *tty_path(const ExecContext *context) {
+        assert(context);
+
+        if (context->tty_path)
+                return context->tty_path;
+
+        return "/dev/console";
+}
+
+static int open_null_as(int flags, int nfd) {
+        int fd, r;
 
-        close_nointr(fd);
+        assert(nfd >= 0);
 
-        if ((nfd = open("/dev/null", flags|O_NOCTTY)) < 0)
+        if ((fd = open("/dev/null", flags|O_NOCTTY)) < 0)
                 return -errno;
 
-        if (nfd != fd) {
-                close_nointr_nofail(nfd);
-                return -EIO;
-        }
+        if (fd != nfd) {
+                r = dup2(fd, nfd) < 0 ? -errno : nfd;
+                close_nointr(fd);
+        } else
+                r = nfd;
 
-        return 0;
+        return r;
 }
 
-static int setup_output(const ExecContext *context, const char *ident) {
-        int r;
+static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, int nfd) {
+        int fd, r;
+        union {
+                struct sockaddr sa;
+                struct sockaddr_un un;
+        } sa;
 
         assert(context);
+        assert(output < _EXEC_OUTPUT_MAX);
+        assert(ident);
+        assert(nfd >= 0);
 
-        switch (context->output) {
+        if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+                return -errno;
 
-        case EXEC_OUTPUT_CONSOLE:
-                return 0;
+        zero(sa);
+        sa.sa.sa_family = AF_UNIX;
+        strncpy(sa.un.sun_path+1, LOGGER_SOCKET, sizeof(sa.un.sun_path)-1);
 
-        case EXEC_OUTPUT_NULL:
+        if (connect(fd, &sa.sa, sizeof(sa)) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
 
-                if ((r = replace_null_fd(STDOUT_FILENO, O_WRONLY)) < 0 ||
-                    (r = replace_null_fd(STDERR_FILENO, O_WRONLY)) < 0)
-                        return r;
+        if (shutdown(fd, SHUT_RD) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
 
-                return 0;
+        /* We speak a very simple protocol between log server
+         * and client: one line for the log destination (kmsg
+         * or syslog), followed by the priority field,
+         * followed by the process name. Since we replaced
+         * stdin/stderr we simple use stdio to write to
+         * it. Note that we use stderr, to minimize buffer
+         * flushing issues. */
+
+        dprintf(fd,
+                "%s\n"
+                "%i\n"
+                "%s\n",
+                output == EXEC_OUTPUT_KERNEL ? "kmsg" : "syslog",
+                context->syslog_priority,
+                context->syslog_identifier ? context->syslog_identifier : ident);
+
+        if (fd != nfd) {
+                r = dup2(fd, nfd) < 0 ? -errno : nfd;
+                close_nointr(fd);
+        } else
+                r = nfd;
 
-        case EXEC_OUTPUT_KERNEL:
-        case EXEC_OUTPUT_SYSLOG: {
+        return r;
+}
+static int open_terminal_as(const char *path, mode_t mode, int nfd) {
+        int fd, r;
 
-                int fd;
-                union {
-                        struct sockaddr sa;
-                        struct sockaddr_un un;
-                } sa;
+        assert(path);
+        assert(nfd >= 0);
 
-                close_nointr(STDOUT_FILENO);
-                close_nointr(STDERR_FILENO);
+        if ((fd = open_terminal(path, mode | O_NOCTTY)) < 0)
+                return fd;
 
-                if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
-                        return -errno;
+        if (fd != nfd) {
+                r = dup2(fd, nfd) < 0 ? -errno : nfd;
+                close_nointr_nofail(fd);
+        } else
+                r = nfd;
 
-                if (fd != STDOUT_FILENO) {
-                        close_nointr_nofail(fd);
-                        return -EIO;
-                }
+        return r;
+}
 
-                zero(sa);
-                sa.sa.sa_family = AF_UNIX;
-                strncpy(sa.un.sun_path+1, LOGGER_SOCKET, sizeof(sa.un.sun_path)-1);
+static bool is_terminal_input(ExecInput i) {
+        return
+                i == EXEC_INPUT_TTY ||
+                i == EXEC_INPUT_TTY_FORCE ||
+                i == EXEC_INPUT_TTY_FAIL;
+}
 
-                if (connect(fd, &sa.sa, sizeof(sa)) < 0) {
-                        close_nointr_nofail(fd);
-                        return -errno;
-                }
+static int setup_input(const ExecContext *context) {
+        assert(context);
 
-                if (shutdown(fd, SHUT_RD) < 0) {
-                        close_nointr_nofail(fd);
-                        return -errno;
-                }
+        switch (context->std_input) {
 
-                if ((fd = dup(fd)) < 0) {
-                        close_nointr_nofail(fd);
-                        return -errno;
-                }
+        case EXEC_INPUT_NULL:
+                return open_null_as(O_RDONLY, STDIN_FILENO);
+
+        case EXEC_INPUT_TTY:
+        case EXEC_INPUT_TTY_FORCE:
+        case EXEC_INPUT_TTY_FAIL: {
+                int fd, r;
 
-                if (fd != STDERR_FILENO) {
+                if ((fd = acquire_terminal(
+                                     tty_path(context),
+                                     context->std_input == EXEC_INPUT_TTY_FAIL,
+                                     context->std_input == EXEC_INPUT_TTY_FORCE)) < 0)
+                        return fd;
+
+                if (fd != STDIN_FILENO) {
+                        r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
                         close_nointr_nofail(fd);
-                        return -EIO;
-                }
+                } else
+                        r = STDIN_FILENO;
+
+                return r;
+        }
+
+        default:
+                assert_not_reached("Unknown input type");
+        }
+}
+
+static int setup_output(const ExecContext *context, const char *ident) {
+        assert(context);
+        assert(ident);
+
+        /* This expects the input is already set up */
+
+        switch (context->std_output) {
 
-                /* We speak a very simple protocol between log server
-                 * and client: one line for the log destination (kmsg
-                 * or syslog), followed by the priority field,
-                 * followed by the process name. Since we replaced
-                 * stdin/stderr we simple use stdio to write to
-                 * it. Note that we use stderr, to minimize buffer
-                 * flushing issues. */
-
-                fprintf(stderr,
-                        "%s\n"
-                        "%i\n"
-                        "%s\n",
-                        context->output == EXEC_OUTPUT_KERNEL ? "kmsg" : "syslog",
-                        context->syslog_priority,
-                        context->syslog_identifier ? context->syslog_identifier : ident);
+        case EXEC_OUTPUT_INHERIT:
+
+                /* If the input is connected to a terminal, inherit that... */
+                if (is_terminal_input(context->std_input))
+                        return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
 
                 return 0;
+
+        case EXEC_OUTPUT_NULL:
+                return open_null_as(O_WRONLY, STDOUT_FILENO);
+
+        case EXEC_OUTPUT_TTY: {
+                if (is_terminal_input(context->std_input))
+                        return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
+
+                /* We don't reset the terminal if this is just about output */
+                return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO);
         }
 
+        case EXEC_OUTPUT_SYSLOG:
+        case EXEC_OUTPUT_KERNEL:
+                return connect_logger_as(context, context->std_output, ident, STDOUT_FILENO);
+
         default:
                 assert_not_reached("Unknown output type");
         }
 }
 
-static int setup_input(const ExecContext *context) {
-        int r;
-
+static int setup_error(const ExecContext *context, const char *ident) {
         assert(context);
 
-        switch (context->input) {
+        /* This expects the input and output are already set up */
 
-        case EXEC_INPUT_CONSOLE:
-                return 0;
+        /* Don't change the stderr file descriptor if we inherit all
+         * the way and are not on a tty */
+        if (context->std_error == EXEC_OUTPUT_INHERIT &&
+            context->std_output == EXEC_OUTPUT_INHERIT &&
+            !is_terminal_input(context->std_input))
+                return STDERR_FILENO;
 
-        case EXEC_INPUT_NULL:
-                if ((r = replace_null_fd(STDIN_FILENO, O_RDONLY)) < 0)
-                        return r;
+        /* Duplicate form stdout if possible */
+        if (context->std_error == context->std_output ||
+            context->std_error == EXEC_OUTPUT_INHERIT)
+                return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
 
-                return 0;
+        switch (context->std_error) {
+
+        case EXEC_OUTPUT_NULL:
+                return open_null_as(O_WRONLY, STDERR_FILENO);
+
+        case EXEC_OUTPUT_TTY:
+                if (is_terminal_input(context->std_input))
+                        return dup2(STDIN_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
+
+                /* We don't reset the terminal if this is just about output */
+                return open_terminal_as(tty_path(context), O_WRONLY, STDERR_FILENO);
+
+        case EXEC_OUTPUT_SYSLOG:
+        case EXEC_OUTPUT_KERNEL:
+                return connect_logger_as(context, context->std_error, ident, STDERR_FILENO);
 
         default:
-                assert_not_reached("Unknown input type");
+                assert_not_reached("Unknown error type");
         }
 }
 
+static int setup_confirm_stdio(const ExecContext *context,
+                               int *_saved_stdin,
+                               int *_saved_stdout) {
+        int fd = -1, saved_stdin, saved_stdout = -1, r;
+
+        assert(context);
+        assert(_saved_stdin);
+        assert(_saved_stdout);
+
+        /* This returns positive EXIT_xxx return values instead of
+         * negative errno style values! */
+
+        if ((saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3)) < 0)
+                return EXIT_STDIN;
+
+        if ((saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3)) < 0) {
+                r = EXIT_STDOUT;
+                goto fail;
+        }
+
+        if ((fd = acquire_terminal(
+                             tty_path(context),
+                             context->std_input == EXEC_INPUT_TTY_FAIL,
+                             context->std_input == EXEC_INPUT_TTY_FORCE)) < 0) {
+                r = EXIT_STDIN;
+                goto fail;
+        }
+
+        if (dup2(fd, STDIN_FILENO) < 0) {
+                r = EXIT_STDIN;
+                goto fail;
+        }
+
+        if (dup2(fd, STDOUT_FILENO) < 0) {
+                r = EXIT_STDOUT;
+                goto fail;
+        }
+
+        if (fd >= 2)
+                close_nointr_nofail(fd);
+
+        *_saved_stdin = saved_stdin;
+        *_saved_stdout = saved_stdout;
+
+        return 0;
+
+fail:
+        if (saved_stdout >= 0)
+                close_nointr_nofail(saved_stdout);
+
+        if (saved_stdin >= 0)
+                close_nointr_nofail(saved_stdin);
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
+
+static int restore_conform_stdio(const ExecContext *context,
+                                 int *saved_stdin,
+                                 int *saved_stdout,
+                                 bool *keep_stdin,
+                                 bool *keep_stdout) {
+
+        assert(context);
+        assert(saved_stdin);
+        assert(*saved_stdin >= 0);
+        assert(saved_stdout);
+        assert(*saved_stdout >= 0);
+
+        /* This returns positive EXIT_xxx return values instead of
+         * negative errno style values! */
+
+        if (is_terminal_input(context->std_input)) {
+
+                /* The service wants terminal input. */
+
+                *keep_stdin = true;
+                *keep_stdout =
+                        context->std_output == EXEC_OUTPUT_INHERIT ||
+                        context->std_output == EXEC_OUTPUT_TTY;
+
+        } else {
+                /* If the service doesn't want a controlling terminal,
+                 * then we need to get rid entirely of what we have
+                 * already. */
+
+                if (release_terminal() < 0)
+                        return EXIT_STDIN;
+
+                if (dup2(*saved_stdin, STDIN_FILENO) < 0)
+                        return EXIT_STDIN;
+
+                if (dup2(*saved_stdout, STDOUT_FILENO) < 0)
+                        return EXIT_STDOUT;
+
+                *keep_stdout = *keep_stdin = false;
+        }
+
+        return 0;
+}
+
 static int get_group_creds(const char *groupname, gid_t *gid) {
         struct group *g;
         unsigned long lu;
@@ -452,6 +642,7 @@ int exec_spawn(ExecCommand *command,
                int *fds, unsigned n_fds,
                bool apply_permissions,
                bool apply_chroot,
+               bool confirm_spawn,
                CGroupBonding *cgroup_bondings,
                pid_t *ret) {
 
@@ -485,6 +676,8 @@ int exec_spawn(ExecCommand *command,
                 gid_t gid = (gid_t) -1;
                 char **our_env = NULL, **final_env = NULL;
                 unsigned n_env = 0;
+                int saved_stdout = -1, saved_stdin = -1;
+                bool keep_stdout = false, keep_stdin = false;
 
                 /* child */
 
@@ -494,27 +687,59 @@ int exec_spawn(ExecCommand *command,
                         goto fail;
                 }
 
-                if (context->new_session) {
-                        if (setsid() < 0) {
-                                r = EXIT_SETSID;
+                if (setsid() < 0) {
+                        r = EXIT_SETSID;
+                        goto fail;
+                }
+
+                umask(context->umask);
+
+                if (confirm_spawn) {
+                        char response;
+
+                        /* Set up terminal for the question */
+                        if ((r = setup_confirm_stdio(context,
+                                                     &saved_stdin, &saved_stdout)))
+                                goto fail;
+
+                        /* Now ask the question. */
+                        if (!(line = exec_command_line(command))) {
+                                r = EXIT_MEMORY;
                                 goto fail;
                         }
-                } else {
-                        if (setpgid(0, 0) < 0) {
-                                r = EXIT_PGID;
+
+                        r = ask(&response, "yns", "Execute %s? [Yes, No, Skip] ", line);
+                        free(line);
+
+                        if (r < 0 || response == 'n') {
+                                r = EXIT_CONFIRM;
+                                goto fail;
+                        } else if (response == 's') {
+                                r = 0;
                                 goto fail;
                         }
+
+                        /* Release terminal for the question */
+                        if ((r = restore_conform_stdio(context,
+                                                       &saved_stdin, &saved_stdout,
+                                                       &keep_stdin, &keep_stdout)))
+                                goto fail;
                 }
 
-                umask(context->umask);
+                if (!keep_stdin)
+                        if (setup_input(context) < 0) {
+                                r = EXIT_STDIN;
+                                goto fail;
+                        }
 
-                if (setup_input(context) < 0) {
-                        r = EXIT_INPUT;
-                        goto fail;
-                }
+                if (!keep_stdout)
+                        if (setup_output(context, file_name_from_path(command->path)) < 0) {
+                                r = EXIT_STDOUT;
+                                goto fail;
+                        }
 
-                if (setup_output(context, file_name_from_path(command->path)) < 0) {
-                        r = EXIT_OUTPUT;
+                if (setup_error(context, file_name_from_path(command->path)) < 0) {
+                        r = EXIT_STDERR;
                         goto fail;
                 }
 
@@ -697,9 +922,25 @@ int exec_spawn(ExecCommand *command,
                 strv_free(our_env);
                 strv_free(final_env);
 
+                if (saved_stdin >= 0)
+                        close_nointr_nofail(saved_stdin);
+
+                if (saved_stdout >= 0)
+                        close_nointr_nofail(saved_stdout);
+
                 _exit(r);
         }
 
+        /* We add the new process to the cgroup both in the child (so
+         * that we can be sure that no user code is ever executed
+         * outside of the cgroup) and in the parent (so that we can be
+         * sure that when we kill the cgroup the process will be
+         * killed too). */
+        if (cgroup_bondings)
+                if ((r = cgroup_bonding_install_list(cgroup_bondings, pid)) < 0) {
+                        r = EXIT_CGROUP;
+                        goto fail;
+                }
 
         log_debug("Forked %s as %llu", command->path, (unsigned long long) pid);
 
@@ -730,10 +971,10 @@ void exec_context_init(ExecContext *c) {
 
         c->cpu_sched_reset_on_fork = false;
         c->non_blocking = false;
-        c->new_session = false;
 
-        c->input = 0;
-        c->output = 0;
+        c->std_input = 0;
+        c->std_output = 0;
+        c->std_error = 0;
         c->syslog_priority = LOG_DAEMON|LOG_INFO;
 
         c->secure_bits = 0;
@@ -758,6 +999,9 @@ void exec_context_done(ExecContext *c) {
         free(c->root_directory);
         c->root_directory = NULL;
 
+        free(c->tty_path);
+        c->tty_path = NULL;
+
         free(c->syslog_identifier);
         c->syslog_identifier = NULL;
 
@@ -826,13 +1070,11 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
                 "%sUMask: %04o\n"
                 "%sWorkingDirectory: %s\n"
                 "%sRootDirectory: %s\n"
-                "%sNonBlocking: %s\n"
-                "%sNewSession: %s\n",
+                "%sNonBlocking: %s\n",
                 prefix, c->umask,
                 prefix, c->working_directory ? c->working_directory : "/",
                 prefix, c->root_directory ? c->root_directory : "/",
-                prefix, yes_no(c->non_blocking),
-                prefix, yes_no(c->new_session));
+                prefix, yes_no(c->non_blocking));
 
         if (c->environment)
                 for (e = c->environment; *e; e++)
@@ -880,12 +1122,20 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
                 fprintf(f, "%sTimerSlackNS: %lu\n", prefix, c->timer_slack_ns);
 
         fprintf(f,
-                "%sInput: %s\n"
-                "%sOutput: %s\n",
-                prefix, exec_input_to_string(c->input),
-                prefix, exec_output_to_string(c->output));
+                "%sStandardInput: %s\n"
+                "%sStandardOutput: %s\n"
+                "%sStandardError: %s\n",
+                prefix, exec_input_to_string(c->std_input),
+                prefix, exec_output_to_string(c->std_output),
+                prefix, exec_output_to_string(c->std_error));
+
+        if (c->tty_path)
+                fprintf(f,
+                        "%sTTYPath: %s\n",
+                        prefix, c->tty_path);
 
-        if (c->output == EXEC_OUTPUT_SYSLOG || c->output == EXEC_OUTPUT_KERNEL)
+        if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KERNEL ||
+            c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KERNEL)
                 fprintf(f,
                         "%sSyslogFacility: %s\n"
                         "%sSyslogLevel: %s\n",
@@ -1104,18 +1354,122 @@ int exec_command_set(ExecCommand *c, const char *path, ...) {
         return 0;
 }
 
+const char* exit_status_to_string(ExitStatus status) {
+        switch (status) {
+
+        case EXIT_SUCCESS:
+                return "SUCCESS";
+
+        case EXIT_FAILURE:
+                return "FAILURE";
+
+        case EXIT_INVALIDARGUMENT:
+                return "INVALIDARGUMENT";
+
+        case EXIT_NOTIMPLEMENTED:
+                return "NOTIMPLEMENTED";
+
+        case EXIT_NOPERMISSION:
+                return "NOPERMISSION";
+
+        case EXIT_NOTINSTALLED:
+                return "NOTINSSTALLED";
+
+        case EXIT_NOTCONFIGURED:
+                return "NOTCONFIGURED";
+
+        case EXIT_NOTRUNNING:
+                return "NOTRUNNING";
+
+        case EXIT_CHDIR:
+                return "CHDIR";
+
+        case EXIT_NICE:
+                return "NICE";
+
+        case EXIT_FDS:
+                return "FDS";
+
+        case EXIT_EXEC:
+                return "EXEC";
+
+        case EXIT_MEMORY:
+                return "MEMORY";
+
+        case EXIT_LIMITS:
+                return "LIMITS";
+
+        case EXIT_OOM_ADJUST:
+                return "OOM_ADJUST";
+
+        case EXIT_SIGNAL_MASK:
+                return "SIGNAL_MASK";
+
+        case EXIT_STDIN:
+                return "STDIN";
+
+        case EXIT_STDOUT:
+                return "STDOUT";
+
+        case EXIT_CHROOT:
+                return "CHROOT";
+
+        case EXIT_IOPRIO:
+                return "IOPRIO";
+
+        case EXIT_TIMERSLACK:
+                return "TIMERSLACK";
+
+        case EXIT_SECUREBITS:
+                return "SECUREBITS";
+
+        case EXIT_SETSCHEDULER:
+                return "SETSCHEDULER";
+
+        case EXIT_CPUAFFINITY:
+                return "CPUAFFINITY";
+
+        case EXIT_GROUP:
+                return "GROUP";
+
+        case EXIT_USER:
+                return "USER";
+
+        case EXIT_CAPABILITIES:
+                return "CAPABILITIES";
+
+        case EXIT_CGROUP:
+                return "CGROUP";
+
+        case EXIT_SETSID:
+                return "SETSID";
+
+        case EXIT_CONFIRM:
+                return "CONFIRM";
+
+        case EXIT_STDERR:
+                return "STDERR";
+
+        default:
+                return NULL;
+        }
+}
+
+static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
+        [EXEC_INPUT_NULL] = "null",
+        [EXEC_INPUT_TTY] = "tty",
+        [EXEC_INPUT_TTY_FORCE] = "tty-force",
+        [EXEC_INPUT_TTY_FAIL] = "tty-fail"
+};
+
 static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
-        [EXEC_OUTPUT_CONSOLE] = "console",
+        [EXEC_OUTPUT_INHERIT] = "inherit",
         [EXEC_OUTPUT_NULL] = "null",
+        [EXEC_OUTPUT_TTY] = "tty",
         [EXEC_OUTPUT_SYSLOG] = "syslog",
         [EXEC_OUTPUT_KERNEL] = "kernel"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
 
-static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
-        [EXEC_INPUT_NULL] = "null",
-        [EXEC_INPUT_CONSOLE] = "console"
-};
-
 DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
index b4bb960..d2ac4a8 100644 (file)
--- a/execute.h
+++ b/execute.h
@@ -41,22 +41,25 @@ struct CGroupBonding;
 /* Abstract namespace! */
 #define LOGGER_SOCKET "/org/freedesktop/systemd1/logger"
 
+typedef enum ExecInput {
+        EXEC_INPUT_NULL,
+        EXEC_INPUT_TTY,
+        EXEC_INPUT_TTY_FORCE,
+        EXEC_INPUT_TTY_FAIL,
+        _EXEC_INPUT_MAX,
+        _EXEC_INPUT_INVALID = -1
+} ExecInput;
+
 typedef enum ExecOutput {
-        EXEC_OUTPUT_CONSOLE,
+        EXEC_OUTPUT_INHERIT,
         EXEC_OUTPUT_NULL,
+        EXEC_OUTPUT_TTY,
         EXEC_OUTPUT_SYSLOG,
         EXEC_OUTPUT_KERNEL,
         _EXEC_OUTPUT_MAX,
         _EXEC_OUTPUT_INVALID = -1
 } ExecOutput;
 
-typedef enum ExecInput {
-        EXEC_INPUT_NULL,
-        EXEC_INPUT_CONSOLE,
-        _EXEC_INPUT_MAX,
-        _EXEC_INPUT_INVALID = -1
-} ExecInput;
-
 struct ExecStatus {
         pid_t pid;
         usec_t start_timestamp;
@@ -94,10 +97,13 @@ struct ExecContext {
 
         bool cpu_sched_reset_on_fork;
         bool non_blocking;
-        bool new_session;
 
-        ExecInput input;
-        ExecOutput output;
+        ExecInput std_input;
+        ExecOutput std_output;
+        ExecOutput std_error;
+
+        char *tty_path;
+
         int syslog_priority;
         char *syslog_identifier;
 
@@ -138,10 +144,9 @@ typedef enum ExitStatus {
         EXIT_LIMITS,
         EXIT_OOM_ADJUST,
         EXIT_SIGNAL_MASK,
-        EXIT_INPUT,
-        EXIT_OUTPUT,
+        EXIT_STDIN,
+        EXIT_STDOUT,
         EXIT_CHROOT,   /* 210 */
-        EXIT_PGID,
         EXIT_IOPRIO,
         EXIT_TIMERSLACK,
         EXIT_SECUREBITS,
@@ -150,8 +155,11 @@ typedef enum ExitStatus {
         EXIT_GROUP,
         EXIT_USER,
         EXIT_CAPABILITIES,
-        EXIT_CGROUP,   /* 220 */
-        EXIT_SETSID
+        EXIT_CGROUP,
+        EXIT_SETSID,   /* 220 */
+        EXIT_CONFIRM,
+        EXIT_STDERR
+
 } ExitStatus;
 
 int exec_spawn(ExecCommand *command,
@@ -159,6 +167,7 @@ int exec_spawn(ExecCommand *command,
                int *fds, unsigned n_fds,
                bool apply_permissions,
                bool apply_chroot,
+               bool confirm_spawn,
                struct CGroupBonding *cgroup_bondings,
                pid_t *ret);
 
@@ -187,4 +196,6 @@ int exec_output_from_string(const char *s);
 const char* exec_input_to_string(ExecInput i);
 int exec_input_from_string(const char *s);
 
+const char* exit_status_to_string(ExitStatus status);
+
 #endif
index d1b8199..9eb0053 100644 (file)
@@ -1227,8 +1227,10 @@ static int load_from_path(Unit *u, const char *path) {
                 { "CPUAffinity",            config_parse_cpu_affinity,    &(context),                                      section   }, \
                 { "UMask",                  config_parse_mode,            &(context).umask,                                section   }, \
                 { "Environment",            config_parse_strv,            &(context).environment,                          section   }, \
-                { "Output",                 config_parse_output,          &(context).output,                               section   }, \
-                { "Input",                  config_parse_input,           &(context).input,                                section   }, \
+                { "StandardInput",          config_parse_input,           &(context).std_input,                            section   }, \
+                { "StandardOutput",         config_parse_output,          &(context).std_output,                           section   }, \
+                { "StandardError",          config_parse_output,          &(context).std_output,                           section   }, \
+                { "TTYPath",                config_parse_path,            &(context).tty_path,                             section   }, \
                 { "SyslogIdentifier",       config_parse_string,          &(context).syslog_identifier,                    section   }, \
                 { "SyslogFacility",         config_parse_facility,        &(context).syslog_priority,                      section   }, \
                 { "SyslogLevel",            config_parse_level,           &(context).syslog_priority,                      section   }, \
@@ -1252,8 +1254,7 @@ static int load_from_path(Unit *u, const char *path) {
                 { "LimitNICE",              config_parse_limit,           &(context).rlimit[RLIMIT_NICE],                  section   }, \
                 { "LimitRTPRIO",            config_parse_limit,           &(context).rlimit[RLIMIT_RTPRIO],                section   }, \
                 { "LimitRTTIME",            config_parse_limit,           &(context).rlimit[RLIMIT_RTTIME],                section   }, \
-                { "ControlGroup",           config_parse_cgroup,          u,                                               section   }, \
-                { "NewSession",             config_parse_bool,            &(context).new_session,                          section   }
+                { "ControlGroup",           config_parse_cgroup,          u,                                               section   }
 
         const ConfigItem items[] = {
                 { "Names",                  config_parse_names,           u,                                               "Meta"    },
diff --git a/main.c b/main.c
index 7dbcc90..0eb64d2 100644 (file)
--- a/main.c
+++ b/main.c
@@ -30,6 +30,7 @@
 #include <getopt.h>
 #include <signal.h>
 #include <sys/wait.h>
+#include <fcntl.h>
 
 #include "manager.h"
 #include "log.h"
@@ -51,6 +52,8 @@ static bool dump_core = true;
 static bool crash_shell = false;
 static int crash_chvt = -1;
 
+static bool confirm_spawn = false;
+
 _noreturn static void freeze(void) {
         for (;;)
                 pause();
@@ -134,6 +137,53 @@ static void install_crash_handler(void) {
         assert_se(sigaction(SIGABRT, &sa, NULL) == 0);
 }
 
+static int console_setup(void) {
+        int tty_fd = -1, null_fd = -1, r = 0;
+
+        /* If we are init, we connect stdout/stderr to /dev/console
+         * and stdin to /dev/null and make sure we don't have a
+         * controlling tty. */
+
+        release_terminal();
+
+        if ((tty_fd = open_terminal("/dev/console", O_WRONLY)) < 0) {
+                log_error("Failed to open /dev/console: %s", strerror(-tty_fd));
+                r = -tty_fd;
+                goto finish;
+        }
+
+        if ((null_fd = open("/dev/null", O_RDONLY)) < 0) {
+                log_error("Failed to open /dev/null: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        assert(tty_fd >= 3);
+        assert(null_fd >= 3);
+
+        if (reset_terminal(tty_fd) < 0)
+                log_error("Failed to reset /dev/console: %m");
+
+        if (dup2(tty_fd, STDOUT_FILENO) < 0 ||
+            dup2(tty_fd, STDERR_FILENO) < 0 ||
+            dup2(null_fd, STDIN_FILENO) < 0) {
+                log_error("Failed to dup2() device: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (tty_fd >= 0)
+                close_nointr(tty_fd);
+
+        if (null_fd >= 0)
+                close_nointr(null_fd);
+
+        return r;
+}
+
 static int set_default_unit(const char *u) {
         char *c;
 
@@ -264,7 +314,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_DEFAULT,
                 ARG_RUNNING_AS,
                 ARG_TEST,
-                ARG_DUMP_CONFIGURATION_ITEMS
+                ARG_DUMP_CONFIGURATION_ITEMS,
+                ARG_CONFIRM_SPAWN
         };
 
         static const struct option options[] = {
@@ -275,6 +326,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "test",       no_argument,       NULL, ARG_TEST },
                 { "help",       no_argument,       NULL, 'h' },
                 { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS },
+                { "confirm-spawn", no_argument,    NULL, ARG_CONFIRM_SPAWN },
                 { NULL,         0,                 NULL, 0 }
         };
 
@@ -333,6 +385,10 @@ static int parse_argv(int argc, char *argv[]) {
                         action = ACTION_DUMP_CONFIGURATION_ITEMS;
                         break;
 
+                case ARG_CONFIRM_SPAWN:
+                        confirm_spawn = true;
+                        break;
+
                 case 'h':
                         action = ACTION_HELP;
                         break;
@@ -357,7 +413,8 @@ static int help(void) {
                "     --log-target=TARGET         Set log target (console, syslog, kmsg)\n"
                "     --running-as=AS             Set running as (init, system, session)\n"
                "     --test                      Determine startup sequence, dump it and exit\n"
-               "     --dump-configuration-items  Dump understood unit configuration items\n",
+               "     --dump-configuration-items  Dump understood unit configuration items\n"
+               "     --confirm-spawn             Ask for confirmation when spawning processes\n",
                __progname);
 
         return 0;
@@ -422,11 +479,16 @@ int main(int argc, char *argv[]) {
         /* Move out of the way, so that we won't block unmounts */
         assert_se(chdir("/")  == 0);
 
-        /* Become a session leader if we aren't one yet. */
-        setsid();
+        if (running_as != MANAGER_SESSION) {
+                /* Become a session leader if we aren't one yet. */
+                setsid();
 
-        /* Disable the umask logic */
-        umask(0);
+                /* Disable the umask logic */
+                umask(0);
+        }
+
+        if (running_as == MANAGER_INIT)
+                console_setup();
 
         /* Make sure D-Bus doesn't fiddle with the SIGPIPE handlers */
         dbus_connection_set_change_sigpipe(FALSE);
@@ -445,7 +507,7 @@ int main(int argc, char *argv[]) {
         if (running_as == MANAGER_INIT)
                 hostname_setup();
 
-        if ((r = manager_new(running_as, &m)) < 0) {
+        if ((r = manager_new(running_as, confirm_spawn, &m)) < 0) {
                 log_error("Failed to allocate manager object: %s", strerror(-r));
                 goto finish;
         }
index b78f3c4..7d288bd 100644 (file)
--- a/manager.c
+++ b/manager.c
@@ -33,6 +33,8 @@
 #include <sys/ioctl.h>
 #include <linux/kd.h>
 #include <libcgroup.h>
+#include <termios.h>
+#include <fcntl.h>
 
 #include "manager.h"
 #include "hashmap.h"
 #include "mount-setup.h"
 #include "utmp-wtmp.h"
 
+static int enable_special_signals(Manager *m) {
+        char fd;
+
+        assert(m);
+
+        /* Enable that we get SIGINT on control-alt-del */
+        if (reboot(RB_DISABLE_CAD) < 0)
+                log_warning("Failed to enable ctrl-alt-del handling: %m");
+
+        if ((fd = open_terminal("/dev/tty0", O_RDWR)) < 0)
+                log_warning("Failed to open /dev/tty0: %m");
+        else {
+                /* Enable that we get SIGWINCH on kbrequest */
+                if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0)
+                        log_warning("Failed to enable kbrequest handling: %s", strerror(errno));
+
+                close_nointr_nofail(fd);
+        }
+
+        return 0;
+}
+
 static int manager_setup_signals(Manager *m) {
         sigset_t mask;
         struct epoll_event ev;
@@ -73,15 +97,8 @@ static int manager_setup_signals(Manager *m) {
         if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0)
                 return -errno;
 
-        if (m->running_as == MANAGER_INIT) {
-                /* Enable that we get SIGINT on control-alt-del */
-                if (reboot(RB_DISABLE_CAD) < 0)
-                        log_warning("Failed to enable ctrl-alt-del handling: %s", strerror(errno));
-
-                /* Enable that we get SIGWINCH on kbrequest */
-                if (ioctl(0, KDSIGACCEPT, SIGWINCH) < 0)
-                        log_warning("Failed to enable kbrequest handling: %s", strerror(errno));
-        }
+        if (m->running_as == MANAGER_INIT)
+                return enable_special_signals(m);
 
         return 0;
 }
@@ -280,7 +297,7 @@ static int manager_find_paths(Manager *m) {
         return 0;
 }
 
-int manager_new(ManagerRunningAs running_as, Manager **_m) {
+int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
         Manager *m;
         int r = -ENOMEM;
 
@@ -294,6 +311,8 @@ int manager_new(ManagerRunningAs running_as, Manager **_m) {
         m->boot_timestamp = now(CLOCK_REALTIME);
 
         m->running_as = running_as;
+        m->confirm_spawn = confirm_spawn;
+
         m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = -1;
         m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
 
@@ -1553,13 +1572,8 @@ static int manager_process_signal_fd(Manager *m, bool *quit) {
                         break;
 
                 case SIGUSR1:
-
-                        printf("→ By units:\n");
                         manager_dump_units(m, stdout, "\t");
-
-                        printf("→ By jobs:\n");
                         manager_dump_jobs(m, stdout, "\t");
-
                         break;
 
                 default:
index 77b39b2..b6030a0 100644 (file)
--- a/manager.h
+++ b/manager.h
@@ -35,7 +35,7 @@ typedef struct Watch Watch;
 typedef enum ManagerRunningAs {
         MANAGER_INIT,      /* root and pid=1 */
         MANAGER_SYSTEM,    /* root and pid!=1 */
-        MANAGER_SESSION,   /* non-root */
+        MANAGER_SESSION,   /* non-root, for a session */
         _MANAGER_RUNNING_AS_MAX,
         _MANAGER_RUNNING_AS_INVALID = -1
 } ManagerRunningAs;
@@ -152,6 +152,8 @@ struct Manager {
 
         bool utmp_reboot_written:1;
 
+        bool confirm_spawn:1;
+
         Hashmap *watch_pids;  /* pid => Unit object n:1 */
 
         int epoll_fd;
@@ -183,7 +185,7 @@ struct Manager {
         usec_t boot_timestamp;
 };
 
-int manager_new(ManagerRunningAs running_as, Manager **m);
+int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **m);
 void manager_free(Manager *m);
 
 int manager_coldplug(Manager *m);
diff --git a/mount.c b/mount.c
index 5a386a6..04edc6e 100644 (file)
--- a/mount.c
+++ b/mount.c
@@ -367,6 +367,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
                             NULL, 0,
                             true,
                             true,
+                            UNIT(m)->meta.manager->confirm_spawn,
                             UNIT(m)->meta.cgroup_bondings,
                             &pid)) < 0)
                 goto fail;
@@ -436,21 +437,28 @@ static void mount_enter_dead(Mount *m, bool success) {
         mount_set_state(m, m->failure ? MOUNT_MAINTAINANCE : MOUNT_DEAD);
 }
 
+static void mount_enter_mounted(Mount *m, bool success) {
+        assert(m);
+
+        if (!success)
+                m->failure = true;
+
+        mount_set_state(m, MOUNT_MOUNTED);
+}
+
 static void mount_enter_signal(Mount *m, MountState state, bool success) {
         int r;
+        bool sent = false;
 
         assert(m);
 
         if (!success)
                 m->failure = true;
 
-        if (m->control_pid > 0) {
-                int sig;
-                bool sent = false;
-
-                sig = (state == MOUNT_MOUNTING_SIGTERM ||
-                       state == MOUNT_UNMOUNTING_SIGTERM ||
-                       state == MOUNT_REMOUNTING_SIGTERM) ? SIGTERM : SIGKILL;
+        if (m->kill_mode != KILL_NONE) {
+                int sig = (state == MOUNT_MOUNTING_SIGTERM ||
+                           state == MOUNT_UNMOUNTING_SIGTERM ||
+                           state == MOUNT_REMOUNTING_SIGTERM) ? SIGTERM : SIGKILL;
 
                 if (m->kill_mode == KILL_CONTROL_GROUP) {
 
@@ -461,32 +469,32 @@ static void mount_enter_signal(Mount *m, MountState state, bool success) {
                                 sent = true;
                 }
 
-                if (!sent)
+                if (!sent && m->control_pid > 0)
                         if (kill(m->kill_mode == KILL_PROCESS ? m->control_pid : -m->control_pid, sig) < 0 && errno != ESRCH) {
                                 r = -errno;
                                 goto fail;
                         }
         }
 
-        mount_set_state(m, state);
+        if (sent) {
+                if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0)
+                        goto fail;
 
-        if (m->control_pid <= 0)
+                mount_set_state(m, state);
+        } else if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL)
+                mount_enter_mounted(m, true);
+        else
                 mount_enter_dead(m, true);
 
         return;
 
 fail:
         log_warning("%s failed to kill processes: %s", unit_id(UNIT(m)), strerror(-r));
-        mount_enter_dead(m, false);
-}
-
-static void mount_enter_mounted(Mount *m, bool success) {
-        assert(m);
 
-        if (!success)
-                m->failure = true;
-
-        mount_set_state(m, MOUNT_MOUNTED);
+        if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL)
+                mount_enter_mounted(m, false);
+        else
+                mount_enter_dead(m, false);
 }
 
 static void mount_enter_unmounting(Mount *m, bool success) {
index ef43537..2d326be 100644 (file)
--- a/service.c
+++ b/service.c
@@ -52,6 +52,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
         [SERVICE_START] = UNIT_ACTIVATING,
         [SERVICE_START_POST] = UNIT_ACTIVATING,
         [SERVICE_RUNNING] = UNIT_ACTIVE,
+        [SERVICE_EXITED] = UNIT_ACTIVE,
         [SERVICE_RELOAD] = UNIT_ACTIVE_RELOADING,
         [SERVICE_STOP] = UNIT_DEACTIVATING,
         [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING,
@@ -619,6 +620,9 @@ static int service_load_sysv_path(Service *s, const char *path) {
                         goto finish;
         }
 
+        /* Special setting for all SysV services */
+        s->valid_no_process = true;
+
         u->meta.load_state = UNIT_LOADED;
         r = 0;
 
@@ -1097,6 +1101,7 @@ static int service_spawn(
                             fds, n_fds,
                             apply_permissions,
                             apply_chroot,
+                            UNIT(s)->meta.manager->confirm_spawn,
                             UNIT(s)->meta.cgroup_bondings,
                             &pid)) < 0)
                 goto fail;
@@ -1119,6 +1124,41 @@ fail:
         return r;
 }
 
+static int main_pid_good(Service *s) {
+        assert(s);
+
+        /* Returns 0 if the pid is dead, 1 if it is good, -1 if we
+         * don't know */
+
+        /* If we know the pid file, then lets just check if it is
+         * still valid */
+        if (s->main_pid_known)
+                return s->main_pid > 0;
+
+        /* We don't know the pid */
+        return -EAGAIN;
+}
+
+static int control_pid_good(Service *s) {
+        assert(s);
+
+        return s->control_pid > 0;
+}
+
+static int cgroup_good(Service *s) {
+        int r;
+
+        assert(s);
+
+        if (s->valid_no_process)
+                return -EAGAIN;
+
+        if ((r = cgroup_bonding_is_empty_list(UNIT(s)->meta.cgroup_bondings)) < 0)
+                return r;
+
+        return !r;
+}
+
 static void service_enter_dead(Service *s, bool success, bool allow_restart) {
         int r;
         assert(s);
@@ -1155,7 +1195,7 @@ static void service_enter_stop_post(Service *s, bool success) {
 
         service_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST]))
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) {
                 if ((r = service_spawn(s,
                                        s->control_command,
                                        true,
@@ -1166,15 +1206,14 @@ static void service_enter_stop_post(Service *s, bool success) {
                         goto fail;
 
 
-        service_set_state(s, SERVICE_STOP_POST);
-
-        if (!s->control_command)
-                service_enter_dead(s, true, true);
+                service_set_state(s, SERVICE_STOP_POST);
+        } else
+                service_enter_signal(s, SERVICE_FINAL_SIGTERM, true);
 
         return;
 
 fail:
-        log_warning("%s failed to run stop executable: %s", unit_id(UNIT(s)), strerror(-r));
+        log_warning("%s failed to run stop-post executable: %s", unit_id(UNIT(s)), strerror(-r));
         service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
 }
 
@@ -1187,10 +1226,8 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) {
         if (!success)
                 s->failure = true;
 
-        if (s->main_pid > 0 || s->control_pid > 0) {
-                int sig;
-
-                sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? SIGTERM : SIGKILL;
+        if (s->kill_mode != KILL_NONE) {
+                int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? SIGTERM : SIGKILL;
 
                 if (s->kill_mode == KILL_CONTROL_GROUP) {
 
@@ -1203,6 +1240,7 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) {
 
                 if (!sent) {
                         r = 0;
+
                         if (s->main_pid > 0) {
                                 if (kill(s->kill_mode == KILL_PROCESS ? s->main_pid : -s->main_pid, sig) < 0 && errno != ESRCH)
                                         r = -errno;
@@ -1222,9 +1260,14 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) {
                 }
         }
 
-        service_set_state(s, state);
+        if (sent) {
+                if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
+                        goto fail;
 
-        if (s->main_pid <= 0 && s->control_pid <= 0)
+                service_set_state(s, state);
+        } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+                service_enter_stop_post(s, true);
+        else
                 service_enter_dead(s, true, true);
 
         return;
@@ -1232,10 +1275,7 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) {
 fail:
         log_warning("%s failed to kill processes: %s", unit_id(UNIT(s)), strerror(-r));
 
-        if (sent)  {
-                s->failure = true;
-                service_set_state(s, state);
-        } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+        if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
                 service_enter_stop_post(s, false);
         else
                 service_enter_dead(s, false, true);
@@ -1250,7 +1290,7 @@ static void service_enter_stop(Service *s, bool success) {
 
         service_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP]))
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) {
                 if ((r = service_spawn(s,
                                        s->control_command,
                                        true,
@@ -1260,9 +1300,8 @@ static void service_enter_stop(Service *s, bool success) {
                                        &s->control_pid)) < 0)
                         goto fail;
 
-        service_set_state(s, SERVICE_STOP);
-
-        if (!s->control_command)
+                service_set_state(s, SERVICE_STOP);
+        } else
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, true);
 
         return;
@@ -1272,13 +1311,27 @@ fail:
         service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
 }
 
+static void service_enter_running(Service *s, bool success) {
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if (main_pid_good(s) != 0 && cgroup_good(s) != 0)
+                service_set_state(s, SERVICE_RUNNING);
+        else if (s->valid_no_process)
+                service_set_state(s, SERVICE_EXITED);
+        else
+                service_enter_stop(s, true);
+}
+
 static void service_enter_start_post(Service *s) {
         int r;
         assert(s);
 
         service_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST]))
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) {
                 if ((r = service_spawn(s,
                                        s->control_command,
                                        true,
@@ -1289,10 +1342,9 @@ static void service_enter_start_post(Service *s) {
                         goto fail;
 
 
-        service_set_state(s, SERVICE_START_POST);
-
-        if (!s->control_command)
-                service_set_state(s, SERVICE_RUNNING);
+                service_set_state(s, SERVICE_START_POST);
+        } else
+                service_enter_running(s, true);
 
         return;
 
@@ -1310,6 +1362,11 @@ static void service_enter_start(Service *s) {
         assert(s->exec_command[SERVICE_EXEC_START]);
         assert(!s->exec_command[SERVICE_EXEC_START]->command_next);
 
+        if (s->type == SERVICE_FORKING)
+                service_unwatch_control_pid(s);
+        else
+                service_unwatch_main_pid(s);
+
         if ((r = service_spawn(s,
                                s->exec_command[SERVICE_EXEC_START],
                                s->type == SERVICE_FORKING,
@@ -1319,16 +1376,13 @@ static void service_enter_start(Service *s) {
                                &pid)) < 0)
                 goto fail;
 
-        service_set_state(s, SERVICE_START);
-
         if (s->type == SERVICE_SIMPLE) {
                 /* For simple services we immediately start
                  * the START_POST binaries. */
 
-                service_unwatch_main_pid(s);
-
                 s->main_pid = pid;
                 s->main_pid_known = true;
+
                 service_enter_start_post(s);
 
         } else  if (s->type == SERVICE_FORKING) {
@@ -1336,20 +1390,21 @@ static void service_enter_start(Service *s) {
                 /* For forking services we wait until the start
                  * process exited. */
 
-                service_unwatch_control_pid(s);
-
                 s->control_pid = pid;
+
                 s->control_command = s->exec_command[SERVICE_EXEC_START];
+                service_set_state(s, SERVICE_START);
+
         } else if (s->type == SERVICE_FINISH) {
 
                 /* For finishing services we wait until the start
                  * process exited, too, but it is our main process. */
 
-                service_unwatch_main_pid(s);
-
                 s->main_pid = pid;
                 s->main_pid_known = true;
+
                 s->control_command = s->exec_command[SERVICE_EXEC_START];
+                service_set_state(s, SERVICE_START);
         } else
                 assert_not_reached("Unknown service type");
 
@@ -1357,7 +1412,7 @@ static void service_enter_start(Service *s) {
 
 fail:
         log_warning("%s failed to run start exectuable: %s", unit_id(UNIT(s)), strerror(-r));
-        service_enter_stop(s, false);
+        service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
 }
 
 static void service_enter_start_pre(Service *s) {
@@ -1367,7 +1422,7 @@ static void service_enter_start_pre(Service *s) {
 
         service_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE]))
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) {
                 if ((r = service_spawn(s,
                                        s->control_command,
                                        true,
@@ -1377,9 +1432,8 @@ static void service_enter_start_pre(Service *s) {
                                        &s->control_pid)) < 0)
                         goto fail;
 
-        service_set_state(s, SERVICE_START_PRE);
-
-        if (!s->control_command)
+                service_set_state(s, SERVICE_START_PRE);
+        } else
                 service_enter_start(s);
 
         return;
@@ -1413,7 +1467,7 @@ static void service_enter_reload(Service *s) {
 
         service_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]))
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) {
                 if ((r = service_spawn(s,
                                        s->control_command,
                                        true,
@@ -1423,10 +1477,9 @@ static void service_enter_reload(Service *s) {
                                        &s->control_pid)) < 0)
                         goto fail;
 
-        service_set_state(s, SERVICE_RELOAD);
-
-        if (!s->control_command)
-                service_set_state(s, SERVICE_RUNNING);
+                service_set_state(s, SERVICE_RELOAD);
+        } else
+                service_enter_running(s, true);
 
         return;
 
@@ -1463,8 +1516,10 @@ static void service_run_next(Service *s, bool success) {
 fail:
         log_warning("%s failed to run spawn next executable: %s", unit_id(UNIT(s)), strerror(-r));
 
-        if (s->state == SERVICE_STOP)
-                service_enter_stop_post(s, false);
+        if (s->state == SERVICE_START_PRE)
+                service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+        else if (s->state == SERVICE_STOP)
+                service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
         else if (s->state == SERVICE_STOP_POST)
                 service_enter_dead(s, false, true);
         else
@@ -1533,7 +1588,7 @@ static int service_stop(Unit *u) {
                 return 0;
         }
 
-        assert(s->state == SERVICE_RUNNING);
+        assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED);
 
         service_enter_stop(s, true);
         return 0;
@@ -1544,7 +1599,7 @@ static int service_reload(Unit *u) {
 
         assert(s);
 
-        assert(s->state == SERVICE_RUNNING);
+        assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED);
 
         service_enter_reload(s);
         return 0;
@@ -1564,36 +1619,6 @@ static UnitActiveState service_active_state(Unit *u) {
         return state_translation_table[SERVICE(u)->state];
 }
 
-static int main_pid_good(Service *s) {
-        assert(s);
-
-        /* Returns 0 if the pid is dead, 1 if it is good, -1 if we
-         * don't know */
-
-        /* If we know the pid file, then lets just check if it is
-         * still valid */
-        if (s->main_pid_known)
-                return s->main_pid > 0;
-
-        /* We don't know the pid */
-        return -EAGAIN;
-}
-
-static bool control_pid_good(Service *s) {
-        assert(s);
-
-        return s->control_pid > 0;
-}
-
-static int cgroup_good(Service *s) {
-        assert(s);
-
-        if (s->valid_no_process)
-                return -EAGAIN;
-
-        return cgroup_bonding_is_empty_list(UNIT(s)->meta.cgroup_bondings);
-}
-
 static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         Service *s = SERVICE(u);
         bool success;
@@ -1635,11 +1660,11 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                         if (success)
                                 service_enter_start_post(s);
                         else
-                                service_enter_stop(s, false);
+                                service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
                         break;
 
                 case SERVICE_RUNNING:
-                        service_enter_stop(s, success);
+                        service_enter_running(s, success);
                         break;
 
                 case SERVICE_STOP_SIGTERM:
@@ -1666,15 +1691,15 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                 /* If we are shutting things down anyway we
                  * don't care about failing commands. */
 
-                if (s->control_command->command_next &&
-                    (success || (s->state == SERVICE_STOP || s->state == SERVICE_STOP_POST)))
+                if (s->control_command->command_next && success) {
 
                         /* There is another command to *
                          * execute, so let's do that. */
 
+                        log_debug("%s running next command for state %s", unit_id(u), service_state_to_string(s->state));
                         service_run_next(s, success);
 
-                else {
+                else {
                         /* No further commands for this step, so let's
                          * figure out what to do next */
 
@@ -1686,7 +1711,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                 if (success)
                                         service_enter_start(s);
                                 else
-                                        service_enter_stop(s, false);
+                                        service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
                                 break;
 
                         case SERVICE_START:
@@ -1705,7 +1730,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
 
                                         service_enter_start_post(s);
                                 } else
-                                        service_enter_stop(s, false);
+                                        service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
 
                                 break;
 
@@ -1725,22 +1750,15 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                 /* Fall through */
 
                         case SERVICE_RELOAD:
-                                if (success) {
-                                        if (main_pid_good(s) != 0 && cgroup_good(s) != 0)
-                                                service_set_state(s, SERVICE_RUNNING);
-                                        else
-                                                service_enter_stop(s, true);
-                                } else
+                                if (success)
+                                        service_enter_running(s, true);
+                                else
                                         service_enter_stop(s, false);
 
                                 break;
 
                         case SERVICE_STOP:
-                                if (main_pid_good(s) > 0)
-                                        /* Still not dead and we know the PID? Let's go hunting. */
-                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, success);
-                                else
-                                        service_enter_stop_post(s, success);
+                                service_enter_signal(s, SERVICE_STOP_SIGTERM, success);
                                 break;
 
                         case SERVICE_STOP_SIGTERM:
@@ -1779,6 +1797,10 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) {
 
         case SERVICE_START_PRE:
         case SERVICE_START:
+                log_warning("%s operation timed out. Terminating.", unit_id(u));
+                service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+                break;
+
         case SERVICE_START_POST:
         case SERVICE_RELOAD:
                 log_warning("%s operation timed out. Stopping.", unit_id(u));
@@ -1845,10 +1867,7 @@ static void service_cgroup_notify_event(Unit *u) {
                  * SIGCHLD for. */
 
         case SERVICE_RUNNING:
-
-                if (!s->valid_no_process && main_pid_good(s) <= 0)
-                        service_enter_stop(s, true);
-
+                service_enter_running(s, true);
                 break;
 
         default:
@@ -1970,6 +1989,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
         [SERVICE_START] = "start",
         [SERVICE_START_POST] = "start-post",
         [SERVICE_RUNNING] = "running",
+        [SERVICE_EXITED] = "exited",
         [SERVICE_RELOAD] = "reload",
         [SERVICE_STOP] = "stop",
         [SERVICE_STOP_SIGTERM] = "stop-sigterm",
index 580cf1e..f357fc8 100644 (file)
--- a/service.h
+++ b/service.h
@@ -33,6 +33,7 @@ typedef enum ServiceState {
         SERVICE_START,
         SERVICE_START_POST,
         SERVICE_RUNNING,
+        SERVICE_EXITED,            /* Nothing is running anymore, but ValidNoProcess is true, ehnce this is OK */
         SERVICE_RELOAD,
         SERVICE_STOP,              /* No STOP_PRE state, instead just register multiple STOP executables */
         SERVICE_STOP_SIGTERM,
index f387ebe..3b74b5e 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -43,8 +43,8 @@ static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
         [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING,
         [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING,
         [SOCKET_STOP_POST] = UNIT_DEACTIVATING,
-        [SOCKET_STOP_POST_SIGTERM] = UNIT_DEACTIVATING,
-        [SOCKET_STOP_POST_SIGKILL] = UNIT_DEACTIVATING,
+        [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING,
+        [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING,
         [SOCKET_MAINTAINANCE] = UNIT_INACTIVE,
 };
 
@@ -58,8 +58,8 @@ static const char* const state_string_table[_SOCKET_STATE_MAX] = {
         [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm",
         [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill",
         [SOCKET_STOP_POST] = "stop-post",
-        [SOCKET_STOP_POST_SIGTERM] = "stop-post-sigterm",
-        [SOCKET_STOP_POST_SIGKILL] = "stop-post-sigkill",
+        [SOCKET_FINAL_SIGTERM] = "final-sigterm",
+        [SOCKET_FINAL_SIGKILL] = "final-sigkill",
         [SOCKET_MAINTAINANCE] = "maintainance"
 };
 
@@ -365,8 +365,8 @@ static void socket_set_state(Socket *s, SocketState state) {
             state != SOCKET_STOP_PRE_SIGTERM &&
             state != SOCKET_STOP_PRE_SIGKILL &&
             state != SOCKET_STOP_POST &&
-            state != SOCKET_STOP_POST_SIGTERM &&
-            state != SOCKET_STOP_POST_SIGKILL) {
+            state != SOCKET_FINAL_SIGTERM &&
+            state != SOCKET_FINAL_SIGKILL) {
                 unit_unwatch_timer(UNIT(s), &s->timer_watch);
                 socket_unwatch_control_pid(s);
                 s->control_command = NULL;
@@ -405,6 +405,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
                             NULL, 0,
                             true,
                             true,
+                            UNIT(s)->meta.manager->confirm_spawn,
                             UNIT(s)->meta.cgroup_bondings,
                             &pid)) < 0)
                 goto fail;
@@ -432,6 +433,8 @@ static void socket_enter_dead(Socket *s, bool success) {
         socket_set_state(s, s->failure ? SOCKET_MAINTAINANCE : SOCKET_DEAD);
 }
 
+static void socket_enter_signal(Socket *s, SocketState state, bool success);
+
 static void socket_enter_stop_post(Socket *s, bool success) {
         int r;
         assert(s);
@@ -441,35 +444,32 @@ static void socket_enter_stop_post(Socket *s, bool success) {
 
         socket_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST]))
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) {
                 if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
                         goto fail;
 
-        socket_set_state(s, SOCKET_STOP_POST);
-
-        if (!s->control_command)
-                socket_enter_dead(s, true);
+                socket_set_state(s, SOCKET_STOP_POST);
+        } else
+                socket_enter_signal(s, SOCKET_FINAL_SIGTERM, true);
 
         return;
 
 fail:
         log_warning("%s failed to run stop-post executable: %s", unit_id(UNIT(s)), strerror(-r));
-        socket_enter_dead(s, false);
+        socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
 }
 
 static void socket_enter_signal(Socket *s, SocketState state, bool success) {
         int r;
+        bool sent = false;
 
         assert(s);
 
         if (!success)
                 s->failure = true;
 
-        if (s->control_pid > 0) {
-                int sig;
-                bool sent = false;
-
-                sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_POST_SIGTERM) ? SIGTERM : SIGKILL;
+        if (s->kill_mode != KILL_NONE) {
+                int sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_FINAL_SIGTERM) ? SIGTERM : SIGKILL;
 
                 if (s->kill_mode == KILL_CONTROL_GROUP) {
 
@@ -480,16 +480,21 @@ static void socket_enter_signal(Socket *s, SocketState state, bool success) {
                                 sent = true;
                 }
 
-                if (!sent)
+                if (!sent && s->control_pid > 0)
                         if (kill(s->kill_mode == KILL_PROCESS ? s->control_pid : -s->control_pid, sig) < 0 && errno != ESRCH) {
                                 r = -errno;
                                 goto fail;
                         }
         }
 
-        socket_set_state(s, state);
+        if (sent) {
+                if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
+                        goto fail;
 
-        if (s->control_pid <= 0)
+                socket_set_state(s, state);
+        } else if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL)
+                socket_enter_stop_post(s, true);
+        else
                 socket_enter_dead(s, true);
 
         return;
@@ -512,13 +517,12 @@ static void socket_enter_stop_pre(Socket *s, bool success) {
 
         socket_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE]))
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) {
                 if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
                         goto fail;
 
-        socket_set_state(s, SOCKET_STOP_PRE);
-
-        if (!s->control_command)
+                socket_set_state(s, SOCKET_STOP_PRE);
+        } else
                 socket_enter_stop_post(s, true);
 
         return;
@@ -555,15 +559,14 @@ static void socket_enter_start_post(Socket *s) {
 
         socket_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST]))
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) {
                 if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) {
                         log_warning("%s failed to run start-post executable: %s", unit_id(UNIT(s)), strerror(-r));
                         goto fail;
                 }
 
-        socket_set_state(s, SOCKET_START_POST);
-
-        if (!s->control_command)
+                socket_set_state(s, SOCKET_START_POST);
+        } else
                 socket_enter_listening(s);
 
         return;
@@ -578,13 +581,12 @@ static void socket_enter_start_pre(Socket *s) {
 
         socket_unwatch_control_pid(s);
 
-        if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE]))
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) {
                 if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
                         goto fail;
 
-        socket_set_state(s, SOCKET_START_PRE);
-
-        if (!s->control_command)
+                socket_set_state(s, SOCKET_START_PRE);
+        } else
                 socket_enter_start_post(s);
 
         return;
@@ -607,7 +609,7 @@ static void socket_enter_running(Socket *s) {
 
 fail:
         log_warning("%s failed to queue socket startup job: %s", unit_id(UNIT(s)), strerror(-r));
-        socket_enter_dead(s, false);
+        socket_enter_stop_pre(s, false);
 }
 
 static void socket_run_next(Socket *s, bool success) {
@@ -630,12 +632,14 @@ static void socket_run_next(Socket *s, bool success) {
         return;
 
 fail:
-        if (s->state == SOCKET_STOP_PRE)
-                socket_enter_stop_post(s, false);
+        log_warning("%s failed to run spawn next executable: %s", unit_id(UNIT(s)), strerror(-r));
+
+        if (s->state == SOCKET_START_POST)
+                socket_enter_stop_pre(s, false);
         else if (s->state == SOCKET_STOP_POST)
                 socket_enter_dead(s, false);
         else
-                socket_enter_stop_pre(s, false);
+                socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
 }
 
 static int socket_start(Unit *u) {
@@ -649,8 +653,8 @@ static int socket_start(Unit *u) {
             s->state == SOCKET_STOP_PRE_SIGKILL ||
             s->state == SOCKET_STOP_PRE_SIGTERM ||
             s->state == SOCKET_STOP_POST ||
-            s->state == SOCKET_STOP_POST_SIGTERM ||
-            s->state == SOCKET_STOP_POST_SIGKILL)
+            s->state == SOCKET_FINAL_SIGTERM ||
+            s->state == SOCKET_FINAL_SIGKILL)
                 return -EAGAIN;
 
         if (s->state == SOCKET_START_PRE ||
@@ -691,8 +695,8 @@ static int socket_stop(Unit *u) {
             s->state == SOCKET_STOP_PRE_SIGTERM ||
             s->state == SOCKET_STOP_PRE_SIGKILL ||
             s->state == SOCKET_STOP_POST ||
-            s->state == SOCKET_STOP_POST_SIGTERM ||
-            s->state == SOCKET_STOP_POST_SIGTERM)
+            s->state == SOCKET_FINAL_SIGTERM ||
+            s->state == SOCKET_FINAL_SIGTERM)
                 return 0;
 
         assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
@@ -738,9 +742,8 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
 
         log_debug("%s control process exited, code=%s status=%i", unit_id(u), sigchld_code_to_string(code), status);
 
-        if (s->control_command->command_next &&
-            (success || (s->state == SOCKET_STOP_PRE || s->state == SOCKET_STOP_POST))) {
-                log_debug("%s running next command for the state %s", unit_id(u), state_string_table[s->state]);
+        if (s->control_command->command_next && success) {
+                log_debug("%s running next command for state %s", unit_id(u), state_string_table[s->state]);
                 socket_run_next(s, success);
         } else {
                 /* No further commands for this step, so let's figure
@@ -754,7 +757,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                         if (success)
                                 socket_enter_start_post(s);
                         else
-                                socket_enter_stop_pre(s, false);
+                                socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
                         break;
 
                 case SOCKET_START_POST:
@@ -771,8 +774,8 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                         break;
 
                 case SOCKET_STOP_POST:
-                case SOCKET_STOP_POST_SIGTERM:
-                case SOCKET_STOP_POST_SIGKILL:
+                case SOCKET_FINAL_SIGTERM:
+                case SOCKET_FINAL_SIGKILL:
                         socket_enter_dead(s, success);
                         break;
 
@@ -792,6 +795,9 @@ static void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
         switch (s->state) {
 
         case SOCKET_START_PRE:
+                log_warning("%s starting timed out. Terminating.", unit_id(u));
+                socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
+
         case SOCKET_START_POST:
                 log_warning("%s starting timed out. Stopping.", unit_id(u));
                 socket_enter_stop_pre(s, false);
@@ -814,15 +820,15 @@ static void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
 
         case SOCKET_STOP_POST:
                 log_warning("%s stopping timed out (2). Terminating.", unit_id(u));
-                socket_enter_signal(s, SOCKET_STOP_POST_SIGTERM, false);
+                socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
                 break;
 
-        case SOCKET_STOP_POST_SIGTERM:
+        case SOCKET_FINAL_SIGTERM:
                 log_warning("%s stopping timed out (2). Killing.", unit_id(u));
-                socket_enter_signal(s, SOCKET_STOP_POST_SIGKILL, false);
+                socket_enter_signal(s, SOCKET_FINAL_SIGKILL, false);
                 break;
 
-        case SOCKET_STOP_POST_SIGKILL:
+        case SOCKET_FINAL_SIGKILL:
                 log_warning("%s still around after SIGKILL (2). Entering maintainance mode.", unit_id(u));
                 socket_enter_dead(s, false);
                 break;
index 4e053c0..6f2a7e8 100644 (file)
--- a/socket.h
+++ b/socket.h
@@ -38,8 +38,8 @@ typedef enum SocketState {
         SOCKET_STOP_PRE_SIGTERM,
         SOCKET_STOP_PRE_SIGKILL,
         SOCKET_STOP_POST,
-        SOCKET_STOP_POST_SIGTERM,
-        SOCKET_STOP_POST_SIGKILL,
+        SOCKET_FINAL_SIGTERM,
+        SOCKET_FINAL_SIGKILL,
         SOCKET_MAINTAINANCE,
         _SOCKET_STATE_MAX,
         _SOCKET_STATE_INVALID = -1
index cb81764..1a2f893 100644 (file)
@@ -33,7 +33,7 @@ int main(int argc, char *argv[]) {
 
         assert_se(set_unit_path("test2") >= 0);
 
-        assert_se(manager_new(MANAGER_INIT, &m) >= 0);
+        assert_se(manager_new(MANAGER_INIT, false, &m) >= 0);
 
         printf("Load1:\n");
         assert_se(manager_load_unit(m, "a.service", &a) == 0);
diff --git a/unit.c b/unit.c
index d1416e0..7d57005 100644 (file)
--- a/unit.c
+++ b/unit.c
@@ -471,7 +471,7 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
         assert(u);
         assert(c);
 
-        if (c->output != EXEC_OUTPUT_KERNEL && c->output != EXEC_OUTPUT_SYSLOG)
+        if (c->std_output != EXEC_OUTPUT_KERNEL && c->std_output != EXEC_OUTPUT_SYSLOG)
                 return 0;
 
         /* If syslog or kernel logging is requested, make sure our own
@@ -1520,9 +1520,10 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);
 
 static const char* const kill_mode_table[_KILL_MODE_MAX] = {
-        [KILL_PROCESS] = "process",
+        [KILL_CONTROL_GROUP] = "control-group",
         [KILL_PROCESS_GROUP] = "process-group",
-        [KILL_CONTROL_GROUP] = "control-group"
+        [KILL_PROCESS] = "process",
+        [KILL_NONE] = "none"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode);
diff --git a/unit.h b/unit.h
index 4b732c0..ae6e445 100644 (file)
--- a/unit.h
+++ b/unit.h
@@ -47,6 +47,7 @@ typedef enum KillMode {
         KILL_CONTROL_GROUP = 0,
         KILL_PROCESS_GROUP,
         KILL_PROCESS,
+        KILL_NONE,
         _KILL_MODE_MAX,
         _KILL_MODE_INVALID = -1
 } KillMode;
diff --git a/util.c b/util.c
index f9eae6b..e32e2f2 100644 (file)
--- a/util.c
+++ b/util.c
 #include <sys/ioctl.h>
 #include <linux/vt.h>
 #include <linux/tiocl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
 
 #include "macro.h"
 #include "util.h"
@@ -164,11 +168,14 @@ int close_nointr(int fd) {
 }
 
 void close_nointr_nofail(int fd) {
+        int saved_errno = errno;
 
         /* like close_nointr() but cannot fail, and guarantees errno
          * is unchanged */
 
         assert_se(close_nointr(fd) == 0);
+
+        errno = saved_errno;
 }
 
 int parse_boolean(const char *v) {
@@ -1322,6 +1329,314 @@ int chvt(int vt) {
         return r;
 }
 
+int read_one_char(FILE *f, char *ret, bool *need_nl) {
+        struct termios old_termios, new_termios;
+        char c;
+        char line[1024];
+
+        assert(f);
+        assert(ret);
+
+        if (tcgetattr(fileno(f), &old_termios) >= 0) {
+                new_termios = old_termios;
+
+                new_termios.c_lflag &= ~ICANON;
+                new_termios.c_cc[VMIN] = 1;
+                new_termios.c_cc[VTIME] = 0;
+
+                if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
+                        size_t k;
+
+                        k = fread(&c, 1, 1, f);
+
+                        tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+
+                        if (k <= 0)
+                                return -EIO;
+
+                        if (need_nl)
+                                *need_nl = c != '\n';
+
+                        *ret = c;
+                        return 0;
+                }
+        }
+
+        if (!(fgets(line, sizeof(line), f)))
+                return -EIO;
+
+        truncate_nl(line);
+
+        if (strlen(line) != 1)
+                return -EBADMSG;
+
+        if (need_nl)
+                *need_nl = false;
+
+        *ret = line[0];
+        return 0;
+}
+
+int ask(char *ret, const char *replies, const char *text, ...) {
+        assert(ret);
+        assert(replies);
+        assert(text);
+
+        for (;;) {
+                va_list ap;
+                char c;
+                int r;
+                bool need_nl = true;
+
+                va_start(ap, text);
+                vprintf(text, ap);
+                va_end(ap);
+
+                fflush(stdout);
+
+                if ((r = read_one_char(stdin, &c, &need_nl)) < 0) {
+
+                        if (r == -EBADMSG) {
+                                puts("Bad input, please try again.");
+                                continue;
+                        }
+
+                        putchar('\n');
+                        return r;
+                }
+
+                if (need_nl)
+                        putchar('\n');
+
+                if (strchr(replies, c)) {
+                        *ret = c;
+                        return 0;
+                }
+
+                puts("Read unexpected character, please try again.");
+        }
+}
+
+int reset_terminal(int fd) {
+        struct termios termios;
+        int r = 0;
+
+        assert(fd >= 0);
+
+        /* Set terminal up for job control */
+
+        if (tcgetattr(fd, &termios) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        termios.c_iflag &= ~(IGNBRK | BRKINT);
+        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
+        termios.c_oflag |= ONLCR;
+        termios.c_cflag |= CREAD;
+        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
+
+        termios.c_cc[VINTR]    =   03;  /* ^C */
+        termios.c_cc[VQUIT]    =  034;  /* ^\ */
+        termios.c_cc[VERASE]   = 0177;
+        termios.c_cc[VKILL]    =  025;  /* ^X */
+        termios.c_cc[VEOF]     =   04;  /* ^D */
+        termios.c_cc[VSTART]   =  021;  /* ^Q */
+        termios.c_cc[VSTOP]    =  023;  /* ^S */
+        termios.c_cc[VSUSP]    =  032;  /* ^Z */
+        termios.c_cc[VLNEXT]   =  026;  /* ^V */
+        termios.c_cc[VWERASE]  =  027;  /* ^W */
+        termios.c_cc[VREPRINT] =  022;  /* ^R */
+
+        termios.c_cc[VTIME]  = 0;
+        termios.c_cc[VMIN]   = 1;
+
+        if (tcsetattr(fd, TCSANOW, &termios) < 0)
+                r = -errno;
+
+finish:
+        /* Just in case, flush all crap out */
+        tcflush(fd, TCIOFLUSH);
+
+        return r;
+}
+
+int open_terminal(const char *name, int mode) {
+        int fd, r;
+
+        if ((fd = open(name, mode)) < 0)
+                return -errno;
+
+        if ((r = isatty(fd)) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        if (!r) {
+                close_nointr_nofail(fd);
+                return -ENOTTY;
+        }
+
+        return fd;
+}
+
+int flush_fd(int fd) {
+        struct pollfd pollfd;
+
+        zero(pollfd);
+        pollfd.fd = fd;
+        pollfd.events = POLLIN;
+
+        for (;;) {
+                char buf[1024];
+                ssize_t l;
+                int r;
+
+                if ((r = poll(&pollfd, 1, 0)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+                }
+
+                if (r == 0)
+                        return 0;
+
+                if ((l = read(fd, buf, sizeof(buf))) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN)
+                                return 0;
+
+                        return -errno;
+                }
+
+                if (l <= 0)
+                        return 0;
+        }
+}
+
+int acquire_terminal(const char *name, bool fail, bool force) {
+        int fd = -1, notify = -1, r, wd;
+
+        assert(name);
+
+        /* We use inotify to be notified when the tty is closed. We
+         * create the watch before checking if we can actually acquire
+         * it, so that we don't lose any event.
+         *
+         * Note: strictly speaking this actually watches for the
+         * device being closed, it does *not* really watch whether a
+         * tty loses its controlling process. However, unless some
+         * rogue process uses TIOCNOTTY on /dev/tty *after* closing
+         * its tty otherwise this will not become a problem. As long
+         * as the administrator makes sure not configure any service
+         * on the same tty as an untrusted user this should not be a
+         * problem. (Which he probably should not do anyway.) */
+
+        if (!fail && !force) {
+                if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+        }
+
+        for (;;) {
+                if ((r = flush_fd(notify)) < 0)
+                        goto fail;
+
+                /* We pass here O_NOCTTY only so that we can check the return
+                 * value TIOCSCTTY and have a reliable way to figure out if we
+                 * successfully became the controlling process of the tty */
+                if ((fd = open_terminal(name, O_RDWR|O_NOCTTY)) < 0)
+                        return -errno;
+
+                /* First, try to get the tty */
+                if ((r = ioctl(fd, TIOCSCTTY, force)) < 0 &&
+                    (force || fail || errno != EPERM)) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                if (r >= 0)
+                        break;
+
+                assert(!fail);
+                assert(!force);
+                assert(notify >= 0);
+
+                for (;;) {
+                        struct inotify_event e;
+                        ssize_t l;
+
+                        if ((l = read(notify, &e, sizeof(e))) != sizeof(e)) {
+
+                                if (l < 0) {
+
+                                        if (errno == EINTR)
+                                                continue;
+
+                                        r = -errno;
+                                } else
+                                        r = -EIO;
+
+                                goto fail;
+                        }
+
+                        if (e.wd != wd || !(e.mask & IN_CLOSE)) {
+                                r = -errno;
+                                goto fail;
+                        }
+
+                        break;
+                }
+
+                /* We close the tty fd here since if the old session
+                 * ended our handle will be dead. It's important that
+                 * we do this after sleeping, so that we don't enter
+                 * an endless loop. */
+                close_nointr_nofail(fd);
+        }
+
+        if (notify >= 0)
+                close_nointr(notify);
+
+        if ((r = reset_terminal(fd)) < 0)
+                log_warning("Failed to reset terminal: %s", strerror(-r));
+
+        return fd;
+
+fail:
+        if (fd >= 0)
+                close_nointr(fd);
+
+        if (notify >= 0)
+                close_nointr(notify);
+
+        return r;
+}
+
+int release_terminal(void) {
+        int r = 0, fd;
+
+        if ((fd = open("/dev/tty", O_RDWR)) < 0)
+                return -errno;
+
+        if (ioctl(fd, TIOCNOTTY) < 0)
+                r = -errno;
+
+        close_nointr_nofail(fd);
+        return r;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
diff --git a/util.h b/util.h
index ba27b5a..6d1af6e 100644 (file)
--- a/util.h
+++ b/util.h
@@ -27,6 +27,7 @@
 #include <sys/time.h>
 #include <stdbool.h>
 #include <stdlib.h>
+#include <stdio.h>
 
 typedef uint64_t usec_t;
 
@@ -200,6 +201,16 @@ bool fstype_is_network(const char *fstype);
 
 int chvt(int vt);
 
+int read_one_char(FILE *f, char *ret, bool *need_nl);
+int ask(char *ret, const char *replies, const char *text, ...);
+
+int reset_terminal(int fd);
+int open_terminal(const char *name, int mode);
+int acquire_terminal(const char *name, bool fail, bool force);
+int release_terminal(void);
+
+int flush_fd(int fd);
+
 extern char * __progname;
 
 const char *ioprio_class_to_string(int i);