summaryrefslogtreecommitdiffstats
path: root/src/core/job.c
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2021-07-01 08:58:14 +0200
committerGitHub <noreply@github.com>2021-07-01 08:58:14 +0200
commitcc03890a9d76103769213da9f13d18387e17212f (patch)
tree0be8fc1ff6f7b9aee7c752d2ff14a97ddf3b048e /src/core/job.c
parentMerge pull request #20030 from keszybz/exec_fd-event-source (diff)
parentunits: adjust description of systemd-update-utmp.service (diff)
downloadsystemd-cc03890a9d76103769213da9f13d18387e17212f.tar.xz
systemd-cc03890a9d76103769213da9f13d18387e17212f.zip
Merge pull request #20058 from keszybz/status-format
Implement StatusUnitFormat=combined and prettify Descriptions
Diffstat (limited to 'src/core/job.c')
-rw-r--r--src/core/job.c488
1 files changed, 208 insertions, 280 deletions
diff --git a/src/core/job.c b/src/core/job.c
index afe0985ce1..eb6728ad87 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -506,95 +506,241 @@ static void job_change_type(Job *j, JobType newtype) {
j->type = newtype;
}
-_pure_ static const char* job_get_begin_status_message_format(Unit *u, JobType t) {
- const char *format;
-
+static const char* job_start_message_format(Unit *u, JobType t) {
assert(u);
+ assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD));
if (t == JOB_RELOAD)
- return "Reloading %s.";
+ return "Reloading %s...";
+ else if (t == JOB_START)
+ return UNIT_VTABLE(u)->status_message_formats.starting_stopping[0] ?: "Starting %s...";
+ else
+ return UNIT_VTABLE(u)->status_message_formats.starting_stopping[1] ?: "Stopping %s...";
+}
- assert(IN_SET(t, JOB_START, JOB_STOP));
+static void job_emit_start_message(Unit *u, uint32_t job_id, JobType t) {
+ _cleanup_free_ char *free_ident = NULL;
+ const char *ident, *format;
- format = UNIT_VTABLE(u)->status_message_formats.starting_stopping[t == JOB_STOP];
- if (format)
- return format;
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+ assert(u->id); /* We better don't try to run a unit that doesn't even have an id. */
- /* Return generic strings */
- if (t == JOB_START)
- return "Starting %s.";
- else {
- assert(t == JOB_STOP);
- return "Stopping %s.";
+ if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD))
+ return;
+
+ if (!unit_log_level_test(u, LOG_INFO))
+ return;
+
+ format = job_start_message_format(u, t);
+ ident = unit_status_string(u, &free_ident);
+
+ bool do_console = t != JOB_RELOAD;
+ bool console_only = do_console && log_on_console(); /* Reload status messages have traditionally
+ * not been printed to the console. */
+
+ /* Print to the log first. */
+ if (!console_only) { /* Skip this if it would only go on the console anyway */
+
+ const char *mid =
+ t == JOB_START ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTING_STR :
+ t == JOB_STOP ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPING_STR :
+ "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADING_STR;
+ const char *msg_fmt = strjoina("MESSAGE=", format);
+
+ /* Note that we deliberately use LOG_MESSAGE() instead of LOG_UNIT_MESSAGE() here, since this
+ * is supposed to mimic closely what is written to screen using the status output, which is
+ * supposed to be high level friendly output. */
+
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ log_unit_struct(u, LOG_INFO,
+ msg_fmt, ident,
+ "JOB_ID=%" PRIu32, job_id,
+ "JOB_TYPE=%s", job_type_to_string(t),
+ LOG_UNIT_INVOCATION_ID(u),
+ mid);
+ REENABLE_WARNING;
+ }
+
+ /* Log to the console second. */
+ if (do_console) {
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ unit_status_printf(u, STATUS_TYPE_NORMAL, "", format, ident);
+ REENABLE_WARNING;
}
}
-static void job_print_begin_status_message(Unit *u, JobType t) {
+static const char* job_done_message_format(Unit *u, JobType t, JobResult result) {
+ static const char* const generic_finished_start_job[_JOB_RESULT_MAX] = {
+ [JOB_DONE] = "Started %s.",
+ [JOB_TIMEOUT] = "Timed out starting %s.",
+ [JOB_FAILED] = "Failed to start %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ [JOB_ASSERT] = "Assertion failed for %s.",
+ [JOB_UNSUPPORTED] = "Starting of %s unsupported.",
+ [JOB_COLLECTED] = "Unnecessary job was removed for %s.",
+ [JOB_ONCE] = "Unit %s has been started before and cannot be started again.",
+ };
+ static const char* const generic_finished_stop_job[_JOB_RESULT_MAX] = {
+ [JOB_DONE] = "Stopped %s.",
+ [JOB_FAILED] = "Stopped %s with error.",
+ [JOB_TIMEOUT] = "Timed out stopping %s.",
+ };
+ static const char* const generic_finished_reload_job[_JOB_RESULT_MAX] = {
+ [JOB_DONE] = "Reloaded %s.",
+ [JOB_FAILED] = "Reload failed for %s.",
+ [JOB_TIMEOUT] = "Timed out reloading %s.",
+ };
+ /* When verify-active detects the unit is inactive, report it.
+ * Most likely a DEPEND warning from a requisiting unit will
+ * occur next and it's nice to see what was requisited. */
+ static const char* const generic_finished_verify_active_job[_JOB_RESULT_MAX] = {
+ [JOB_SKIPPED] = "%s is inactive.",
+ };
const char *format;
assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
- /* Reload status messages have traditionally not been printed to console. */
- if (!IN_SET(t, JOB_START, JOB_STOP))
- return;
+ /* Show condition check message if the job did not actually do anything due to failed condition. */
+ if (t == JOB_START && result == JOB_DONE && !u->condition_result)
+ return "Condition check resulted in %s being skipped.";
+
+ if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) {
+ const UnitStatusMessageFormats *formats = &UNIT_VTABLE(u)->status_message_formats;
+ if (formats->finished_job) {
+ format = formats->finished_job(u, t, result);
+ if (format)
+ return format;
+ }
- format = job_get_begin_status_message_format(u, t);
+ format = (t == JOB_START ? formats->finished_start_job : formats->finished_stop_job)[result];
+ if (format)
+ return format;
+ }
- DISABLE_WARNING_FORMAT_NONLITERAL;
- unit_status_printf(u, STATUS_TYPE_NORMAL, "", format);
- REENABLE_WARNING;
+ /* Return generic strings */
+ switch (t) {
+ case JOB_START:
+ return generic_finished_start_job[result];
+ case JOB_STOP:
+ case JOB_RESTART:
+ return generic_finished_stop_job[result];
+ case JOB_RELOAD:
+ return generic_finished_reload_job[result];
+ case JOB_VERIFY_ACTIVE:
+ return generic_finished_verify_active_job[result];
+ default:
+ return NULL;
+ }
+}
+
+static const struct {
+ int log_level;
+ const char *color, *word;
+} job_done_messages[_JOB_RESULT_MAX] = {
+ [JOB_DONE] = { LOG_INFO, ANSI_OK_COLOR, " OK " },
+ [JOB_CANCELED] = { LOG_INFO, },
+ [JOB_TIMEOUT] = { LOG_ERR, ANSI_HIGHLIGHT_RED, " TIME " },
+ [JOB_FAILED] = { LOG_ERR, ANSI_HIGHLIGHT_RED, "FAILED" },
+ [JOB_DEPENDENCY] = { LOG_WARNING, ANSI_HIGHLIGHT_YELLOW, "DEPEND" },
+ [JOB_SKIPPED] = { LOG_NOTICE, ANSI_HIGHLIGHT, " INFO " },
+ [JOB_INVALID] = { LOG_INFO, },
+ [JOB_ASSERT] = { LOG_WARNING, ANSI_HIGHLIGHT_YELLOW, "ASSERT" },
+ [JOB_UNSUPPORTED] = { LOG_WARNING, ANSI_HIGHLIGHT_YELLOW, "UNSUPP" },
+ [JOB_COLLECTED] = { LOG_INFO, },
+ [JOB_ONCE] = { LOG_ERR, ANSI_HIGHLIGHT_RED, " ONCE " },
+};
+
+static const char* job_done_mid(JobType type, JobResult result) {
+ switch (type) {
+ case JOB_START:
+ if (result == JOB_DONE)
+ return "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTED_STR;
+ else
+ return "MESSAGE_ID=" SD_MESSAGE_UNIT_FAILED_STR;
+
+ case JOB_RELOAD:
+ return "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADED_STR;
+
+ case JOB_STOP:
+ case JOB_RESTART:
+ return "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPED_STR;
+
+ default:
+ return NULL;
+ }
}
-static void job_log_begin_status_message(Unit *u, uint32_t job_id, JobType t) {
- const char *format, *mid;
- char buf[LINE_MAX];
+static void job_emit_done_message(Unit *u, uint32_t job_id, JobType t, JobResult result) {
+ _cleanup_free_ char *free_ident = NULL;
+ const char *ident, *format;
assert(u);
assert(t >= 0);
assert(t < _JOB_TYPE_MAX);
- if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD))
+ if (!unit_log_level_test(u, job_done_messages[result].log_level))
return;
- if (!unit_log_level_test(u, LOG_INFO))
+ format = job_done_message_format(u, t, result);
+ if (!format)
return;
- if (log_on_console()) /* Skip this if it would only go on the console anyway */
- return;
+ ident = unit_status_string(u, &free_ident);
- /* We log status messages for all units and all operations. */
-
- format = job_get_begin_status_message_format(u, t);
-
- DISABLE_WARNING_FORMAT_NONLITERAL;
- (void) snprintf(buf, sizeof buf, format, unit_status_string(u));
- REENABLE_WARNING;
-
- mid = t == JOB_START ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTING_STR :
- t == JOB_STOP ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPING_STR :
- "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADING_STR;
-
- /* Note that we deliberately use LOG_MESSAGE() instead of
- * LOG_UNIT_MESSAGE() here, since this is supposed to mimic
- * closely what is written to screen using the status output,
- * which is supposed the highest level, friendliest output
- * possible, which means we should avoid the low-level unit
- * name. */
- log_unit_struct(u, LOG_INFO,
- LOG_MESSAGE("%s", buf),
- "JOB_ID=%" PRIu32, job_id,
- "JOB_TYPE=%s", job_type_to_string(t),
- LOG_UNIT_INVOCATION_ID(u),
- mid);
-}
+ const char *status = job_done_messages[result].word;
+ bool do_console = t != JOB_RELOAD && status;
+ bool console_only = do_console && log_on_console();
-static void job_emit_begin_status_message(Unit *u, uint32_t job_id, JobType t) {
- assert(u);
- assert(t >= 0);
- assert(t < _JOB_TYPE_MAX);
+ if (t == JOB_START && result == JOB_DONE && !u->condition_result) {
+ /* No message on the console if the job did not actually do anything due to failed condition. */
+ if (console_only)
+ return;
+ else
+ do_console = false;
+ }
+
+ if (!console_only) { /* Skip printing if output goes to the console, and job_print_status_message()
+ * will actually print something to the console. */
- job_log_begin_status_message(u, job_id, t);
- job_print_begin_status_message(u, t);
+ const char *mid = job_done_mid(t, result); /* mid may be NULL. log_unit_struct() will ignore it. */
+ const char *msg_fmt = strjoina("MESSAGE=", format);
+
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ log_unit_struct(u, job_done_messages[result].log_level,
+ msg_fmt, ident,
+ "JOB_ID=%" PRIu32, job_id,
+ "JOB_TYPE=%s", job_type_to_string(t),
+ "JOB_RESULT=%s", job_result_to_string(result),
+ LOG_UNIT_INVOCATION_ID(u),
+ mid);
+ REENABLE_WARNING;
+ }
+
+ if (do_console) {
+ if (log_get_show_color())
+ status = strjoina(job_done_messages[result].color,
+ status,
+ ANSI_NORMAL);
+
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ unit_status_printf(u,
+ result == JOB_DONE ? STATUS_TYPE_NORMAL : STATUS_TYPE_NOTICE,
+ status, format, ident);
+ REENABLE_WARNING;
+
+ if (t == JOB_START && result == JOB_FAILED) {
+ _cleanup_free_ char *quoted = NULL;
+
+ quoted = shell_maybe_quote(u->id, 0);
+ if (quoted)
+ manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL,
+ "See 'systemctl status %s' for details.", quoted);
+ }
+ }
}
static int job_perform_on_unit(Job **j) {
@@ -643,7 +789,7 @@ static int job_perform_on_unit(Job **j) {
* 'active') we'll possibly skip the "Starting..." message. */
*j = manager_get_job(m, id);
if (*j && r > 0)
- job_emit_begin_status_message(u, id, t);
+ job_emit_start_message(u, id, t);
return r;
}
@@ -732,224 +878,6 @@ int job_run_and_invalidate(Job *j) {
return r;
}
-_pure_ static const char *job_get_done_status_message_format(Unit *u, JobType t, JobResult result) {
-
- static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = {
- [JOB_DONE] = "Started %s.",
- [JOB_TIMEOUT] = "Timed out starting %s.",
- [JOB_FAILED] = "Failed to start %s.",
- [JOB_DEPENDENCY] = "Dependency failed for %s.",
- [JOB_ASSERT] = "Assertion failed for %s.",
- [JOB_UNSUPPORTED] = "Starting of %s not supported.",
- [JOB_COLLECTED] = "Unnecessary job for %s was removed.",
- [JOB_ONCE] = "Unit %s has been started before and cannot be started again."
- };
- static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = {
- [JOB_DONE] = "Stopped %s.",
- [JOB_FAILED] = "Stopped (with error) %s.",
- [JOB_TIMEOUT] = "Timed out stopping %s.",
- };
- static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = {
- [JOB_DONE] = "Reloaded %s.",
- [JOB_FAILED] = "Reload failed for %s.",
- [JOB_TIMEOUT] = "Timed out reloading %s.",
- };
- /* When verify-active detects the unit is inactive, report it.
- * Most likely a DEPEND warning from a requisiting unit will
- * occur next and it's nice to see what was requisited. */
- static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = {
- [JOB_SKIPPED] = "%s is not active.",
- };
-
- const char *format;
-
- assert(u);
- assert(t >= 0);
- assert(t < _JOB_TYPE_MAX);
-
- if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) {
- const UnitStatusMessageFormats *formats = &UNIT_VTABLE(u)->status_message_formats;
- if (formats->finished_job) {
- format = formats->finished_job(u, t, result);
- if (format)
- return format;
- }
- format = t == JOB_START ?
- formats->finished_start_job[result] :
- formats->finished_stop_job[result];
- if (format)
- return format;
- }
-
- /* Return generic strings */
- if (t == JOB_START)
- return generic_finished_start_job[result];
- else if (IN_SET(t, JOB_STOP, JOB_RESTART))
- return generic_finished_stop_job[result];
- else if (t == JOB_RELOAD)
- return generic_finished_reload_job[result];
- else if (t == JOB_VERIFY_ACTIVE)
- return generic_finished_verify_active_job[result];
-
- return NULL;
-}
-
-static const struct {
- const char *color, *word;
-} job_print_done_status_messages[_JOB_RESULT_MAX] = {
- [JOB_DONE] = { ANSI_OK_COLOR, " OK " },
- [JOB_TIMEOUT] = { ANSI_HIGHLIGHT_RED, " TIME " },
- [JOB_FAILED] = { ANSI_HIGHLIGHT_RED, "FAILED" },
- [JOB_DEPENDENCY] = { ANSI_HIGHLIGHT_YELLOW, "DEPEND" },
- [JOB_SKIPPED] = { ANSI_HIGHLIGHT, " INFO " },
- [JOB_ASSERT] = { ANSI_HIGHLIGHT_YELLOW, "ASSERT" },
- [JOB_UNSUPPORTED] = { ANSI_HIGHLIGHT_YELLOW, "UNSUPP" },
- /* JOB_COLLECTED */
- [JOB_ONCE] = { ANSI_HIGHLIGHT_RED, " ONCE " },
-};
-
-static void job_print_done_status_message(Unit *u, JobType t, JobResult result) {
- const char *format;
- const char *status;
-
- assert(u);
- assert(t >= 0);
- assert(t < _JOB_TYPE_MAX);
-
- /* Reload status messages have traditionally not been printed to console. */
- if (t == JOB_RELOAD)
- return;
-
- /* No message if the job did not actually do anything due to failed condition. */
- if (t == JOB_START && result == JOB_DONE && !u->condition_result)
- return;
-
- if (!job_print_done_status_messages[result].word)
- return;
-
- format = job_get_done_status_message_format(u, t, result);
- if (!format)
- return;
-
- if (log_get_show_color())
- status = strjoina(job_print_done_status_messages[result].color,
- job_print_done_status_messages[result].word,
- ANSI_NORMAL);
- else
- status = job_print_done_status_messages[result].word;
-
- DISABLE_WARNING_FORMAT_NONLITERAL;
- unit_status_printf(u,
- result == JOB_DONE ? STATUS_TYPE_NORMAL : STATUS_TYPE_NOTICE,
- status, format);
- REENABLE_WARNING;
-
- if (t == JOB_START && result == JOB_FAILED) {
- _cleanup_free_ char *quoted = NULL;
-
- quoted = shell_maybe_quote(u->id, 0);
- manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted));
- }
-}
-
-static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) {
- const char *format, *mid;
- char buf[LINE_MAX];
- static const int job_result_log_level[_JOB_RESULT_MAX] = {
- [JOB_DONE] = LOG_INFO,
- [JOB_CANCELED] = LOG_INFO,
- [JOB_TIMEOUT] = LOG_ERR,
- [JOB_FAILED] = LOG_ERR,
- [JOB_DEPENDENCY] = LOG_WARNING,
- [JOB_SKIPPED] = LOG_NOTICE,
- [JOB_INVALID] = LOG_INFO,
- [JOB_ASSERT] = LOG_WARNING,
- [JOB_UNSUPPORTED] = LOG_WARNING,
- [JOB_COLLECTED] = LOG_INFO,
- [JOB_ONCE] = LOG_ERR,
- };
-
- assert(u);
- assert(t >= 0);
- assert(t < _JOB_TYPE_MAX);
-
- /* Skip printing if output goes to the console, and job_print_status_message()
- will actually print something to the console. */
- if (log_on_console() && job_print_done_status_messages[result].word)
- return;
-
- /* Show condition check message if the job did not actually do anything due to failed condition. */
- if (t == JOB_START && result == JOB_DONE && !u->condition_result) {
- log_unit_struct(u, LOG_INFO,
- "MESSAGE=Condition check resulted in %s being skipped.", unit_status_string(u),
- "JOB_ID=%" PRIu32, job_id,
- "JOB_TYPE=%s", job_type_to_string(t),
- "JOB_RESULT=%s", job_result_to_string(result),
- LOG_UNIT_INVOCATION_ID(u),
- "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTED_STR);
-
- return;
- }
-
- if (!unit_log_level_test(u, job_result_log_level[result]))
- return;
-
- format = job_get_done_status_message_format(u, t, result);
- if (!format)
- return;
-
- /* The description might be longer than the buffer, but that's OK,
- * we'll just truncate it here. Note that we use snprintf() rather than
- * xsprintf() on purpose here: we are fine with truncation and don't
- * consider that an error. */
- DISABLE_WARNING_FORMAT_NONLITERAL;
- (void) snprintf(buf, sizeof(buf), format, unit_status_string(u));
- REENABLE_WARNING;
-
- switch (t) {
-
- case JOB_START:
- if (result == JOB_DONE)
- mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTED_STR;
- else
- mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_FAILED_STR;
- break;
-
- case JOB_RELOAD:
- mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADED_STR;
- break;
-
- case JOB_STOP:
- case JOB_RESTART:
- mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPED_STR;
- break;
-
- default:
- log_unit_struct(u, job_result_log_level[result],
- LOG_MESSAGE("%s", buf),
- "JOB_ID=%" PRIu32, job_id,
- "JOB_TYPE=%s", job_type_to_string(t),
- "JOB_RESULT=%s", job_result_to_string(result),
- LOG_UNIT_INVOCATION_ID(u));
- return;
- }
-
- log_unit_struct(u, job_result_log_level[result],
- LOG_MESSAGE("%s", buf),
- "JOB_ID=%" PRIu32, job_id,
- "JOB_TYPE=%s", job_type_to_string(t),
- "JOB_RESULT=%s", job_result_to_string(result),
- LOG_UNIT_INVOCATION_ID(u),
- mid);
-}
-
-static void job_emit_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) {
- assert(u);
-
- job_log_done_status_message(u, job_id, t, result);
- job_print_done_status_message(u, t, result);
-}
-
static void job_fail_dependencies(Unit *u, UnitDependencyAtom match_atom) {
Unit *other;
@@ -985,7 +913,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr
/* If this job did nothing to the respective unit we don't log the status message */
if (!already)
- job_emit_done_status_message(u, j->id, t, result);
+ job_emit_done_message(u, j->id, t, result);
/* Patch restart jobs so that they become normal start jobs */
if (result == JOB_DONE && t == JOB_RESTART) {