1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

Merge pull request #7991 from poettering/n-on-console

a comprehensive fix for the n_on_console miscounting issue
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-01-25 13:48:08 +03:00 committed by GitHub
commit 5eb83fa645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 42 deletions

View File

@ -2449,8 +2449,15 @@ static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32
assert(m);
assert(m->idle_pipe[2] == fd);
/* There's at least one Type=idle child that just gave up on us waiting for the boot process to complete. Let's
* now turn off any further console output if there's at least one service that needs console access, so that
* from now on our own output should not spill into that service's output anymore. After all, we support
* Type=idle only to beautify console output and it generally is set on services that want to own the console
* exclusively without our interference. */
m->no_console_output = m->n_on_console > 0;
/* Acknowledge the child's request, and let all all other children know too that they shouldn't wait any longer
* by closing the pipes towards them, which is what they are waiting for. */
manager_close_idle_pipe(m);
return 0;
@ -3567,10 +3574,7 @@ static bool manager_get_show_status(Manager *m, StatusType type) {
if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0)
return false;
if (m->show_status > 0)
return true;
return false;
return m->show_status > 0;
}
const char *manager_get_confirm_spawn(Manager *m) {
@ -4094,6 +4098,21 @@ char *manager_taint_string(Manager *m) {
return buf;
}
void manager_ref_console(Manager *m) {
assert(m);
m->n_on_console++;
}
void manager_unref_console(Manager *m) {
assert(m->n_on_console > 0);
m->n_on_console--;
if (m->n_on_console == 0)
m->no_console_output = false; /* unset no_console_output flag, since the console is definitely free now */
}
static const char *const manager_state_table[_MANAGER_STATE_MAX] = {
[MANAGER_INITIALIZING] = "initializing",
[MANAGER_STARTING] = "starting",

View File

@ -455,6 +455,9 @@ void manager_deserialize_gid_refs_one(Manager *m, const char *value);
char *manager_taint_string(Manager *m);
void manager_ref_console(Manager *m);
void manager_unref_console(Manager *m);
const char *manager_state_to_string(ManagerState m) _const_;
ManagerState manager_state_from_string(const char *s) _pure_;

View File

@ -1060,26 +1060,6 @@ static void service_set_state(Service *s, ServiceState state) {
if (state == SERVICE_EXITED && !MANAGER_IS_RELOADING(UNIT(s)->manager))
unit_prune_cgroup(UNIT(s));
/* For remain_after_exit services, let's see if we can "release" the
* hold on the console, since unit_notify() only does that in case of
* change of state */
if (state == SERVICE_EXITED &&
s->remain_after_exit &&
UNIT(s)->manager->n_on_console > 0) {
ExecContext *ec;
ec = unit_get_exec_context(UNIT(s));
if (ec && exec_context_may_touch_console(ec)) {
Manager *m = UNIT(s)->manager;
m->n_on_console--;
if (m->n_on_console == 0)
/* unset no_console_output flag, since the console is free */
m->no_console_output = false;
}
}
if (old_state != state)
log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state));
@ -3806,6 +3786,32 @@ static int service_control_pid(Unit *u) {
return s->control_pid;
}
static bool service_needs_console(Unit *u) {
Service *s = SERVICE(u);
assert(s);
/* We provide our own implementation of this here, instead of relying of the generic implementation
* unit_needs_console() provides, since we want to return false if we are in SERVICE_EXITED state. */
if (!exec_context_may_touch_console(&s->exec_context))
return false;
return IN_SET(s->state,
SERVICE_START_PRE,
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RELOAD,
SERVICE_STOP,
SERVICE_STOP_SIGABRT,
SERVICE_STOP_SIGTERM,
SERVICE_STOP_SIGKILL,
SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM,
SERVICE_FINAL_SIGKILL);
}
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
[SERVICE_RESTART_NO] = "no",
[SERVICE_RESTART_ON_SUCCESS] = "on-success",
@ -3921,6 +3927,7 @@ const UnitVTable service_vtable = {
.bus_commit_properties = bus_service_commit_properties,
.get_timeout = service_get_timeout,
.needs_console = service_needs_console,
.can_transient = true,
.status_message_formats = {

View File

@ -630,6 +630,9 @@ void unit_free(Unit *u) {
if (u->in_cgroup_empty_queue)
LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u);
if (u->on_console)
manager_unref_console(u->manager);
unit_release_cgroup(u);
if (!MANAGER_IS_RELOADING(u->manager))
@ -2305,6 +2308,23 @@ finish:
}
static void unit_update_on_console(Unit *u) {
bool b;
assert(u);
b = unit_needs_console(u);
if (u->on_console == b)
return;
u->on_console = b;
if (b)
manager_ref_console(u->manager);
else
manager_unref_console(u->manager);
}
void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) {
Manager *m;
bool unexpected;
@ -2345,24 +2365,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
unit_unlink_state_files(u);
}
/* Note that this doesn't apply to RemainAfterExit services exiting
* successfully, since there's no change of state in that case. Which is
* why it is handled in service_set_state() */
if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) {
ExecContext *ec;
ec = unit_get_exec_context(u);
if (ec && exec_context_may_touch_console(ec)) {
if (UNIT_IS_INACTIVE_OR_FAILED(ns)) {
m->n_on_console--;
if (m->n_on_console == 0)
/* unset no_console_output flag, since the console is free */
m->no_console_output = false;
} else
m->n_on_console++;
}
}
unit_update_on_console(u);
if (u->job) {
unexpected = false;
@ -5350,6 +5353,28 @@ void unit_warn_leftover_processes(Unit *u) {
(void) cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, 0, 0, NULL, log_leftover, u);
}
bool unit_needs_console(Unit *u) {
ExecContext *ec;
UnitActiveState state;
assert(u);
state = unit_active_state(u);
if (UNIT_IS_INACTIVE_OR_FAILED(state))
return false;
if (UNIT_VTABLE(u)->needs_console)
return UNIT_VTABLE(u)->needs_console(u);
/* If this unit type doesn't implement this call, let's use a generic fallback implementation: */
ec = unit_get_exec_context(u);
if (!ec)
return false;
return exec_context_may_touch_console(ec);
}
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
[COLLECT_INACTIVE] = "inactive",
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",

View File

@ -340,6 +340,7 @@ struct Unit {
bool sent_dbus_new_signal:1;
bool in_audit:1;
bool on_console:1;
bool cgroup_realized:1;
bool cgroup_members_mask_valid:1;
@ -541,6 +542,9 @@ struct UnitVTable {
/* Returns the main PID if there is any defined, or 0. */
pid_t (*control_pid)(Unit *u);
/* Returns true if the unit currently needs access to the console */
bool (*needs_console)(Unit *u);
/* This is called for each unit type and should be used to
* enumerate existing devices and load them. However,
* everything that is loaded here should still stay in
@ -795,6 +799,8 @@ int unit_prepare_exec(Unit *u);
void unit_warn_leftover_processes(Unit *u);
bool unit_needs_console(Unit *u);
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \