--- src/tty-ask-password-agent/tty-ask-password-agent.c | 171 +++++++++++++++++++- 1 file changed, 166 insertions(+), 5 deletions(-) Index: systemd/src/tty-ask-password-agent/tty-ask-password-agent.c =================================================================== --- systemd.orig/src/tty-ask-password-agent/tty-ask-password-agent.c +++ systemd/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -31,6 +31,10 @@ #include #include #include +#include +#include +#include +#include #include "util.h" #include "mkdir.h" @@ -42,6 +46,9 @@ #include "strv.h" #include "build.h" #include "def.h" +#include "fileio.h" +#include "macro.h" +#include "list.h" static enum { ACTION_LIST, @@ -50,6 +57,22 @@ static enum { ACTION_WALL } arg_action = ACTION_QUERY; +struct console { + LIST_FIELDS(struct console, handle); + const char *tty; + pid_t pid; + int id; + char dev[]; +}; + +static volatile unsigned long *usemask; +static volatile sig_atomic_t sigchild; +static void chld_handler(int sig) +{ + (void)sig; + ++sigchild; +} + static bool arg_plymouth = false; static bool arg_console = false; @@ -208,6 +231,58 @@ static int ask_password_plymouth( return 0; } +static const char *current_dev = "/dev/console"; +static LIST_HEAD(struct console, consoles); +static int collect_consoles(void) { + _cleanup_free_ char *active = NULL; + const char *word, *state; + struct console *con; + size_t len; + int ret, id = 0; + + ret = read_one_line_file("/sys/class/tty/console/active", &active); + if (ret < 0) + return ret; + FOREACH_WORD(word, len, active, state) { + _cleanup_free_ char *tty = NULL; + + if (strneq(word, "tty0", len) && + read_one_line_file("/sys/class/tty/tty0/active", &tty) >= 0) { + word = tty; + len = strlen(tty); + } + con = malloc0(sizeof(*con) + strlen("/dev/") + len + 1); + if (con == NULL) { + log_oom(); + continue; + } + sprintf(con->dev, "/dev/%.*s", (int)len, word); + con->tty = con->dev; + con->id = id++; + LIST_PREPEND(handle, consoles, con); + } + if (consoles == NULL) { + con = malloc0(sizeof(*con)); + if (con == NULL) { + log_oom(); + return -ENOMEM; + } + con->tty = current_dev; + con->id = id++; + LIST_PREPEND(handle, consoles, con); + } + return 0; +} + +static void free_consoles(void) { + struct console *c; + LIST_FOREACH(handle, c, consoles) { + LIST_REMOVE(handle, consoles, c); + free(c); + } + LIST_HEAD_INIT(consoles); +} + static int parse_password(const char *filename, char **wall) { _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL; uint64_t not_after = 0; @@ -308,7 +383,7 @@ static int parse_password(const char *fi _cleanup_free_ char *password = NULL; if (arg_console) { - tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY); + tty_fd = acquire_terminal(current_dev, false, false, false, USEC_INFINITY); if (tty_fd < 0) return tty_fd; } @@ -612,9 +687,85 @@ static int parse_argv(int argc, char *ar return 1; } +static int zzz(void) +{ + struct console *con; + struct sigaction sig = { + .sa_handler = chld_handler, + .sa_flags = SA_NOCLDSTOP | SA_RESTART, + }; + struct sigaction oldsig; + sigset_t set, oldset; + int status = 0, ret; + pid_t job; + + collect_consoles(); + if (!consoles->handle_next) { + consoles->pid = 0; + con = consoles; + goto nofork; + } + + assert_se(sigemptyset(&set) == 0); + assert_se(sigaddset(&set, SIGHUP) == 0); + assert_se(sigaddset(&set, SIGCHLD) == 0); + assert_se(sigemptyset(&sig.sa_mask) == 0); + assert_se(sigprocmask(SIG_UNBLOCK, &set, &oldset) == 0); + assert_se(sigaction(SIGCHLD, &sig, &oldsig) == 0); + sig.sa_handler = SIG_DFL; + assert_se(sigaction(SIGHUP, &sig, NULL) == 0); + + LIST_FOREACH(handle, con, consoles) { + switch ((con->pid = fork())) { + case 0: + if (prctl(PR_SET_PDEATHSIG, SIGHUP) < 0) + _exit(EXIT_FAILURE); + zero(sig); + assert_se(sigprocmask(SIG_UNBLOCK, &oldset, NULL) == 0); + assert_se(sigaction(SIGCHLD, &oldsig, NULL) == 0); + nofork: + setsid(); + release_terminal(); + *usemask |= 1 << con->id; + current_dev = con->tty; + return con->id; /* child */ + case -1: + log_error("Failed to query password: %s", strerror(errno)); + exit(EXIT_FAILURE); + default: + break; + } + } + + ret = 0; + while ((job = wait(&status)) != 0) { + if (job < 0) { + if (errno != EINTR) + break; + continue; + } + LIST_FOREACH(handle, con, consoles) { + if (con->pid == job || kill(con->pid, 0) < 0) { + *usemask &= ~(1 << con->id); + continue; + } + if (*usemask & (1 << con->id)) + continue; + kill(con->pid, SIGHUP); + usleep(50000); + kill(con->pid, SIGKILL); + } + if (WIFEXITED(status) && ret == 0) + ret = WEXITSTATUS(status); + } + free_consoles(); + exit(ret != 0 ? EXIT_FAILURE : EXIT_SUCCESS); /* parent */ +} + int main(int argc, char *argv[]) { - int r; + int r, id = 0; + LIST_HEAD_INIT(consoles); log_set_target(LOG_TARGET_AUTO); log_parse_environment(); log_open(); @@ -625,11 +776,19 @@ int main(int argc, char *argv[]) { if (r <= 0) goto finish; + usemask = mmap(NULL, sizeof(*usemask), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, -1, 0); + assert_se(usemask != NULL); + if (arg_console) { - setsid(); - release_terminal(); + if (!arg_plymouth && arg_action != ACTION_WALL && + arg_action != ACTION_LIST) { + id = zzz(); + } else { + setsid(); + release_terminal(); + } } - if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL)) r = watch_passwords(); else @@ -638,6 +797,8 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Error: %m"); + free_consoles(); + *usemask &= ~(1 << id); finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }