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