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