1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <unistd.h>
4
5 #include "sd-login.h"
6
7 #include "bus-map-properties.h"
8 #include "hostname-util.h"
9 #include "locale-util.h"
10 #include "memory-util.h"
11 #include "sort-util.h"
12 #include "systemctl-list-machines.h"
13 #include "systemctl-util.h"
14 #include "systemctl.h"
15 #include "terminal-util.h"
16
17 const struct bus_properties_map machine_info_property_map[] = {
18 /* Might good to keep same order here as in bus_manager_vtable[], server side */
19 { "Version", "s", NULL, offsetof(struct machine_info, version) },
20 { "Tainted", "s", NULL, offsetof(struct machine_info, tainted) },
21 { "UserspaceTimestamp", "t", NULL, offsetof(struct machine_info, timestamp) },
22 { "NNames", "u", NULL, offsetof(struct machine_info, n_names) },
23 { "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) },
24 { "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) },
25 { "ControlGroup", "s", NULL, offsetof(struct machine_info, control_group) },
26 { "SystemState", "s", NULL, offsetof(struct machine_info, state) },
27 {}
28 };
29
machine_info_clear(struct machine_info * info)30 void machine_info_clear(struct machine_info *info) {
31 assert(info);
32
33 free(info->name);
34 free(info->version);
35 free(info->tainted);
36 free(info->control_group);
37 free(info->state);
38 zero(*info);
39 }
40
free_machines_list(struct machine_info * machine_infos,int n)41 static void free_machines_list(struct machine_info *machine_infos, int n) {
42 if (!machine_infos)
43 return;
44
45 for (int i = 0; i < n; i++)
46 machine_info_clear(&machine_infos[i]);
47
48 free(machine_infos);
49 }
50
compare_machine_info(const struct machine_info * a,const struct machine_info * b)51 static int compare_machine_info(const struct machine_info *a, const struct machine_info *b) {
52 int r;
53
54 r = CMP(b->is_host, a->is_host);
55 if (r != 0)
56 return r;
57
58 return strcasecmp(a->name, b->name);
59 }
60
get_machine_properties(sd_bus * bus,struct machine_info * mi)61 static int get_machine_properties(sd_bus *bus, struct machine_info *mi) {
62 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL;
63 int r;
64
65 assert(mi);
66
67 if (!bus) {
68 r = sd_bus_open_system_machine(&container, mi->name);
69 if (r < 0)
70 return r;
71
72 bus = container;
73 }
74
75 r = bus_map_all_properties(
76 bus,
77 "org.freedesktop.systemd1",
78 "/org/freedesktop/systemd1",
79 machine_info_property_map,
80 BUS_MAP_STRDUP,
81 NULL,
82 NULL,
83 mi);
84 if (r < 0)
85 return r;
86
87 return 0;
88 }
89
output_show_machine(const char * name,char ** patterns)90 static bool output_show_machine(const char *name, char **patterns) {
91 return strv_fnmatch_or_empty(patterns, name, FNM_NOESCAPE);
92 }
93
get_machine_list(sd_bus * bus,struct machine_info ** _machine_infos,char ** patterns)94 static int get_machine_list(
95 sd_bus *bus,
96 struct machine_info **_machine_infos,
97 char **patterns) {
98
99 struct machine_info *machine_infos = NULL;
100 _cleanup_strv_free_ char **m = NULL;
101 _cleanup_free_ char *hn = NULL;
102 int c = 0, r;
103
104 hn = gethostname_malloc();
105 if (!hn)
106 return log_oom();
107
108 if (output_show_machine(hn, patterns)) {
109 if (!GREEDY_REALLOC0(machine_infos, c+1))
110 return log_oom();
111
112 machine_infos[c].is_host = true;
113 machine_infos[c].name = TAKE_PTR(hn);
114
115 (void) get_machine_properties(bus, &machine_infos[c]);
116 c++;
117 }
118
119 r = sd_get_machine_names(&m);
120 if (r < 0)
121 return log_error_errno(r, "Failed to get machine list: %m");
122
123 STRV_FOREACH(i, m) {
124 _cleanup_free_ char *class = NULL;
125
126 if (!output_show_machine(*i, patterns))
127 continue;
128
129 sd_machine_get_class(*i, &class);
130 if (!streq_ptr(class, "container"))
131 continue;
132
133 if (!GREEDY_REALLOC0(machine_infos, c+1)) {
134 free_machines_list(machine_infos, c);
135 return log_oom();
136 }
137
138 machine_infos[c].is_host = false;
139 machine_infos[c].name = strdup(*i);
140 if (!machine_infos[c].name) {
141 free_machines_list(machine_infos, c);
142 return log_oom();
143 }
144
145 (void) get_machine_properties(NULL, &machine_infos[c]);
146 c++;
147 }
148
149 *_machine_infos = machine_infos;
150 return c;
151 }
152
output_machines_list(struct machine_info * machine_infos,unsigned n)153 static int output_machines_list(struct machine_info *machine_infos, unsigned n) {
154 _cleanup_(table_unrefp) Table *table = NULL;
155 bool state_missing = false;
156 int r;
157
158 assert(machine_infos || n == 0);
159
160 table = table_new("", "name", "state", "failed", "jobs");
161 if (!table)
162 return log_oom();
163
164 table_set_header(table, arg_legend != 0);
165 if (arg_plain) {
166 /* Hide the 'glyph' column when --plain is requested */
167 r = table_hide_column_from_display(table, 0);
168 if (r < 0)
169 return log_error_errno(r, "Failed to hide column: %m");
170 }
171 if (arg_full)
172 table_set_width(table, 0);
173
174 (void) table_set_empty_string(table, "-");
175
176 for (struct machine_info *m = machine_infos; m < machine_infos + n; m++) {
177 _cleanup_free_ char *mname = NULL;
178 const char *on_state = "", *on_failed = "";
179 bool circle = false;
180
181 if (streq_ptr(m->state, "degraded")) {
182 on_state = ansi_highlight_red();
183 circle = true;
184 } else if (!streq_ptr(m->state, "running")) {
185 on_state = ansi_highlight_yellow();
186 circle = true;
187 }
188
189 if (m->n_failed_units > 0)
190 on_failed = ansi_highlight_red();
191 else
192 on_failed = "";
193
194 if (!m->state)
195 state_missing = true;
196
197 if (m->is_host)
198 mname = strjoin(strna(m->name), " (host)");
199
200 r = table_add_many(table,
201 TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ",
202 TABLE_SET_COLOR, on_state,
203 TABLE_STRING, m->is_host ? mname : strna(m->name),
204 TABLE_STRING, strna(m->state),
205 TABLE_SET_COLOR, on_state,
206 TABLE_UINT32, m->n_failed_units,
207 TABLE_SET_COLOR, on_failed,
208 TABLE_UINT32, m->n_jobs);
209 if (r < 0)
210 return table_log_add_error(r);
211 }
212
213 r = output_table(table);
214 if (r < 0)
215 return r;
216
217 if (arg_legend != 0) {
218 printf("\n");
219 if (state_missing && geteuid() != 0)
220 printf("Notice: some information only available to privileged users was not shown.\n");
221 printf("%u machines listed.\n", n);
222 }
223
224 return 0;
225 }
226
verb_list_machines(int argc,char * argv[],void * userdata)227 int verb_list_machines(int argc, char *argv[], void *userdata) {
228 struct machine_info *machine_infos = NULL;
229 sd_bus *bus;
230 int r, rc;
231
232 r = acquire_bus(BUS_MANAGER, &bus);
233 if (r < 0)
234 return r;
235
236 r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1));
237 if (r < 0)
238 return r;
239
240 pager_open(arg_pager_flags);
241
242 typesafe_qsort(machine_infos, r, compare_machine_info);
243 rc = output_machines_list(machine_infos, r);
244 free_machines_list(machine_infos, r);
245
246 return rc;
247 }
248