]>
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 | ||
22 | #include <dbus/dbus.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 | ||
30 | #include "log.h" | |
31 | #include "util.h" | |
32 | #include "macro.h" | |
33 | #include "pager.h" | |
34 | #include "dbus-common.h" | |
35 | #include "build.h" | |
36 | #include "strv.h" | |
aa1936ea | 37 | #include "unit-name.h" |
1ee306e1 | 38 | #include "cgroup-show.h" |
9d127096 | 39 | #include "cgroup-util.h" |
1ee306e1 LP |
40 | #include "spawn-polkit-agent.h" |
41 | ||
42 | static char **arg_property = NULL; | |
43 | static bool arg_all = false; | |
44 | static bool arg_full = false; | |
45 | static bool arg_no_pager = false; | |
46 | static const char *arg_kill_who = NULL; | |
47 | static int arg_signal = SIGTERM; | |
48 | static enum transport { | |
49 | TRANSPORT_NORMAL, | |
50 | TRANSPORT_SSH, | |
51 | TRANSPORT_POLKIT | |
52 | } arg_transport = TRANSPORT_NORMAL; | |
53 | static bool arg_ask_password = true; | |
54 | static char *arg_host = NULL; | |
55 | static char *arg_user = NULL; | |
56 | ||
57 | static void pager_open_if_enabled(void) { | |
58 | ||
59 | /* Cache result before we open the pager */ | |
60 | if (arg_no_pager) | |
61 | return; | |
62 | ||
63 | pager_open(false); | |
64 | } | |
65 | ||
66 | static int list_machines(DBusConnection *bus, char **args, unsigned n) { | |
67 | _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; | |
68 | DBusMessageIter iter, sub, sub2; | |
69 | unsigned k = 0; | |
70 | int r; | |
71 | ||
72 | pager_open_if_enabled(); | |
73 | ||
74 | r = bus_method_call_with_reply ( | |
75 | bus, | |
76 | "org.freedesktop.machine1", | |
77 | "/org/freedesktop/machine1", | |
78 | "org.freedesktop.machine1.Manager", | |
79 | "ListMachines", | |
80 | &reply, | |
81 | NULL, | |
82 | DBUS_TYPE_INVALID); | |
83 | if (r) | |
84 | return r; | |
85 | ||
86 | if (!dbus_message_iter_init(reply, &iter) || | |
87 | dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || | |
88 | dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { | |
89 | log_error("Failed to parse reply."); | |
90 | return -EIO; | |
91 | } | |
92 | ||
93 | dbus_message_iter_recurse(&iter, &sub); | |
94 | ||
95 | if (on_tty()) | |
96 | printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE"); | |
97 | ||
98 | while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { | |
99 | const char *name, *class, *service, *object; | |
100 | ||
101 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { | |
102 | log_error("Failed to parse reply."); | |
103 | return -EIO; | |
104 | } | |
105 | ||
106 | dbus_message_iter_recurse(&sub, &sub2); | |
107 | ||
108 | if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 || | |
109 | bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &class, true) < 0 || | |
110 | bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &service, true) < 0 || | |
111 | bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) { | |
112 | log_error("Failed to parse reply."); | |
113 | return -EIO; | |
114 | } | |
115 | ||
116 | printf("%-32s %-9s %-16s\n", name, class, service); | |
117 | ||
118 | k++; | |
119 | ||
120 | dbus_message_iter_next(&sub); | |
121 | } | |
122 | ||
123 | if (on_tty()) | |
124 | printf("\n%u machines listed.\n", k); | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
9d127096 | 129 | static int show_scope_cgroup(DBusConnection *bus, const char *unit, pid_t leader) { |
aa1936ea LP |
130 | const char *interface = "org.freedesktop.systemd1.Scope"; |
131 | const char *property = "ControlGroup"; | |
132 | _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; | |
133 | _cleanup_free_ char *path = NULL; | |
134 | DBusMessageIter iter, sub; | |
135 | const char *cgroup; | |
136 | DBusError error; | |
137 | int r, output_flags; | |
138 | unsigned c; | |
139 | ||
140 | assert(bus); | |
141 | assert(unit); | |
142 | ||
143 | if (arg_transport == TRANSPORT_SSH) | |
144 | return 0; | |
145 | ||
146 | path = unit_dbus_path_from_name(unit); | |
147 | if (!path) | |
148 | return log_oom(); | |
149 | ||
150 | r = bus_method_call_with_reply( | |
151 | bus, | |
152 | "org.freedesktop.systemd1", | |
153 | path, | |
154 | "org.freedesktop.DBus.Properties", | |
155 | "Get", | |
156 | &reply, | |
157 | &error, | |
158 | DBUS_TYPE_STRING, &interface, | |
159 | DBUS_TYPE_STRING, &property, | |
160 | DBUS_TYPE_INVALID); | |
161 | if (r < 0) { | |
162 | log_error("Failed to query ControlGroup: %s", bus_error(&error, r)); | |
163 | dbus_error_free(&error); | |
164 | return r; | |
165 | } | |
166 | ||
167 | if (!dbus_message_iter_init(reply, &iter) || | |
168 | dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { | |
169 | log_error("Failed to parse reply."); | |
170 | return -EINVAL; | |
171 | } | |
172 | ||
173 | dbus_message_iter_recurse(&iter, &sub); | |
174 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { | |
175 | log_error("Failed to parse reply."); | |
176 | return -EINVAL; | |
177 | } | |
178 | ||
179 | dbus_message_iter_get_basic(&sub, &cgroup); | |
180 | ||
9d127096 LP |
181 | if (isempty(cgroup)) |
182 | return 0; | |
183 | ||
184 | if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0) | |
185 | return 0; | |
186 | ||
aa1936ea LP |
187 | output_flags = |
188 | arg_all * OUTPUT_SHOW_ALL | | |
189 | arg_full * OUTPUT_FULL_WIDTH; | |
190 | ||
191 | c = columns(); | |
192 | if (c > 18) | |
193 | c -= 18; | |
194 | else | |
195 | c = 0; | |
196 | ||
9d127096 | 197 | show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags); |
aa1936ea LP |
198 | return 0; |
199 | } | |
200 | ||
1ee306e1 LP |
201 | typedef struct MachineStatusInfo { |
202 | const char *name; | |
203 | sd_id128_t id; | |
1ee306e1 LP |
204 | const char *class; |
205 | const char *service; | |
aa1936ea | 206 | const char *scope; |
1ee306e1 LP |
207 | const char *root_directory; |
208 | pid_t leader; | |
209 | usec_t timestamp; | |
210 | } MachineStatusInfo; | |
211 | ||
aa1936ea | 212 | static void print_machine_status_info(DBusConnection *bus, MachineStatusInfo *i) { |
1ee306e1 LP |
213 | char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; |
214 | char since2[FORMAT_TIMESTAMP_MAX], *s2; | |
215 | assert(i); | |
216 | ||
217 | fputs(strna(i->name), stdout); | |
218 | ||
219 | if (!sd_id128_equal(i->id, SD_ID128_NULL)) | |
220 | printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); | |
221 | else | |
222 | putchar('\n'); | |
223 | ||
224 | s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp); | |
225 | s2 = format_timestamp(since2, sizeof(since2), i->timestamp); | |
226 | ||
227 | if (s1) | |
228 | printf("\t Since: %s; %s\n", s2, s1); | |
229 | else if (s2) | |
230 | printf("\t Since: %s\n", s2); | |
231 | ||
232 | if (i->leader > 0) { | |
233 | _cleanup_free_ char *t = NULL; | |
234 | ||
235 | printf("\t Leader: %u", (unsigned) i->leader); | |
236 | ||
237 | get_process_comm(i->leader, &t); | |
238 | if (t) | |
239 | printf(" (%s)", t); | |
240 | ||
241 | putchar('\n'); | |
242 | } | |
243 | ||
244 | if (i->service) { | |
245 | printf("\t Service: %s", i->service); | |
246 | ||
247 | if (i->class) | |
248 | printf("; class %s", i->class); | |
249 | ||
250 | putchar('\n'); | |
251 | } else if (i->class) | |
252 | printf("\t Class: %s\n", i->class); | |
253 | ||
1ee306e1 LP |
254 | if (i->root_directory) |
255 | printf("\t Root: %s\n", i->root_directory); | |
256 | ||
aa1936ea LP |
257 | if (i->scope) { |
258 | printf("\t Unit: %s\n", i->scope); | |
9d127096 | 259 | show_scope_cgroup(bus, i->scope, i->leader); |
1ee306e1 LP |
260 | } |
261 | } | |
262 | ||
263 | static int status_property_machine(const char *name, DBusMessageIter *iter, MachineStatusInfo *i) { | |
264 | assert(name); | |
265 | assert(iter); | |
266 | assert(i); | |
267 | ||
268 | switch (dbus_message_iter_get_arg_type(iter)) { | |
269 | ||
270 | case DBUS_TYPE_STRING: { | |
271 | const char *s; | |
272 | ||
273 | dbus_message_iter_get_basic(iter, &s); | |
274 | ||
275 | if (!isempty(s)) { | |
276 | if (streq(name, "Name")) | |
277 | i->name = s; | |
1ee306e1 LP |
278 | else if (streq(name, "Class")) |
279 | i->class = s; | |
280 | else if (streq(name, "Service")) | |
281 | i->service = s; | |
aa1936ea LP |
282 | else if (streq(name, "Scope")) |
283 | i->scope = s; | |
1ee306e1 LP |
284 | else if (streq(name, "RootDirectory")) |
285 | i->root_directory = s; | |
286 | } | |
287 | break; | |
288 | } | |
289 | ||
290 | case DBUS_TYPE_UINT32: { | |
291 | uint32_t u; | |
292 | ||
293 | dbus_message_iter_get_basic(iter, &u); | |
294 | ||
295 | if (streq(name, "Leader")) | |
296 | i->leader = (pid_t) u; | |
297 | ||
298 | break; | |
299 | } | |
300 | ||
301 | case DBUS_TYPE_UINT64: { | |
302 | uint64_t u; | |
303 | ||
304 | dbus_message_iter_get_basic(iter, &u); | |
305 | ||
306 | if (streq(name, "Timestamp")) | |
307 | i->timestamp = (usec_t) u; | |
308 | ||
309 | break; | |
310 | } | |
311 | ||
312 | case DBUS_TYPE_ARRAY: { | |
313 | DBusMessageIter sub; | |
314 | ||
315 | dbus_message_iter_recurse(iter, &sub); | |
316 | ||
317 | if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE && streq(name, "Id")) { | |
318 | void *v; | |
319 | int n; | |
320 | ||
321 | dbus_message_iter_get_fixed_array(&sub, &v, &n); | |
322 | if (n == 0) | |
323 | i->id = SD_ID128_NULL; | |
324 | else if (n == 16) | |
325 | memcpy(&i->id, v, n); | |
326 | } | |
327 | ||
328 | break; | |
329 | } | |
330 | } | |
331 | ||
332 | return 0; | |
333 | } | |
334 | ||
335 | static int print_property(const char *name, DBusMessageIter *iter) { | |
336 | assert(name); | |
337 | assert(iter); | |
338 | ||
339 | if (arg_property && !strv_find(arg_property, name)) | |
340 | return 0; | |
341 | ||
342 | if (generic_print_property(name, iter, arg_all) > 0) | |
343 | return 0; | |
344 | ||
345 | if (arg_all) | |
346 | printf("%s=[unprintable]\n", name); | |
347 | ||
348 | return 0; | |
349 | } | |
350 | ||
351 | static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) { | |
352 | _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; | |
353 | const char *interface = ""; | |
354 | int r; | |
355 | DBusMessageIter iter, sub, sub2, sub3; | |
356 | MachineStatusInfo machine_info = {}; | |
357 | ||
358 | assert(path); | |
359 | assert(new_line); | |
360 | ||
361 | r = bus_method_call_with_reply( | |
362 | bus, | |
363 | "org.freedesktop.machine1", | |
364 | path, | |
365 | "org.freedesktop.DBus.Properties", | |
366 | "GetAll", | |
367 | &reply, | |
368 | NULL, | |
369 | DBUS_TYPE_STRING, &interface, | |
370 | DBUS_TYPE_INVALID); | |
371 | if (r < 0) | |
372 | goto finish; | |
373 | ||
374 | if (!dbus_message_iter_init(reply, &iter) || | |
375 | dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || | |
376 | dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) { | |
377 | log_error("Failed to parse reply."); | |
378 | r = -EIO; | |
379 | goto finish; | |
380 | } | |
381 | ||
382 | dbus_message_iter_recurse(&iter, &sub); | |
383 | ||
384 | if (*new_line) | |
385 | printf("\n"); | |
386 | ||
387 | *new_line = true; | |
388 | ||
389 | while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { | |
390 | const char *name; | |
391 | ||
392 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) { | |
393 | log_error("Failed to parse reply."); | |
394 | r = -EIO; | |
395 | goto finish; | |
396 | } | |
397 | ||
398 | dbus_message_iter_recurse(&sub, &sub2); | |
399 | ||
400 | if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) { | |
401 | log_error("Failed to parse reply."); | |
402 | r = -EIO; | |
403 | goto finish; | |
404 | } | |
405 | ||
406 | if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) { | |
407 | log_error("Failed to parse reply."); | |
408 | r = -EIO; | |
409 | goto finish; | |
410 | } | |
411 | ||
412 | dbus_message_iter_recurse(&sub2, &sub3); | |
413 | ||
414 | if (show_properties) | |
415 | r = print_property(name, &sub3); | |
416 | else | |
417 | r = status_property_machine(name, &sub3, &machine_info); | |
418 | ||
419 | if (r < 0) { | |
420 | log_error("Failed to parse reply."); | |
421 | goto finish; | |
422 | } | |
423 | ||
424 | dbus_message_iter_next(&sub); | |
425 | } | |
426 | ||
427 | if (!show_properties) | |
aa1936ea | 428 | print_machine_status_info(bus, &machine_info); |
1ee306e1 LP |
429 | |
430 | r = 0; | |
431 | ||
432 | finish: | |
433 | ||
434 | return r; | |
435 | } | |
436 | ||
437 | static int show(DBusConnection *bus, char **args, unsigned n) { | |
438 | _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; | |
439 | int r, ret = 0; | |
440 | DBusError error; | |
441 | unsigned i; | |
442 | bool show_properties, new_line = false; | |
443 | ||
444 | assert(bus); | |
445 | assert(args); | |
446 | ||
447 | dbus_error_init(&error); | |
448 | ||
449 | show_properties = !strstr(args[0], "status"); | |
450 | ||
451 | pager_open_if_enabled(); | |
452 | ||
453 | if (show_properties && n <= 1) { | |
454 | /* If not argument is specified inspect the manager | |
455 | * itself */ | |
456 | ||
457 | ret = show_one(args[0], bus, "/org/freedesktop/machine1", show_properties, &new_line); | |
458 | goto finish; | |
459 | } | |
460 | ||
461 | for (i = 1; i < n; i++) { | |
462 | const char *path = NULL; | |
463 | ||
464 | ret = bus_method_call_with_reply( | |
465 | bus, | |
466 | "org.freedesktop.machine1", | |
467 | "/org/freedesktop/machine1", | |
468 | "org.freedesktop.machine1.Manager", | |
469 | "GetMachine", | |
470 | &reply, | |
471 | NULL, | |
472 | DBUS_TYPE_STRING, &args[i], | |
473 | DBUS_TYPE_INVALID); | |
474 | if (ret < 0) | |
475 | goto finish; | |
476 | ||
477 | if (!dbus_message_get_args(reply, &error, | |
478 | DBUS_TYPE_OBJECT_PATH, &path, | |
479 | DBUS_TYPE_INVALID)) { | |
480 | log_error("Failed to parse reply: %s", bus_error_message(&error)); | |
481 | ret = -EIO; | |
482 | goto finish; | |
483 | } | |
484 | ||
485 | r = show_one(args[0], bus, path, show_properties, &new_line); | |
486 | if (r != 0) | |
487 | ret = r; | |
488 | } | |
489 | ||
490 | finish: | |
491 | dbus_error_free(&error); | |
492 | ||
493 | return ret; | |
494 | } | |
495 | ||
496 | static int kill_machine(DBusConnection *bus, char **args, unsigned n) { | |
497 | unsigned i; | |
498 | ||
499 | assert(args); | |
500 | ||
501 | if (!arg_kill_who) | |
502 | arg_kill_who = "all"; | |
503 | ||
504 | for (i = 1; i < n; i++) { | |
505 | int r; | |
506 | ||
507 | r = bus_method_call_with_reply ( | |
508 | bus, | |
509 | "org.freedesktop.machine1", | |
510 | "/org/freedesktop/machine1", | |
511 | "org.freedesktop.machine1.Manager", | |
512 | "KillMachine", | |
513 | NULL, | |
514 | NULL, | |
515 | DBUS_TYPE_STRING, &args[i], | |
516 | DBUS_TYPE_STRING, &arg_kill_who, | |
517 | DBUS_TYPE_INT32, &arg_signal, | |
518 | DBUS_TYPE_INVALID); | |
519 | if (r) | |
520 | return r; | |
521 | } | |
522 | ||
523 | return 0; | |
524 | } | |
525 | ||
526 | static int terminate_machine(DBusConnection *bus, char **args, unsigned n) { | |
527 | unsigned i; | |
528 | ||
529 | assert(args); | |
530 | ||
531 | for (i = 1; i < n; i++) { | |
532 | int r; | |
533 | ||
534 | r = bus_method_call_with_reply ( | |
535 | bus, | |
536 | "org.freedesktop.machine1", | |
537 | "/org/freedesktop/machine1", | |
538 | "org.freedesktop.machine1.Manager", | |
539 | "TerminateMachine", | |
540 | NULL, | |
541 | NULL, | |
542 | DBUS_TYPE_STRING, &args[i], | |
543 | DBUS_TYPE_INVALID); | |
544 | if (r) | |
545 | return r; | |
546 | } | |
547 | ||
548 | return 0; | |
549 | } | |
550 | ||
551 | static int help(void) { | |
552 | ||
553 | printf("%s [OPTIONS...] {COMMAND} ...\n\n" | |
554 | "Send control commands to or query the virtual machine and container registration manager.\n\n" | |
555 | " -h --help Show this help\n" | |
556 | " --version Show package version\n" | |
557 | " -p --property=NAME Show only properties by this name\n" | |
558 | " -a --all Show all properties, including empty ones\n" | |
559 | " --kill-who=WHO Who to send signal to\n" | |
560 | " -l --full Do not ellipsize output\n" | |
561 | " -s --signal=SIGNAL Which signal to send\n" | |
562 | " --no-ask-password Don't prompt for password\n" | |
563 | " -H --host=[USER@]HOST Show information for remote host\n" | |
564 | " -P --privileged Acquire privileges before execution\n" | |
565 | " --no-pager Do not pipe output into a pager\n\n" | |
566 | "Commands:\n" | |
567 | " list List running VMs and containers\n" | |
568 | " status [NAME...] Show VM/container status\n" | |
19887cd0 | 569 | " show [NAME...] Show properties of one or more VMs/containers\n" |
1ee306e1 LP |
570 | " terminate [NAME...] Terminate one or more VMs/containers\n" |
571 | " kill [NAME...] Send signal to processes of a VM/container\n", | |
572 | program_invocation_short_name); | |
573 | ||
574 | return 0; | |
575 | } | |
576 | ||
577 | static int parse_argv(int argc, char *argv[]) { | |
578 | ||
579 | enum { | |
580 | ARG_VERSION = 0x100, | |
581 | ARG_NO_PAGER, | |
582 | ARG_KILL_WHO, | |
583 | ARG_NO_ASK_PASSWORD, | |
584 | }; | |
585 | ||
586 | static const struct option options[] = { | |
587 | { "help", no_argument, NULL, 'h' }, | |
588 | { "version", no_argument, NULL, ARG_VERSION }, | |
589 | { "property", required_argument, NULL, 'p' }, | |
590 | { "all", no_argument, NULL, 'a' }, | |
591 | { "full", no_argument, NULL, 'l' }, | |
592 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
593 | { "kill-who", required_argument, NULL, ARG_KILL_WHO }, | |
594 | { "signal", required_argument, NULL, 's' }, | |
595 | { "host", required_argument, NULL, 'H' }, | |
596 | { "privileged", no_argument, NULL, 'P' }, | |
597 | { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, | |
598 | { NULL, 0, NULL, 0 } | |
599 | }; | |
600 | ||
601 | int c; | |
602 | ||
603 | assert(argc >= 0); | |
604 | assert(argv); | |
605 | ||
606 | while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) { | |
607 | ||
608 | switch (c) { | |
609 | ||
610 | case 'h': | |
611 | help(); | |
612 | return 0; | |
613 | ||
614 | case ARG_VERSION: | |
615 | puts(PACKAGE_STRING); | |
616 | puts(SYSTEMD_FEATURES); | |
617 | return 0; | |
618 | ||
619 | case 'p': { | |
620 | char **l; | |
621 | ||
622 | l = strv_append(arg_property, optarg); | |
623 | if (!l) | |
624 | return -ENOMEM; | |
625 | ||
626 | strv_free(arg_property); | |
627 | arg_property = l; | |
628 | ||
629 | /* If the user asked for a particular | |
630 | * property, show it to him, even if it is | |
631 | * empty. */ | |
632 | arg_all = true; | |
633 | break; | |
634 | } | |
635 | ||
636 | case 'a': | |
637 | arg_all = true; | |
638 | break; | |
639 | ||
640 | case 'l': | |
641 | arg_full = true; | |
642 | break; | |
643 | ||
644 | case ARG_NO_PAGER: | |
645 | arg_no_pager = true; | |
646 | break; | |
647 | ||
648 | case ARG_NO_ASK_PASSWORD: | |
649 | arg_ask_password = false; | |
650 | break; | |
651 | ||
652 | case ARG_KILL_WHO: | |
653 | arg_kill_who = optarg; | |
654 | break; | |
655 | ||
656 | case 's': | |
657 | arg_signal = signal_from_string_try_harder(optarg); | |
658 | if (arg_signal < 0) { | |
659 | log_error("Failed to parse signal string %s.", optarg); | |
660 | return -EINVAL; | |
661 | } | |
662 | break; | |
663 | ||
664 | case 'P': | |
665 | arg_transport = TRANSPORT_POLKIT; | |
666 | break; | |
667 | ||
668 | case 'H': | |
669 | arg_transport = TRANSPORT_SSH; | |
670 | parse_user_at_host(optarg, &arg_user, &arg_host); | |
671 | break; | |
672 | ||
673 | case '?': | |
674 | return -EINVAL; | |
675 | ||
676 | default: | |
677 | log_error("Unknown option code %c", c); | |
678 | return -EINVAL; | |
679 | } | |
680 | } | |
681 | ||
682 | return 1; | |
683 | } | |
684 | ||
685 | static int machinectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) { | |
686 | ||
687 | static const struct { | |
688 | const char* verb; | |
689 | const enum { | |
690 | MORE, | |
691 | LESS, | |
692 | EQUAL | |
693 | } argc_cmp; | |
694 | const int argc; | |
695 | int (* const dispatch)(DBusConnection *bus, char **args, unsigned n); | |
696 | } verbs[] = { | |
697 | { "list", LESS, 1, list_machines }, | |
698 | { "status", MORE, 2, show }, | |
699 | { "show", MORE, 1, show }, | |
700 | { "terminate", MORE, 2, terminate_machine }, | |
701 | { "kill", MORE, 2, kill_machine }, | |
702 | }; | |
703 | ||
704 | int left; | |
705 | unsigned i; | |
706 | ||
707 | assert(argc >= 0); | |
708 | assert(argv); | |
709 | assert(error); | |
710 | ||
711 | left = argc - optind; | |
712 | ||
713 | if (left <= 0) | |
714 | /* Special rule: no arguments means "list-sessions" */ | |
715 | i = 0; | |
716 | else { | |
717 | if (streq(argv[optind], "help")) { | |
718 | help(); | |
719 | return 0; | |
720 | } | |
721 | ||
722 | for (i = 0; i < ELEMENTSOF(verbs); i++) | |
723 | if (streq(argv[optind], verbs[i].verb)) | |
724 | break; | |
725 | ||
726 | if (i >= ELEMENTSOF(verbs)) { | |
727 | log_error("Unknown operation %s", argv[optind]); | |
728 | return -EINVAL; | |
729 | } | |
730 | } | |
731 | ||
732 | switch (verbs[i].argc_cmp) { | |
733 | ||
734 | case EQUAL: | |
735 | if (left != verbs[i].argc) { | |
736 | log_error("Invalid number of arguments."); | |
737 | return -EINVAL; | |
738 | } | |
739 | ||
740 | break; | |
741 | ||
742 | case MORE: | |
743 | if (left < verbs[i].argc) { | |
744 | log_error("Too few arguments."); | |
745 | return -EINVAL; | |
746 | } | |
747 | ||
748 | break; | |
749 | ||
750 | case LESS: | |
751 | if (left > verbs[i].argc) { | |
752 | log_error("Too many arguments."); | |
753 | return -EINVAL; | |
754 | } | |
755 | ||
756 | break; | |
757 | ||
758 | default: | |
759 | assert_not_reached("Unknown comparison operator."); | |
760 | } | |
761 | ||
762 | if (!bus) { | |
763 | log_error("Failed to get D-Bus connection: %s", error->message); | |
764 | return -EIO; | |
765 | } | |
766 | ||
767 | return verbs[i].dispatch(bus, argv + optind, left); | |
768 | } | |
769 | ||
770 | int main(int argc, char*argv[]) { | |
771 | int r, retval = EXIT_FAILURE; | |
772 | DBusConnection *bus = NULL; | |
773 | DBusError error; | |
774 | ||
775 | dbus_error_init(&error); | |
776 | ||
777 | setlocale(LC_ALL, ""); | |
778 | log_parse_environment(); | |
779 | log_open(); | |
780 | ||
781 | r = parse_argv(argc, argv); | |
782 | if (r < 0) | |
783 | goto finish; | |
784 | else if (r == 0) { | |
785 | retval = EXIT_SUCCESS; | |
786 | goto finish; | |
787 | } | |
788 | ||
789 | if (arg_transport == TRANSPORT_NORMAL) | |
790 | bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); | |
791 | else if (arg_transport == TRANSPORT_POLKIT) | |
792 | bus_connect_system_polkit(&bus, &error); | |
793 | else if (arg_transport == TRANSPORT_SSH) | |
794 | bus_connect_system_ssh(NULL, arg_host, &bus, &error); | |
795 | else | |
796 | assert_not_reached("Uh, invalid transport..."); | |
797 | ||
798 | r = machinectl_main(bus, argc, argv, &error); | |
799 | retval = r < 0 ? EXIT_FAILURE : r; | |
800 | ||
801 | finish: | |
802 | if (bus) { | |
803 | dbus_connection_flush(bus); | |
804 | dbus_connection_close(bus); | |
805 | dbus_connection_unref(bus); | |
806 | } | |
807 | ||
808 | dbus_error_free(&error); | |
809 | dbus_shutdown(); | |
810 | ||
811 | strv_free(arg_property); | |
812 | ||
813 | pager_close(); | |
814 | ||
815 | return retval; | |
816 | } |