]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/machine/machinectl.c
Unify parse_argv style
[thirdparty/systemd.git] / src / machine / machinectl.c
CommitLineData
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
48static char **arg_property = NULL;
49static bool arg_all = false;
50static bool arg_full = false;
51static bool arg_no_pager = false;
e56056e9 52static bool arg_legend = true;
1ee306e1
LP
53static const char *arg_kill_who = NULL;
54static int arg_signal = SIGTERM;
d21ed1ea 55static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
1ee306e1 56static char *arg_host = NULL;
1ee306e1
LP
57
58static 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
67static 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 115static 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 171static 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
232static 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 274typedef 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 287static 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
369static 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 390static 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
437static 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
452static 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
505static 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
535static 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
542static 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 549static 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 576static 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
661static 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 782static 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
808static 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 909static 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
991int 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
1011finish:
1ee306e1
LP
1012 pager_close();
1013
84f6181c
LP
1014 strv_free(arg_property);
1015
1016 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1ee306e1 1017}