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