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