#include "sd-login.h"
#include "bus-error.h"
+#include "bus-locator.h"
#include "format-table.h"
#include "locale-util.h"
+#include "path-util.h"
#include "set.h"
#include "sort-util.h"
#include "systemctl-list-units.h"
sd_bus *bus,
char **patterns,
UnitInfo **ret_unit_infos,
- Set **ret_replies,
- char ***ret_machines) {
+ Set **ret_replies) {
_cleanup_free_ UnitInfo *unit_infos = NULL;
_cleanup_(message_set_freep) Set *replies = NULL;
- sd_bus_message *reply;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int c, r;
assert(bus);
assert(ret_replies);
assert(ret_unit_infos);
- assert(ret_machines);
replies = set_new(NULL);
if (!replies)
return c;
r = set_put(replies, reply);
- if (r < 0) {
- sd_bus_message_unref(reply);
+ if (r < 0)
return log_oom();
- }
+ TAKE_PTR(reply);
if (arg_recursive) {
_cleanup_strv_free_ char **machines = NULL;
c = k;
r = set_put(replies, reply);
- if (r < 0) {
- sd_bus_message_unref(reply);
+ if (r < 0)
return log_oom();
- }
+ TAKE_PTR(reply);
}
-
- *ret_machines = TAKE_PTR(machines);
- } else
- *ret_machines = NULL;
+ }
*ret_unit_infos = TAKE_PTR(unit_infos);
*ret_replies = TAKE_PTR(replies);
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 table_add_triggered(Table *table, char **triggered) {
+ assert(table);
+
+ if (strv_isempty(triggered))
+ return table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ else if (strv_length(triggered) == 1)
+ return table_add_cell(table, NULL, TABLE_STRING, 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? */
+ return table_add_cell(table, NULL, TABLE_STRV, triggered);
+}
+
+static char *format_unit_id(const char *unit, const char *machine) {
+ assert(unit);
+
+ return machine ? strjoin(machine, ":", unit) : strdup(unit);
+}
+
static int output_units_list(const UnitInfo *unit_infos, size_t c) {
_cleanup_(table_unrefp) Table *table = NULL;
size_t job_count = 0;
if (arg_full)
table_set_width(table, 0);
- (void) table_set_empty_string(table, "-");
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
for (const UnitInfo *u = unit_infos; unit_infos && (size_t) (u - unit_infos) < c; u++) {
- _cleanup_free_ char *j = NULL;
- const char *on_underline = "", *on_loaded = "", *on_active = "";
- const char *on_circle = "", *id;
+ _cleanup_free_ char *id = NULL;
+ const char *on_underline = "", *on_loaded = "", *on_active = "", *on_circle = "";
bool circle = false, underline = false;
if (u + 1 < unit_infos + c &&
on_loaded = on_underline;
}
- if (u->machine) {
- j = strjoin(u->machine, ":", u->id);
- if (!j)
- return log_oom();
-
- id = j;
- } else
- id = u->id;
+ id = format_unit_id(u->id, u->machine);
+ if (!id)
+ return log_oom();
r = table_add_many(table,
TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ",
"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",
int verb_list_units(int argc, char *argv[], void *userdata) {
_cleanup_free_ UnitInfo *unit_infos = NULL;
_cleanup_(message_set_freep) Set *replies = NULL;
- _cleanup_strv_free_ char **machines = NULL;
sd_bus *bus;
int r;
if (r < 0)
return r;
- r = get_unit_list_recursive(bus, names, &unit_infos, &replies, &machines);
+ r = get_unit_list_recursive(bus, names, &unit_infos, &replies);
if (r < 0)
return r;
} else {
- r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
+ r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies);
if (r < 0)
return r;
}
return 0;
}
-static int get_listening(
+typedef struct SocketInfo {
+ const char *machine;
+ const char* id;
+
+ char* type;
+ char* path; /* absolute path or socket address */
+
+ /* Note: triggered is a list here, although it almost certainly will always be one
+ * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
+ char** triggered;
+} SocketInfo;
+
+static void socket_info_array_free(SocketInfo *sockets, size_t n_sockets) {
+ assert(sockets || n_sockets == 0);
+
+ for (SocketInfo *s = sockets; s < sockets + n_sockets; s++) {
+ free(s->type);
+ free(s->path);
+ strv_free(s->triggered);
+ }
+
+ free(sockets);
+}
+
+static int socket_info_compare(const SocketInfo *a, const SocketInfo *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = strcasecmp_ptr(a->machine, b->machine);
+ if (r != 0)
+ return r;
+
+ r = CMP(path_is_absolute(a->path), path_is_absolute(b->path));
+ if (r != 0)
+ return r;
+
+ r = path_is_absolute(a->path) ? path_compare(a->path, b->path) : strcmp(a->path, b->path);
+ if (r != 0)
+ return r;
+
+ return strcmp(a->type, b->type);
+}
+
+static int socket_info_add(
sd_bus *bus,
- const char* unit_path,
- char*** listening) {
+ const UnitInfo *u,
+ SocketInfo **sockets,
+ size_t *n_sockets) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_strv_free_ char **triggered = NULL;
const char *type, *path;
- int r, n = 0;
+ int r;
+
+ assert(bus);
+ assert(u);
+ assert(sockets);
+ assert(n_sockets);
+
+ if (!endswith(u->id, ".socket"))
+ return 0;
+
+ r = get_triggered_units(bus, u->unit_path, &triggered);
+ if (r < 0)
+ return r;
r = sd_bus_get_property(
bus,
"org.freedesktop.systemd1",
- unit_path,
+ u->unit_path,
"org.freedesktop.systemd1.Socket",
"Listen",
&error,
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) {
+ _cleanup_free_ char *type_dup = NULL, *path_dup = NULL;
+ _cleanup_strv_free_ char **triggered_dup = NULL;
- r = strv_extend(listening, type);
- if (r < 0)
+ type_dup = strdup(type);
+ if (!type_dup)
return log_oom();
- r = strv_extend(listening, path);
- if (r < 0)
+ path_dup = strdup(path);
+ if (!path_dup)
+ return log_oom();
+
+ triggered_dup = strv_copy(triggered);
+ if (!triggered_dup)
+ return log_oom();
+
+ if (!GREEDY_REALLOC(*sockets, *n_sockets + 1))
return log_oom();
- n++;
+ (*sockets)[(*n_sockets)++] = (SocketInfo) {
+ .machine = u->machine,
+ .id = u->id,
+ .type = TAKE_PTR(type_dup),
+ .path = TAKE_PTR(path_dup),
+ .triggered = TAKE_PTR(triggered_dup),
+ };
}
if (r < 0)
return bus_log_parse_error(r);
if (r < 0)
return bus_log_parse_error(r);
- return n;
-}
-
-struct socket_info {
- const char *machine;
- const char* id;
-
- char* type;
- char* path;
-
- /* Note: triggered is a list here, although it almost certainly will always be one
- * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
- char** triggered;
-
- /* The strv above is shared. free is set only in the first one. */
- bool own_triggered;
-};
-
-static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) {
- int r;
-
- assert(a);
- assert(b);
-
- r = strcasecmp_ptr(a->machine, b->machine);
- if (r != 0)
- return r;
-
- r = strcmp(a->path, b->path);
- if (r != 0)
- return r;
-
- return strcmp(a->type, b->type);
+ return 0;
}
-static int output_sockets_list(struct socket_info *socket_infos, size_t cs) {
+static int output_sockets_list(const SocketInfo *sockets, size_t n_sockets) {
_cleanup_(table_unrefp) Table *table = NULL;
int r;
+ assert(sockets || n_sockets == 0);
+
table = table_new("listen", "type", "unit", "activates");
if (!table)
return log_oom();
if (arg_full)
table_set_width(table, 0);
- (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;
-
- 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);
-
- 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);
- }
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ for (const SocketInfo *s = sockets; s < sockets + n_sockets; s++) {
+ _cleanup_free_ char *unit = NULL;
+
+ unit = format_unit_id(s->id, s->machine);
+ if (!unit)
+ return log_oom();
+
+ r = table_add_many(table,
+ TABLE_STRING, s->path,
+ TABLE_STRING, s->type,
+ TABLE_STRING, unit);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_triggered(table, s->triggered);
+ if (r < 0)
+ return table_log_add_error(r);
}
r = output_table(table);
if (r < 0)
return r;
- if (arg_legend != 0) {
- const char *on, *off;
-
- on = cs > 0 ? ansi_highlight() : ansi_highlight_red();
- off = ansi_normal();
-
- 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", n_sockets);
return 0;
}
int verb_list_sockets(int argc, char *argv[], void *userdata) {
_cleanup_(message_set_freep) Set *replies = NULL;
- _cleanup_strv_free_ char **machines = NULL;
_cleanup_strv_free_ char **sockets_with_suffix = NULL;
_cleanup_free_ UnitInfo *unit_infos = NULL;
- _cleanup_free_ struct socket_info *socket_infos = NULL;
- size_t cs = 0;
- int r, n;
+ SocketInfo *sockets = NULL;
+ size_t n_sockets = 0;
sd_bus *bus;
+ int r;
+
+ CLEANUP_ARRAY(sockets, n_sockets, socket_info_array_free);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
if (argc == 1 || sockets_with_suffix) {
- n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies, &machines);
+ int n;
+
+ n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies);
if (n < 0)
return n;
for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
- _cleanup_strv_free_ char **listening = NULL, **triggered = NULL;
- int c;
-
- if (!endswith(u->id, ".socket"))
- continue;
-
- r = get_triggered_units(bus, u->unit_path, &triggered);
+ r = socket_info_add(bus, u, &sockets, &n_sockets);
if (r < 0)
- goto cleanup;
-
- c = get_listening(bus, u->unit_path, &listening);
- if (c < 0) {
- r = c;
- goto cleanup;
- }
-
- if (!GREEDY_REALLOC(socket_infos, cs + c)) {
- r = log_oom();
- goto cleanup;
- }
-
- for (int i = 0; i < c; i++)
- socket_infos[cs + i] = (struct socket_info) {
- .machine = u->machine,
- .id = u->id,
- .type = listening[i*2],
- .path = listening[i*2 + 1],
- .triggered = triggered,
- .own_triggered = i==0,
- };
-
- /* from this point on we will cleanup those socket_infos */
- cs += c;
- free(listening);
- listening = triggered = NULL; /* avoid cleanup */
+ return r;
}
-
- typesafe_qsort(socket_infos, cs, socket_info_compare);
}
- output_sockets_list(socket_infos, cs);
+ typesafe_qsort(sockets, n_sockets, socket_info_compare);
+ output_sockets_list(sockets, n_sockets);
- cleanup:
- assert(cs == 0 || socket_infos);
- for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) {
- free(s->type);
- free(s->path);
- if (s->own_triggered)
- strv_free(s->triggered);
- }
-
- return r;
+ return 0;
}
static int get_next_elapse(
return 0;
}
-struct timer_info {
+typedef struct TimerInfo {
const char* machine;
const char* id;
usec_t next_elapse;
usec_t last_trigger;
- char** triggered;
-};
+ char **triggered;
+} TimerInfo;
-static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) {
+static void timer_info_array_free(TimerInfo *timers, size_t n_timers) {
+ assert(timers || n_timers == 0);
+
+ for (TimerInfo *t = timers; t < timers + n_timers; t++)
+ strv_free(t->triggered);
+
+ free(timers);
+}
+
+static int timer_info_compare(const TimerInfo *a, const TimerInfo *b) {
int r;
assert(a);
return strcmp(a->id, b->id);
}
-static int output_timers_list(struct timer_info *timer_infos, size_t n) {
+static int output_timers_list(const TimerInfo *timers, size_t n_timers) {
_cleanup_(table_unrefp) Table *table = NULL;
int r;
- assert(timer_infos || n == 0);
+ assert(timers || n_timers == 0);
table = table_new("next", "left", "last", "passed", "unit", "activates");
if (!table)
if (arg_full)
table_set_width(table, 0);
- (void) table_set_empty_string(table, "-");
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
- for (struct timer_info *t = timer_infos; t < timer_infos + n; t++) {
- _cleanup_free_ char *j = NULL, *activates = NULL;
- const char *unit;
+ (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
+ (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
- if (t->machine) {
- j = strjoin(t->machine, ":", t->id);
- if (!j)
- return log_oom();
- unit = j;
- } else
- unit = t->id;
+ for (const TimerInfo *t = timers; t < timers + n_timers; t++) {
+ _cleanup_free_ char *unit = NULL;
- activates = strv_join(t->triggered, ", ");
- if (!activates)
+ unit = format_unit_id(t->id, t->machine);
+ if (!unit)
return log_oom();
r = table_add_many(table,
TABLE_TIMESTAMP, t->next_elapse,
- TABLE_TIMESTAMP_RELATIVE, t->next_elapse,
+ TABLE_TIMESTAMP_LEFT, t->next_elapse,
TABLE_TIMESTAMP, t->last_trigger,
TABLE_TIMESTAMP_RELATIVE, t->last_trigger,
- TABLE_STRING, unit,
- TABLE_STRING, activates);
+ TABLE_STRING, unit);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_triggered(table, t->triggered);
if (r < 0)
return table_log_add_error(r);
}
if (r < 0)
return r;
- if (arg_legend != 0) {
- const char *on, *off;
-
- on = n > 0 ? ansi_highlight() : ansi_highlight_red();
- off = ansi_normal();
-
- 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_timers);
return 0;
}
-usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) {
+usec_t calc_next_elapse(const dual_timestamp *nw, const dual_timestamp *next) {
usec_t next_elapse;
assert(nw);
return next_elapse;
}
+static int add_timer_info(
+ sd_bus *bus,
+ const UnitInfo *u,
+ const dual_timestamp *nw,
+ TimerInfo **timers,
+ size_t *n_timers) {
+
+ _cleanup_strv_free_ char **triggered = NULL;
+ dual_timestamp next = DUAL_TIMESTAMP_NULL;
+ usec_t m, last = 0;
+ int r;
+
+ assert(bus);
+ assert(u);
+ assert(nw);
+ assert(timers);
+ assert(n_timers);
+
+ if (!endswith(u->id, ".timer"))
+ return 0;
+
+ r = get_triggered_units(bus, u->unit_path, &triggered);
+ if (r < 0)
+ return r;
+
+ r = get_next_elapse(bus, u->unit_path, &next);
+ if (r < 0)
+ return r;
+
+ r = get_last_trigger(bus, u->unit_path, &last);
+ if (r < 0)
+ return r;
+
+ m = calc_next_elapse(nw, &next);
+
+ if (!GREEDY_REALLOC(*timers, *n_timers + 1))
+ return log_oom();
+
+ (*timers)[(*n_timers)++] = (TimerInfo) {
+ .machine = u->machine,
+ .id = u->id,
+ .next_elapse = m,
+ .last_trigger = last,
+ .triggered = TAKE_PTR(triggered),
+ };
+
+ return 0;
+}
+
int verb_list_timers(int argc, char *argv[], void *userdata) {
_cleanup_(message_set_freep) Set *replies = NULL;
- _cleanup_strv_free_ char **machines = NULL;
_cleanup_strv_free_ char **timers_with_suffix = NULL;
- _cleanup_free_ struct timer_info *timer_infos = NULL;
_cleanup_free_ UnitInfo *unit_infos = NULL;
- dual_timestamp nw;
- size_t c = 0;
+ TimerInfo *timers = NULL;
+ size_t n_timers = 0;
sd_bus *bus;
- int n, r;
+ int r;
+
+ CLEANUP_ARRAY(timers, n_timers, timer_info_array_free);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
if (argc == 1 || timers_with_suffix) {
- n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies, &machines);
+ dual_timestamp nw;
+ int n;
+
+ n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies);
if (n < 0)
return n;
dual_timestamp_get(&nw);
for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
- _cleanup_strv_free_ char **triggered = NULL;
- dual_timestamp next = DUAL_TIMESTAMP_NULL;
- usec_t m, last = 0;
+ r = add_timer_info(bus, u, &nw, &timers, &n_timers);
+ if (r < 0)
+ return r;
+ }
+ }
- if (!endswith(u->id, ".timer"))
- continue;
+ typesafe_qsort(timers, n_timers, timer_info_compare);
+ output_timers_list(timers, n_timers);
- r = get_triggered_units(bus, u->unit_path, &triggered);
- if (r < 0)
- goto cleanup;
+ return 0;
+}
- r = get_next_elapse(bus, u->unit_path, &next);
- if (r < 0)
- goto cleanup;
+typedef struct AutomountInfo {
+ const char *machine;
+ const char *id;
+ char *what;
+ char *where;
+ usec_t timeout_idle_usec;
+ bool mounted;
+} AutomountInfo;
+
+static void automount_info_array_free(AutomountInfo *automounts, size_t n_automounts) {
+ assert(automounts || n_automounts == 0);
+
+ for (AutomountInfo *i = automounts; i < automounts + n_automounts; i++) {
+ free(i->what);
+ free(i->where);
+ }
- get_last_trigger(bus, u->unit_path, &last);
+ free(automounts);
+}
- if (!GREEDY_REALLOC(timer_infos, c+1)) {
- r = log_oom();
- goto cleanup;
- }
+static int automount_info_compare(const AutomountInfo *a, const AutomountInfo *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = strcasecmp_ptr(a->machine, b->machine);
+ if (r != 0)
+ return r;
+
+ return path_compare(a->where, b->where);
+}
+
+static int automount_info_add(
+ sd_bus* bus,
+ const UnitInfo *info,
+ AutomountInfo **automounts,
+ size_t *n_automounts) {
+
+ _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;
+ uint64_t timeout_idle_usec;
+ BusLocator locator;
+ int r;
+
+ assert(bus);
+ assert(info);
+ assert(automounts);
+ assert(n_automounts);
- m = calc_next_elapse(&nw, &next);
+ if (!endswith(info->id, ".automount"))
+ return 0;
- timer_infos[c++] = (struct timer_info) {
- .machine = u->machine,
- .id = u->id,
- .next_elapse = m,
- .last_trigger = last,
- .triggered = TAKE_PTR(triggered),
- };
+ 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));
+
+ if (!GREEDY_REALLOC(*automounts, *n_automounts + 1))
+ return log_oom();
+
+ (*automounts)[(*n_automounts)++] = (AutomountInfo) {
+ .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(const AutomountInfo *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);
+
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ for (const AutomountInfo *info = infos; info < infos + n_infos; info++) {
+ _cleanup_free_ char *unit = NULL;
+
+ unit = format_unit_id(info->id, info->machine);
+ if (!unit)
+ return log_oom();
+
+ r = table_add_many(table,
+ TABLE_STRING, info->what,
+ TABLE_STRING, info->where,
+ TABLE_BOOLEAN, info->mounted);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (timestamp_is_set(info->timeout_idle_usec))
+ r = table_add_cell(table, NULL, TABLE_TIMESPAN_MSEC, &info->timeout_idle_usec);
+ else
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell(table, NULL, 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 **names = NULL;
+ _cleanup_free_ UnitInfo *unit_infos = NULL;
+ AutomountInfo *automounts = NULL;
+ size_t n_automounts = 0;
+ sd_bus *bus;
+ int r;
+
+ CLEANUP_ARRAY(automounts, n_automounts, automount_info_array_free);
+
+ 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", &names, NULL);
+ if (r < 0)
+ return r;
+
+ if (argc == 1 || automounts) {
+ int n;
+
+ n = get_unit_list_recursive(bus, names, &unit_infos, &replies);
+ if (n < 0)
+ return n;
+
+ for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
+ r = automount_info_add(bus, u, &automounts, &n_automounts);
+ if (r < 0)
+ return r;
}
- typesafe_qsort(timer_infos, c, timer_info_compare);
}
- output_timers_list(timer_infos, c);
+ typesafe_qsort(automounts, n_automounts, automount_info_compare);
+ output_automounts_list(automounts, n_automounts);
- cleanup:
- for (struct timer_info *t = timer_infos; t < timer_infos + c; t++)
- strv_free(t->triggered);
+ return 0;
+}
+
+typedef struct PathInfo {
+ const char *machine;
+ const char *id;
+
+ char *path;
+ char *condition;
+
+ /* Note: triggered is a list here, although it almost certainly will always be one
+ * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
+ char** triggered;
+} PathInfo;
+
+static int path_info_compare(const PathInfo *a, const PathInfo *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = strcasecmp_ptr(a->machine, b->machine);
+ if (r != 0)
+ return r;
+
+ r = path_compare(a->path, b->path);
+ if (r != 0)
+ return r;
+
+ r = strcmp(a->condition, b->condition);
+ if (r != 0)
+ return r;
- return r;
+ return strcasecmp_ptr(a->id, b->id);
+}
+
+static void path_info_array_free(PathInfo *paths, size_t n_paths) {
+ assert(paths || n_paths == 0);
+
+ for (PathInfo *p = paths; p < paths + n_paths; p++) {
+ free(p->condition);
+ free(p->path);
+ strv_free(p->triggered);
+ }
+
+ free(paths);
+}
+
+static int path_info_add(
+ sd_bus *bus,
+ const struct UnitInfo *u,
+ PathInfo **paths,
+ size_t *n_paths) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_strv_free_ char **triggered = NULL;
+ const char *condition, *path;
+ int r;
+
+ assert(bus);
+ assert(u);
+ assert(paths);
+ assert(n_paths);
+
+ if (!endswith(u->id, ".path"))
+ return 0;
+
+ r = get_triggered_units(bus, u->unit_path, &triggered);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_get_property(bus,
+ "org.freedesktop.systemd1",
+ u->unit_path,
+ "org.freedesktop.systemd1.Path",
+ "Paths",
+ &error,
+ &reply,
+ "a(ss)");
+ if (r < 0)
+ return log_error_errno(r, "Failed to get paths: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ while ((r = sd_bus_message_read(reply, "(ss)", &condition, &path)) > 0) {
+ _cleanup_free_ char *condition_dup = NULL, *path_dup = NULL;
+ _cleanup_strv_free_ char **triggered_dup = NULL;
+
+ condition_dup = strdup(condition);
+ if (!condition_dup)
+ return log_oom();
+
+ path_dup = strdup(path);
+ if (!path_dup)
+ return log_oom();
+
+ triggered_dup = strv_copy(triggered);
+ if (!triggered_dup)
+ return log_oom();
+
+ if (!GREEDY_REALLOC(*paths, *n_paths + 1))
+ return log_oom();
+
+ (*paths)[(*n_paths)++] = (PathInfo) {
+ .machine = u->machine,
+ .id = u->id,
+ .condition = TAKE_PTR(condition_dup),
+ .path = TAKE_PTR(path_dup),
+ .triggered = TAKE_PTR(triggered_dup),
+ };
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return 0;
+}
+
+static int output_paths_list(const PathInfo *paths, size_t n_paths) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ int r;
+
+ assert(paths || n_paths == 0);
+
+ table = table_new("path", "condition", "unit", "activates");
+ if (!table)
+ return log_oom();
+
+ table_set_header(table, arg_legend != 0);
+ if (arg_full)
+ table_set_width(table, 0);
+
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ for (const PathInfo *p = paths; p < paths + n_paths; p++) {
+ _cleanup_free_ char *unit = NULL;
+
+ unit = format_unit_id(p->id, p->machine);
+ if (!unit)
+ return log_oom();
+
+ r = table_add_many(table,
+ TABLE_STRING, p->path,
+ TABLE_STRING, p->condition,
+ TABLE_STRING, unit);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_triggered(table, p->triggered);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = output_table(table);
+ if (r < 0)
+ return r;
+
+ if (arg_legend != 0)
+ output_legend("path", n_paths);
+
+ return 0;
+}
+
+int verb_list_paths(int argc, char *argv[], void *userdata) {
+ _cleanup_(message_set_freep) Set *replies = NULL;
+ _cleanup_strv_free_ char **units = NULL;
+ _cleanup_free_ UnitInfo *unit_infos = NULL;
+ PathInfo *paths = NULL;
+ size_t n_paths = 0;
+ sd_bus *bus;
+ int r;
+
+ CLEANUP_ARRAY(paths, n_paths, path_info_array_free);
+
+ 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), ".path", &units, NULL);
+ if (r < 0)
+ return r;
+
+ if (argc == 1 || units) {
+ int n;
+
+ n = get_unit_list_recursive(bus, units, &unit_infos, &replies);
+ if (n < 0)
+ return n;
+
+ for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
+ r = path_info_add(bus, u, &paths, &n_paths);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ typesafe_qsort(paths, n_paths, path_info_compare);
+ output_paths_list(paths, n_paths);
+
+ return 0;
}