Merge pull request #4538 from fbuihuu/confirm-spawn-fixes

Confirm spawn fixes/enhancements
This commit is contained in:
Lennart Poettering 2016-11-18 11:08:06 +01:00 committed by GitHub
commit 2e6dbc0fcd
16 changed files with 283 additions and 76 deletions

17
NEWS
View file

@ -1,5 +1,22 @@
systemd System and Service Manager
CHANGES WITH 233 in spe
* The confirmation spawn prompt has been reworked to offer the
following choices:
(c)ontinue, proceed without asking anymore
(D)ump, show the state of the unit
(f)ail, don't execute the command and pretend it failed
(h)elp
(i)nfo, show a short summary of the unit
(j)obs, show jobs that are in progress
(s)kip, don't execute the command and pretend it succeeded
(y)es, execute the command
The 'n' choice for the confirmation spawn prompt has been removed,
because its meaning was confusing.
CHANGES WITH 232:
* The new RemoveIPC= option can be used to remove IPC objects owned by

View file

@ -940,10 +940,14 @@
<varlistentry>
<term><varname>systemd.confirm_spawn=</varname></term>
<listitem><para>Takes a boolean argument. If
<option>yes</option>, the system manager (PID 1) asks for
confirmation when spawning processes. Defaults to
<option>no</option>.</para></listitem>
<listitem><para>Takes a boolean argument or a path to the
virtual console where the confirmation messages should be
emitted. If <option>yes</option>, the system manager (PID 1)
asks for confirmation when spawning processes using
<option>/dev/console</option>. If a path or a console name
(such as <literal>ttyS0</literal>) is provided, the virtual
console pointed to by this path or described by the give name
will be used instead. Defaults to <option>no</option>.</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -36,7 +36,9 @@
#include "log.h"
#include "pager.h"
#include "parse-util.h"
#ifdef HAVE_SECCOMP
#include "seccomp-util.h"
#endif
#include "special.h"
#include "strv.h"
#include "strxcpyx.h"

View file

