]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localectl.c
sd-bus: rename sd_bus_open_system_container() to sd_bus_open_system_machine()
[thirdparty/systemd.git] / src / locale / localectl.c
CommitLineData
2087a7af
LP
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
4d7859d1 7 Copyright 2013 Kay Sievers
2087a7af
LP
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
a9cdc94f 23#include <locale.h>
2087a7af
LP
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
4d7859d1
KS
33#include "sd-bus.h"
34#include "bus-util.h"
35#include "bus-error.h"
36#include "bus-message.h"
2087a7af
LP
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"
0affed79 44#include "utf8.h"
0732ef7a 45#include "def.h"
a3428668
MS
46#include "virt.h"
47#include "fileio.h"
75683450 48#include "locale-util.h"
2087a7af
LP
49
50static bool arg_no_pager = false;
2087a7af 51static bool arg_ask_password = true;
4d7859d1 52static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
7085053a 53static char *arg_host = NULL;
2087a7af
LP
54static bool arg_convert = true;
55
56static void pager_open_if_enabled(void) {
57
58 if (arg_no_pager)
59 return;
60
1b12a7b5 61 pager_open(false);
2087a7af
LP
62}
63
64static void polkit_agent_open_if_enabled(void) {
65
66 /* Open the polkit agent as a child process if necessary */
2087a7af
LP
67 if (!arg_ask_password)
68 return;
69
46e65dcc
LP
70 if (arg_transport != BUS_TRANSPORT_LOCAL)
71 return;
72
2087a7af
LP
73 polkit_agent_open();
74}
75
76typedef struct StatusInfo {
77 char **locale;
78 const char *vconsole_keymap;
79 const char *vconsole_keymap_toggle;
80 const char *x11_layout;
81 const char *x11_model;
82 const char *x11_variant;
83 const char *x11_options;
84} StatusInfo;
85
a3428668
MS
86static void print_overriden_variables(void) {
87 int r;
88 char *variables[_VARIABLE_LC_MAX] = {};
89 LocaleVariable j;
90 bool print_warning = true;
91
92 if (detect_container(NULL) > 0 || arg_host)
93 return;
94
95 r = parse_env_file("/proc/cmdline", WHITESPACE,
96 "locale.LANG", &variables[VARIABLE_LANG],
97 "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
98 "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
99 "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
100 "locale.LC_TIME", &variables[VARIABLE_LC_TIME],
101 "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
102 "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
103 "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
104 "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER],
105 "locale.LC_NAME", &variables[VARIABLE_LC_NAME],
106 "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
107 "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
108 "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
109 "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
110 NULL);
111
112 if (r < 0 && r != -ENOENT) {
da927ba9 113 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
a3428668
MS
114 goto finish;
115 }
116
63229aa1 117 for (j = 0; j < _VARIABLE_LC_MAX; j++)
a3428668
MS
118 if (variables[j]) {
119 if (print_warning) {
b344bcbb 120 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
ff49bc32 121 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
a3428668
MS
122
123 print_warning = false;
b344bcbb 124 } else
ff49bc32 125 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
a3428668
MS
126 }
127 finish:
63229aa1 128 for (j = 0; j < _VARIABLE_LC_MAX; j++)
a3428668
MS
129 free(variables[j]);
130}
131
2087a7af
LP
132static void print_status_info(StatusInfo *i) {
133 assert(i);
134
135 if (strv_isempty(i->locale))
136 puts(" System Locale: n/a\n");
137 else {
138 char **j;
139
140 printf(" System Locale: %s\n", i->locale[0]);
141 STRV_FOREACH(j, i->locale + 1)
142 printf(" %s\n", *j);
143 }
144
145 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
146 if (!isempty(i->vconsole_keymap_toggle))
147 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
148
149 printf(" X11 Layout: %s\n", strna(i->x11_layout));
150 if (!isempty(i->x11_model))
151 printf(" X11 Model: %s\n", i->x11_model);
152 if (!isempty(i->x11_variant))
153 printf(" X11 Variant: %s\n", i->x11_variant);
154 if (!isempty(i->x11_options))
155 printf(" X11 Options: %s\n", i->x11_options);
156}
157
4d7859d1 158static int show_status(sd_bus *bus, char **args, unsigned n) {
b92bea5d 159 StatusInfo info = {};
9f6eb1cd
KS
160 static const struct bus_properties_map map[] = {
161 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
162 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
163 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
164 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
165 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
166 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
167 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
168 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
ffc06c35
KS
169 {}
170 };
171 int r;
2087a7af 172
ffc06c35 173 assert(bus);
2087a7af 174
ffc06c35
KS
175 r = bus_map_all_properties(bus,
176 "org.freedesktop.locale1",
177 "/org/freedesktop/locale1",
9f6eb1cd
KS
178 map,
179 &info);
6046278f 180 if (r < 0) {
da927ba9 181 log_error_errno(r, "Could not get properties: %m");
4d7859d1 182 goto fail;
6046278f 183 }
2087a7af 184
a3428668 185 print_overriden_variables();
2087a7af 186 print_status_info(&info);
4d7859d1
KS
187
188fail:
2087a7af 189 strv_free(info.locale);
4d7859d1 190 return r;
2087a7af
LP
191}
192
4d7859d1
KS
193static int set_locale(sd_bus *bus, char **args, unsigned n) {
194 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
4d7859d1 195 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af
LP
196 int r;
197
198 assert(bus);
199 assert(args);
200
2087a7af
LP
201 polkit_agent_open_if_enabled();
202
151b9b96
LP
203 r = sd_bus_message_new_method_call(
204 bus,
205 &m,
2087a7af
LP
206 "org.freedesktop.locale1",
207 "/org/freedesktop/locale1",
208 "org.freedesktop.locale1",
151b9b96 209 "SetLocale");
4d7859d1 210 if (r < 0)
94676f3e 211 return bus_log_create_error(r);
2087a7af 212
4d7859d1
KS
213 r = sd_bus_message_append_strv(m, args + 1);
214 if (r < 0)
94676f3e 215 return bus_log_create_error(r);
2087a7af 216
4d7859d1 217 r = sd_bus_message_append(m, "b", arg_ask_password);
2087a7af 218 if (r < 0)
94676f3e 219 return bus_log_create_error(r);
2087a7af 220
c49b30a2 221 r = sd_bus_call(bus, m, 0, &error, NULL);
4d7859d1 222 if (r < 0) {
e1636421 223 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
4d7859d1 224 return r;
2087a7af
LP
225 }
226
4d7859d1 227 return 0;
2087a7af
LP
228}
229
4d7859d1 230static int list_locales(sd_bus *bus, char **args, unsigned n) {
17d33cec 231 _cleanup_strv_free_ char **l = NULL;
17d33cec
GC
232 int r;
233
75683450 234 assert(args);
17d33cec 235
75683450 236 r = get_locales(&l);
f647962d
MS
237 if (r < 0)
238 return log_error_errno(r, "Failed to read list of locales: %m");
2087a7af
LP
239
240 pager_open_if_enabled();
7c2d8094 241 strv_print(l);
2087a7af 242
bac3c8ee 243 return 0;
2087a7af
LP
244}
245
4d7859d1 246static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
4d7859d1 247 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af 248 const char *map, *toggle_map;
e1636421 249 int r;
2087a7af
LP
250
251 assert(bus);
252 assert(args);
253
254 if (n > 3) {
255 log_error("Too many arguments.");
256 return -EINVAL;
257 }
258
259 polkit_agent_open_if_enabled();
260
261 map = args[1];
262 toggle_map = n > 2 ? args[2] : "";
2087a7af 263
e1636421
LP
264 r = sd_bus_call_method(
265 bus,
2087a7af
LP
266 "org.freedesktop.locale1",
267 "/org/freedesktop/locale1",
268 "org.freedesktop.locale1",
269 "SetVConsoleKeyboard",
4d7859d1 270 &error,
2087a7af 271 NULL,
4d7859d1 272 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
e1636421
LP
273 if (r < 0)
274 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
275
276 return r;
2087a7af
LP
277}
278
279static Set *keymaps = NULL;
280
281static int nftw_cb(
282 const char *fpath,
283 const struct stat *sb,
284 int tflag,
285 struct FTW *ftwbuf) {
286
287 char *p, *e;
288 int r;
289
290 if (tflag != FTW_F)
291 return 0;
292
293 if (!endswith(fpath, ".map") &&
294 !endswith(fpath, ".map.gz"))
295 return 0;
296
2b6bf07d 297 p = strdup(basename(fpath));
2087a7af
LP
298 if (!p)
299 return log_oom();
300
301 e = endswith(p, ".map");
302 if (e)
303 *e = 0;
304
305 e = endswith(p, ".map.gz");
306 if (e)
307 *e = 0;
308
ef42202a 309 r = set_consume(keymaps, p);
f647962d
MS
310 if (r < 0 && r != -EEXIST)
311 return log_error_errno(r, "Can't add keymap: %m");
2087a7af
LP
312
313 return 0;
314}
315
4d7859d1 316static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
7fd1b19b 317 _cleanup_strv_free_ char **l = NULL;
0732ef7a 318 const char *dir;
2087a7af 319
d5099efc 320 keymaps = set_new(&string_hash_ops);
2087a7af
LP
321 if (!keymaps)
322 return log_oom();
323
0732ef7a
ZJS
324 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
325 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
2087a7af
LP
326
327 l = set_get_strv(keymaps);
328 if (!l) {
329 set_free_free(keymaps);
330 return log_oom();
331 }
332
333 set_free(keymaps);
334
335 if (strv_isempty(l)) {
336 log_error("Couldn't find any console keymaps.");
337 return -ENOENT;
338 }
339
340 strv_sort(l);
341
342 pager_open_if_enabled();
343
7c2d8094 344 strv_print(l);
2087a7af
LP
345
346 return 0;
347}
348
4d7859d1 349static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
4d7859d1 350 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af 351 const char *layout, *model, *variant, *options;
e1636421 352 int r;
2087a7af
LP
353
354 assert(bus);
355 assert(args);
356
357 if (n > 5) {
358 log_error("Too many arguments.");
359 return -EINVAL;
360 }
361
362 polkit_agent_open_if_enabled();
363
364 layout = args[1];
365 model = n > 2 ? args[2] : "";
366 variant = n > 3 ? args[3] : "";
6b2b6f30 367 options = n > 4 ? args[4] : "";
2087a7af 368
e1636421
LP
369 r = sd_bus_call_method(
370 bus,
2087a7af
LP
371 "org.freedesktop.locale1",
372 "/org/freedesktop/locale1",
373 "org.freedesktop.locale1",
374 "SetX11Keyboard",
4d7859d1 375 &error,
2087a7af 376 NULL,
4d7859d1
KS
377 "ssssbb", layout, model, variant, options,
378 arg_convert, arg_ask_password);
e1636421
LP
379 if (r < 0)
380 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
381
382 return r;
2087a7af
LP
383}
384
4d7859d1 385static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
50cfc579 386 _cleanup_fclose_ FILE *f = NULL;
7fd1b19b 387 _cleanup_strv_free_ char **list = NULL;
50cfc579
LP
388 char line[LINE_MAX];
389 enum {
390 NONE,
391 MODELS,
392 LAYOUTS,
393 VARIANTS,
394 OPTIONS
395 } state = NONE, look_for;
396 int r;
397
398 if (n > 2) {
399 log_error("Too many arguments.");
400 return -EINVAL;
401 }
402
c62e11ce 403 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
4a62c710
MS
404 if (!f)
405 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
50cfc579
LP
406
407 if (streq(args[0], "list-x11-keymap-models"))
408 look_for = MODELS;
409 else if (streq(args[0], "list-x11-keymap-layouts"))
410 look_for = LAYOUTS;
411 else if (streq(args[0], "list-x11-keymap-variants"))
412 look_for = VARIANTS;
413 else if (streq(args[0], "list-x11-keymap-options"))
414 look_for = OPTIONS;
415 else
416 assert_not_reached("Wrong parameter");
417
418 FOREACH_LINE(line, f, break) {
419 char *l, *w;
420
421 l = strstrip(line);
422
423 if (isempty(l))
424 continue;
425
426 if (l[0] == '!') {
427 if (startswith(l, "! model"))
428 state = MODELS;
429 else if (startswith(l, "! layout"))
430 state = LAYOUTS;
431 else if (startswith(l, "! variant"))
432 state = VARIANTS;
433 else if (startswith(l, "! option"))
434 state = OPTIONS;
435 else
436 state = NONE;
437
438 continue;
439 }
440
441 if (state != look_for)
442 continue;
443
444 w = l + strcspn(l, WHITESPACE);
445
446 if (n > 1) {
447 char *e;
448
449 if (*w == 0)
450 continue;
451
452 *w = 0;
453 w++;
454 w += strspn(w, WHITESPACE);
455
456 e = strchr(w, ':');
457 if (!e)
458 continue;
459
460 *e = 0;
461
462 if (!streq(w, args[1]))
463 continue;
464 } else
465 *w = 0;
466
467 r = strv_extend(&list, l);
468 if (r < 0)
469 return log_oom();
470 }
471
472 if (strv_isempty(list)) {
473 log_error("Couldn't find any entries.");
474 return -ENOENT;
475 }
476
477 strv_sort(list);
478 strv_uniq(list);
479
480 pager_open_if_enabled();
481
482 strv_print(list);
483 return 0;
484}
485
601185b4 486static void help(void) {
2087a7af 487 printf("%s [OPTIONS...] COMMAND ...\n\n"
82c1d8f4 488 "Query or change system locale and keyboard settings.\n\n"
50cfc579
LP
489 " -h --help Show this help\n"
490 " --version Show package version\n"
50cfc579
LP
491 " --no-pager Do not pipe output into a pager\n"
492 " --no-ask-password Do not prompt for password\n"
4d7859d1 493 " -H --host=[USER@]HOST Operate on remote host\n"
a86a47ce
LP
494 " -M --machine=CONTAINER Operate on local container\n"
495 " --no-convert Don't convert keyboard mappings\n\n"
2087a7af 496 "Commands:\n"
50cfc579
LP
497 " status Show current locale settings\n"
498 " set-locale LOCALE... Set system locale\n"
499 " list-locales Show known locales\n"
2ebcf936 500 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
50cfc579 501 " list-keymaps Show known virtual console keyboard mappings\n"
31cf921a 502 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
2ebcf936 503 " Set X11 and console keyboard mappings\n"
50cfc579
LP
504 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
505 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
506 " list-x11-keymap-variants [LAYOUT]\n"
507 " Show known X11 keyboard mapping variants\n"
601185b4
ZJS
508 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
509 , program_invocation_short_name);
2087a7af
LP
510}
511
512static int parse_argv(int argc, char *argv[]) {
513
514 enum {
515 ARG_VERSION = 0x100,
516 ARG_NO_PAGER,
517 ARG_NO_CONVERT,
518 ARG_NO_ASK_PASSWORD
519 };
520
521 static const struct option options[] = {
4d7859d1
KS
522 { "help", no_argument, NULL, 'h' },
523 { "version", no_argument, NULL, ARG_VERSION },
524 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
525 { "host", required_argument, NULL, 'H' },
526 { "machine", required_argument, NULL, 'M' },
527 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
528 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
eb9da376 529 {}
2087a7af
LP
530 };
531
532 int c;
533
534 assert(argc >= 0);
535 assert(argv);
536
601185b4 537 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
2087a7af
LP
538
539 switch (c) {
540
541 case 'h':
601185b4
ZJS
542 help();
543 return 0;
2087a7af
LP
544
545 case ARG_VERSION:
546 puts(PACKAGE_STRING);
2087a7af
LP
547 puts(SYSTEMD_FEATURES);
548 return 0;
549
2087a7af
LP
550 case ARG_NO_CONVERT:
551 arg_convert = false;
552 break;
553
554 case ARG_NO_PAGER:
555 arg_no_pager = true;
556 break;
557
546158bc
JJ
558 case ARG_NO_ASK_PASSWORD:
559 arg_ask_password = false;
560 break;
561
4d7859d1
KS
562 case 'H':
563 arg_transport = BUS_TRANSPORT_REMOTE;
564 arg_host = optarg;
565 break;
566
567 case 'M':
de33fc62 568 arg_transport = BUS_TRANSPORT_MACHINE;
4d7859d1
KS
569 arg_host = optarg;
570 break;
571
2087a7af
LP
572 case '?':
573 return -EINVAL;
574
575 default:
eb9da376 576 assert_not_reached("Unhandled option");
2087a7af 577 }
2087a7af
LP
578
579 return 1;
580}
581
4d7859d1 582static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
2087a7af
LP
583
584 static const struct {
585 const char* verb;
586 const enum {
587 MORE,
588 LESS,
589 EQUAL
590 } argc_cmp;
591 const int argc;
4d7859d1 592 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
2087a7af 593 } verbs[] = {
50cfc579
LP
594 { "status", LESS, 1, show_status },
595 { "set-locale", MORE, 2, set_locale },
596 { "list-locales", EQUAL, 1, list_locales },
597 { "set-keymap", MORE, 2, set_vconsole_keymap },
598 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
599 { "set-x11-keymap", MORE, 2, set_x11_keymap },
600 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
601 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
602 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
603 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
2087a7af
LP
604 };
605
606 int left;
607 unsigned i;
608
609 assert(argc >= 0);
610 assert(argv);
2087a7af
LP
611
612 left = argc - optind;
613
614 if (left <= 0)
615 /* Special rule: no arguments means "status" */
616 i = 0;
617 else {
618 if (streq(argv[optind], "help")) {
619 help();
620 return 0;
621 }
622
623 for (i = 0; i < ELEMENTSOF(verbs); i++)
624 if (streq(argv[optind], verbs[i].verb))
625 break;
626
627 if (i >= ELEMENTSOF(verbs)) {
628 log_error("Unknown operation %s", argv[optind]);
629 return -EINVAL;
630 }
631 }
632
633 switch (verbs[i].argc_cmp) {
634
635 case EQUAL:
636 if (left != verbs[i].argc) {
637 log_error("Invalid number of arguments.");
638 return -EINVAL;
639 }
640
641 break;
642
643 case MORE:
644 if (left < verbs[i].argc) {
645 log_error("Too few arguments.");
646 return -EINVAL;
647 }
648
649 break;
650
651 case LESS:
652 if (left > verbs[i].argc) {
653 log_error("Too many arguments.");
654 return -EINVAL;
655 }
656
657 break;
658
659 default:
660 assert_not_reached("Unknown comparison operator.");
661 }
662
2087a7af
LP
663 return verbs[i].dispatch(bus, argv + optind, left);
664}
665
4d7859d1 666int main(int argc, char*argv[]) {
24996861 667 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
84f6181c 668 int r;
2087a7af 669
a9cdc94f 670 setlocale(LC_ALL, "");
2087a7af
LP
671 log_parse_environment();
672 log_open();
673
674 r = parse_argv(argc, argv);
84f6181c 675 if (r <= 0)
2087a7af 676 goto finish;
2087a7af 677
4d7859d1
KS
678 r = bus_open_transport(arg_transport, arg_host, false, &bus);
679 if (r < 0) {
da927ba9 680 log_error_errno(r, "Failed to create bus connection: %m");
4d7859d1 681 goto finish;
2087a7af
LP
682 }
683
4d7859d1 684 r = localectl_main(bus, argc, argv);
2087a7af 685
4d7859d1 686finish:
2087a7af
LP
687 pager_close();
688
84f6181c 689 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
2087a7af 690}