chiark / gitweb /
f7fea48ffeae642b5eba95688c2f2b19a5ea0e7b
[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         if (arg_transport != BUS_TRANSPORT_LOCAL)
67                 return;
68
69         polkit_agent_open();
70 }
71
72 typedef struct StatusInfo {
73         char **locale;
74         const char *vconsole_keymap;
75         const char *vconsole_keymap_toggle;
76         const char *x11_layout;
77         const char *x11_model;
78         const char *x11_variant;
79         const char *x11_options;
80 } StatusInfo;
81
82 static void print_status_info(StatusInfo *i) {
83         assert(i);
84
85         if (strv_isempty(i->locale))
86                 puts("   System Locale: n/a\n");
87         else {
88                 char **j;
89
90                 printf("   System Locale: %s\n", i->locale[0]);
91                 STRV_FOREACH(j, i->locale + 1)
92                         printf("                  %s\n", *j);
93         }
94
95         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
96         if (!isempty(i->vconsole_keymap_toggle))
97                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
98
99         printf("      X11 Layout: %s\n", strna(i->x11_layout));
100         if (!isempty(i->x11_model))
101                 printf("       X11 Model: %s\n", i->x11_model);
102         if (!isempty(i->x11_variant))
103                 printf("     X11 Variant: %s\n", i->x11_variant);
104         if (!isempty(i->x11_options))
105                 printf("     X11 Options: %s\n", i->x11_options);
106 }
107
108 static int show_status(sd_bus *bus, char **args, unsigned n) {
109         StatusInfo info = {};
110         static const struct bus_properties_map map[]  = {
111                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
112                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
113                 { "VConsoleKeymapToggle", "s",  NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
114                 { "X11Layout",            "s",  NULL, offsetof(StatusInfo, x11_layout) },
115                 { "X11Model",             "s",  NULL, offsetof(StatusInfo, x11_model) },
116                 { "X11Variant",           "s",  NULL, offsetof(StatusInfo, x11_variant) },
117                 { "X11Options",           "s",  NULL, offsetof(StatusInfo, x11_options) },
118                 { "Locale",               "as", NULL, offsetof(StatusInfo, locale) },
119                 {}
120         };
121         int r;
122
123         assert(bus);
124
125         r = bus_map_all_properties(bus,
126                                    "org.freedesktop.locale1",
127                                    "/org/freedesktop/locale1",
128                                    map,
129                                    &info);
130         if (r < 0)
131                 goto fail;
132
133         print_status_info(&info);
134
135 fail:
136         strv_free(info.locale);
137         return r;
138 }
139
140 static int set_locale(sd_bus *bus, char **args, unsigned n) {
141         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
142         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
143         int r;
144
145         assert(bus);
146         assert(args);
147
148         polkit_agent_open_if_enabled();
149
150         r = sd_bus_message_new_method_call(bus,
151                         "org.freedesktop.locale1",
152                         "/org/freedesktop/locale1",
153                         "org.freedesktop.locale1",
154                         "SetLocale", &m);
155         if (r < 0)
156                 return bus_log_create_error(r);
157
158         r = sd_bus_message_append_strv(m, args + 1);
159         if (r < 0)
160                 return bus_log_create_error(r);
161
162         r = sd_bus_message_append(m, "b", arg_ask_password);
163         if (r < 0)
164                 return bus_log_create_error(r);
165
166         r = sd_bus_call(bus, m, 0, &error, NULL);
167         if (r < 0) {
168                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
169                 return r;
170         }
171
172         return 0;
173 }
174
175 static int add_locales_from_archive(Set *locales) {
176         /* Stolen from glibc... */
177
178         struct locarhead {
179                 uint32_t magic;
180                 /* Serial number.  */
181                 uint32_t serial;
182                 /* Name hash table.  */
183                 uint32_t namehash_offset;
184                 uint32_t namehash_used;
185                 uint32_t namehash_size;
186                 /* String table.  */
187                 uint32_t string_offset;
188                 uint32_t string_used;
189                 uint32_t string_size;
190                 /* Table with locale records.  */
191                 uint32_t locrectab_offset;
192                 uint32_t locrectab_used;
193                 uint32_t locrectab_size;
194                 /* MD5 sum hash table.  */
195                 uint32_t sumhash_offset;
196                 uint32_t sumhash_used;
197                 uint32_t sumhash_size;
198         };
199
200         struct namehashent {
201                 /* Hash value of the name.  */
202                 uint32_t hashval;
203                 /* Offset of the name in the string table.  */
204                 uint32_t name_offset;
205                 /* Offset of the locale record.  */
206                 uint32_t locrec_offset;
207         };
208
209         const struct locarhead *h;
210         const struct namehashent *e;
211         const void *p = MAP_FAILED;
212         _cleanup_close_ int fd = -1;
213         size_t sz = 0;
214         struct stat st;
215         unsigned i;
216         int r;
217
218         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
219         if (fd < 0) {
220                 if (errno != ENOENT)
221                         log_error("Failed to open locale archive: %m");
222                 r = -errno;
223                 goto finish;
224         }
225
226         if (fstat(fd, &st) < 0) {
227                 log_error("fstat() failed: %m");
228                 r = -errno;
229                 goto finish;
230         }
231
232         if (!S_ISREG(st.st_mode)) {
233                 log_error("Archive file is not regular");
234                 r = -EBADMSG;
235                 goto finish;
236         }
237
238         if (st.st_size < (off_t) sizeof(struct locarhead)) {
239                 log_error("Archive has invalid size");
240                 r = -EBADMSG;
241                 goto finish;
242         }
243
244         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
245         if (p == MAP_FAILED) {
246                 log_error("Failed to map archive: %m");
247                 r = -errno;
248                 goto finish;
249         }
250
251         h = (const struct locarhead *) p;
252         if (h->magic != 0xde020109 ||
253             h->namehash_offset + h->namehash_size > st.st_size ||
254             h->string_offset + h->string_size > st.st_size ||
255             h->locrectab_offset + h->locrectab_size > st.st_size ||
256             h->sumhash_offset + h->sumhash_size > st.st_size) {
257                 log_error("Invalid archive file.");
258                 r = -EBADMSG;
259                 goto finish;
260         }
261
262         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
263         for (i = 0; i < h->namehash_size; i++) {
264                 char *z;
265
266                 if (e[i].locrec_offset == 0)
267                         continue;
268
269                 if (!utf8_is_valid((char*) p + e[i].name_offset))
270                         continue;
271
272                 z = strdup((char*) p + e[i].name_offset);
273                 if (!z) {
274                         r = log_oom();
275                         goto finish;
276                 }
277
278                 r = set_consume(locales, z);
279                 if (r < 0) {
280                         log_error("Failed to add locale: %s", strerror(-r));
281                         goto finish;
282                 }
283         }
284
285         r = 0;
286
287  finish:
288         if (p != MAP_FAILED)
289                 munmap((void*) p, sz);
290
291         return r;
292 }
293
294 static int add_locales_from_libdir (Set *locales) {
295         _cleanup_closedir_ DIR *dir;
296         struct dirent *entry;
297         int r;
298
299         dir = opendir("/usr/lib/locale");
300         if (!dir) {
301                 log_error("Failed to open locale directory: %m");
302                 return -errno;
303         }
304
305         errno = 0;
306         while ((entry = readdir(dir))) {
307                 char *z;
308
309                 if (entry->d_type != DT_DIR)
310                         continue;
311
312                 if (ignore_file(entry->d_name))
313                         continue;
314
315                 z = strdup(entry->d_name);
316                 if (!z)
317                         return log_oom();
318
319                 r = set_consume(locales, z);
320                 if (r < 0 && r != -EEXIST) {
321                         log_error("Failed to add locale: %s", strerror(-r));
322                         return r;
323                 }
324
325                 errno = 0;
326         }
327
328         if (errno > 0) {
329                 log_error("Failed to read locale directory: %m");
330                 return -errno;
331         }
332
333         return 0;
334 }
335
336 static int list_locales(sd_bus *bus, char **args, unsigned n) {
337         _cleanup_set_free_ Set *locales;
338         _cleanup_strv_free_ char **l = NULL;
339         int r;
340
341         locales = set_new(string_hash_func, string_compare_func);
342         if (!locales)
343                 return log_oom();
344
345         r = add_locales_from_archive(locales);
346         if (r < 0 && r != -ENOENT)
347                 return r;
348
349         r = add_locales_from_libdir(locales);
350         if (r < 0)
351                 return r;
352
353         l = set_get_strv(locales);
354         if (!l)
355                 return log_oom();
356
357         strv_sort(l);
358
359         pager_open_if_enabled();
360
361         strv_print(l);
362
363         return 0;
364 }
365
366 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
367         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
368         const char *map, *toggle_map;
369         int r;
370
371         assert(bus);
372         assert(args);
373
374         if (n > 3) {
375                 log_error("Too many arguments.");
376                 return -EINVAL;
377         }
378
379         polkit_agent_open_if_enabled();
380
381         map = args[1];
382         toggle_map = n > 2 ? args[2] : "";
383
384         r = sd_bus_call_method(
385                         bus,
386                         "org.freedesktop.locale1",
387                         "/org/freedesktop/locale1",
388                         "org.freedesktop.locale1",
389                         "SetVConsoleKeyboard",
390                         &error,
391                         NULL,
392                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
393         if (r < 0)
394                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
395
396         return r;
397 }
398
399 static Set *keymaps = NULL;
400
401 static int nftw_cb(
402                 const char *fpath,
403                 const struct stat *sb,
404                 int tflag,
405                 struct FTW *ftwbuf) {
406
407         char *p, *e;
408         int r;
409
410         if (tflag != FTW_F)
411                 return 0;
412
413         if (!endswith(fpath, ".map") &&
414             !endswith(fpath, ".map.gz"))
415                 return 0;
416
417         p = strdup(path_get_file_name(fpath));
418         if (!p)
419                 return log_oom();
420
421         e = endswith(p, ".map");
422         if (e)
423                 *e = 0;
424
425         e = endswith(p, ".map.gz");
426         if (e)
427                 *e = 0;
428
429         r = set_consume(keymaps, p);
430         if (r < 0 && r != -EEXIST) {
431                 log_error("Can't add keymap: %s", strerror(-r));
432                 return r;
433         }
434
435         return 0;
436 }
437
438 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
439         _cleanup_strv_free_ char **l = NULL;
440
441         keymaps = set_new(string_hash_func, string_compare_func);
442         if (!keymaps)
443                 return log_oom();
444
445         nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
446         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
447         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
448         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
449
450         l = set_get_strv(keymaps);
451         if (!l) {
452                 set_free_free(keymaps);
453                 return log_oom();
454         }
455
456         set_free(keymaps);
457
458         if (strv_isempty(l)) {
459                 log_error("Couldn't find any console keymaps.");
460                 return -ENOENT;
461         }
462
463         strv_sort(l);
464
465         pager_open_if_enabled();
466
467         strv_print(l);
468
469         return 0;
470 }
471
472 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
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-pager            Do not pipe output into a pager\n"
618                "     --no-ask-password     Do not prompt for password\n"
619                "  -H --host=[USER@]HOST    Operate on remote host\n"
620                "  -M --machine=CONTAINER   Operate on local container\n"
621                "     --no-convert          Don't convert keyboard mappings\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 }