chiark / gitweb /
clients: unify how we invoke getopt_long()
[elogind.git] / src / locale / localectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7   Copyright 2013 Kay Sievers
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <locale.h>
24 #include <stdlib.h>
25 #include <stdbool.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #include <string.h>
29 #include <ftw.h>
30 #include <sys/mman.h>
31 #include <fcntl.h>
32
33 #include "sd-bus.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "bus-message.h"
37 #include "util.h"
38 #include "spawn-polkit-agent.h"
39 #include "build.h"
40 #include "strv.h"
41 #include "pager.h"
42 #include "set.h"
43 #include "path-util.h"
44 #include "utf8.h"
45
46 static bool arg_no_pager = false;
47 static bool arg_ask_password = true;
48 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
49 static char *arg_host = NULL;
50 static bool arg_convert = true;
51
52 static void pager_open_if_enabled(void) {
53
54         if (arg_no_pager)
55                 return;
56
57         pager_open(false);
58 }
59
60 static void polkit_agent_open_if_enabled(void) {
61
62         /* Open the polkit agent as a child process if necessary */
63         if (!arg_ask_password)
64                 return;
65
66         polkit_agent_open();
67 }
68
69 typedef struct StatusInfo {
70         char **locale;
71         const char *vconsole_keymap;
72         const char *vconsole_keymap_toggle;
73         const char *x11_layout;
74         const char *x11_model;
75         const char *x11_variant;
76         const char *x11_options;
77 } StatusInfo;
78
79 static void print_status_info(StatusInfo *i) {
80         assert(i);
81
82         if (strv_isempty(i->locale))
83                 puts("   System Locale: n/a\n");
84         else {
85                 char **j;
86
87                 printf("   System Locale: %s\n", i->locale[0]);
88                 STRV_FOREACH(j, i->locale + 1)
89                         printf("                  %s\n", *j);
90         }
91
92         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
93         if (!isempty(i->vconsole_keymap_toggle))
94                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
95
96         printf("      X11 Layout: %s\n", strna(i->x11_layout));
97         if (!isempty(i->x11_model))
98                 printf("       X11 Model: %s\n", i->x11_model);
99         if (!isempty(i->x11_variant))
100                 printf("     X11 Variant: %s\n", i->x11_variant);
101         if (!isempty(i->x11_options))
102                 printf("     X11 Options: %s\n", i->x11_options);
103 }
104
105 static int show_status(sd_bus *bus, char **args, unsigned n) {
106         StatusInfo info = {};
107         static const struct bus_properties_map map[]  = {
108                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
109                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
110                 { "VConsoleKeymapToggle", "s",  NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
111                 { "X11Layout",            "s",  NULL, offsetof(StatusInfo, x11_layout) },
112                 { "X11Model",             "s",  NULL, offsetof(StatusInfo, x11_model) },
113                 { "X11Variant",           "s",  NULL, offsetof(StatusInfo, x11_variant) },
114                 { "X11Options",           "s",  NULL, offsetof(StatusInfo, x11_options) },
115                 { "Locale",               "as", NULL, offsetof(StatusInfo, locale) },
116                 {}
117         };
118         int r;
119
120         assert(bus);
121
122         r = bus_map_all_properties(bus,
123                                    "org.freedesktop.locale1",
124                                    "/org/freedesktop/locale1",
125                                    map,
126                                    &info);
127         if (r < 0)
128                 goto fail;
129
130         print_status_info(&info);
131
132 fail:
133         strv_free(info.locale);
134         return r;
135 }
136
137 static int set_locale(sd_bus *bus, char **args, unsigned n) {
138         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
139         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
140         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
141         int r;
142
143         assert(bus);
144         assert(args);
145
146         polkit_agent_open_if_enabled();
147
148         r = sd_bus_message_new_method_call(bus,
149                         "org.freedesktop.locale1",
150                         "/org/freedesktop/locale1",
151                         "org.freedesktop.locale1",
152                         "SetLocale", &m);
153         if (r < 0)
154                 return r;
155
156         r = sd_bus_message_append_strv(m, args + 1);
157         if (r < 0)
158                 return r;
159
160         r = sd_bus_message_append(m, "b", arg_ask_password);
161         if (r < 0)
162                 return r;
163
164         r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, NULL);
165         if (r < 0) {
166                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
167                 return r;
168         }
169
170         return 0;
171 }
172
173 static int add_locales_from_archive(Set *locales) {
174         /* Stolen from glibc... */
175
176         struct locarhead {
177                 uint32_t magic;
178                 /* Serial number.  */
179                 uint32_t serial;
180                 /* Name hash table.  */
181                 uint32_t namehash_offset;
182                 uint32_t namehash_used;
183                 uint32_t namehash_size;
184                 /* String table.  */
185                 uint32_t string_offset;
186                 uint32_t string_used;
187                 uint32_t string_size;
188                 /* Table with locale records.  */
189                 uint32_t locrectab_offset;
190                 uint32_t locrectab_used;
191                 uint32_t locrectab_size;
192                 /* MD5 sum hash table.  */
193                 uint32_t sumhash_offset;
194                 uint32_t sumhash_used;
195                 uint32_t sumhash_size;
196         };
197
198         struct namehashent {
199                 /* Hash value of the name.  */
200                 uint32_t hashval;
201                 /* Offset of the name in the string table.  */
202                 uint32_t name_offset;
203                 /* Offset of the locale record.  */
204                 uint32_t locrec_offset;
205         };
206
207         const struct locarhead *h;
208         const struct namehashent *e;
209         const void *p = MAP_FAILED;
210         _cleanup_close_ int fd = -1;
211         size_t sz = 0;
212         struct stat st;
213         unsigned i;
214         int r;
215
216         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
217         if (fd < 0) {
218                 if (errno != ENOENT)
219                         log_error("Failed to open locale archive: %m");
220                 r = -errno;
221                 goto finish;
222         }
223
224         if (fstat(fd, &st) < 0) {
225                 log_error("fstat() failed: %m");
226                 r = -errno;
227                 goto finish;
228         }
229
230         if (!S_ISREG(st.st_mode)) {
231                 log_error("Archive file is not regular");
232                 r = -EBADMSG;
233                 goto finish;
234         }
235
236         if (st.st_size < (off_t) sizeof(struct locarhead)) {
237                 log_error("Archive has invalid size");
238                 r = -EBADMSG;
239                 goto finish;
240         }
241
242         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
243         if (p == MAP_FAILED) {
244                 log_error("Failed to map archive: %m");
245                 r = -errno;
246                 goto finish;
247         }
248
249         h = (const struct locarhead *) p;
250         if (h->magic != 0xde020109 ||
251             h->namehash_offset + h->namehash_size > st.st_size ||
252             h->string_offset + h->string_size > st.st_size ||
253             h->locrectab_offset + h->locrectab_size > st.st_size ||
254             h->sumhash_offset + h->sumhash_size > st.st_size) {
255                 log_error("Invalid archive file.");
256                 r = -EBADMSG;
257                 goto finish;
258         }
259
260         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
261         for (i = 0; i < h->namehash_size; i++) {
262                 char *z;
263
264                 if (e[i].locrec_offset == 0)
265                         continue;
266
267                 if (!utf8_is_valid((char*) p + e[i].name_offset))
268                         continue;
269
270                 z = strdup((char*) p + e[i].name_offset);
271                 if (!z) {
272                         r = log_oom();
273                         goto finish;
274                 }
275
276                 r = set_consume(locales, z);
277                 if (r < 0) {
278                         log_error("Failed to add locale: %s", strerror(-r));
279                         goto finish;
280                 }
281         }
282
283         r = 0;
284
285  finish:
286         if (p != MAP_FAILED)
287                 munmap((void*) p, sz);
288
289         return r;
290 }
291
292 static int add_locales_from_libdir (Set *locales) {
293         _cleanup_closedir_ DIR *dir;
294         struct dirent *entry;
295         int r;
296
297         dir = opendir("/usr/lib/locale");
298         if (!dir) {
299                 log_error("Failed to open locale directory: %m");
300                 return -errno;
301         }
302
303         errno = 0;
304         while ((entry = readdir(dir))) {
305                 char *z;
306
307                 if (entry->d_type != DT_DIR)
308                         continue;
309
310                 if (ignore_file(entry->d_name))
311                         continue;
312
313                 z = strdup(entry->d_name);
314                 if (!z)
315                         return log_oom();
316
317                 r = set_consume(locales, z);
318                 if (r < 0 && r != -EEXIST) {
319                         log_error("Failed to add locale: %s", strerror(-r));
320                         return r;
321                 }
322
323                 errno = 0;
324         }
325
326         if (errno > 0) {
327                 log_error("Failed to read locale directory: %m");
328                 return -errno;
329         }
330
331         return 0;
332 }
333
334 static int list_locales(sd_bus *bus, char **args, unsigned n) {
335         _cleanup_set_free_ Set *locales;
336         _cleanup_strv_free_ char **l = NULL;
337         int r;
338
339         locales = set_new(string_hash_func, string_compare_func);
340         if (!locales)
341                 return log_oom();
342
343         r = add_locales_from_archive(locales);
344         if (r < 0 && r != -ENOENT)
345                 return r;
346
347         r = add_locales_from_libdir(locales);
348         if (r < 0)
349                 return r;
350
351         l = set_get_strv(locales);
352         if (!l)
353                 return log_oom();
354
355         strv_sort(l);
356
357         pager_open_if_enabled();
358
359         strv_print(l);
360
361         return 0;
362 }
363
364 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
365         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
366         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
367         const char *map, *toggle_map;
368         int r;
369
370         assert(bus);
371         assert(args);
372
373         if (n > 3) {
374                 log_error("Too many arguments.");
375                 return -EINVAL;
376         }
377
378         polkit_agent_open_if_enabled();
379
380         map = args[1];
381         toggle_map = n > 2 ? args[2] : "";
382
383         r = sd_bus_call_method(
384                         bus,
385                         "org.freedesktop.locale1",
386                         "/org/freedesktop/locale1",
387                         "org.freedesktop.locale1",
388                         "SetVConsoleKeyboard",
389                         &error,
390                         NULL,
391                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
392         if (r < 0)
393                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
394
395         return r;
396 }
397
398 static Set *keymaps = NULL;
399
400 static int nftw_cb(
401                 const char *fpath,
402                 const struct stat *sb,
403                 int tflag,
404                 struct FTW *ftwbuf) {
405
406         char *p, *e;
407         int r;
408
409         if (tflag != FTW_F)
410                 return 0;
411
412         if (!endswith(fpath, ".map") &&
413             !endswith(fpath, ".map.gz"))
414                 return 0;
415
416         p = strdup(path_get_file_name(fpath));
417         if (!p)
418                 return log_oom();
419
420         e = endswith(p, ".map");
421         if (e)
422                 *e = 0;
423
424         e = endswith(p, ".map.gz");
425         if (e)
426                 *e = 0;
427
428         r = set_consume(keymaps, p);
429         if (r < 0 && r != -EEXIST) {
430                 log_error("Can't add keymap: %s", strerror(-r));
431                 return r;
432         }
433
434         return 0;
435 }
436
437 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
438         _cleanup_strv_free_ char **l = NULL;
439
440         keymaps = set_new(string_hash_func, string_compare_func);
441         if (!keymaps)
442                 return log_oom();
443
444         nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
445         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
446         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
447         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
448
449         l = set_get_strv(keymaps);
450         if (!l) {
451                 set_free_free(keymaps);
452                 return log_oom();
453         }
454
455         set_free(keymaps);
456
457         if (strv_isempty(l)) {
458                 log_error("Couldn't find any console keymaps.");
459                 return -ENOENT;
460         }
461
462         strv_sort(l);
463
464         pager_open_if_enabled();
465
466         strv_print(l);
467
468         return 0;
469 }
470
471 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
472         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
473         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
474         const char *layout, *model, *variant, *options;
475         int r;
476
477         assert(bus);
478         assert(args);
479
480         if (n > 5) {
481                 log_error("Too many arguments.");
482                 return -EINVAL;
483         }
484
485         polkit_agent_open_if_enabled();
486
487         layout = args[1];
488         model = n > 2 ? args[2] : "";
489         variant = n > 3 ? args[3] : "";
490         options = n > 4 ? args[4] : "";
491
492         r = sd_bus_call_method(
493                         bus,
494                         "org.freedesktop.locale1",
495                         "/org/freedesktop/locale1",
496                         "org.freedesktop.locale1",
497                         "SetX11Keyboard",
498                         &error,
499                         NULL,
500                         "ssssbb", layout, model, variant, options,
501                                   arg_convert, arg_ask_password);
502         if (r < 0)
503                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
504
505         return r;
506 }
507
508 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
509         _cleanup_fclose_ FILE *f = NULL;
510         _cleanup_strv_free_ char **list = NULL;
511         char line[LINE_MAX];
512         enum {
513                 NONE,
514                 MODELS,
515                 LAYOUTS,
516                 VARIANTS,
517                 OPTIONS
518         } state = NONE, look_for;
519         int r;
520
521         if (n > 2) {
522                 log_error("Too many arguments.");
523                 return -EINVAL;
524         }
525
526         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
527         if (!f) {
528                 log_error("Failed to open keyboard mapping list. %m");
529                 return -errno;
530         }
531
532         if (streq(args[0], "list-x11-keymap-models"))
533                 look_for = MODELS;
534         else if (streq(args[0], "list-x11-keymap-layouts"))
535                 look_for = LAYOUTS;
536         else if (streq(args[0], "list-x11-keymap-variants"))
537                 look_for = VARIANTS;
538         else if (streq(args[0], "list-x11-keymap-options"))
539                 look_for = OPTIONS;
540         else
541                 assert_not_reached("Wrong parameter");
542
543         FOREACH_LINE(line, f, break) {
544                 char *l, *w;
545
546                 l = strstrip(line);
547
548                 if (isempty(l))
549                         continue;
550
551                 if (l[0] == '!') {
552                         if (startswith(l, "! model"))
553                                 state = MODELS;
554                         else if (startswith(l, "! layout"))
555                                 state = LAYOUTS;
556                         else if (startswith(l, "! variant"))
557                                 state = VARIANTS;
558                         else if (startswith(l, "! option"))
559                                 state = OPTIONS;
560                         else
561                                 state = NONE;
562
563                         continue;
564                 }
565
566                 if (state != look_for)
567                         continue;
568
569                 w = l + strcspn(l, WHITESPACE);
570
571                 if (n > 1) {
572                         char *e;
573
574                         if (*w == 0)
575                                 continue;
576
577                         *w = 0;
578                         w++;
579                         w += strspn(w, WHITESPACE);
580
581                         e = strchr(w, ':');
582                         if (!e)
583                                 continue;
584
585                         *e = 0;
586
587                         if (!streq(w, args[1]))
588                                 continue;
589                 } else
590                         *w = 0;
591
592                  r = strv_extend(&list, l);
593                  if (r < 0)
594                          return log_oom();
595         }
596
597         if (strv_isempty(list)) {
598                 log_error("Couldn't find any entries.");
599                 return -ENOENT;
600         }
601
602         strv_sort(list);
603         strv_uniq(list);
604
605         pager_open_if_enabled();
606
607         strv_print(list);
608         return 0;
609 }
610
611 static int help(void) {
612
613         printf("%s [OPTIONS...] COMMAND ...\n\n"
614                "Query or change system locale and keyboard settings.\n\n"
615                "  -h --help                Show this help\n"
616                "     --version             Show package version\n"
617                "     --no-convert          Don't convert keyboard mappings\n"
618                "     --no-pager            Do not pipe output into a pager\n"
619                "     --no-ask-password     Do not prompt for password\n"
620                "  -H --host=[USER@]HOST    Operate on remote host\n"
621                "  -M --machine=CONTAINER   Operate on local container\n\n"
622                "Commands:\n"
623                "  status                   Show current locale settings\n"
624                "  set-locale LOCALE...     Set system locale\n"
625                "  list-locales             Show known locales\n"
626                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
627                "  list-keymaps             Show known virtual console keyboard mappings\n"
628                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
629                "                           Set X11 keyboard mapping\n"
630                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
631                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
632                "  list-x11-keymap-variants [LAYOUT]\n"
633                "                           Show known X11 keyboard mapping variants\n"
634                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
635                program_invocation_short_name);
636
637         return 0;
638 }
639
640 static int parse_argv(int argc, char *argv[]) {
641
642         enum {
643                 ARG_VERSION = 0x100,
644                 ARG_NO_PAGER,
645                 ARG_NO_CONVERT,
646                 ARG_NO_ASK_PASSWORD
647         };
648
649         static const struct option options[] = {
650                 { "help",            no_argument,       NULL, 'h'                 },
651                 { "version",         no_argument,       NULL, ARG_VERSION         },
652                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
653                 { "host",            required_argument, NULL, 'H'                 },
654                 { "machine",         required_argument, NULL, 'M'                 },
655                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
656                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
657                 {}
658         };
659
660         int c;
661
662         assert(argc >= 0);
663         assert(argv);
664
665         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
666
667                 switch (c) {
668
669                 case 'h':
670                         return help();
671
672                 case ARG_VERSION:
673                         puts(PACKAGE_STRING);
674                         puts(SYSTEMD_FEATURES);
675                         return 0;
676
677                 case ARG_NO_CONVERT:
678                         arg_convert = false;
679                         break;
680
681                 case ARG_NO_PAGER:
682                         arg_no_pager = true;
683                         break;
684
685                 case ARG_NO_ASK_PASSWORD:
686                         arg_ask_password = false;
687                         break;
688
689                 case 'H':
690                         arg_transport = BUS_TRANSPORT_REMOTE;
691                         arg_host = optarg;
692                         break;
693
694                 case 'M':
695                         arg_transport = BUS_TRANSPORT_CONTAINER;
696                         arg_host = optarg;
697                         break;
698
699                 case '?':
700                         return -EINVAL;
701
702                 default:
703                         assert_not_reached("Unhandled option");
704                 }
705         }
706
707         return 1;
708 }
709
710 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
711
712         static const struct {
713                 const char* verb;
714                 const enum {
715                         MORE,
716                         LESS,
717                         EQUAL
718                 } argc_cmp;
719                 const int argc;
720                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
721         } verbs[] = {
722                 { "status",                   LESS,   1, show_status           },
723                 { "set-locale",               MORE,   2, set_locale            },
724                 { "list-locales",             EQUAL,  1, list_locales          },
725                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
726                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
727                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
728                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
729                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
730                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
731                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
732         };
733
734         int left;
735         unsigned i;
736
737         assert(argc >= 0);
738         assert(argv);
739
740         left = argc - optind;
741
742         if (left <= 0)
743                 /* Special rule: no arguments means "status" */
744                 i = 0;
745         else {
746                 if (streq(argv[optind], "help")) {
747                         help();
748                         return 0;
749                 }
750
751                 for (i = 0; i < ELEMENTSOF(verbs); i++)
752                         if (streq(argv[optind], verbs[i].verb))
753                                 break;
754
755                 if (i >= ELEMENTSOF(verbs)) {
756                         log_error("Unknown operation %s", argv[optind]);
757                         return -EINVAL;
758                 }
759         }
760
761         switch (verbs[i].argc_cmp) {
762
763         case EQUAL:
764                 if (left != verbs[i].argc) {
765                         log_error("Invalid number of arguments.");
766                         return -EINVAL;
767                 }
768
769                 break;
770
771         case MORE:
772                 if (left < verbs[i].argc) {
773                         log_error("Too few arguments.");
774                         return -EINVAL;
775                 }
776
777                 break;
778
779         case LESS:
780                 if (left > verbs[i].argc) {
781                         log_error("Too many arguments.");
782                         return -EINVAL;
783                 }
784
785                 break;
786
787         default:
788                 assert_not_reached("Unknown comparison operator.");
789         }
790
791         return verbs[i].dispatch(bus, argv + optind, left);
792 }
793
794 int main(int argc, char*argv[]) {
795         _cleanup_bus_unref_ sd_bus *bus = NULL;
796         int r;
797
798         setlocale(LC_ALL, "");
799         log_parse_environment();
800         log_open();
801
802         r = parse_argv(argc, argv);
803         if (r <= 0)
804                 goto finish;
805
806         r = bus_open_transport(arg_transport, arg_host, false, &bus);
807         if (r < 0) {
808                 log_error("Failed to create bus connection: %s", strerror(-r));
809                 goto finish;
810         }
811
812         r = localectl_main(bus, argc, argv);
813
814 finish:
815         pager_close();
816
817         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
818 }