From 0d292f5e7597a729365644edfbcdf2e5fac8beb4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 12 Mar 2014 21:13:58 +0100 Subject: [PATCH] systemctl: add new "list-machines" command "systemctl list-machines" shows one line per local container which includes the current system state of the container, the number of failed units as well as the number of currently queued jobs. --- man/systemctl.xml | 18 +++ src/systemctl/systemctl.c | 243 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 249 insertions(+), 12 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index fef957895..174988c55 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1041,6 +1041,24 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service + + Machine Commands + + + + list-machines PATTERN... + + + List the host and all running local containers with + their state. If one or more + PATTERNs are specified, only + containers matching one of them are shown. + + + + + + Job Commands diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 17ad7f046..d4c71f561 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1031,7 +1031,7 @@ static bool output_show_unit_file(const UnitFileList *u, char **patterns) { } static void output_unit_file_list(const UnitFileList *units, unsigned c) { - unsigned max_id_len, id_cols, state_cols, n_shown = 0; + unsigned max_id_len, id_cols, state_cols; const UnitFileList *u; max_id_len = sizeof("UNIT FILE")-1; @@ -1062,8 +1062,6 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { const char *on, *off; const char *id; - n_shown++; - if (u->state == UNIT_FILE_MASKED || u->state == UNIT_FILE_MASKED_RUNTIME || u->state == UNIT_FILE_DISABLED || @@ -1086,7 +1084,7 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { } if (!arg_no_legend) - printf("\n%u unit files listed.\n", n_shown); + printf("\n%u unit files listed.\n", c); } static int list_unit_files(sd_bus *bus, char **args) { @@ -1414,6 +1412,223 @@ static int list_dependencies(sd_bus *bus, char **args) { return list_dependencies_one(bus, u, 0, &units, 0); } +struct machine_info { + bool is_host; + char *name; + unsigned n_failed_units; + unsigned n_jobs; + char *state; +}; + +static void free_machines_list(struct machine_info *machine_infos, int n) { + int i; + + if (!machine_infos) + return; + + for (i = 0; i < n; i++) { + free(machine_infos[i].name); + free(machine_infos[i].state); + } + + free(machine_infos); +} + +static int compare_machine_info(const void *a, const void *b) { + const struct machine_info *u = a, *v = b; + + if (u->is_host != v->is_host) + return u->is_host > v->is_host ? 1 : -1; + + return strcasecmp(u->name, v->name); +} + +static int get_machine_properties(sd_bus *bus, struct machine_info *mi) { + static const struct bus_properties_map map[] = { + { "SystemState", "s", NULL, offsetof(struct machine_info, state) }, + { "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) }, + { "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) }, + {} + }; + + _cleanup_bus_unref_ sd_bus *container = NULL; + int r; + + assert(mi); + + if (!bus) { + r = sd_bus_open_system_container(&container, mi->name); + if (r < 0) + return r; + + bus = container; + } + + r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", map, mi); + if (r < 0) + return r; + + return 0; +} + +static bool output_show_machine(const char *name, char **patterns) { + char **i; + + assert(name); + + if (strv_isempty(patterns)) + return true; + + STRV_FOREACH(i, patterns) + if (fnmatch(*i, name, FNM_NOESCAPE) == 0) + return true; + + return false; +} + +static int get_machine_list( + sd_bus *bus, + struct machine_info **_machine_infos, + char **patterns) { + + struct machine_info *machine_infos = NULL; + _cleanup_strv_free_ char **m = NULL; + _cleanup_free_ char *hn = NULL; + size_t sz = 0; + char **i; + int c = 0; + + hn = gethostname_malloc(); + if (!hn) + return log_oom(); + + if (output_show_machine(hn, patterns)) { + if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) + return log_oom(); + + machine_infos[c].is_host = true; + machine_infos[c].name = hn; + hn = NULL; + + get_machine_properties(bus, &machine_infos[c]); + c++; + } + + sd_get_machine_names(&m); + STRV_FOREACH(i, m) { + _cleanup_free_ char *class = NULL; + + if (!output_show_machine(*i, patterns)) + continue; + + sd_machine_get_class(*i, &class); + if (!streq_ptr(class, "container")) + continue; + + if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) { + free_machines_list(machine_infos, c); + return log_oom(); + } + + machine_infos[c].is_host = false; + machine_infos[c].name = strdup(*i); + if (!machine_infos[c].name) { + free_machines_list(machine_infos, c); + return log_oom(); + } + + get_machine_properties(NULL, &machine_infos[c]); + c++; + } + + *_machine_infos = machine_infos; + return c; +} + +static void output_machines_list(struct machine_info *machine_infos, unsigned n) { + struct machine_info *m; + unsigned + namelen = sizeof("NAME") - 1, + statelen = sizeof("STATE") - 1, + failedlen = sizeof("FAILED") - 1, + jobslen = sizeof("JOBS") - 1; + + assert(machine_infos || n == 0); + + for (m = machine_infos; m < machine_infos + n; m++) { + namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); + statelen = MAX(statelen, m->state ? strlen(m->state) : 0); + failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); + jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); + } + + if (!arg_no_legend) + printf("%-*s %-*s %-*s %-*s\n", + namelen, "NAME", + statelen, "STATE", + failedlen, "FAILED", + jobslen, "JOBS"); + + for (m = machine_infos; m < machine_infos + n; m++) { + const char *on_state, *off_state, *on_failed, *off_failed; + + if (streq_ptr(m->state, "degraded")) { + on_state = ansi_highlight_red(); + off_state = ansi_highlight_off(); + } else if (!streq_ptr(m->state, "running")) { + on_state = ansi_highlight_yellow(); + off_state = ansi_highlight_off(); + } else + on_state = off_state = ""; + + if (m->n_failed_units > 0) { + on_failed = ansi_highlight_red(); + off_failed = ansi_highlight_off(); + } else + on_failed = off_failed = ""; + + if (m->is_host) + printf("%-*s (host) %s%-*s%s %s%*u%s %*u\n", + (int) (namelen - (sizeof(" (host)")-1)), strna(m->name), + on_state, statelen, strna(m->state), off_state, + on_failed, failedlen, m->n_failed_units, off_failed, + jobslen, m->n_jobs); + else + printf("%-*s %s%-*s%s %s%*u%s %*u\n", + namelen, strna(m->name), + on_state, statelen, strna(m->state), off_state, + on_failed, failedlen, m->n_failed_units, off_failed, + jobslen, m->n_jobs); + } + + if (!arg_no_legend) + printf("\n%u machines listed.\n", n); +} + +static int list_machines(sd_bus *bus, char **args) { + struct machine_info *machine_infos = NULL; + int r; + + assert(bus); + + if (geteuid() != 0) { + log_error("Must be root."); + return -EPERM; + } + + pager_open_if_enabled(); + + r = get_machine_list(bus, &machine_infos, strv_skip_first(args)); + if (r < 0) + return r; + + qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info); + output_machines_list(machine_infos, r); + free_machines_list(machine_infos, r); + + return 0; +} + static int get_default(sd_bus *bus, char **args) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1625,16 +1840,17 @@ static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipp } static bool output_show_job(struct job_info *job, char **patterns) { - if (!strv_isempty(patterns)) { - char **pattern; + char **pattern; - STRV_FOREACH(pattern, patterns) - if (fnmatch(*pattern, job->name, FNM_NOESCAPE) == 0) - return true; - return false; - } + assert(job); - return true; + if (strv_isempty(patterns)) + return true; + + STRV_FOREACH(pattern, patterns) + if (fnmatch(*pattern, job->name, FNM_NOESCAPE) == 0) + return true; + return false; } static int list_jobs(sd_bus *bus, char **args) { @@ -4940,6 +5156,8 @@ static int systemctl_help(void) { " the search path\n" " get-default Get the name of the default target\n" " set-default NAME Set the default target\n\n" + "Machine Commands:\n" + " list-machines [PATTERN...] List local containers and host\n\n" "Job Commands:\n" " list-jobs [PATTERN...] List jobs\n" " cancel [JOB...] Cancel all, one, or more jobs\n\n" @@ -5882,6 +6100,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { { "list-sockets", MORE, 1, list_sockets }, { "list-timers", MORE, 1, list_timers }, { "list-jobs", MORE, 1, list_jobs }, + { "list-machines", MORE, 1, list_machines }, { "clear-jobs", EQUAL, 1, daemon_reload }, { "cancel", MORE, 2, cancel_job }, { "start", MORE, 2, start_unit }, -- 2.30.2