diff --git a/man/systemctl.xml b/man/systemctl.xml index 42dd667aa9..483db5179c 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -108,6 +108,25 @@ To show all installed unit files use 'systemctl list-unit-files'. + + list-automounts PATTERN… + + + List automount units currently in memory, ordered by mount path. If one or more + PATTERNs are specified, only automount units matching one of them are shown. + Produces output similar to + +WHAT WHERE MOUNTED IDLE TIMEOUT UNIT +/dev/sdb1 /mnt/test no 120s mnt-test.automount +binfmt_misc /proc/sys/fs/binfmt_misc yes 0 proc-sys-fs-binfmt_misc.automount + +2 automounts listed. + + + Also see , , and . + + + list-sockets PATTERN… diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index b61f88b012..c8ca2882c0 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -215,7 +215,7 @@ _systemctl () { suspend-then-hibernate kexec list-jobs list-sockets list-timers list-units list-unit-files poweroff reboot rescue show-environment suspend get-default - is-system-running preset-all' + is-system-running preset-all list-automounts' [FILE]='link switch-root bind mount-image' [TARGETS]='set-default' [MACHINES]='list-machines' diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in index 6fbe8737c2..8b8c59e0d7 100644 --- a/shell-completion/zsh/_systemctl.in +++ b/shell-completion/zsh/_systemctl.in @@ -7,6 +7,7 @@ local -a unit_commands=( # Unit Commands + "list-automounts:List automounts" "list-sockets:List sockets" "list-timers:List timers" "list-units:List units" diff --git a/src/systemctl/systemctl-list-units.c b/src/systemctl/systemctl-list-units.c index 5b0ce3854d..5a1311f5ea 100644 --- a/src/systemctl/systemctl-list-units.c +++ b/src/systemctl/systemctl-list-units.c @@ -3,6 +3,7 @@ #include "sd-login.h" #include "bus-error.h" +#include "bus-locator.h" #include "format-table.h" #include "locale-util.h" #include "set.h" @@ -87,6 +88,19 @@ static int get_unit_list_recursive( return c; } +static void output_legend(const char *type, size_t n_items) { + const char *on, *off; + + assert(type); + + on = n_items > 0 ? ansi_highlight() : ansi_highlight_red(); + off = ansi_normal(); + + printf("\n%s%zu %ss listed.%s\n", on, n_items, type, off); + if (!arg_all) + printf("Pass --all to see loaded but inactive %ss, too.\n", type); +} + static int output_units_list(const UnitInfo *unit_infos, size_t c) { _cleanup_(table_unrefp) Table *table = NULL; size_t job_count = 0; @@ -187,13 +201,11 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { "SUB = The low-level unit activation state, values depend on unit type."); if (job_count > 0) puts("JOB = Pending job for the unit.\n"); - on = ansi_highlight(); - off = ansi_normal(); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); } + on = records > 0 ? ansi_highlight() : ansi_highlight_red(); + off = ansi_normal(); + if (arg_all || strv_contains(arg_states, "inactive")) printf("%s%zu loaded units listed.%s\n" "To show all installed unit files use 'systemctl list-unit-files'.\n", @@ -350,9 +362,10 @@ static int socket_info_compare(const struct socket_info *a, const struct socket_ static int output_sockets_list(struct socket_info *socket_infos, size_t cs) { _cleanup_(table_unrefp) Table *table = NULL; - const char *on, *off; int r; + assert(socket_infos || cs == 0); + table = table_new("listen", "type", "unit", "activates"); if (!table) return log_oom(); @@ -370,56 +383,44 @@ static int output_sockets_list(struct socket_info *socket_infos, size_t cs) { (void) table_set_empty_string(table, "-"); - if (cs > 0) { - for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) { - _cleanup_free_ char *j = NULL; - const char *path; + for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) { + _cleanup_free_ char *j = NULL; + const char *path; - if (s->machine) { - j = strjoin(s->machine, ":", s->path); - if (!j) - return log_oom(); - path = j; - } else - path = s->path; + if (s->machine) { + j = strjoin(s->machine, ":", s->path); + if (!j) + return log_oom(); + path = j; + } else + path = s->path; - r = table_add_many(table, - TABLE_STRING, path, - TABLE_STRING, s->type, - TABLE_STRING, s->id); - if (r < 0) - return table_log_add_error(r); + r = table_add_many(table, + TABLE_STRING, path, + TABLE_STRING, s->type, + TABLE_STRING, s->id); + if (r < 0) + return table_log_add_error(r); - if (strv_isempty(s->triggered)) - r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); - else if (strv_length(s->triggered) == 1) - r = table_add_cell(table, NULL, TABLE_STRING, s->triggered[0]); - else - /* This should never happen, currently our socket units can only trigger a - * single unit. But let's handle this anyway, who knows what the future - * brings? */ - r = table_add_cell(table, NULL, TABLE_STRV, s->triggered); - if (r < 0) - return table_log_add_error(r); - - } - - on = ansi_highlight(); - off = ansi_normal(); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); + if (strv_isempty(s->triggered)) + r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); + else if (strv_length(s->triggered) == 1) + r = table_add_cell(table, NULL, TABLE_STRING, s->triggered[0]); + else + /* This should never happen, currently our socket units can only trigger a + * single unit. But let's handle this anyway, who knows what the future + * brings? */ + r = table_add_cell(table, NULL, TABLE_STRV, s->triggered); + if (r < 0) + return table_log_add_error(r); } r = output_table(table); if (r < 0) return r; - if (arg_legend != 0) { - printf("\n%s%zu sockets listed.%s\n", on, cs, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive sockets, too.\n"); - } + if (arg_legend != 0) + output_legend("socket", cs); return 0; } @@ -599,7 +600,6 @@ static int timer_info_compare(const struct timer_info *a, const struct timer_inf static int output_timers_list(struct timer_info *timer_infos, size_t n) { _cleanup_(table_unrefp) Table *table = NULL; - const char *on, *off; int r; assert(timer_infos || n == 0); @@ -641,23 +641,12 @@ static int output_timers_list(struct timer_info *timer_infos, size_t n) { return table_log_add_error(r); } - if (n > 0) { - on = ansi_highlight(); - off = ansi_normal(); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - r = output_table(table); if (r < 0) return r; - if (arg_legend != 0) { - printf("\n%s%zu timers listed.%s\n", on, n, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive timers, too.\n"); - } + if (arg_legend != 0) + output_legend("timer", n); return 0; } @@ -760,3 +749,186 @@ int verb_list_timers(int argc, char *argv[], void *userdata) { return r; } + +struct automount_info { + const char *machine; + const char *id; + char *what; + char *where; + usec_t timeout_idle_usec; + bool mounted; +}; + +static int automount_info_compare(const struct automount_info *a, const struct automount_info *b) { + int r; + + assert(a); + assert(b); + + r = strcasecmp_ptr(a->machine, b->machine); + if (r != 0) + return r; + + return strcmp(a->where, b->where); +} + +static int collect_automount_info(sd_bus* bus, const UnitInfo* info, struct automount_info *ret_info) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *mount = NULL, *mount_path = NULL, *where = NULL, *what = NULL, *state = NULL; + usec_t timeout_idle_usec; + BusLocator locator; + int r; + + assert(bus); + assert(info); + assert(ret_info); + + locator = (BusLocator) { + .destination = "org.freedesktop.systemd1", + .path = info->unit_path, + .interface = "org.freedesktop.systemd1.Automount", + }; + + r = bus_get_property_string(bus, &locator, "Where", &error, &where); + if (r < 0) + return log_error_errno(r, "Failed to get automount target: %s", bus_error_message(&error, r)); + + r = bus_get_property_trivial(bus, &locator, "TimeoutIdleUSec", &error, 't', &timeout_idle_usec); + if (r < 0) + return log_error_errno(r, "Failed to get idle timeout: %s", bus_error_message(&error, r)); + + r = unit_name_from_path(where, ".mount", &mount); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name from path: %m"); + + mount_path = unit_dbus_path_from_name(mount); + if (!mount_path) + return log_oom(); + + locator.path = mount_path; + locator.interface = "org.freedesktop.systemd1.Mount"; + + r = bus_get_property_string(bus, &locator, "What", &error, &what); + if (r < 0) + return log_error_errno(r, "Failed to get mount source: %s", bus_error_message(&error, r)); + + locator.interface = "org.freedesktop.systemd1.Unit"; + + r = bus_get_property_string(bus, &locator, "ActiveState", &error, &state); + if (r < 0) + return log_error_errno(r, "Failed to get mount state: %s", bus_error_message(&error, r)); + + *ret_info = (struct automount_info) { + .machine = info->machine, + .id = info->id, + .what = TAKE_PTR(what), + .where = TAKE_PTR(where), + .timeout_idle_usec = timeout_idle_usec, + .mounted = streq_ptr(state, "active"), + }; + + return 0; +} + +static int output_automounts_list(struct automount_info *infos, size_t n_infos) { + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + assert(infos || n_infos == 0); + + table = table_new("what", "where", "mounted", "idle timeout", "unit"); + if (!table) + return log_oom(); + + table_set_header(table, arg_legend != 0); + if (arg_full) + table_set_width(table, 0); + + (void) table_set_empty_string(table, "-"); + + for (struct automount_info *info = infos; info < infos + n_infos; info++) { + _cleanup_free_ char *j = NULL; + const char *unit; + + if (info->machine) { + j = strjoin(info->machine, ":", info->id); + if (!j) + return log_oom(); + unit = j; + } else + unit = info->id; + + r = table_add_many(table, + TABLE_STRING, info->what, + TABLE_STRING, info->where, + TABLE_BOOLEAN, info->mounted, + TABLE_TIMESPAN_MSEC, info->timeout_idle_usec, + TABLE_STRING, unit); + if (r < 0) + return table_log_add_error(r); + } + + r = output_table(table); + if (r < 0) + return r; + + if (arg_legend != 0) + output_legend("automount", n_infos); + + return 0; +} + +int verb_list_automounts(int argc, char *argv[], void *userdata) { + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL, **automounts = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_free_ struct automount_info *automount_infos = NULL; + size_t c = 0; + int r, n; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + pager_open(arg_pager_flags); + + r = expand_unit_names(bus, strv_skip(argv, 1), ".automount", &automounts, NULL); + if (r < 0) + return r; + + if (argc == 1 || automounts) { + n = get_unit_list_recursive(bus, automounts, &unit_infos, &replies, &machines); + if (n < 0) + return n; + + for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { + if (!endswith(u->id, ".automount")) + continue; + + if (!GREEDY_REALLOC(automount_infos, c + 1)) { + r = log_oom(); + goto cleanup; + } + + r = collect_automount_info(bus, u, &automount_infos[c]); + if (r < 0) + goto cleanup; + + c++; + } + + typesafe_qsort(automount_infos, c, automount_info_compare); + } + + output_automounts_list(automount_infos, c); + + cleanup: + assert(c == 0 || automount_infos); + for (struct automount_info *info = automount_infos; info < automount_infos + c; info++) { + free(info->what); + free(info->where); + } + + return r; +} diff --git a/src/systemctl/systemctl-list-units.h b/src/systemctl/systemctl-list-units.h index 7f4cee0d10..117b7e378a 100644 --- a/src/systemctl/systemctl-list-units.h +++ b/src/systemctl/systemctl-list-units.h @@ -4,5 +4,6 @@ int verb_list_units(int argc, char *argv[], void *userdata); int verb_list_sockets(int argc, char *argv[], void *userdata); int verb_list_timers(int argc, char *argv[], void *userdata); +int verb_list_automounts(int argc, char *argv[], void *userdata); usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index fa32c67a08..fb40c478c2 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -138,6 +138,8 @@ static int systemctl_help(void) { "%5$sQuery or send control commands to the system manager.%6$s\n" "\n%3$sUnit Commands:%4$s\n" " list-units [PATTERN...] List units currently in memory\n" + " list-automounts [PATTERN...] List automount units currently in memory,\n" + " ordered by path\n" " list-sockets [PATTERN...] List socket units currently in memory,\n" " ordered by address\n" " list-timers [PATTERN...] List timer units currently in memory,\n" @@ -1022,6 +1024,7 @@ static int systemctl_main(int argc, char *argv[]) { static const Verb verbs[] = { { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_units }, { "list-unit-files", VERB_ANY, VERB_ANY, 0, verb_list_unit_files }, + { "list-automounts", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_automounts }, { "list-sockets", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_sockets }, { "list-timers", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_timers }, { "list-jobs", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_jobs },