]>
Commit | Line | Data |
---|---|---|
1ee306e1 LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2013 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
8bdbb8d9 | 22 | #include <sys/socket.h> |
1ee306e1 LP |
23 | #include <unistd.h> |
24 | #include <errno.h> | |
25 | #include <string.h> | |
26 | #include <getopt.h> | |
27 | #include <pwd.h> | |
28 | #include <locale.h> | |
04d39279 | 29 | #include <fcntl.h> |
878cd7e9 LP |
30 | #include <netinet/in.h> |
31 | #include <arpa/inet.h> | |
f48e75cb | 32 | #include <net/if.h> |
1ee306e1 | 33 | |
a1da8583 | 34 | #include "sd-bus.h" |
1ee306e1 LP |
35 | #include "log.h" |
36 | #include "util.h" | |
37 | #include "macro.h" | |
38 | #include "pager.h" | |
a1da8583 TG |
39 | #include "bus-util.h" |
40 | #include "bus-error.h" | |
1ee306e1 LP |
41 | #include "build.h" |
42 | #include "strv.h" | |
aa1936ea | 43 | #include "unit-name.h" |
1ee306e1 | 44 | #include "cgroup-show.h" |
9d127096 | 45 | #include "cgroup-util.h" |
04d39279 | 46 | #include "ptyfwd.h" |
1ee306e1 LP |
47 | |
48 | static char **arg_property = NULL; | |
49 | static bool arg_all = false; | |
50 | static bool arg_full = false; | |
51 | static bool arg_no_pager = false; | |
e56056e9 | 52 | static bool arg_legend = true; |
1ee306e1 LP |
53 | static const char *arg_kill_who = NULL; |
54 | static int arg_signal = SIGTERM; | |
d21ed1ea | 55 | static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; |
1ee306e1 | 56 | static char *arg_host = NULL; |
1ee306e1 LP |
57 | |
58 | static void pager_open_if_enabled(void) { | |
59 | ||
60 | /* Cache result before we open the pager */ | |
61 | if (arg_no_pager) | |
62 | return; | |
63 | ||
64 | pager_open(false); | |
65 | } | |
66 | ||
a1da8583 TG |
67 | static int list_machines(sd_bus *bus, char **args, unsigned n) { |
68 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; | |
69 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
70 | const char *name, *class, *service, *object; | |
1ee306e1 LP |
71 | unsigned k = 0; |
72 | int r; | |
73 | ||
74 | pager_open_if_enabled(); | |
75 | ||
a1da8583 TG |
76 | r = sd_bus_call_method( |
77 | bus, | |
78 | "org.freedesktop.machine1", | |
79 | "/org/freedesktop/machine1", | |
80 | "org.freedesktop.machine1.Manager", | |
81 | "ListMachines", | |
82 | &error, | |
83 | &reply, | |
84 | ""); | |
85 | if (r < 0) { | |
86 | log_error("Could not get machines: %s", bus_error_message(&error, -r)); | |
1ee306e1 | 87 | return r; |
1ee306e1 LP |
88 | } |
89 | ||
e56056e9 TA |
90 | if (arg_legend) |
91 | printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE"); | |
1ee306e1 | 92 | |
a1da8583 TG |
93 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)"); |
94 | if (r < 0) | |
5b30bef8 | 95 | return bus_log_parse_error(r); |
1ee306e1 | 96 | |
a1da8583 | 97 | while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { |
1ee306e1 LP |
98 | printf("%-32s %-9s %-16s\n", name, class, service); |
99 | ||
100 | k++; | |
1ee306e1 | 101 | } |
a1da8583 | 102 | if (r < 0) |
5b30bef8 | 103 | return bus_log_parse_error(r); |
a1da8583 TG |
104 | |
105 | r = sd_bus_message_exit_container(reply); | |
106 | if (r < 0) | |
5b30bef8 | 107 | return bus_log_parse_error(r); |
1ee306e1 | 108 | |
e56056e9 TA |
109 | if (arg_legend) |
110 | printf("\n%u machines listed.\n", k); | |
1ee306e1 LP |
111 | |
112 | return 0; | |
113 | } | |
114 | ||
89f7c846 | 115 | static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { |
a1da8583 TG |
116 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; |
117 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
aa1936ea | 118 | _cleanup_free_ char *path = NULL; |
aa1936ea | 119 | const char *cgroup; |
aa1936ea LP |
120 | int r, output_flags; |
121 | unsigned c; | |
122 | ||
123 | assert(bus); | |
124 | assert(unit); | |
125 | ||
d21ed1ea | 126 | if (arg_transport == BUS_TRANSPORT_REMOTE) |
aa1936ea LP |
127 | return 0; |
128 | ||
129 | path = unit_dbus_path_from_name(unit); | |
130 | if (!path) | |
131 | return log_oom(); | |
132 | ||
a7893c6b | 133 | r = sd_bus_get_property( |
aa1936ea LP |
134 | bus, |
135 | "org.freedesktop.systemd1", | |
136 | path, | |
89f7c846 | 137 | endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service", |
a7893c6b | 138 | "ControlGroup", |
aa1936ea | 139 | &error, |
a1da8583 | 140 | &reply, |
a7893c6b | 141 | "s"); |
aa1936ea | 142 | if (r < 0) { |
a1da8583 | 143 | log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); |
aa1936ea LP |
144 | return r; |
145 | } | |
146 | ||
a7893c6b | 147 | r = sd_bus_message_read(reply, "s", &cgroup); |
5b30bef8 LP |
148 | if (r < 0) |
149 | return bus_log_parse_error(r); | |
aa1936ea | 150 | |
9d127096 LP |
151 | if (isempty(cgroup)) |
152 | return 0; | |
153 | ||
154 | if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0) | |
155 | return 0; | |
156 | ||
aa1936ea LP |
157 | output_flags = |
158 | arg_all * OUTPUT_SHOW_ALL | | |
159 | arg_full * OUTPUT_FULL_WIDTH; | |
160 | ||
161 | c = columns(); | |
162 | if (c > 18) | |
163 | c -= 18; | |
164 | else | |
165 | c = 0; | |
166 | ||
9d127096 | 167 | show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags); |
aa1936ea LP |
168 | return 0; |
169 | } | |
170 | ||
f48e75cb | 171 | static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { |
878cd7e9 LP |
172 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; |
173 | int r; | |
174 | ||
175 | assert(bus); | |
176 | assert(name); | |
177 | assert(prefix); | |
178 | assert(prefix2); | |
179 | ||
180 | r = sd_bus_call_method(bus, | |
181 | "org.freedesktop.machine1", | |
182 | "/org/freedesktop/machine1", | |
183 | "org.freedesktop.machine1.Manager", | |
184 | "GetMachineAddresses", | |
185 | NULL, | |
186 | &reply, | |
187 | "s", name); | |
188 | if (r < 0) | |
189 | return r; | |
190 | ||
0dd25fb9 | 191 | r = sd_bus_message_enter_container(reply, 'a', "(iay)"); |
878cd7e9 LP |
192 | if (r < 0) |
193 | return bus_log_parse_error(r); | |
194 | ||
0dd25fb9 LP |
195 | while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { |
196 | int family; | |
878cd7e9 LP |
197 | const void *a; |
198 | size_t sz; | |
199 | char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; | |
200 | ||
0dd25fb9 | 201 | r = sd_bus_message_read(reply, "i", &family); |
878cd7e9 LP |
202 | if (r < 0) |
203 | return bus_log_parse_error(r); | |
204 | ||
205 | r = sd_bus_message_read_array(reply, 'y', &a, &sz); | |
206 | if (r < 0) | |
207 | return bus_log_parse_error(r); | |
208 | ||
f48e75cb LP |
209 | fputs(prefix, stdout); |
210 | fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); | |
211 | if (family == AF_INET6 && ifi > 0) | |
212 | printf("%%%i", ifi); | |
213 | fputc('\n', stdout); | |
878cd7e9 LP |
214 | |
215 | r = sd_bus_message_exit_container(reply); | |
216 | if (r < 0) | |
217 | return bus_log_parse_error(r); | |
218 | ||
219 | if (prefix != prefix2) | |
220 | prefix = prefix2; | |
221 | } | |
222 | if (r < 0) | |
223 | return bus_log_parse_error(r); | |
224 | ||
225 | r = sd_bus_message_exit_container(reply); | |
226 | if (r < 0) | |
227 | return bus_log_parse_error(r); | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
717603e3 LP |
232 | static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { |
233 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; | |
234 | const char *k, *v, *pretty = NULL; | |
235 | int r; | |
236 | ||
237 | assert(bus); | |
238 | assert(name); | |
239 | assert(prefix); | |
240 | ||
241 | r = sd_bus_call_method(bus, | |
242 | "org.freedesktop.machine1", | |
243 | "/org/freedesktop/machine1", | |
244 | "org.freedesktop.machine1.Manager", | |
245 | "GetMachineOSRelease", | |
246 | NULL, | |
247 | &reply, | |
248 | "s", name); | |
249 | if (r < 0) | |
250 | return r; | |
251 | ||
252 | r = sd_bus_message_enter_container(reply, 'a', "{ss}"); | |
253 | if (r < 0) | |
254 | return bus_log_parse_error(r); | |
255 | ||
256 | while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { | |
257 | if (streq(k, "PRETTY_NAME")) | |
258 | pretty = v; | |
259 | ||
260 | } | |
261 | if (r < 0) | |
262 | return bus_log_parse_error(r); | |
263 | ||
264 | r = sd_bus_message_exit_container(reply); | |
265 | if (r < 0) | |
266 | return bus_log_parse_error(r); | |
267 | ||
268 | if (pretty) | |
269 | printf("%s%s\n", prefix, pretty); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
1ee306e1 | 274 | typedef struct MachineStatusInfo { |
9f6eb1cd | 275 | char *name; |
1ee306e1 | 276 | sd_id128_t id; |
9f6eb1cd KS |
277 | char *class; |
278 | char *service; | |
89f7c846 | 279 | char *unit; |
9f6eb1cd | 280 | char *root_directory; |
1ee306e1 LP |
281 | pid_t leader; |
282 | usec_t timestamp; | |
f48e75cb LP |
283 | int *netif; |
284 | unsigned n_netif; | |
1ee306e1 LP |
285 | } MachineStatusInfo; |
286 | ||
a1da8583 | 287 | static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { |
1ee306e1 LP |
288 | char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; |
289 | char since2[FORMAT_TIMESTAMP_MAX], *s2; | |
f48e75cb LP |
290 | int ifi = -1; |
291 | ||
1ee306e1 LP |
292 | assert(i); |
293 | ||
294 | fputs(strna(i->name), stdout); | |
295 | ||
296 | if (!sd_id128_equal(i->id, SD_ID128_NULL)) | |
297 | printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); | |
298 | else | |
299 | putchar('\n'); | |
300 | ||
301 | s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp); | |
302 | s2 = format_timestamp(since2, sizeof(since2), i->timestamp); | |
303 | ||
304 | if (s1) | |
305 | printf("\t Since: %s; %s\n", s2, s1); | |
306 | else if (s2) | |
307 | printf("\t Since: %s\n", s2); | |
308 | ||
309 | if (i->leader > 0) { | |
310 | _cleanup_free_ char *t = NULL; | |
311 | ||
312 | printf("\t Leader: %u", (unsigned) i->leader); | |
313 | ||
314 | get_process_comm(i->leader, &t); | |
315 | if (t) | |
316 | printf(" (%s)", t); | |
317 | ||
318 | putchar('\n'); | |
319 | } | |
320 | ||
321 | if (i->service) { | |
322 | printf("\t Service: %s", i->service); | |
323 | ||
324 | if (i->class) | |
325 | printf("; class %s", i->class); | |
326 | ||
327 | putchar('\n'); | |
328 | } else if (i->class) | |
329 | printf("\t Class: %s\n", i->class); | |
330 | ||
1ee306e1 LP |
331 | if (i->root_directory) |
332 | printf("\t Root: %s\n", i->root_directory); | |
333 | ||
f48e75cb LP |
334 | if (i->n_netif > 0) { |
335 | unsigned c; | |
336 | ||
337 | fputs("\t Iface:", stdout); | |
338 | ||
339 | for (c = 0; c < i->n_netif; c++) { | |
340 | char name[IF_NAMESIZE+1] = ""; | |
341 | ||
342 | if (if_indextoname(i->netif[c], name)) { | |
343 | fputc(' ', stdout); | |
344 | fputs(name, stdout); | |
345 | ||
346 | if (ifi < 0) | |
347 | ifi = i->netif[c]; | |
348 | else | |
349 | ifi = 0; | |
350 | } else | |
351 | printf(" %i", i->netif[c]); | |
352 | } | |
353 | ||
354 | fputc('\n', stdout); | |
355 | } | |
356 | ||
357 | print_addresses(bus, i->name, ifi, | |
878cd7e9 LP |
358 | "\t Address: ", |
359 | "\t "); | |
360 | ||
717603e3 LP |
361 | print_os_release(bus, i->name, "\t OS: "); |
362 | ||
89f7c846 LP |
363 | if (i->unit) { |
364 | printf("\t Unit: %s\n", i->unit); | |
365 | show_unit_cgroup(bus, i->unit, i->leader); | |
1ee306e1 LP |
366 | } |
367 | } | |
368 | ||
f48e75cb LP |
369 | static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { |
370 | MachineStatusInfo *i = userdata; | |
371 | size_t l; | |
372 | const void *v; | |
373 | int r; | |
374 | ||
375 | assert_cc(sizeof(int32_t) == sizeof(int)); | |
376 | r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); | |
377 | if (r < 0) | |
378 | return r; | |
e7e9b6bb ZJS |
379 | if (r == 0) |
380 | return -EBADMSG; | |
f48e75cb LP |
381 | |
382 | i->n_netif = l / sizeof(int32_t); | |
383 | i->netif = memdup(v, l); | |
384 | if (!i->netif) | |
385 | return -ENOMEM; | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
9f6eb1cd | 390 | static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { |
a6c61602 | 391 | |
9f6eb1cd | 392 | static const struct bus_properties_map map[] = { |
f48e75cb LP |
393 | { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, |
394 | { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, | |
395 | { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, | |
396 | { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, | |
397 | { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, | |
398 | { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, | |
399 | { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp) }, | |
400 | { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, | |
401 | { "NetworkInterfaces", "ai", map_netif, 0 }, | |
9f6eb1cd KS |
402 | {} |
403 | }; | |
a6c61602 LP |
404 | |
405 | MachineStatusInfo info = {}; | |
a1da8583 TG |
406 | int r; |
407 | ||
1ee306e1 LP |
408 | assert(path); |
409 | assert(new_line); | |
410 | ||
9f6eb1cd KS |
411 | r = bus_map_all_properties(bus, |
412 | "org.freedesktop.machine1", | |
413 | path, | |
414 | map, | |
415 | &info); | |
a1da8583 | 416 | if (r < 0) { |
9f6eb1cd | 417 | log_error("Could not get properties: %s", strerror(-r)); |
a1da8583 | 418 | return r; |
1ee306e1 LP |
419 | } |
420 | ||
1ee306e1 LP |
421 | if (*new_line) |
422 | printf("\n"); | |
1ee306e1 LP |
423 | *new_line = true; |
424 | ||
9f6eb1cd | 425 | print_machine_status_info(bus, &info); |
1ee306e1 | 426 | |
9f6eb1cd KS |
427 | free(info.name); |
428 | free(info.class); | |
429 | free(info.service); | |
89f7c846 | 430 | free(info.unit); |
9f6eb1cd | 431 | free(info.root_directory); |
f48e75cb | 432 | free(info.netif); |
1ee306e1 | 433 | |
9f6eb1cd KS |
434 | return r; |
435 | } | |
1ee306e1 | 436 | |
9f6eb1cd KS |
437 | static int show_properties(sd_bus *bus, const char *path, bool *new_line) { |
438 | int r; | |
1ee306e1 | 439 | |
9f6eb1cd KS |
440 | if (*new_line) |
441 | printf("\n"); | |
1ee306e1 | 442 | |
9f6eb1cd | 443 | *new_line = true; |
a1da8583 | 444 | |
27e72d6b | 445 | r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); |
a1da8583 | 446 | if (r < 0) |
9f6eb1cd | 447 | log_error("Could not get properties: %s", strerror(-r)); |
1ee306e1 | 448 | |
a7893c6b | 449 | return r; |
1ee306e1 LP |
450 | } |
451 | ||
a1da8583 TG |
452 | static int show(sd_bus *bus, char **args, unsigned n) { |
453 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; | |
454 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
9f6eb1cd | 455 | int r = 0; |
1ee306e1 | 456 | unsigned i; |
9f6eb1cd | 457 | bool properties, new_line = false; |
1ee306e1 LP |
458 | |
459 | assert(bus); | |
460 | assert(args); | |
461 | ||
9f6eb1cd | 462 | properties = !strstr(args[0], "status"); |
1ee306e1 LP |
463 | |
464 | pager_open_if_enabled(); | |
465 | ||
9f6eb1cd | 466 | if (properties && n <= 1) { |
a1da8583 | 467 | |
9f6eb1cd | 468 | /* If no argument is specified, inspect the manager |
1ee306e1 | 469 | * itself */ |
9f6eb1cd | 470 | r = show_properties(bus, "/org/freedesktop/machine1", &new_line); |
8c841f21 | 471 | if (r < 0) |
9f6eb1cd | 472 | return r; |
1ee306e1 LP |
473 | } |
474 | ||
475 | for (i = 1; i < n; i++) { | |
476 | const char *path = NULL; | |
477 | ||
a1da8583 TG |
478 | r = sd_bus_call_method( |
479 | bus, | |
480 | "org.freedesktop.machine1", | |
481 | "/org/freedesktop/machine1", | |
482 | "org.freedesktop.machine1.Manager", | |
483 | "GetMachine", | |
484 | &error, | |
485 | &reply, | |
486 | "s", args[i]); | |
487 | if (r < 0) { | |
488 | log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); | |
489 | return r; | |
490 | } | |
491 | ||
492 | r = sd_bus_message_read(reply, "o", &path); | |
5b30bef8 LP |
493 | if (r < 0) |
494 | return bus_log_parse_error(r); | |
1ee306e1 | 495 | |
9f6eb1cd KS |
496 | if (properties) |
497 | r = show_properties(bus, path, &new_line); | |
498 | else | |
499 | r = show_info(args[0], bus, path, &new_line); | |
1ee306e1 LP |
500 | } |
501 | ||
9f6eb1cd | 502 | return r; |
1ee306e1 LP |
503 | } |
504 | ||
a1da8583 TG |
505 | static int kill_machine(sd_bus *bus, char **args, unsigned n) { |
506 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
1ee306e1 LP |
507 | unsigned i; |
508 | ||
509 | assert(args); | |
510 | ||
511 | if (!arg_kill_who) | |
512 | arg_kill_who = "all"; | |
513 | ||
514 | for (i = 1; i < n; i++) { | |
515 | int r; | |
516 | ||
a1da8583 TG |
517 | r = sd_bus_call_method( |
518 | bus, | |
519 | "org.freedesktop.machine1", | |
520 | "/org/freedesktop/machine1", | |
521 | "org.freedesktop.machine1.Manager", | |
522 | "KillMachine", | |
523 | &error, | |
524 | NULL, | |
525 | "ssi", args[i], arg_kill_who, arg_signal); | |
526 | if (r < 0) { | |
527 | log_error("Could not kill machine: %s", bus_error_message(&error, -r)); | |
1ee306e1 | 528 | return r; |
a1da8583 | 529 | } |
1ee306e1 LP |
530 | } |
531 | ||
532 | return 0; | |
533 | } | |
534 | ||
1dba654b LP |
535 | static int reboot_machine(sd_bus *bus, char **args, unsigned n) { |
536 | arg_kill_who = "leader"; | |
537 | arg_signal = SIGINT; /* sysvinit + systemd */ | |
1ee306e1 | 538 | |
1dba654b LP |
539 | return kill_machine(bus, args, n); |
540 | } | |
1ee306e1 | 541 | |
1dba654b LP |
542 | static int poweroff_machine(sd_bus *bus, char **args, unsigned n) { |
543 | arg_kill_who = "leader"; | |
544 | arg_signal = SIGRTMIN+4; /* only systemd */ | |
1ee306e1 | 545 | |
1dba654b | 546 | return kill_machine(bus, args, n); |
1ee306e1 LP |
547 | } |
548 | ||
1dba654b | 549 | static int terminate_machine(sd_bus *bus, char **args, unsigned n) { |
923d8fd3 LP |
550 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; |
551 | unsigned i; | |
923d8fd3 LP |
552 | |
553 | assert(args); | |
554 | ||
923d8fd3 | 555 | for (i = 1; i < n; i++) { |
1dba654b | 556 | int r; |
923d8fd3 LP |
557 | |
558 | r = sd_bus_call_method( | |
559 | bus, | |
560 | "org.freedesktop.machine1", | |
561 | "/org/freedesktop/machine1", | |
562 | "org.freedesktop.machine1.Manager", | |
1dba654b | 563 | "TerminateMachine", |
923d8fd3 | 564 | &error, |
1dba654b | 565 | NULL, |
923d8fd3 | 566 | "s", args[i]); |
923d8fd3 | 567 | if (r < 0) { |
1dba654b | 568 | log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); |
923d8fd3 LP |
569 | return r; |
570 | } | |
923d8fd3 LP |
571 | } |
572 | ||
573 | return 0; | |
574 | } | |
575 | ||
04d39279 | 576 | static int openpt_in_namespace(pid_t pid, int flags) { |
3d94f76c | 577 | _cleanup_close_pair_ int pair[2] = { -1, -1 }; |
a4475f57 | 578 | _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; |
04d39279 LP |
579 | union { |
580 | struct cmsghdr cmsghdr; | |
581 | uint8_t buf[CMSG_SPACE(sizeof(int))]; | |
34a6778f ZJS |
582 | } control = {}; |
583 | struct msghdr mh = { | |
584 | .msg_control = &control, | |
585 | .msg_controllen = sizeof(control), | |
586 | }; | |
04d39279 | 587 | struct cmsghdr *cmsg; |
f69157a6 | 588 | int master = -1, r; |
04d39279 LP |
589 | pid_t child; |
590 | siginfo_t si; | |
591 | ||
878cd7e9 | 592 | r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd); |
bc9fd78c LP |
593 | if (r < 0) |
594 | return r; | |
04d39279 | 595 | |
bc9fd78c | 596 | if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) |
04d39279 LP |
597 | return -errno; |
598 | ||
04d39279 LP |
599 | child = fork(); |
600 | if (child < 0) | |
601 | return -errno; | |
602 | ||
603 | if (child == 0) { | |
03e334a1 | 604 | pair[0] = safe_close(pair[0]); |
04d39279 | 605 | |
878cd7e9 | 606 | r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd); |
04d39279 LP |
607 | if (r < 0) |
608 | _exit(EXIT_FAILURE); | |
609 | ||
04d39279 LP |
610 | master = posix_openpt(flags); |
611 | if (master < 0) | |
612 | _exit(EXIT_FAILURE); | |
613 | ||
614 | cmsg = CMSG_FIRSTHDR(&mh); | |
615 | cmsg->cmsg_level = SOL_SOCKET; | |
616 | cmsg->cmsg_type = SCM_RIGHTS; | |
617 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); | |
618 | memcpy(CMSG_DATA(cmsg), &master, sizeof(int)); | |
619 | ||
620 | mh.msg_controllen = cmsg->cmsg_len; | |
621 | ||
bc9fd78c | 622 | if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0) |
04d39279 LP |
623 | _exit(EXIT_FAILURE); |
624 | ||
625 | _exit(EXIT_SUCCESS); | |
626 | } | |
627 | ||
03e334a1 | 628 | pair[1] = safe_close(pair[1]); |
04d39279 | 629 | |
fbadf045 | 630 | r = wait_for_terminate(child, &si); |
878cd7e9 LP |
631 | if (r < 0) |
632 | return r; | |
633 | if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) | |
634 | return -EIO; | |
fbadf045 | 635 | |
bc9fd78c | 636 | if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0) |
04d39279 LP |
637 | return -errno; |
638 | ||
639 | for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) | |
640 | if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { | |
641 | int *fds; | |
642 | unsigned n_fds; | |
643 | ||
644 | fds = (int*) CMSG_DATA(cmsg); | |
645 | n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); | |
646 | ||
647 | if (n_fds != 1) { | |
648 | close_many(fds, n_fds); | |
649 | return -EIO; | |
650 | } | |
651 | ||
652 | master = fds[0]; | |
653 | } | |
654 | ||
6261f11f LP |
655 | if (master < 0) |
656 | return -EIO; | |
657 | ||
04d39279 LP |
658 | return master; |
659 | } | |
660 | ||
661 | static int login_machine(sd_bus *bus, char **args, unsigned n) { | |
662 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL; | |
663 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
664 | _cleanup_bus_unref_ sd_bus *container_bus = NULL; | |
665 | _cleanup_close_ int master = -1; | |
666 | _cleanup_free_ char *getty = NULL; | |
667 | const char *path, *pty, *p; | |
668 | uint32_t leader; | |
669 | sigset_t mask; | |
670 | int r; | |
671 | ||
672 | assert(bus); | |
673 | assert(args); | |
674 | ||
675 | if (arg_transport != BUS_TRANSPORT_LOCAL) { | |
923d8fd3 | 676 | log_error("Login only supported on local machines."); |
04d39279 LP |
677 | return -ENOTSUP; |
678 | } | |
679 | ||
680 | r = sd_bus_call_method( | |
681 | bus, | |
682 | "org.freedesktop.machine1", | |
683 | "/org/freedesktop/machine1", | |
684 | "org.freedesktop.machine1.Manager", | |
685 | "GetMachine", | |
686 | &error, | |
687 | &reply, | |
688 | "s", args[1]); | |
689 | if (r < 0) { | |
690 | log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); | |
691 | return r; | |
692 | } | |
693 | ||
694 | r = sd_bus_message_read(reply, "o", &path); | |
5b30bef8 LP |
695 | if (r < 0) |
696 | return bus_log_parse_error(r); | |
04d39279 LP |
697 | |
698 | r = sd_bus_get_property( | |
699 | bus, | |
700 | "org.freedesktop.machine1", | |
701 | path, | |
702 | "org.freedesktop.machine1.Machine", | |
703 | "Leader", | |
704 | &error, | |
705 | &reply2, | |
706 | "u"); | |
707 | if (r < 0) { | |
708 | log_error("Failed to retrieve PID of leader: %s", strerror(-r)); | |
709 | return r; | |
710 | } | |
711 | ||
712 | r = sd_bus_message_read(reply2, "u", &leader); | |
5b30bef8 LP |
713 | if (r < 0) |
714 | return bus_log_parse_error(r); | |
04d39279 LP |
715 | |
716 | master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); | |
717 | if (master < 0) { | |
718 | log_error("Failed to acquire pseudo tty: %s", strerror(-master)); | |
719 | return master; | |
720 | } | |
721 | ||
722 | pty = ptsname(master); | |
723 | if (!pty) { | |
724 | log_error("Failed to get pty name: %m"); | |
725 | return -errno; | |
726 | } | |
727 | ||
728 | p = startswith(pty, "/dev/pts/"); | |
729 | if (!p) { | |
730 | log_error("Invalid pty name %s.", pty); | |
731 | return -EIO; | |
732 | } | |
733 | ||
3db729cb | 734 | r = sd_bus_open_system_container(&container_bus, args[1]); |
04d39279 LP |
735 | if (r < 0) { |
736 | log_error("Failed to get container bus: %s", strerror(-r)); | |
737 | return r; | |
738 | } | |
739 | ||
740 | getty = strjoin("container-getty@", p, ".service", NULL); | |
741 | if (!getty) | |
742 | return log_oom(); | |
743 | ||
744 | if (unlockpt(master) < 0) { | |
745 | log_error("Failed to unlock tty: %m"); | |
746 | return -errno; | |
747 | } | |
748 | ||
749 | r = sd_bus_call_method(container_bus, | |
750 | "org.freedesktop.systemd1", | |
751 | "/org/freedesktop/systemd1", | |
752 | "org.freedesktop.systemd1.Manager", | |
753 | "StartUnit", | |
754 | &error, &reply3, | |
755 | "ss", getty, "replace"); | |
756 | if (r < 0) { | |
757 | log_error("Failed to start getty service: %s", bus_error_message(&error, r)); | |
758 | return r; | |
759 | } | |
760 | ||
a6c61602 LP |
761 | container_bus = sd_bus_unref(container_bus); |
762 | ||
04d39279 LP |
763 | assert_se(sigemptyset(&mask) == 0); |
764 | sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1); | |
765 | assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0); | |
766 | ||
767 | log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]); | |
768 | ||
769 | r = process_pty(master, &mask, 0, 0); | |
770 | if (r < 0) { | |
771 | log_error("Failed to process pseudo tty: %s", strerror(-r)); | |
772 | return r; | |
773 | } | |
774 | ||
775 | fputc('\n', stdout); | |
776 | ||
777 | log_info("Connection to container %s terminated.", args[1]); | |
778 | ||
779 | return 0; | |
780 | } | |
781 | ||
601185b4 | 782 | static void help(void) { |
1ee306e1 LP |
783 | printf("%s [OPTIONS...] {COMMAND} ...\n\n" |
784 | "Send control commands to or query the virtual machine and container registration manager.\n\n" | |
785 | " -h --help Show this help\n" | |
786 | " --version Show package version\n" | |
a7893c6b | 787 | " --no-pager Do not pipe output into a pager\n" |
e56056e9 | 788 | " --no-legend Do not show the headers and footers\n" |
53755121 LP |
789 | " -H --host=[USER@]HOST Operate on remote host\n" |
790 | " -M --machine=CONTAINER Operate on local container\n" | |
1ee306e1 LP |
791 | " -p --property=NAME Show only properties by this name\n" |
792 | " -a --all Show all properties, including empty ones\n" | |
1ee306e1 | 793 | " -l --full Do not ellipsize output\n" |
a7893c6b LP |
794 | " --kill-who=WHO Who to send signal to\n" |
795 | " -s --signal=SIGNAL Which signal to send\n\n" | |
1ee306e1 LP |
796 | "Commands:\n" |
797 | " list List running VMs and containers\n" | |
4f8f66cb ZJS |
798 | " status NAME... Show VM/container status\n" |
799 | " show NAME... Show properties of one or more VMs/containers\n" | |
1dba654b LP |
800 | " login NAME Get a login prompt on a container\n" |
801 | " poweroff NAME... Power off one or more containers\n" | |
923d8fd3 | 802 | " reboot NAME... Reboot one or more containers\n" |
1dba654b LP |
803 | " kill NAME... Send signal to processes of a VM/container\n" |
804 | " terminate NAME... Terminate one or more VMs/containers\n", | |
1ee306e1 | 805 | program_invocation_short_name); |
1ee306e1 LP |
806 | } |
807 | ||
808 | static int parse_argv(int argc, char *argv[]) { | |
809 | ||
810 | enum { | |
811 | ARG_VERSION = 0x100, | |
812 | ARG_NO_PAGER, | |
e56056e9 | 813 | ARG_NO_LEGEND, |
1ee306e1 | 814 | ARG_KILL_WHO, |
1ee306e1 LP |
815 | }; |
816 | ||
817 | static const struct option options[] = { | |
818 | { "help", no_argument, NULL, 'h' }, | |
819 | { "version", no_argument, NULL, ARG_VERSION }, | |
820 | { "property", required_argument, NULL, 'p' }, | |
821 | { "all", no_argument, NULL, 'a' }, | |
822 | { "full", no_argument, NULL, 'l' }, | |
823 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
e56056e9 | 824 | { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, |
1ee306e1 LP |
825 | { "kill-who", required_argument, NULL, ARG_KILL_WHO }, |
826 | { "signal", required_argument, NULL, 's' }, | |
827 | { "host", required_argument, NULL, 'H' }, | |
a7893c6b | 828 | { "machine", required_argument, NULL, 'M' }, |
eb9da376 | 829 | {} |
1ee306e1 LP |
830 | }; |
831 | ||
a7893c6b | 832 | int c, r; |
1ee306e1 LP |
833 | |
834 | assert(argc >= 0); | |
835 | assert(argv); | |
836 | ||
601185b4 | 837 | while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) |
1ee306e1 LP |
838 | |
839 | switch (c) { | |
840 | ||
841 | case 'h': | |
601185b4 ZJS |
842 | help(); |
843 | return 0; | |
1ee306e1 LP |
844 | |
845 | case ARG_VERSION: | |
846 | puts(PACKAGE_STRING); | |
847 | puts(SYSTEMD_FEATURES); | |
848 | return 0; | |
849 | ||
a7893c6b LP |
850 | case 'p': |
851 | r = strv_extend(&arg_property, optarg); | |
852 | if (r < 0) | |
853 | return log_oom(); | |
1ee306e1 LP |
854 | |
855 | /* If the user asked for a particular | |
856 | * property, show it to him, even if it is | |
857 | * empty. */ | |
858 | arg_all = true; | |
859 | break; | |
1ee306e1 LP |
860 | |
861 | case 'a': | |
862 | arg_all = true; | |
863 | break; | |
864 | ||
865 | case 'l': | |
866 | arg_full = true; | |
867 | break; | |
868 | ||
869 | case ARG_NO_PAGER: | |
870 | arg_no_pager = true; | |
871 | break; | |
872 | ||
e56056e9 TA |
873 | case ARG_NO_LEGEND: |
874 | arg_legend = false; | |
875 | break; | |
876 | ||
1ee306e1 LP |
877 | case ARG_KILL_WHO: |
878 | arg_kill_who = optarg; | |
879 | break; | |
880 | ||
881 | case 's': | |
882 | arg_signal = signal_from_string_try_harder(optarg); | |
883 | if (arg_signal < 0) { | |
884 | log_error("Failed to parse signal string %s.", optarg); | |
885 | return -EINVAL; | |
886 | } | |
887 | break; | |
888 | ||
1ee306e1 | 889 | case 'H': |
d21ed1ea | 890 | arg_transport = BUS_TRANSPORT_REMOTE; |
a7893c6b LP |
891 | arg_host = optarg; |
892 | break; | |
893 | ||
894 | case 'M': | |
d21ed1ea | 895 | arg_transport = BUS_TRANSPORT_CONTAINER; |
a7893c6b | 896 | arg_host = optarg; |
1ee306e1 LP |
897 | break; |
898 | ||
899 | case '?': | |
900 | return -EINVAL; | |
901 | ||
902 | default: | |
eb9da376 | 903 | assert_not_reached("Unhandled option"); |
1ee306e1 | 904 | } |
1ee306e1 LP |
905 | |
906 | return 1; | |
907 | } | |
908 | ||
04d39279 | 909 | static int machinectl_main(sd_bus *bus, int argc, char *argv[]) { |
1ee306e1 LP |
910 | |
911 | static const struct { | |
912 | const char* verb; | |
913 | const enum { | |
914 | MORE, | |
915 | LESS, | |
916 | EQUAL | |
917 | } argc_cmp; | |
918 | const int argc; | |
a1da8583 | 919 | int (* const dispatch)(sd_bus *bus, char **args, unsigned n); |
1ee306e1 LP |
920 | } verbs[] = { |
921 | { "list", LESS, 1, list_machines }, | |
922 | { "status", MORE, 2, show }, | |
923 | { "show", MORE, 1, show }, | |
924 | { "terminate", MORE, 2, terminate_machine }, | |
923d8fd3 | 925 | { "reboot", MORE, 2, reboot_machine }, |
1dba654b | 926 | { "poweroff", MORE, 2, poweroff_machine }, |
1ee306e1 | 927 | { "kill", MORE, 2, kill_machine }, |
04d39279 | 928 | { "login", MORE, 2, login_machine }, |
1ee306e1 LP |
929 | }; |
930 | ||
931 | int left; | |
932 | unsigned i; | |
933 | ||
934 | assert(argc >= 0); | |
935 | assert(argv); | |
1ee306e1 LP |
936 | |
937 | left = argc - optind; | |
938 | ||
939 | if (left <= 0) | |
46e65dcc | 940 | /* Special rule: no arguments means "list" */ |
1ee306e1 LP |
941 | i = 0; |
942 | else { | |
943 | if (streq(argv[optind], "help")) { | |
944 | help(); | |
945 | return 0; | |
946 | } | |
947 | ||
948 | for (i = 0; i < ELEMENTSOF(verbs); i++) | |
949 | if (streq(argv[optind], verbs[i].verb)) | |
950 | break; | |
951 | ||
952 | if (i >= ELEMENTSOF(verbs)) { | |
953 | log_error("Unknown operation %s", argv[optind]); | |
954 | return -EINVAL; | |
955 | } | |
956 | } | |
957 | ||
958 | switch (verbs[i].argc_cmp) { | |
959 | ||
960 | case EQUAL: | |
961 | if (left != verbs[i].argc) { | |
962 | log_error("Invalid number of arguments."); | |
963 | return -EINVAL; | |
964 | } | |
965 | ||
966 | break; | |
967 | ||
968 | case MORE: | |
969 | if (left < verbs[i].argc) { | |
970 | log_error("Too few arguments."); | |
971 | return -EINVAL; | |
972 | } | |
973 | ||
974 | break; | |
975 | ||
976 | case LESS: | |
977 | if (left > verbs[i].argc) { | |
978 | log_error("Too many arguments."); | |
979 | return -EINVAL; | |
980 | } | |
981 | ||
982 | break; | |
983 | ||
984 | default: | |
985 | assert_not_reached("Unknown comparison operator."); | |
986 | } | |
987 | ||
1ee306e1 LP |
988 | return verbs[i].dispatch(bus, argv + optind, left); |
989 | } | |
990 | ||
991 | int main(int argc, char*argv[]) { | |
a1da8583 | 992 | _cleanup_bus_unref_ sd_bus *bus = NULL; |
84f6181c | 993 | int r; |
1ee306e1 LP |
994 | |
995 | setlocale(LC_ALL, ""); | |
996 | log_parse_environment(); | |
997 | log_open(); | |
998 | ||
999 | r = parse_argv(argc, argv); | |
84f6181c | 1000 | if (r <= 0) |
1ee306e1 | 1001 | goto finish; |
1ee306e1 | 1002 | |
d21ed1ea | 1003 | r = bus_open_transport(arg_transport, arg_host, false, &bus); |
a1da8583 | 1004 | if (r < 0) { |
d21ed1ea | 1005 | log_error("Failed to create bus connection: %s", strerror(-r)); |
a1da8583 TG |
1006 | goto finish; |
1007 | } | |
1ee306e1 | 1008 | |
04d39279 | 1009 | r = machinectl_main(bus, argc, argv); |
1ee306e1 LP |
1010 | |
1011 | finish: | |
1ee306e1 LP |
1012 | pager_close(); |
1013 | ||
84f6181c LP |
1014 | strv_free(arg_property); |
1015 | ||
1016 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
1ee306e1 | 1017 | } |