chiark / gitweb /
bus: use internal helper to read org.freedesktop.DBus.Properties::GetAll variables
[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         const struct bus_properties_map map[]  = {
108                 { "s",  "VConsoleKeymap",       &info.vconsole_keymap },
109                 { "s",  "VConsoleKeymap",       &info.vconsole_keymap },
110                 { "s",  "VConsoleKeymapToggle", &info.vconsole_keymap_toggle},
111                 { "s",  "X11Layout",            &info.x11_layout },
112                 { "s",  "X11Model",             &info.x11_model },
113                 { "s",  "X11Variant",           &info.x11_variant },
114                 { "s",  "X11Options",           &info.x11_options },
115                 { "as", "Locale",               &info.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         if (r < 0)
127                 goto fail;
128
129         print_status_info(&info);
130
131 fail:
132         strv_free(info.locale);
133         return r;
134 }
135
136 static int set_locale(sd_bus *bus, char **args, unsigned n) {
137         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
138         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
139         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
140         int r;
141
142         assert(bus);
143         assert(args);
144
145         polkit_agent_open_if_enabled();
146
147         r = sd_bus_message_new_method_call(bus,
148                         "org.freedesktop.locale1",
149                         "/org/freedesktop/locale1",
150                         "org.freedesktop.locale1",
151                         "SetLocale", &m);
152         if (r < 0)
153                 return r;
154
155         r = sd_bus_message_append_strv(m, args + 1);
156         if (r < 0)
157                 return r;
158
159         r = sd_bus_message_append(m, "b", arg_ask_password);
160         if (r < 0)
161                 return r;
162
163         r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, NULL);
164         if (r < 0) {
165                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
166                 return r;
167         }
168
169         return 0;
170 }
171
172 static int add_locales_from_archive(Set *locales) {
173         /* Stolen from glibc... */
174
175         struct locarhead {
176                 uint32_t magic;
177                 /* Serial number.  */
178                 uint32_t serial;
179                 /* Name hash table.  */
180                 uint32_t namehash_offset;
181                 uint32_t namehash_used;
182                 uint32_t namehash_size;
183                 /* String table.  */
184                 uint32_t string_offset;
185                 uint32_t string_used;
186                 uint32_t string_size;
187                 /* Table with locale records.  */
188                 uint32_t locrectab_offset;
189                 uint32_t locrectab_used;
190                 uint32_t locrectab_size;
191                 /* MD5 sum hash table.  */
192                 uint32_t sumhash_offset;
193                 uint32_t sumhash_used;
194                 uint32_t sumhash_size;
195         };
196
197         struct namehashent {
198                 /* Hash value of the name.  */
199                 uint32_t hashval;
200                 /* Offset of the name in the string table.  */
201                 uint32_t name_offset;
202                 /* Offset of the locale record.  */
203                 uint32_t locrec_offset;
204         };
205
206         const struct locarhead *h;
207         const struct namehashent *e;
208         const void *p = MAP_FAILED;
209         _cleanup_close_ int fd = -1;
210         size_t sz = 0;
211         struct stat st;
212         unsigned i;
213         int r;
214
215         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
216         if (fd < 0) {
217                 if (errno != ENOENT)
218                         log_error("Failed to open locale archive: %m");
219                 r = -errno;
220                 goto finish;
221         }
222
223         if (fstat(fd, &st) < 0) {
224                 log_error("fstat() failed: %m");
225                 r = -errno;
226                 goto finish;
227         }
228
229         if (!S_ISREG(st.st_mode)) {
230                 log_error("Archive file is not regular");
231                 r = -EBADMSG;
232                 goto finish;
233         }
234
235         if (st.st_size < (off_t) sizeof(struct locarhead)) {
236                 log_error("Archive has invalid size");
237                 r = -EBADMSG;
238                 goto finish;
239         }
240
241         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
242         if (p == MAP_FAILED) {
243                 log_error("Failed to map archive: %m");
244                 r = -errno;
245                 goto finish;
246         }
247
248         h = (const struct locarhead *) p;
249         if (h->magic != 0xde020109 ||
250             h->namehash_offset + h->namehash_size > st.st_size ||
251             h->string_offset + h->string_size > st.st_size ||
252             h->locrectab_offset + h->locrectab_size > st.st_size ||
253             h->sumhash_offset + h->sumhash_size > st.st_size) {
254                 log_error("Invalid archive file.");
255                 r = -EBADMSG;
256                 goto finish;
257         }
258
259         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
260         for (i = 0; i < h->namehash_size; i++) {
261                 char *z;
262
263                 if (e[i].locrec_offset == 0)
264                         continue;
265
266                 if (!utf8_is_valid((char*) p + e[i].name_offset))
267                         continue;
268
269                 z = strdup((char*) p + e[i].name_offset);
270                 if (!z) {
271                         r = log_oom();
272                         goto finish;
273                 }
274
275                 r = set_consume(locales, z);
276                 if (r < 0) {
277                         log_error("Failed to add locale: %s", strerror(-r));
278                         goto finish;
279                 }
280         }
281
282         r = 0;
283
284  finish:
285         if (p != MAP_FAILED)
286                 munmap((void*) p, sz);
287
288         return r;
289 }
290
291 static int add_locales_from_libdir (Set *locales) {
292         _cleanup_closedir_ DIR *dir;
293         struct dirent *entry;
294         int r;
295
296         dir = opendir("/usr/lib/locale");
297         if (!dir) {
298                 log_error("Failed to open locale directory: %m");
299                 return -errno;
300         }
301
302         errno = 0;
303         while ((entry = readdir(dir))) {
304                 char *z;
305
306                 if (entry->d_type != DT_DIR)
307                         continue;
308
309                 if (ignore_file(entry->d_name))
310                         continue;
311
312                 z = strdup(entry->d_name);
313                 if (!z)
314                         return log_oom();
315
316                 r = set_consume(locales, z);
317                 if (r < 0 && r != -EEXIST) {
318                         log_error("Failed to add locale: %s", strerror(-r));
319                         return r;
320                 }
321
322                 errno = 0;
323         }
324
325         if (errno > 0) {
326                 log_error("Failed to read locale directory: %m");
327                 return -errno;
328         }
329
330         return 0;
331 }
332
333 static int list_locales(sd_bus *bus, char **args, unsigned n) {
334         _cleanup_set_free_ Set *locales;
335         _cleanup_strv_free_ char **l = NULL;
336         int r;
337
338         locales = set_new(string_hash_func, string_compare_func);
339         if (!locales)
340                 return log_oom();
341
342         r = add_locales_from_archive(locales);
343         if (r < 0 && r != -ENOENT)
344                 return r;
345
346         r = add_locales_from_libdir(locales);
347         if (r < 0)
348                 return r;
349
350         l = set_get_strv(locales);
351         if (!l)
352                 return log_oom();
353
354         strv_sort(l);
355
356         pager_open_if_enabled();
357
358         strv_print(l);
359
360         return 0;
361 }
362
363 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
364         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
365         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
366         const char *map, *toggle_map;
367         int r;
368
369         assert(bus);
370         assert(args);
371
372         if (n > 3) {
373                 log_error("Too many arguments.");
374                 return -EINVAL;
375         }
376
377         polkit_agent_open_if_enabled();
378
379         map = args[1];
380         toggle_map = n > 2 ? args[2] : "";
381
382         r = sd_bus_call_method(
383                         bus,
384                         "org.freedesktop.locale1",
385                         "/org/freedesktop/locale1",
386                         "org.freedesktop.locale1",
387                         "SetVConsoleKeyboard",
388                         &error,
389                         NULL,
390                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
391         if (r < 0)
392                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
393
394         return r;
395 }
396
397 static Set *keymaps = NULL;
398
399 static int nftw_cb(
400                 const char *fpath,
401                 const struct stat *sb,
402                 int tflag,
403                 struct FTW *ftwbuf) {
404
405         char *p, *e;
406         int r;
407
408         if (tflag != FTW_F)
409                 return 0;
410
411         if (!endswith(fpath, ".map") &&
412             !endswith(fpath, ".map.gz"))
413                 return 0;
414
415         p = strdup(path_get_file_name(fpath));
416         if (!p)
417                 return log_oom();
418
419         e = endswith(p, ".map");
420         if (e)
421                 *e = 0;
422
423         e = endswith(p, ".map.gz");
424         if (e)
425                 *e = 0;
426
427         r = set_consume(keymaps, p);
428         if (r < 0 && r != -EEXIST) {
429                 log_error("Can't add keymap: %s", strerror(-r));
430                 return r;
431         }
432
433         return 0;
434 }
435
436 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
437         _cleanup_strv_free_ char **l = NULL;
438
439         keymaps = set_new(string_hash_func, string_compare_func);
440         if (!keymaps)
441                 return log_oom();
442
443         nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
444         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
445         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
446         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
447
448         l = set_get_strv(keymaps);
449         if (!l) {
450                 set_free_free(keymaps);
451                 return log_oom();
452         }
453
454         set_free(keymaps);
455
456         if (strv_isempty(l)) {
457                 log_error("Couldn't find any console keymaps.");
458                 return -ENOENT;
459         }
460
461         strv_sort(l);
462
463         pager_open_if_enabled();
464
465         strv_print(l);
466
467         return 0;
468 }
469
470 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
471         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
472         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
473         const char *layout, *model, *variant, *options;
474         int r;
475
476         assert(bus);
477         assert(args);
478
479         if (n > 5) {
480                 log_error("Too many arguments.");
481                 return -EINVAL;
482         }
483
484         polkit_agent_open_if_enabled();
485
486         layout = args[1];
487         model = n > 2 ? args[2] : "";
488         variant = n > 3 ? args[3] : "";
489         options = n > 4 ? args[4] : "";
490
491         r = sd_bus_call_method(
492                         bus,
493                         "org.freedesktop.locale1",
494                         "/org/freedesktop/locale1",
495                         "org.freedesktop.locale1",
496                         "SetX11Keyboard",
497                         &error,
498                         NULL,
499                         "ssssbb", layout, model, variant, options,
500                                   arg_convert, arg_ask_password);
501         if (r < 0)
502                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
503
504         return r;
505 }
506
507 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
508         _cleanup_fclose_ FILE *f = NULL;
509         _cleanup_strv_free_ char **list = NULL;
510         char line[LINE_MAX];
511         enum {
512                 NONE,
513                 MODELS,
514                 LAYOUTS,
515                 VARIANTS,
516                 OPTIONS
517         } state = NONE, look_for;
518         int r;
519
520         if (n > 2) {
521                 log_error("Too many arguments.");
522                 return -EINVAL;
523         }
524
525         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
526         if (!f) {
527                 log_error("Failed to open keyboard mapping list. %m");
528                 return -errno;
529         }
530
531         if (streq(args[0], "list-x11-keymap-models"))
532                 look_for = MODELS;
533         else if (streq(args[0], "list-x11-keymap-layouts"))
534                 look_for = LAYOUTS;
535         else if (streq(args[0], "list-x11-keymap-variants"))
536                 look_for = VARIANTS;
537         else if (streq(args[0], "list-x11-keymap-options"))
538                 look_for = OPTIONS;
539         else
540                 assert_not_reached("Wrong parameter");
541
542         FOREACH_LINE(line, f, break) {
543                 char *l, *w;
544
545                 l = strstrip(line);
546
547                 if (isempty(l))
548                         continue;
549
550                 if (l[0] == '!') {
551                         if (startswith(l, "! model"))
552                                 state = MODELS;
553                         else if (startswith(l, "! layout"))
554                                 state = LAYOUTS;
555                         else if (startswith(l, "! variant"))
556                                 state = VARIANTS;
557                         else if (startswith(l, "! option"))
558                                 state = OPTIONS;
559                         else
560                                 state = NONE;
561
562                         continue;
563                 }
564
565                 if (state != look_for)
566                         continue;
567
568                 w = l + strcspn(l, WHITESPACE);
569
570                 if (n > 1) {
571                         char *e;
572
573                         if (*w == 0)
574                                 continue;
575
576                         *w = 0;
577                         w++;
578                         w += strspn(w, WHITESPACE);
579
580                         e = strchr(w, ':');
581                         if (!e)
582                                 continue;
583
584                         *e = 0;
585
586                         if (!streq(w, args[1]))
587                                 continue;
588                 } else
589                         *w = 0;
590
591                  r = strv_extend(&list, l);
592                  if (r < 0)
593                          return log_oom();
594         }
595
596         if (strv_isempty(list)) {
597                 log_error("Couldn't find any entries.");
598                 return -ENOENT;
599         }
600
601         strv_sort(list);
602         strv_uniq(list);
603
604         pager_open_if_enabled();
605
606         strv_print(list);
607         return 0;
608 }
609
610 static int help(void) {
611
612         printf("%s [OPTIONS...] COMMAND ...\n\n"
613                "Query or change system locale and keyboard settings.\n\n"
614                "  -h --help                Show this help\n"
615                "     --version             Show package version\n"
616                "     --no-convert          Don't convert keyboard mappings\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\n"
621                "Commands:\n"
622                "  status                   Show current locale settings\n"
623                "  set-locale LOCALE...     Set system locale\n"
624                "  list-locales             Show known locales\n"
625                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
626                "  list-keymaps             Show known virtual console keyboard mappings\n"
627                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
628                "                           Set X11 keyboard mapping\n"
629                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
630                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
631                "  list-x11-keymap-variants [LAYOUT]\n"
632                "                           Show known X11 keyboard mapping variants\n"
633                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
634                program_invocation_short_name);
635
636         return 0;
637 }
638
639 static int parse_argv(int argc, char *argv[]) {
640
641         enum {
642                 ARG_VERSION = 0x100,
643                 ARG_NO_PAGER,
644                 ARG_NO_CONVERT,
645                 ARG_NO_ASK_PASSWORD
646         };
647
648         static const struct option options[] = {
649                 { "help",            no_argument,       NULL, 'h'                 },
650                 { "version",         no_argument,       NULL, ARG_VERSION         },
651                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
652                 { "host",            required_argument, NULL, 'H'                 },
653                 { "machine",         required_argument, NULL, 'M'                 },
654                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
655                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
656                 { NULL,              0,                 NULL, 0                   }
657         };
658
659         int c;
660
661         assert(argc >= 0);
662         assert(argv);
663
664         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
665
666                 switch (c) {
667
668                 case 'h':
669                         help();
670                         return 0;
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                         log_error("Unknown option code %c", c);
704                         return -EINVAL;
705                 }
706         }
707
708         return 1;
709 }
710
711 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
712
713         static const struct {
714                 const char* verb;
715                 const enum {
716                         MORE,
717                         LESS,
718                         EQUAL
719                 } argc_cmp;
720                 const int argc;
721                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
722         } verbs[] = {
723                 { "status",                   LESS,   1, show_status           },
724                 { "set-locale",               MORE,   2, set_locale            },
725                 { "list-locales",             EQUAL,  1, list_locales          },
726                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
727                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
728                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
729                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
730                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
731                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
732                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
733         };
734
735         int left;
736         unsigned i;
737
738         assert(argc >= 0);
739         assert(argv);
740
741         left = argc - optind;
742
743         if (left <= 0)
744                 /* Special rule: no arguments means "status" */
745                 i = 0;
746         else {
747                 if (streq(argv[optind], "help")) {
748                         help();
749                         return 0;
750                 }
751
752                 for (i = 0; i < ELEMENTSOF(verbs); i++)
753                         if (streq(argv[optind], verbs[i].verb))
754                                 break;
755
756                 if (i >= ELEMENTSOF(verbs)) {
757                         log_error("Unknown operation %s", argv[optind]);
758                         return -EINVAL;
759                 }
760         }
761
762         switch (verbs[i].argc_cmp) {
763
764         case EQUAL:
765                 if (left != verbs[i].argc) {
766                         log_error("Invalid number of arguments.");
767                         return -EINVAL;
768                 }
769
770                 break;
771
772         case MORE:
773                 if (left < verbs[i].argc) {
774                         log_error("Too few arguments.");
775                         return -EINVAL;
776                 }
777
778                 break;
779
780         case LESS:
781                 if (left > verbs[i].argc) {
782                         log_error("Too many arguments.");
783                         return -EINVAL;
784                 }
785
786                 break;
787
788         default:
789                 assert_not_reached("Unknown comparison operator.");
790         }
791
792         return verbs[i].dispatch(bus, argv + optind, left);
793 }
794
795 int main(int argc, char*argv[]) {
796         int r, ret = EXIT_FAILURE;
797         _cleanup_bus_unref_ sd_bus *bus = NULL;
798
799         setlocale(LC_ALL, "");
800         log_parse_environment();
801         log_open();
802
803         r = parse_argv(argc, argv);
804         if (r < 0)
805                 goto finish;
806         else if (r == 0) {
807                 ret = EXIT_SUCCESS;
808                 goto finish;
809         }
810
811         r = bus_open_transport(arg_transport, arg_host, false, &bus);
812         if (r < 0) {
813                 log_error("Failed to create bus connection: %s", strerror(-r));
814                 ret = EXIT_FAILURE;
815                 goto finish;
816         }
817
818         r = localectl_main(bus, argc, argv);
819         ret = r < 0 ? EXIT_FAILURE : r;
820
821 finish:
822         pager_close();
823
824         return ret;
825 }