diff options
-rw-r--r-- | execute.c | 574 | ||||
-rw-r--r-- | execute.h | 43 | ||||
-rw-r--r-- | load-fragment.c | 9 | ||||
-rw-r--r-- | main.c | 76 | ||||
-rw-r--r-- | manager.c | 44 | ||||
-rw-r--r-- | manager.h | 6 | ||||
-rw-r--r-- | mount.c | 46 | ||||
-rw-r--r-- | service.c | 218 | ||||
-rw-r--r-- | service.h | 1 | ||||
-rw-r--r-- | socket.c | 106 | ||||
-rw-r--r-- | socket.h | 4 | ||||
-rw-r--r-- | test-engine.c | 2 | ||||
-rw-r--r-- | unit.c | 7 | ||||
-rw-r--r-- | unit.h | 1 | ||||
-rw-r--r-- | util.c | 315 | ||||
-rw-r--r-- | util.h | 11 |
16 files changed, 1135 insertions, 328 deletions
@@ -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); @@ -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 diff --git a/load-fragment.c b/load-fragment.c index d1b8199330..9eb00531c9 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -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" }, @@ -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; } @@ -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" @@ -45,6 +47,28 @@ #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: @@ -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); @@ -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) { @@ -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", @@ -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, @@ -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; @@ -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 diff --git a/test-engine.c b/test-engine.c index cb817645ac..1a2f8930d8 100644 --- a/test-engine.c +++ b/test-engine.c @@ -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); @@ -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); @@ -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; @@ -37,6 +37,10 @@ #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", @@ -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); |