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