]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localectl.c
treewide: use log_*_errno whenever %m is in the format string
[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");
50cfc579 404 if (!f) {
56f64d95 405 log_error_errno(errno, "Failed to open keyboard mapping list. %m");
50cfc579
LP
406 return -errno;
407 }
408
409 if (streq(args[0], "list-x11-keymap-models"))
410 look_for = MODELS;
411 else if (streq(args[0], "list-x11-keymap-layouts"))
412 look_for = LAYOUTS;
413 else if (streq(args[0], "list-x11-keymap-variants"))
414 look_for = VARIANTS;
415 else if (streq(args[0], "list-x11-keymap-options"))
416 look_for = OPTIONS;
417 else
418 assert_not_reached("Wrong parameter");
419
420 FOREACH_LINE(line, f, break) {
421 char *l, *w;
422
423 l = strstrip(line);
424
425 if (isempty(l))
426 continue;
427
428 if (l[0] == '!') {
429 if (startswith(l, "! model"))
430 state = MODELS;
431 else if (startswith(l, "! layout"))
432 state = LAYOUTS;
433 else if (startswith(l, "! variant"))
434 state = VARIANTS;
435 else if (startswith(l, "! option"))
436 state = OPTIONS;
437 else
438 state = NONE;
439
440 continue;
441 }
442
443 if (state != look_for)
444 continue;
445
446 w = l + strcspn(l, WHITESPACE);
447
448 if (n > 1) {
449 char *e;
450
451 if (*w == 0)
452 continue;
453
454 *w = 0;
455 w++;
456 w += strspn(w, WHITESPACE);
457
458 e = strchr(w, ':');
459 if (!e)
460 continue;
461
462 *e = 0;
463
464 if (!streq(w, args[1]))
465 continue;
466 } else
467 *w = 0;
468
469 r = strv_extend(&list, l);
470 if (r < 0)
471 return log_oom();
472 }
473
474 if (strv_isempty(list)) {
475 log_error("Couldn't find any entries.");
476 return -ENOENT;
477 }
478
479 strv_sort(list);
480 strv_uniq(list);
481
482 pager_open_if_enabled();
483
484 strv_print(list);
485 return 0;
486}
487
601185b4 488static void help(void) {
2087a7af 489 printf("%s [OPTIONS...] COMMAND ...\n\n"
82c1d8f4 490 "Query or change system locale and keyboard settings.\n\n"
50cfc579
LP
491 " -h --help Show this help\n"
492 " --version Show package version\n"
50cfc579
LP
493 " --no-pager Do not pipe output into a pager\n"
494 " --no-ask-password Do not prompt for password\n"
4d7859d1 495 " -H --host=[USER@]HOST Operate on remote host\n"
a86a47ce
LP
496 " -M --machine=CONTAINER Operate on local container\n"
497 " --no-convert Don't convert keyboard mappings\n\n"
2087a7af 498 "Commands:\n"
50cfc579
LP
499 " status Show current locale settings\n"
500 " set-locale LOCALE... Set system locale\n"
501 " list-locales Show known locales\n"
502 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
503 " list-keymaps Show known virtual console keyboard mappings\n"
31cf921a 504 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
50cfc579
LP
505 " Set X11 keyboard mapping\n"
506 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
507 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
508 " list-x11-keymap-variants [LAYOUT]\n"
509 " Show known X11 keyboard mapping variants\n"
601185b4
ZJS
510 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
511 , program_invocation_short_name);
2087a7af
LP
512}
513
514static int parse_argv(int argc, char *argv[]) {
515
516 enum {
517 ARG_VERSION = 0x100,
518 ARG_NO_PAGER,
519 ARG_NO_CONVERT,
520 ARG_NO_ASK_PASSWORD
521 };
522
523 static const struct option options[] = {
4d7859d1
KS
524 { "help", no_argument, NULL, 'h' },
525 { "version", no_argument, NULL, ARG_VERSION },
526 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
527 { "host", required_argument, NULL, 'H' },
528 { "machine", required_argument, NULL, 'M' },
529 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
530 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
eb9da376 531 {}
2087a7af
LP
532 };
533
534 int c;
535
536 assert(argc >= 0);
537 assert(argv);
538
601185b4 539 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
2087a7af
LP
540
541 switch (c) {
542
543 case 'h':
601185b4
ZJS
544 help();
545 return 0;
2087a7af
LP
546
547 case ARG_VERSION:
548 puts(PACKAGE_STRING);
2087a7af
LP
549 puts(SYSTEMD_FEATURES);
550 return 0;
551
2087a7af
LP
552 case ARG_NO_CONVERT:
553 arg_convert = false;
554 break;
555
556 case ARG_NO_PAGER:
557 arg_no_pager = true;
558 break;
559
546158bc
JJ
560 case ARG_NO_ASK_PASSWORD:
561 arg_ask_password = false;
562 break;
563
4d7859d1
KS
564 case 'H':
565 arg_transport = BUS_TRANSPORT_REMOTE;
566 arg_host = optarg;
567 break;
568
569 case 'M':
570 arg_transport = BUS_TRANSPORT_CONTAINER;
571 arg_host = optarg;
572 break;
573
2087a7af
LP
574 case '?':
575 return -EINVAL;
576
577 default:
eb9da376 578 assert_not_reached("Unhandled option");
2087a7af 579 }
2087a7af
LP
580
581 return 1;
582}
583
4d7859d1 584static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
2087a7af
LP
585
586 static const struct {
587 const char* verb;
588 const enum {
589 MORE,
590 LESS,
591 EQUAL
592 } argc_cmp;
593 const int argc;
4d7859d1 594 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
2087a7af 595 } verbs[] = {
50cfc579
LP
596 { "status", LESS, 1, show_status },
597 { "set-locale", MORE, 2, set_locale },
598 { "list-locales", EQUAL, 1, list_locales },
599 { "set-keymap", MORE, 2, set_vconsole_keymap },
600 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
601 { "set-x11-keymap", MORE, 2, set_x11_keymap },
602 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
603 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
604 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
605 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
2087a7af
LP
606 };
607
608 int left;
609 unsigned i;
610
611 assert(argc >= 0);
612 assert(argv);
2087a7af
LP
613
614 left = argc - optind;
615
616 if (left <= 0)
617 /* Special rule: no arguments means "status" */
618 i = 0;
619 else {
620 if (streq(argv[optind], "help")) {
621 help();
622 return 0;
623 }
624
625 for (i = 0; i < ELEMENTSOF(verbs); i++)
626 if (streq(argv[optind], verbs[i].verb))
627 break;
628
629 if (i >= ELEMENTSOF(verbs)) {
630 log_error("Unknown operation %s", argv[optind]);
631 return -EINVAL;
632 }
633 }
634
635 switch (verbs[i].argc_cmp) {
636
637 case EQUAL:
638 if (left != verbs[i].argc) {
639 log_error("Invalid number of arguments.");
640 return -EINVAL;
641 }
642
643 break;
644
645 case MORE:
646 if (left < verbs[i].argc) {
647 log_error("Too few arguments.");
648 return -EINVAL;
649 }
650
651 break;
652
653 case LESS:
654 if (left > verbs[i].argc) {
655 log_error("Too many arguments.");
656 return -EINVAL;
657 }
658
659 break;
660
661 default:
662 assert_not_reached("Unknown comparison operator.");
663 }
664
2087a7af
LP
665 return verbs[i].dispatch(bus, argv + optind, left);
666}
667
4d7859d1 668int main(int argc, char*argv[]) {
24996861 669 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
84f6181c 670 int r;
2087a7af 671
a9cdc94f 672 setlocale(LC_ALL, "");
2087a7af
LP
673 log_parse_environment();
674 log_open();
675
676 r = parse_argv(argc, argv);
84f6181c 677 if (r <= 0)
2087a7af 678 goto finish;
2087a7af 679
4d7859d1
KS
680 r = bus_open_transport(arg_transport, arg_host, false, &bus);
681 if (r < 0) {
da927ba9 682 log_error_errno(r, "Failed to create bus connection: %m");
4d7859d1 683 goto finish;
2087a7af
LP
684 }
685
4d7859d1 686 r = localectl_main(bus, argc, argv);
2087a7af 687
4d7859d1 688finish:
2087a7af
LP
689 pager_close();
690
84f6181c 691 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
2087a7af 692}