chiark / gitweb /
clients: various simplifications
[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                 { NULL,              0,                 NULL, 0                   }
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                         help();
671                         return 0;
672
673                 case ARG_VERSION:
674                         puts(PACKAGE_STRING);
675                         puts(SYSTEMD_FEATURES);
676                         return 0;
677
678                 case ARG_NO_CONVERT:
679                         arg_convert = false;
680                         break;
681
682                 case ARG_NO_PAGER:
683                         arg_no_pager = true;
684                         break;
685
686                 case ARG_NO_ASK_PASSWORD:
687                         arg_ask_password = false;
688                         break;
689
690                 case 'H':
691                         arg_transport = BUS_TRANSPORT_REMOTE;
692                         arg_host = optarg;
693                         break;
694
695                 case 'M':
696                         arg_transport = BUS_TRANSPORT_CONTAINER;
697                         arg_host = optarg;
698                         break;
699
700                 case '?':
701                         return -EINVAL;
702
703                 default:
704                         log_error("Unknown option code %c", c);
705                         return -EINVAL;
706                 }
707         }
708
709         return 1;
710 }
711
712 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
713
714         static const struct {
715                 const char* verb;
716                 const enum {
717                         MORE,
718                         LESS,
719                         EQUAL
720                 } argc_cmp;
721                 const int argc;
722                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
723         } verbs[] = {
724                 { "status",                   LESS,   1, show_status           },
725                 { "set-locale",               MORE,   2, set_locale            },
726                 { "list-locales",             EQUAL,  1, list_locales          },
727                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
728                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
729                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
730                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
731                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
732                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
733                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
734         };
735
736         int left;
737         unsigned i;
738
739         assert(argc >= 0);
740         assert(argv);
741
742         left = argc - optind;
743
744         if (left <= 0)
745                 /* Special rule: no arguments means "status" */
746                 i = 0;
747         else {
748                 if (streq(argv[optind], "help")) {
749                         help();
750                         return 0;
751                 }
752
753                 for (i = 0; i < ELEMENTSOF(verbs); i++)
754                         if (streq(argv[optind], verbs[i].verb))
755                                 break;
756
757                 if (i >= ELEMENTSOF(verbs)) {
758                         log_error("Unknown operation %s", argv[optind]);
759                         return -EINVAL;
760                 }
761         }
762
763         switch (verbs[i].argc_cmp) {
764
765         case EQUAL:
766                 if (left != verbs[i].argc) {
767                         log_error("Invalid number of arguments.");
768                         return -EINVAL;
769                 }
770
771                 break;
772
773         case MORE:
774                 if (left < verbs[i].argc) {
775                         log_error("Too few arguments.");
776                         return -EINVAL;
777                 }
778
779                 break;
780
781         case LESS:
782                 if (left > verbs[i].argc) {
783                         log_error("Too many arguments.");
784                         return -EINVAL;
785                 }
786
787                 break;
788
789         default:
790                 assert_not_reached("Unknown comparison operator.");
791         }
792
793         return verbs[i].dispatch(bus, argv + optind, left);
794 }
795
796 int main(int argc, char*argv[]) {
797         _cleanup_bus_unref_ sd_bus *bus = NULL;
798         int r;
799
800         setlocale(LC_ALL, "");
801         log_parse_environment();
802         log_open();
803
804         r = parse_argv(argc, argv);
805         if (r <= 0)
806                 goto finish;
807
808         r = bus_open_transport(arg_transport, arg_host, false, &bus);
809         if (r < 0) {
810                 log_error("Failed to create bus connection: %s", strerror(-r));
811                 goto finish;
812         }
813
814         r = localectl_main(bus, argc, argv);
815
816 finish:
817         pager_close();
818
819         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
820 }