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