@ -144,12 +144,14 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
return 0;
}
int ask_char(char *ret, const char *replies, const char *text, ...) {
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
int ask_char(char *ret, const char *replies, const char *fmt, ...) {
int r;
assert(ret);
assert(replies);
assert(text);
assert(fmt);
for (;;) {
va_list ap;
@ -159,8 +161,10 @@ int ask_char(char *ret, const char *replies, const char *text, ...) {
if (colors_enabled())
fputs(ANSI_HIGHLIGHT, stdout);
va_start(ap, text);
vprintf(text, ap);
putchar('\r');
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
if (colors_enabled())
@ -168,9 +172,12 @@ int ask_char(char *ret, const char *replies, const char *text, ...) {
fflush(stdout);
r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl);
r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, &need_nl);
if (r < 0) {
if (r == -ETIMEDOUT)
continue;
if (r == -EBADMSG) {
puts("Bad input, please try again.");
continue;
@ -455,7 +462,7 @@ int acquire_terminal(
goto fail;
}
r = fd_wait_for_event(fd, POLLIN, ts + timeout - n);
r = fd_wait_for_event(notify, POLLIN, ts + timeout - n);
if (r < 0)
goto fail;

View file

@ -624,7 +624,7 @@ static int chown_terminal(int fd, uid_t uid) {
return 0;
}
static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) {
static int setup_confirm_stdio(const char *vc, int *_saved_stdin, int *_saved_stdout) {
_cleanup_close_ int fd = -1, saved_stdin = -1, saved_stdout = -1;
int r;
@ -639,12 +639,7 @@ static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) {
if (saved_stdout < 0)
return -errno;
fd = acquire_terminal(
"/dev/console",
false,
false,
false,
DEFAULT_CONFIRM_USEC);
fd = acquire_terminal(vc, false, false, false, DEFAULT_CONFIRM_USEC);
if (fd < 0)
return fd;
@ -674,21 +669,27 @@ static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) {
return 0;
}
_printf_(1, 2) static int write_confirm_message(const char *format, ...) {
static void write_confirm_error_fd(int err, int fd, const Unit *u) {
assert(err < 0);
if (err == -ETIMEDOUT)
dprintf(fd, "Confirmation question timed out for %s, assuming positive response.\n", u->id);
else {
errno = -err;
dprintf(fd, "Couldn't ask confirmation for %s: %m, assuming positive response.\n", u->id);
}
}
static void write_confirm_error(int err, const char *vc, const Unit *u) {
_cleanup_close_ int fd = -1;
va_list ap;
assert(format);
assert(vc);
fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
fd = open_terminal(vc, O_WRONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return fd;
return;
va_start(ap, format);
vdprintf(fd, format, ap);
va_end(ap);
return 0;
write_confirm_error_fd(err, fd, u);
}
static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) {
@ -713,22 +714,96 @@ static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) {
return r;
}
static int ask_for_confirmation(char *response, char **argv) {
enum {
CONFIRM_PRETEND_FAILURE = -1,
CONFIRM_PRETEND_SUCCESS = 0,
CONFIRM_EXECUTE = 1,
};
static int ask_for_confirmation(const char *vc, Unit *u, const char *cmdline) {
int saved_stdout = -1, saved_stdin = -1, r;
_cleanup_free_ char *line = NULL;
_cleanup_free_ char *e = NULL;
char c;
r = setup_confirm_stdio(&saved_stdin, &saved_stdout);
if (r < 0)
return r;
/* For any internal errors, assume a positive response. */
r = setup_confirm_stdio(vc, &saved_stdin, &saved_stdout);
if (r < 0) {
write_confirm_error(r, vc, u);
return CONFIRM_EXECUTE;
}
line = exec_command_line(argv);
if (!line)
return -ENOMEM;
/* confirm_spawn might have been disabled while we were sleeping. */
if (manager_is_confirm_spawn_disabled(u->manager)) {
r = 1;
goto restore_stdio;
}
r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line);
e = ellipsize(cmdline, 60, 100);
if (!e) {
log_oom();
r = CONFIRM_EXECUTE;
goto restore_stdio;
}
for (;;) {
r = ask_char(&c, "yfshiDjcn", "Execute %s? [y, f, s h for help] ", e);
if (r < 0) {
write_confirm_error_fd(r, STDOUT_FILENO, u);
r = CONFIRM_EXECUTE;
goto restore_stdio;
}
switch (c) {
case 'c':
printf("Resuming normal execution.\n");
manager_disable_confirm_spawn();
r = 1;
break;
case 'D':
unit_dump(u, stdout, " ");
continue; /* ask again */
case 'f':
printf("Failing execution.\n");
r = CONFIRM_PRETEND_FAILURE;
break;
case 'h':
printf(" c - continue, proceed without asking anymore\n"
" D - dump, show the state of the unit\n"
" f - fail, don't execute the command and pretend it failed\n"
" h - help\n"
" i - info, show a short summary of the unit\n"
" j - jobs, show jobs that are in progress\n"
" s - skip, don't execute the command and pretend it succeeded\n"
" y - yes, execute the command\n");
continue; /* ask again */
case 'i':
printf(" Description: %s\n"
" Unit: %s\n"
" Command: %s\n",
u->id, u->description, cmdline);
continue; /* ask again */
case 'j':
manager_dump_jobs(u->manager, stdout, " ");
continue; /* ask again */
case 'n':
/* 'n' was removed in favor of 'f'. */
printf("Didn't understand 'n', did you mean 'f'?\n");
continue; /* ask again */
case 's':
printf("Skipping execution.\n");
r = CONFIRM_PRETEND_SUCCESS;
break;
case 'y':
r = CONFIRM_EXECUTE;
break;
default:
assert_not_reached("Unhandled choice");
}
break;
}
restore_stdio:
restore_confirm_stdio(&saved_stdin, &saved_stdout);
return r;
}
@ -2315,22 +2390,24 @@ static int exec_child(
exec_context_tty_reset(context, params);
if (params->flags & EXEC_CONFIRM_SPAWN) {
char response;
if (unit_shall_confirm_spawn(unit)) {
const char *vc = params->confirm_spawn;
_cleanup_free_ char *cmdline = NULL;
r = ask_for_confirmation(&response, argv);
if (r == -ETIMEDOUT)
write_confirm_message("Confirmation question timed out, assuming positive response.\n");
else if (r < 0)
write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-r));
else if (response == 's') {
write_confirm_message("Skipping execution.\n");
cmdline = exec_command_line(argv);
if (!cmdline) {
*exit_status = EXIT_CONFIRM;
return -ENOMEM;
}
r = ask_for_confirmation(vc, unit, cmdline);
if (r != CONFIRM_EXECUTE) {
if (r == CONFIRM_PRETEND_SUCCESS) {
*exit_status = EXIT_SUCCESS;
return 0;
}
*exit_status = EXIT_CONFIRM;
return -ECANCELED;
} else if (response == 'n') {
write_confirm_message("Failing execution.\n");
*exit_status = 0;
return 0;
}
}

View file

@ -225,16 +225,15 @@ static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
}
typedef enum ExecFlags {
EXEC_CONFIRM_SPAWN = 1U << 0,
EXEC_APPLY_PERMISSIONS = 1U << 1,
EXEC_APPLY_CHROOT = 1U << 2,
EXEC_APPLY_TTY_STDIN = 1U << 3,
EXEC_APPLY_PERMISSIONS = 1U << 0,
EXEC_APPLY_CHROOT = 1U << 1,
EXEC_APPLY_TTY_STDIN = 1U << 2,
/* The following are not used by execute.c, but by consumers internally */
EXEC_PASS_FDS = 1U << 4,
EXEC_IS_CONTROL = 1U << 5,
EXEC_SETENV_RESULT = 1U << 6,
EXEC_SET_WATCHDOG = 1U << 7,
EXEC_PASS_FDS = 1U << 3,
EXEC_IS_CONTROL = 1U << 4,
EXEC_SETENV_RESULT = 1U << 5,
EXEC_SET_WATCHDOG = 1U << 6,
} ExecFlags;
struct ExecParameters {
@ -254,6 +253,8 @@ struct ExecParameters {
const char *runtime_prefix;
const char *confirm_spawn;
usec_t watchdog_usec;
int *idle_pipe;

View file

@ -68,6 +68,7 @@
#include "mount-setup.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "raw-clone.h"
@ -104,7 +105,7 @@ static bool arg_dump_core = true;
static int arg_crash_chvt = -1;
static bool arg_crash_shell = false;
static bool arg_crash_reboot = false;
static bool arg_confirm_spawn = false;
static char *arg_confirm_spawn = NULL;
static ShowStatus arg_show_status = _SHOW_STATUS_UNSET;
static bool arg_switched_root = false;
static bool arg_no_pager = false;
@ -294,6 +295,28 @@ static int parse_crash_chvt(const char *value) {
return 0;
}
static int parse_confirm_spawn(const char *value, char **console) {
char *s;
int r;
r = value ? parse_boolean(value) : 1;
if (r == 0) {
*console = NULL;
return 0;
}
if (r > 0) /* on with default tty */
s = strdup("/dev/console");
else if (is_path(value)) /* on with fully qualified path */
s = strdup(value);
else /* on with only a tty file name, not a fully qualified path */
s = strjoin("/dev/", value);
if (!s)
return -ENOMEM;
*console = s;
return 0;
}
static int set_machine_id(const char *m) {
sd_id128_t t;
assert(m);
@ -355,11 +378,11 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
} else if (streq(key, "systemd.confirm_spawn") && value) {
r = parse_boolean(value);
arg_confirm_spawn = mfree(arg_confirm_spawn);
r = parse_confirm_spawn(value, &arg_confirm_spawn);
if (r < 0)
log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value);
else
arg_confirm_spawn = r;
log_warning_errno(r, "Failed to parse confirm_spawn switch %s. Ignoring.", value);
} else if (streq(key, "systemd.show_status") && value) {
@ -952,12 +975,11 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_CONFIRM_SPAWN:
r = optarg ? parse_boolean(optarg) : 1;
if (r < 0) {
log_error("Failed to parse confirm spawn boolean %s.", optarg);
return r;
}
arg_confirm_spawn = r;
arg_confirm_spawn = mfree(arg_confirm_spawn);
r = parse_confirm_spawn(optarg, &arg_confirm_spawn);
if (r < 0)
return log_error_errno(r, "Failed to parse confirm spawn option: %m");
break;
case ARG_SHOW_STATUS:
@ -1991,6 +2013,7 @@ finish:
arg_default_rlimit[j] = mfree(arg_default_rlimit[j]);
arg_default_unit = mfree(arg_default_unit);
arg_confirm_spawn = mfree(arg_confirm_spawn);
arg_join_controllers = strv_free_free(arg_join_controllers);
arg_default_environment = strv_free(arg_default_environment);
arg_syscall_archs = set_free(arg_syscall_archs);

View file

@ -111,6 +111,12 @@ static void manager_watch_jobs_in_progress(Manager *m) {
assert(m);
/* We do not want to show the cylon animation if the user
* needs to confirm service executions otherwise confirmation
* messages will be screwed by the cylon animation. */
if (!manager_is_confirm_spawn_disabled(m))
return;
if (m->jobs_in_progress_event_source)
return;
@ -2993,7 +2999,7 @@ void manager_check_finished(Manager *m) {
manager_close_idle_pipe(m);
/* Turn off confirm spawn now */
m->confirm_spawn = false;
m->confirm_spawn = NULL;
/* No need to update ask password status when we're going non-interactive */
manager_close_ask_password(m);
@ -3178,6 +3184,49 @@ static bool manager_get_show_status(Manager *m, StatusType type) {
return false;
}
const char *manager_get_confirm_spawn(Manager *m) {
static int last_errno = 0;
const char *vc = m->confirm_spawn;
struct stat st;
int r;
/* Here's the deal: we want to test the validity of the console but don't want
* PID1 to go through the whole console process which might block. But we also
* want to warn the user only once if something is wrong with the console so we
* cannot do the sanity checks after spawning our children. So here we simply do
* really basic tests to hopefully trap common errors.
*
* If the console suddenly disappear at the time our children will really it
* then they will simply fail to acquire it and a positive answer will be
* assumed. New children will fallback to /dev/console though.
*
* Note: TTYs are devices that can come and go any time, and frequently aren't
* available yet during early boot (consider a USB rs232 dongle...). If for any
* reason the configured console is not ready, we fallback to the default
* console. */
if (!vc || path_equal(vc, "/dev/console"))
return vc;
r = stat(vc, &st);
if (r < 0)
goto fail;
if (!S_ISCHR(st.st_mode)) {
errno = ENOTTY;
goto fail;
}
last_errno = 0;
return vc;
fail:
if (last_errno != errno) {
last_errno = errno;
log_warning_errno(errno, "Failed to open %s: %m, using default console", vc);
}
return "/dev/console";
}
void manager_set_first_boot(Manager *m, bool b) {
assert(m);
@ -3194,6 +3243,17 @@ void manager_set_first_boot(Manager *m, bool b) {
m->first_boot = b;
}
void manager_disable_confirm_spawn(void) {
(void) touch("/run/systemd/confirm_spawn_disabled");
}
bool manager_is_confirm_spawn_disabled(Manager *m) {
if (!m->confirm_spawn)
return true;
return access("/run/systemd/confirm_spawn_disabled", F_OK) >= 0;
}
void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) {
va_list ap;

View file

@ -246,7 +246,7 @@ struct Manager {
uint8_t return_value;
ShowStatus show_status;
bool confirm_spawn;
char *confirm_spawn;
bool no_console_output;
ExecOutput default_std_output, default_std_error;
@ -403,3 +403,7 @@ void manager_deserialize_gid_refs_one(Manager *m, const char *value);
const char *manager_state_to_string(ManagerState m) _const_;
ManagerState manager_state_from_string(const char *s) _pure_;
const char *manager_get_confirm_spawn(Manager *m);
bool manager_is_confirm_spawn_disabled(Manager *m);
void manager_disable_confirm_spawn(void);

View file

@ -747,7 +747,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
return r;
exec_params.environment = UNIT(m)->manager->environment;
exec_params.flags |= UNIT(m)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0;
exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(m)->manager);
exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported;
exec_params.cgroup_path = UNIT(m)->cgroup_path;
exec_params.cgroup_delegate = m->cgroup_context.delegate;

View file

@ -1335,7 +1335,7 @@ static int service_spawn(
exec_params.fds = fds;
exec_params.fd_names = fd_names;
exec_params.n_fds = n_fds;
exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0;
exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager);
exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported;
exec_params.cgroup_path = path;
exec_params.cgroup_delegate = s->cgroup_context.delegate;

View file

@ -1778,7 +1778,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
exec_params.argv = argv;
exec_params.environment = UNIT(s)->manager->environment;
exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0;
exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager);
exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported;
exec_params.cgroup_path = UNIT(s)->cgroup_path;
exec_params.cgroup_delegate = s->cgroup_context.delegate;

View file

@ -636,7 +636,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
goto fail;
exec_params.environment = UNIT(s)->manager->environment;
exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0;
exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager);
exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported;
exec_params.cgroup_path = UNIT(s)->cgroup_path;
exec_params.cgroup_delegate = s->cgroup_context.delegate;

View file

@ -1511,6 +1511,17 @@ int unit_start_limit_test(Unit *u) {
return emergency_action(u->manager, u->start_limit_action, u->reboot_arg, "unit failed");
}
bool unit_shall_confirm_spawn(Unit *u) {
if (manager_is_confirm_spawn_disabled(u->manager))
return false;
/* For some reasons units remaining in the same process group
* as PID 1 fail to acquire the console even if it's not used
* by any process. So skip the confirmation question for them. */
return !unit_get_exec_context(u)->same_pgrp;
}
/* Errors:
* -EBADR: This unit type does not support starting.
* -EALREADY: Unit is already started.

View file

@ -657,6 +657,8 @@ void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid);
int unit_set_invocation_id(Unit *u, sd_id128_t id);
int unit_acquire_invocation_id(Unit *u);
bool unit_shall_confirm_spawn(Unit *u);
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \

View file

@ -22,7 +22,6 @@
#include "alloc-util.h"
#include "extract-word.h"
#include "nsflags.h"
#include "seccomp-util.h"
#include "string-util.h"
const struct namespace_flag_map namespace_flag_map[] = {