]>
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> |
1ee306e1 | 30 | |
a1da8583 | 31 | #include "sd-bus.h" |
1ee306e1 LP |
32 | #include "log.h" |
33 | #include "util.h" | |
34 | #include "macro.h" | |
35 | #include "pager.h" | |
a1da8583 TG |
36 | #include "bus-util.h" |
37 | #include "bus-error.h" | |
1ee306e1 LP |
38 | #include "build.h" |
39 | #include "strv.h" | |
aa1936ea | 40 | #include "unit-name.h" |
1ee306e1 | 41 | #include "cgroup-show.h" |
9d127096 | 42 | #include "cgroup-util.h" |
04d39279 | 43 | #include "ptyfwd.h" |
1ee306e1 LP |
44 | |
45 | static char **arg_property = NULL; | |
46 | static bool arg_all = false; | |
47 | static bool arg_full = false; | |
48 | static bool arg_no_pager = false; | |
49 | static const char *arg_kill_who = NULL; | |
50 | static int arg_signal = SIGTERM; | |
1ee306e1 | 51 | static bool arg_ask_password = true; |
d21ed1ea | 52 | static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; |
1ee306e1 | 53 | static char *arg_host = NULL; |
1ee306e1 LP |
54 | |
55 | static void pager_open_if_enabled(void) { | |
56 | ||
57 | /* Cache result before we open the pager */ | |
58 | if (arg_no_pager) | |
59 | return; | |
60 | ||
61 | pager_open(false); | |
62 | } | |
63 | ||
a1da8583 TG |
64 | static int list_machines(sd_bus *bus, char **args, unsigned n) { |
65 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; | |
66 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
67 | const char *name, *class, *service, *object; | |
1ee306e1 LP |
68 | unsigned k = 0; |
69 | int r; | |
70 | ||
71 | pager_open_if_enabled(); | |
72 | ||
a1da8583 TG |
73 | r = sd_bus_call_method( |
74 | bus, | |
75 | "org.freedesktop.machine1", | |
76 | "/org/freedesktop/machine1", | |
77 | "org.freedesktop.machine1.Manager", | |
78 | "ListMachines", | |
79 | &error, | |
80 | &reply, | |
81 | ""); | |
82 | if (r < 0) { | |
83 | log_error("Could not get machines: %s", bus_error_message(&error, -r)); | |
1ee306e1 | 84 | return r; |
1ee306e1 LP |
85 | } |
86 | ||
74c7b1ed | 87 | printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE"); |
1ee306e1 | 88 | |
a1da8583 TG |
89 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)"); |
90 | if (r < 0) | |
5b30bef8 | 91 | return bus_log_parse_error(r); |
1ee306e1 | 92 | |
a1da8583 | 93 | while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { |
1ee306e1 LP |
94 | printf("%-32s %-9s %-16s\n", name, class, service); |
95 | ||
96 | k++; | |
1ee306e1 | 97 | } |
a1da8583 | 98 | if (r < 0) |
5b30bef8 | 99 | return bus_log_parse_error(r); |
a1da8583 TG |
100 | |
101 | r = sd_bus_message_exit_container(reply); | |
102 | if (r < 0) | |
5b30bef8 | 103 | return bus_log_parse_error(r); |
1ee306e1 | 104 | |
74c7b1ed | 105 | printf("\n%u machines listed.\n", k); |
1ee306e1 LP |
106 | |
107 | return 0; | |
108 | } | |
109 | ||
a1da8583 TG |
110 | static int show_scope_cgroup(sd_bus *bus, const char *unit, pid_t leader) { |
111 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; | |
112 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
aa1936ea | 113 | _cleanup_free_ char *path = NULL; |
aa1936ea | 114 | const char *cgroup; |
aa1936ea LP |
115 | int r, output_flags; |
116 | unsigned c; | |
117 | ||
118 | assert(bus); | |
119 | assert(unit); | |
120 | ||
d21ed1ea | 121 | if (arg_transport == BUS_TRANSPORT_REMOTE) |
aa1936ea LP |
122 | return 0; |
123 | ||
124 | path = unit_dbus_path_from_name(unit); | |
125 | if (!path) | |
126 | return log_oom(); | |
127 | ||
a7893c6b | 128 | r = sd_bus_get_property( |
aa1936ea LP |
129 | bus, |
130 | "org.freedesktop.systemd1", | |
131 | path, | |
a7893c6b LP |
132 | "org.freedesktop.systemd1.Scope", |
133 | "ControlGroup", | |
aa1936ea | 134 | &error, |
a1da8583 | 135 | &reply, |
a7893c6b | 136 | "s"); |
aa1936ea | 137 | if (r < 0) { |
a1da8583 | 138 | log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); |
aa1936ea LP |
139 | return r; |
140 | } | |
141 | ||
a7893c6b | 142 | r = sd_bus_message_read(reply, "s", &cgroup); |
5b30bef8 LP |
143 | if (r < 0) |
144 | return bus_log_parse_error(r); | |
aa1936ea | 145 | |
9d127096 LP |
146 | if (isempty(cgroup)) |
147 | return 0; | |
148 | ||
149 | if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0) | |
150 | return 0; | |
151 | ||
aa1936ea LP |
152 | output_flags = |
153 | arg_all * OUTPUT_SHOW_ALL | | |
154 | arg_full * OUTPUT_FULL_WIDTH; | |
155 | ||
156 | c = columns(); | |
157 | if (c > 18) | |
158 | c -= 18; | |
159 | else | |
160 | c = 0; | |
161 | ||
9d127096 | 162 | show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags); |
aa1936ea LP |
163 | return 0; |
164 | } | |
165 | ||
1ee306e1 | 166 | typedef struct MachineStatusInfo { |
9f6eb1cd | 167 | char *name; |
1ee306e1 | 168 | sd_id128_t id; |
9f6eb1cd KS |
169 | char *class; |
170 | char *service; | |
171 | char *scope; | |
172 | char *root_directory; | |
1ee306e1 LP |
173 | pid_t leader; |
174 | usec_t timestamp; | |
175 | } MachineStatusInfo; | |
176 | ||
a1da8583 | 177 | static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { |
1ee306e1 LP |
178 | char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; |
179 | char since2[FORMAT_TIMESTAMP_MAX], *s2; | |
180 | assert(i); | |
181 | ||
182 | fputs(strna(i->name), stdout); | |
183 | ||
184 | if (!sd_id128_equal(i->id, SD_ID128_NULL)) | |
185 | printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); | |
186 | else | |
187 | putchar('\n'); | |
188 | ||
189 | s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp); | |
190 | s2 = format_timestamp(since2, sizeof(since2), i->timestamp); | |
191 | ||
192 | if (s1) | |
193 | printf("\t Since: %s; %s\n", s2, s1); | |
194 | else if (s2) | |
195 | printf("\t Since: %s\n", s2); | |
196 | ||
197 | if (i->leader > 0) { | |
198 | _cleanup_free_ char *t = NULL; | |
199 | ||
200 | printf("\t Leader: %u", (unsigned) i->leader); | |
201 | ||
202 | get_process_comm(i->leader, &t); | |
203 | if (t) | |
204 | printf(" (%s)", t); | |
205 | ||
206 | putchar('\n'); | |
207 | } | |
208 | ||
209 | if (i->service) { | |
210 | printf("\t Service: %s", i->service); | |
211 | ||
212 | if (i->class) | |
213 | printf("; class %s", i->class); | |
214 | ||
215 | putchar('\n'); | |
216 | } else if (i->class) | |
217 | printf("\t Class: %s\n", i->class); | |
218 | ||
1ee306e1 LP |
219 | if (i->root_directory) |
220 | printf("\t Root: %s\n", i->root_directory); | |
221 | ||
aa1936ea LP |
222 | if (i->scope) { |
223 | printf("\t Unit: %s\n", i->scope); | |
9d127096 | 224 | show_scope_cgroup(bus, i->scope, i->leader); |
1ee306e1 LP |
225 | } |
226 | } | |
227 | ||
9f6eb1cd | 228 | static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { |
a6c61602 | 229 | |
9f6eb1cd KS |
230 | static const struct bus_properties_map map[] = { |
231 | { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, | |
232 | { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, | |
233 | { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, | |
234 | { "Scope", "s", NULL, offsetof(MachineStatusInfo, scope) }, | |
235 | { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, | |
236 | { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, | |
237 | { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp) }, | |
238 | { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, | |
239 | {} | |
240 | }; | |
a6c61602 LP |
241 | |
242 | MachineStatusInfo info = {}; | |
a1da8583 TG |
243 | int r; |
244 | ||
1ee306e1 LP |
245 | assert(path); |
246 | assert(new_line); | |
247 | ||
9f6eb1cd KS |
248 | r = bus_map_all_properties(bus, |
249 | "org.freedesktop.machine1", | |
250 | path, | |
251 | map, | |
252 | &info); | |
a1da8583 | 253 | if (r < 0) { |
9f6eb1cd | 254 | log_error("Could not get properties: %s", strerror(-r)); |
a1da8583 | 255 | return r; |
1ee306e1 LP |
256 | } |
257 | ||
1ee306e1 LP |
258 | if (*new_line) |
259 | printf("\n"); | |
1ee306e1 LP |
260 | *new_line = true; |
261 | ||
9f6eb1cd | 262 | print_machine_status_info(bus, &info); |
1ee306e1 | 263 | |
9f6eb1cd KS |
264 | free(info.name); |
265 | free(info.class); | |
266 | free(info.service); | |
267 | free(info.scope); | |
268 | free(info.root_directory); | |
1ee306e1 | 269 | |
9f6eb1cd KS |
270 | return r; |
271 | } | |
1ee306e1 | 272 | |
9f6eb1cd KS |
273 | static int show_properties(sd_bus *bus, const char *path, bool *new_line) { |
274 | int r; | |
1ee306e1 | 275 | |
9f6eb1cd KS |
276 | if (*new_line) |
277 | printf("\n"); | |
1ee306e1 | 278 | |
9f6eb1cd | 279 | *new_line = true; |
a1da8583 | 280 | |
27e72d6b | 281 | r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); |
a1da8583 | 282 | if (r < 0) |
9f6eb1cd | 283 | log_error("Could not get properties: %s", strerror(-r)); |
1ee306e1 | 284 | |
a7893c6b | 285 | return r; |
1ee306e1 LP |
286 | } |
287 | ||
a1da8583 TG |
288 | static int show(sd_bus *bus, char **args, unsigned n) { |
289 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; | |
290 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
9f6eb1cd | 291 | int r = 0; |
1ee306e1 | 292 | unsigned i; |
9f6eb1cd | 293 | bool properties, new_line = false; |
1ee306e1 LP |
294 | |
295 | assert(bus); | |
296 | assert(args); | |
297 | ||
9f6eb1cd | 298 | properties = !strstr(args[0], "status"); |
1ee306e1 LP |
299 | |
300 | pager_open_if_enabled(); | |
301 | ||
9f6eb1cd | 302 | if (properties && n <= 1) { |
a1da8583 | 303 | |
9f6eb1cd | 304 | /* If no argument is specified, inspect the manager |
1ee306e1 | 305 | * itself */ |
9f6eb1cd KS |
306 | r = show_properties(bus, "/org/freedesktop/machine1", &new_line); |
307 | if (r < 0) { | |
308 | log_error("Failed to query properties: %s", strerror(-r)); | |
309 | return r; | |
310 | } | |
1ee306e1 LP |
311 | } |
312 | ||
313 | for (i = 1; i < n; i++) { | |
314 | const char *path = NULL; | |
315 | ||
a1da8583 TG |
316 | r = sd_bus_call_method( |
317 | bus, | |
318 | "org.freedesktop.machine1", | |
319 | "/org/freedesktop/machine1", | |
320 | "org.freedesktop.machine1.Manager", | |
321 | "GetMachine", | |
322 | &error, | |
323 | &reply, | |
324 | "s", args[i]); | |
325 | if (r < 0) { | |
326 | log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); | |
327 | return r; | |
328 | } | |
329 | ||
330 | r = sd_bus_message_read(reply, "o", &path); | |
5b30bef8 LP |
331 | if (r < 0) |
332 | return bus_log_parse_error(r); | |
1ee306e1 | 333 | |
9f6eb1cd KS |
334 | if (properties) |
335 | r = show_properties(bus, path, &new_line); | |
336 | else | |
337 | r = show_info(args[0], bus, path, &new_line); | |
1ee306e1 LP |
338 | } |
339 | ||
9f6eb1cd | 340 | return r; |
1ee306e1 LP |
341 | } |
342 | ||
a1da8583 TG |
343 | static int kill_machine(sd_bus *bus, char **args, unsigned n) { |
344 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
1ee306e1 LP |
345 | unsigned i; |
346 | ||
347 | assert(args); | |
348 | ||
349 | if (!arg_kill_who) | |
350 | arg_kill_who = "all"; | |
351 | ||
352 | for (i = 1; i < n; i++) { | |
353 | int r; | |
354 | ||
a1da8583 TG |
355 | r = sd_bus_call_method( |
356 | bus, | |
357 | "org.freedesktop.machine1", | |
358 | "/org/freedesktop/machine1", | |
359 | "org.freedesktop.machine1.Manager", | |
360 | "KillMachine", | |
361 | &error, | |
362 | NULL, | |
363 | "ssi", args[i], arg_kill_who, arg_signal); | |
364 | if (r < 0) { | |
365 | log_error("Could not kill machine: %s", bus_error_message(&error, -r)); | |
1ee306e1 | 366 | return r; |
a1da8583 | 367 | } |
1ee306e1 LP |
368 | } |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
a1da8583 TG |
373 | static int terminate_machine(sd_bus *bus, char **args, unsigned n) { |
374 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
1ee306e1 LP |
375 | unsigned i; |
376 | ||
377 | assert(args); | |
378 | ||
379 | for (i = 1; i < n; i++) { | |
380 | int r; | |
381 | ||
a1da8583 TG |
382 | r = sd_bus_call_method( |
383 | bus, | |
384 | "org.freedesktop.machine1", | |
385 | "/org/freedesktop/machine1", | |
386 | "org.freedesktop.machine1.Manager", | |
387 | "TerminateMachine", | |
388 | &error, | |
389 | NULL, | |
390 | "s", args[i]); | |
391 | if (r < 0) { | |
392 | log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); | |
1ee306e1 | 393 | return r; |
a1da8583 | 394 | } |
1ee306e1 LP |
395 | } |
396 | ||
397 | return 0; | |
398 | } | |
399 | ||
04d39279 LP |
400 | static int openpt_in_namespace(pid_t pid, int flags) { |
401 | _cleanup_close_ int nsfd = -1, rootfd = -1; | |
402 | _cleanup_free_ char *ns = NULL, *root = NULL; | |
403 | _cleanup_close_pipe_ int sock[2] = { -1, -1 }; | |
04d39279 LP |
404 | union { |
405 | struct cmsghdr cmsghdr; | |
406 | uint8_t buf[CMSG_SPACE(sizeof(int))]; | |
34a6778f ZJS |
407 | } control = {}; |
408 | struct msghdr mh = { | |
409 | .msg_control = &control, | |
410 | .msg_controllen = sizeof(control), | |
411 | }; | |
04d39279 | 412 | struct cmsghdr *cmsg; |
f69157a6 | 413 | int master = -1, r; |
04d39279 LP |
414 | pid_t child; |
415 | siginfo_t si; | |
416 | ||
417 | r = asprintf(&ns, "/proc/%lu/ns/mnt", (unsigned long) pid); | |
418 | if (r < 0) | |
419 | return -ENOMEM; | |
420 | ||
421 | nsfd = open(ns, O_RDONLY|O_NOCTTY|O_CLOEXEC); | |
422 | if (nsfd < 0) | |
423 | return -errno; | |
424 | ||
425 | r = asprintf(&root, "/proc/%lu/root", (unsigned long) pid); | |
426 | if (r < 0) | |
427 | return -ENOMEM; | |
428 | ||
429 | rootfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); | |
430 | if (rootfd < 0) | |
431 | return -errno; | |
432 | ||
433 | if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sock) < 0) | |
434 | return -errno; | |
435 | ||
04d39279 LP |
436 | child = fork(); |
437 | if (child < 0) | |
438 | return -errno; | |
439 | ||
440 | if (child == 0) { | |
441 | close_nointr_nofail(sock[0]); | |
442 | sock[0] = -1; | |
443 | ||
444 | r = setns(nsfd, CLONE_NEWNS); | |
445 | if (r < 0) | |
446 | _exit(EXIT_FAILURE); | |
447 | ||
448 | if (fchdir(rootfd) < 0) | |
449 | _exit(EXIT_FAILURE); | |
450 | ||
451 | if (chroot(".") < 0) | |
452 | _exit(EXIT_FAILURE); | |
453 | ||
454 | master = posix_openpt(flags); | |
455 | if (master < 0) | |
456 | _exit(EXIT_FAILURE); | |
457 | ||
458 | cmsg = CMSG_FIRSTHDR(&mh); | |
459 | cmsg->cmsg_level = SOL_SOCKET; | |
460 | cmsg->cmsg_type = SCM_RIGHTS; | |
461 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); | |
462 | memcpy(CMSG_DATA(cmsg), &master, sizeof(int)); | |
463 | ||
464 | mh.msg_controllen = cmsg->cmsg_len; | |
465 | ||
466 | r = sendmsg(sock[1], &mh, MSG_NOSIGNAL); | |
467 | close_nointr_nofail(master); | |
468 | if (r < 0) | |
469 | _exit(EXIT_FAILURE); | |
470 | ||
471 | _exit(EXIT_SUCCESS); | |
472 | } | |
473 | ||
474 | close_nointr_nofail(sock[1]); | |
475 | sock[1] = -1; | |
476 | ||
477 | if (recvmsg(sock[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0) | |
478 | return -errno; | |
479 | ||
480 | for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) | |
481 | if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { | |
482 | int *fds; | |
483 | unsigned n_fds; | |
484 | ||
485 | fds = (int*) CMSG_DATA(cmsg); | |
486 | n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); | |
487 | ||
488 | if (n_fds != 1) { | |
489 | close_many(fds, n_fds); | |
490 | return -EIO; | |
491 | } | |
492 | ||
493 | master = fds[0]; | |
494 | } | |
495 | ||
496 | r = wait_for_terminate(child, &si); | |
497 | if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS || master < 0) { | |
498 | ||
499 | if (master >= 0) | |
500 | close_nointr_nofail(master); | |
501 | ||
502 | return r < 0 ? r : -EIO; | |
503 | } | |
504 | ||
505 | return master; | |
506 | } | |
507 | ||
508 | static int login_machine(sd_bus *bus, char **args, unsigned n) { | |
509 | _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL; | |
510 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; | |
511 | _cleanup_bus_unref_ sd_bus *container_bus = NULL; | |
512 | _cleanup_close_ int master = -1; | |
513 | _cleanup_free_ char *getty = NULL; | |
514 | const char *path, *pty, *p; | |
515 | uint32_t leader; | |
516 | sigset_t mask; | |
517 | int r; | |
518 | ||
519 | assert(bus); | |
520 | assert(args); | |
521 | ||
522 | if (arg_transport != BUS_TRANSPORT_LOCAL) { | |
523 | log_error("Login only support on local machines."); | |
524 | return -ENOTSUP; | |
525 | } | |
526 | ||
527 | r = sd_bus_call_method( | |
528 | bus, | |
529 | "org.freedesktop.machine1", | |
530 | "/org/freedesktop/machine1", | |
531 | "org.freedesktop.machine1.Manager", | |
532 | "GetMachine", | |
533 | &error, | |
534 | &reply, | |
535 | "s", args[1]); | |
536 | if (r < 0) { | |
537 | log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); | |
538 | return r; | |
539 | } | |
540 | ||
541 | r = sd_bus_message_read(reply, "o", &path); | |
5b30bef8 LP |
542 | if (r < 0) |
543 | return bus_log_parse_error(r); | |
04d39279 LP |
544 | |
545 | r = sd_bus_get_property( | |
546 | bus, | |
547 | "org.freedesktop.machine1", | |
548 | path, | |
549 | "org.freedesktop.machine1.Machine", | |
550 | "Leader", | |
551 | &error, | |
552 | &reply2, | |
553 | "u"); | |
554 | if (r < 0) { | |
555 | log_error("Failed to retrieve PID of leader: %s", strerror(-r)); | |
556 | return r; | |
557 | } | |
558 | ||
559 | r = sd_bus_message_read(reply2, "u", &leader); | |
5b30bef8 LP |
560 | if (r < 0) |
561 | return bus_log_parse_error(r); | |
04d39279 LP |
562 | |
563 | master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); | |
564 | if (master < 0) { | |
565 | log_error("Failed to acquire pseudo tty: %s", strerror(-master)); | |
566 | return master; | |
567 | } | |
568 | ||
569 | pty = ptsname(master); | |
570 | if (!pty) { | |
571 | log_error("Failed to get pty name: %m"); | |
572 | return -errno; | |
573 | } | |
574 | ||
575 | p = startswith(pty, "/dev/pts/"); | |
576 | if (!p) { | |
577 | log_error("Invalid pty name %s.", pty); | |
578 | return -EIO; | |
579 | } | |
580 | ||
581 | r = sd_bus_open_system_container(args[1], &container_bus); | |
582 | if (r < 0) { | |
583 | log_error("Failed to get container bus: %s", strerror(-r)); | |
584 | return r; | |
585 | } | |
586 | ||
587 | getty = strjoin("container-getty@", p, ".service", NULL); | |
588 | if (!getty) | |
589 | return log_oom(); | |
590 | ||
591 | if (unlockpt(master) < 0) { | |
592 | log_error("Failed to unlock tty: %m"); | |
593 | return -errno; | |
594 | } | |
595 | ||
596 | r = sd_bus_call_method(container_bus, | |
597 | "org.freedesktop.systemd1", | |
598 | "/org/freedesktop/systemd1", | |
599 | "org.freedesktop.systemd1.Manager", | |
600 | "StartUnit", | |
601 | &error, &reply3, | |
602 | "ss", getty, "replace"); | |
603 | if (r < 0) { | |
604 | log_error("Failed to start getty service: %s", bus_error_message(&error, r)); | |
605 | return r; | |
606 | } | |
607 | ||
a6c61602 LP |
608 | container_bus = sd_bus_unref(container_bus); |
609 | ||
04d39279 LP |
610 | assert_se(sigemptyset(&mask) == 0); |
611 | sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1); | |
612 | assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0); | |
613 | ||
614 | log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]); | |
615 | ||
616 | r = process_pty(master, &mask, 0, 0); | |
617 | if (r < 0) { | |
618 | log_error("Failed to process pseudo tty: %s", strerror(-r)); | |
619 | return r; | |
620 | } | |
621 | ||
622 | fputc('\n', stdout); | |
623 | ||
624 | log_info("Connection to container %s terminated.", args[1]); | |
625 | ||
626 | return 0; | |
627 | } | |
628 | ||
1ee306e1 LP |
629 | static int help(void) { |
630 | ||
631 | printf("%s [OPTIONS...] {COMMAND} ...\n\n" | |
632 | "Send control commands to or query the virtual machine and container registration manager.\n\n" | |
633 | " -h --help Show this help\n" | |
634 | " --version Show package version\n" | |
a7893c6b LP |
635 | " --no-pager Do not pipe output into a pager\n" |
636 | " --no-ask-password Don't prompt for password\n" | |
53755121 LP |
637 | " -H --host=[USER@]HOST Operate on remote host\n" |
638 | " -M --machine=CONTAINER Operate on local container\n" | |
1ee306e1 LP |
639 | " -p --property=NAME Show only properties by this name\n" |
640 | " -a --all Show all properties, including empty ones\n" | |
1ee306e1 | 641 | " -l --full Do not ellipsize output\n" |
a7893c6b LP |
642 | " --kill-who=WHO Who to send signal to\n" |
643 | " -s --signal=SIGNAL Which signal to send\n\n" | |
1ee306e1 LP |
644 | "Commands:\n" |
645 | " list List running VMs and containers\n" | |
646 | " status [NAME...] Show VM/container status\n" | |
19887cd0 | 647 | " show [NAME...] Show properties of one or more VMs/containers\n" |
1ee306e1 | 648 | " terminate [NAME...] Terminate one or more VMs/containers\n" |
04d39279 LP |
649 | " kill [NAME...] Send signal to processes of a VM/container\n" |
650 | " login [NAME] Get a login prompt on a container\n", | |
1ee306e1 LP |
651 | program_invocation_short_name); |
652 | ||
653 | return 0; | |
654 | } | |
655 | ||
656 | static int parse_argv(int argc, char *argv[]) { | |
657 | ||
658 | enum { | |
659 | ARG_VERSION = 0x100, | |
660 | ARG_NO_PAGER, | |
661 | ARG_KILL_WHO, | |
662 | ARG_NO_ASK_PASSWORD, | |
663 | }; | |
664 | ||
665 | static const struct option options[] = { | |
666 | { "help", no_argument, NULL, 'h' }, | |
667 | { "version", no_argument, NULL, ARG_VERSION }, | |
668 | { "property", required_argument, NULL, 'p' }, | |
669 | { "all", no_argument, NULL, 'a' }, | |
670 | { "full", no_argument, NULL, 'l' }, | |
671 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
672 | { "kill-who", required_argument, NULL, ARG_KILL_WHO }, | |
673 | { "signal", required_argument, NULL, 's' }, | |
674 | { "host", required_argument, NULL, 'H' }, | |
a7893c6b | 675 | { "machine", required_argument, NULL, 'M' }, |
1ee306e1 | 676 | { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, |
eb9da376 | 677 | {} |
1ee306e1 LP |
678 | }; |
679 | ||
a7893c6b | 680 | int c, r; |
1ee306e1 LP |
681 | |
682 | assert(argc >= 0); | |
683 | assert(argv); | |
684 | ||
a7893c6b | 685 | while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) { |
1ee306e1 LP |
686 | |
687 | switch (c) { | |
688 | ||
689 | case 'h': | |
eb9da376 | 690 | return help(); |
1ee306e1 LP |
691 | |
692 | case ARG_VERSION: | |
693 | puts(PACKAGE_STRING); | |
694 | puts(SYSTEMD_FEATURES); | |
695 | return 0; | |
696 | ||
a7893c6b LP |
697 | case 'p': |
698 | r = strv_extend(&arg_property, optarg); | |
699 | if (r < 0) | |
700 | return log_oom(); | |
1ee306e1 LP |
701 | |
702 | /* If the user asked for a particular | |
703 | * property, show it to him, even if it is | |
704 | * empty. */ | |
705 | arg_all = true; | |
706 | break; | |
1ee306e1 LP |
707 | |
708 | case 'a': | |
709 | arg_all = true; | |
710 | break; | |
711 | ||
712 | case 'l': | |
713 | arg_full = true; | |
714 | break; | |
715 | ||
716 | case ARG_NO_PAGER: | |
717 | arg_no_pager = true; | |
718 | break; | |
719 | ||
720 | case ARG_NO_ASK_PASSWORD: | |
721 | arg_ask_password = false; | |
722 | break; | |
723 | ||
724 | case ARG_KILL_WHO: | |
725 | arg_kill_who = optarg; | |
726 | break; | |
727 | ||
728 | case 's': | |
729 | arg_signal = signal_from_string_try_harder(optarg); | |
730 | if (arg_signal < 0) { | |
731 | log_error("Failed to parse signal string %s.", optarg); | |
732 | return -EINVAL; | |
733 | } | |
734 | break; | |
735 | ||
1ee306e1 | 736 | case 'H': |
d21ed1ea | 737 | arg_transport = BUS_TRANSPORT_REMOTE; |
a7893c6b LP |
738 | arg_host = optarg; |
739 | break; | |
740 | ||
741 | case 'M': | |
d21ed1ea | 742 | arg_transport = BUS_TRANSPORT_CONTAINER; |
a7893c6b | 743 | arg_host = optarg; |
1ee306e1 LP |
744 | break; |
745 | ||
746 | case '?': | |
747 | return -EINVAL; | |
748 | ||
749 | default: | |
eb9da376 | 750 | assert_not_reached("Unhandled option"); |
1ee306e1 LP |
751 | } |
752 | } | |
753 | ||
754 | return 1; | |
755 | } | |
756 | ||
04d39279 | 757 | static int machinectl_main(sd_bus *bus, int argc, char *argv[]) { |
1ee306e1 LP |
758 | |
759 | static const struct { | |
760 | const char* verb; | |
761 | const enum { | |
762 | MORE, | |
763 | LESS, | |
764 | EQUAL | |
765 | } argc_cmp; | |
766 | const int argc; | |
a1da8583 | 767 | int (* const dispatch)(sd_bus *bus, char **args, unsigned n); |
1ee306e1 LP |
768 | } verbs[] = { |
769 | { "list", LESS, 1, list_machines }, | |
770 | { "status", MORE, 2, show }, | |
771 | { "show", MORE, 1, show }, | |
772 | { "terminate", MORE, 2, terminate_machine }, | |
773 | { "kill", MORE, 2, kill_machine }, | |
04d39279 | 774 | { "login", MORE, 2, login_machine }, |
1ee306e1 LP |
775 | }; |
776 | ||
777 | int left; | |
778 | unsigned i; | |
779 | ||
780 | assert(argc >= 0); | |
781 | assert(argv); | |
1ee306e1 LP |
782 | |
783 | left = argc - optind; | |
784 | ||
785 | if (left <= 0) | |
46e65dcc | 786 | /* Special rule: no arguments means "list" */ |
1ee306e1 LP |
787 | i = 0; |
788 | else { | |
789 | if (streq(argv[optind], "help")) { | |
790 | help(); | |
791 | return 0; | |
792 | } | |
793 | ||
794 | for (i = 0; i < ELEMENTSOF(verbs); i++) | |
795 | if (streq(argv[optind], verbs[i].verb)) | |
796 | break; | |
797 | ||
798 | if (i >= ELEMENTSOF(verbs)) { | |
799 | log_error("Unknown operation %s", argv[optind]); | |
800 | return -EINVAL; | |
801 | } | |
802 | } | |
803 | ||
804 | switch (verbs[i].argc_cmp) { | |
805 | ||
806 | case EQUAL: | |
807 | if (left != verbs[i].argc) { | |
808 | log_error("Invalid number of arguments."); | |
809 | return -EINVAL; | |
810 | } | |
811 | ||
812 | break; | |
813 | ||
814 | case MORE: | |
815 | if (left < verbs[i].argc) { | |
816 | log_error("Too few arguments."); | |
817 | return -EINVAL; | |
818 | } | |
819 | ||
820 | break; | |
821 | ||
822 | case LESS: | |
823 | if (left > verbs[i].argc) { | |
824 | log_error("Too many arguments."); | |
825 | return -EINVAL; | |
826 | } | |
827 | ||
828 | break; | |
829 | ||
830 | default: | |
831 | assert_not_reached("Unknown comparison operator."); | |
832 | } | |
833 | ||
1ee306e1 LP |
834 | return verbs[i].dispatch(bus, argv + optind, left); |
835 | } | |
836 | ||
837 | int main(int argc, char*argv[]) { | |
a1da8583 | 838 | _cleanup_bus_unref_ sd_bus *bus = NULL; |
84f6181c | 839 | int r; |
1ee306e1 LP |
840 | |
841 | setlocale(LC_ALL, ""); | |
842 | log_parse_environment(); | |
843 | log_open(); | |
844 | ||
845 | r = parse_argv(argc, argv); | |
84f6181c | 846 | if (r <= 0) |
1ee306e1 | 847 | goto finish; |
1ee306e1 | 848 | |
d21ed1ea | 849 | r = bus_open_transport(arg_transport, arg_host, false, &bus); |
a1da8583 | 850 | if (r < 0) { |
d21ed1ea | 851 | log_error("Failed to create bus connection: %s", strerror(-r)); |
a1da8583 TG |
852 | goto finish; |
853 | } | |
1ee306e1 | 854 | |
04d39279 | 855 | r = machinectl_main(bus, argc, argv); |
1ee306e1 LP |
856 | |
857 | finish: | |
1ee306e1 LP |
858 | pager_close(); |
859 | ||
84f6181c LP |
860 | strv_free(arg_property); |
861 | ||
862 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
1ee306e1 | 863 | } |