chiark / gitweb /
systemctl: drop compat with really really old systemd versions
[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
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <locale.h>
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <string.h>
28 #include <ftw.h>
29 #include <sys/mman.h>
30 #include <fcntl.h>
31
32 #include "dbus-common.h"
33 #include "util.h"
34 #include "spawn-polkit-agent.h"
35 #include "build.h"
36 #include "strv.h"
37 #include "pager.h"
38 #include "set.h"
39 #include "path-util.h"
40
41 static bool arg_no_pager = false;
42 static enum transport {
43         TRANSPORT_NORMAL,
44         TRANSPORT_SSH,
45         TRANSPORT_POLKIT
46 } arg_transport = TRANSPORT_NORMAL;
47 static bool arg_ask_password = true;
48 static const char *arg_host = NULL;
49 static bool arg_convert = true;
50
51 static void pager_open_if_enabled(void) {
52
53         if (arg_no_pager)
54                 return;
55
56         pager_open();
57 }
58
59 static void polkit_agent_open_if_enabled(void) {
60
61         /* Open the polkit agent as a child process if necessary */
62
63         if (!arg_ask_password)
64                 return;
65
66         polkit_agent_open();
67 }
68
69 typedef struct StatusInfo {
70         char **locale;
71         const char *vconsole_keymap;
72         const char *vconsole_keymap_toggle;
73         const char *x11_layout;
74         const char *x11_model;
75         const char *x11_variant;
76         const char *x11_options;
77 } StatusInfo;
78
79 static void print_status_info(StatusInfo *i) {
80         assert(i);
81
82         if (strv_isempty(i->locale))
83                 puts("   System Locale: n/a\n");
84         else {
85                 char **j;
86
87                 printf("   System Locale: %s\n", i->locale[0]);
88                 STRV_FOREACH(j, i->locale + 1)
89                         printf("                  %s\n", *j);
90         }
91
92         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
93         if (!isempty(i->vconsole_keymap_toggle))
94                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
95
96         printf("      X11 Layout: %s\n", strna(i->x11_layout));
97         if (!isempty(i->x11_model))
98                 printf("       X11 Model: %s\n", i->x11_model);
99         if (!isempty(i->x11_variant))
100                 printf("     X11 Variant: %s\n", i->x11_variant);
101         if (!isempty(i->x11_options))
102                 printf("     X11 Options: %s\n", i->x11_options);
103 }
104
105 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
106         int r;
107
108         assert(name);
109         assert(iter);
110
111         switch (dbus_message_iter_get_arg_type(iter)) {
112
113         case DBUS_TYPE_STRING: {
114                 const char *s;
115
116                 dbus_message_iter_get_basic(iter, &s);
117                 if (!isempty(s)) {
118                         if (streq(name, "VConsoleKeymap"))
119                                 i->vconsole_keymap = s;
120                         else if (streq(name, "VConsoleKeymapToggle"))
121                                 i->vconsole_keymap_toggle = s;
122                         else if (streq(name, "X11Layout"))
123                                 i->x11_layout = s;
124                         else if (streq(name, "X11Model"))
125                                 i->x11_model = s;
126                         else if (streq(name, "X11Variant"))
127                                 i->x11_variant = s;
128                         else if (streq(name, "X11Options"))
129                                 i->x11_options = s;
130                 }
131                 break;
132         }
133
134         case DBUS_TYPE_ARRAY:
135
136                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
137                         char **l;
138
139                         r = bus_parse_strv_iter(iter, &l);
140                         if (r < 0)
141                                 return r;
142
143                         if (streq(name, "Locale")) {
144                                 strv_free(i->locale);
145                                 i->locale = l;
146                                 l = NULL;
147                         }
148
149                         strv_free(l);
150                 }
151         }
152
153         return 0;
154 }
155
156 static int show_status(DBusConnection *bus, char **args, unsigned n) {
157         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
158         const char *interface = "";
159         int r;
160         DBusMessageIter iter, sub, sub2, sub3;
161         StatusInfo info;
162
163         assert(args);
164
165         r = bus_method_call_with_reply(
166                         bus,
167                         "org.freedesktop.locale1",
168                         "/org/freedesktop/locale1",
169                         "org.freedesktop.DBus.Properties",
170                         "GetAll",
171                         &reply,
172                         NULL,
173                         DBUS_TYPE_STRING, &interface,
174                         DBUS_TYPE_INVALID);
175         if (r < 0)
176                 return r;
177
178         if (!dbus_message_iter_init(reply, &iter) ||
179             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
180             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
181                 log_error("Failed to parse reply.");
182                 return -EIO;
183         }
184
185         zero(info);
186         dbus_message_iter_recurse(&iter, &sub);
187
188         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
189                 const char *name;
190
191                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
192                         log_error("Failed to parse reply.");
193                         return -EIO;
194                 }
195
196                 dbus_message_iter_recurse(&sub, &sub2);
197
198                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
199                         log_error("Failed to parse reply.");
200                         return -EIO;
201                 }
202
203                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
204                         log_error("Failed to parse reply.");
205                         return -EIO;
206                 }
207
208                 dbus_message_iter_recurse(&sub2, &sub3);
209
210                 r = status_property(name, &sub3, &info);
211                 if (r < 0) {
212                         log_error("Failed to parse reply.");
213                         return r;
214                 }
215
216                 dbus_message_iter_next(&sub);
217         }
218
219         print_status_info(&info);
220         strv_free(info.locale);
221         return 0;
222 }
223
224 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
225         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
226         dbus_bool_t interactive = true;
227         DBusError error;
228         DBusMessageIter iter;
229         int r;
230
231         assert(bus);
232         assert(args);
233
234         dbus_error_init(&error);
235
236         polkit_agent_open_if_enabled();
237
238         m = dbus_message_new_method_call(
239                         "org.freedesktop.locale1",
240                         "/org/freedesktop/locale1",
241                         "org.freedesktop.locale1",
242                         "SetLocale");
243         if (!m)
244                 return log_oom();
245
246         dbus_message_iter_init_append(m, &iter);
247
248         r = bus_append_strv_iter(&iter, args + 1);
249         if (r < 0)
250                 return log_oom();
251
252         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
253                 return log_oom();
254
255         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
256         if (!reply) {
257                 log_error("Failed to issue method call: %s", bus_error_message(&error));
258                 r = -EIO;
259                 goto finish;
260         }
261
262         r = 0;
263
264 finish:
265         dbus_error_free(&error);
266         return r;
267 }
268
269 static int add_locales_from_archive(Set *locales) {
270         /* Stolen from glibc... */
271
272         struct locarhead {
273                 uint32_t magic;
274                 /* Serial number.  */
275                 uint32_t serial;
276                 /* Name hash table.  */
277                 uint32_t namehash_offset;
278                 uint32_t namehash_used;
279                 uint32_t namehash_size;
280                 /* String table.  */
281                 uint32_t string_offset;
282                 uint32_t string_used;
283                 uint32_t string_size;
284                 /* Table with locale records.  */
285                 uint32_t locrectab_offset;
286                 uint32_t locrectab_used;
287                 uint32_t locrectab_size;
288                 /* MD5 sum hash table.  */
289                 uint32_t sumhash_offset;
290                 uint32_t sumhash_used;
291                 uint32_t sumhash_size;
292         };
293
294         struct namehashent {
295                 /* Hash value of the name.  */
296                 uint32_t hashval;
297                 /* Offset of the name in the string table.  */
298                 uint32_t name_offset;
299                 /* Offset of the locale record.  */
300                 uint32_t locrec_offset;
301         };
302
303         const struct locarhead *h;
304         const struct namehashent *e;
305         const void *p = MAP_FAILED;
306         _cleanup_close_ int fd = -1;
307         size_t sz = 0;
308         struct stat st;
309         unsigned i;
310         int r;
311
312         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
313         if (fd < 0) {
314                 if (errno != ENOENT)
315                         log_error("Failed to open locale archive: %m");
316                 r = -errno;
317                 goto finish;
318         }
319
320         if (fstat(fd, &st) < 0) {
321                 log_error("fstat() failed: %m");
322                 r = -errno;
323                 goto finish;
324         }
325
326         if (!S_ISREG(st.st_mode)) {
327                 log_error("Archive file is not regular");
328                 r = -EBADMSG;
329                 goto finish;
330         }
331
332         if (st.st_size < (off_t) sizeof(struct locarhead)) {
333                 log_error("Archive has invalid size");
334                 r = -EBADMSG;
335                 goto finish;
336         }
337
338         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
339         if (p == MAP_FAILED) {
340                 log_error("Failed to map archive: %m");
341                 r = -errno;
342                 goto finish;
343         }
344
345         h = (const struct locarhead *) p;
346         if (h->magic != 0xde020109 ||
347             h->namehash_offset + h->namehash_size > st.st_size ||
348             h->string_offset + h->string_size > st.st_size ||
349             h->locrectab_offset + h->locrectab_size > st.st_size ||
350             h->sumhash_offset + h->sumhash_size > st.st_size) {
351                 log_error("Invalid archive file.");
352                 r = -EBADMSG;
353                 goto finish;
354         }
355
356         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
357         for (i = 0; i < h->namehash_size; i++) {
358                 char *z;
359
360                 if (e[i].locrec_offset == 0)
361                         continue;
362
363                 z = strdup((char*) p + e[i].name_offset);
364                 if (!z) {
365                         r = log_oom();
366                         goto finish;
367                 }
368
369                 r = set_put(locales, z);
370                 if (r < 0) {
371                         free(z);
372                         log_error("Failed to add locale: %s", strerror(-r));
373                         goto finish;
374                 }
375         }
376
377         r = 0;
378
379  finish:
380         if (p != MAP_FAILED)
381                 munmap((void*) p, sz);
382
383         return r;
384 }
385
386 static int add_locales_from_libdir (Set *locales) {
387         DIR _cleanup_closedir_ *dir;
388         struct dirent *entry;
389         int r;
390
391         dir = opendir("/usr/lib/locale");
392         if (!dir) {
393                 log_error("Failed to open locale directory: %m");
394                 return -errno;
395         }
396
397         errno = 0;
398         while ((entry = readdir(dir))) {
399                 char *z;
400
401                 if (entry->d_type != DT_DIR)
402                         continue;
403
404                 if (ignore_file(entry->d_name))
405                         continue;
406
407                 z = strdup(entry->d_name);
408                 if (!z)
409                         return log_oom();
410
411                 r = set_put(locales, z);
412                 if (r < 0) {
413                         free(z);
414
415                         if (r != -EEXIST) {
416                                 log_error("Failed to add locale: %s", strerror(-r));
417                                 return r;
418                         }
419                 }
420
421                 errno = 0;
422         }
423
424         if (errno != 0) {
425                 log_error("Failed to read locale directory: %m");
426                 return -errno;
427         }
428
429         return 0;
430 }
431
432 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
433         _cleanup_set_free_ Set *locales;
434         _cleanup_strv_free_ char **l = NULL;
435         char **j;
436         int r;
437
438         locales = set_new(string_hash_func, string_compare_func);
439         if (!locales)
440                 return log_oom();
441
442         r = add_locales_from_archive(locales);
443         if (r < 0 && r != -ENOENT)
444                 return r;
445
446         r = add_locales_from_libdir(locales);
447         if (r < 0)
448                 return r;
449
450         l = set_get_strv(locales);
451         if (!l)
452                 return log_oom();
453
454         strv_sort(l);
455
456         pager_open_if_enabled();
457
458         STRV_FOREACH(j, l)
459                 puts(*j);
460
461         return 0;
462 }
463
464 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
465         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
466         dbus_bool_t interactive = true, b;
467         const char *map, *toggle_map;
468
469         assert(bus);
470         assert(args);
471
472         if (n > 3) {
473                 log_error("Too many arguments.");
474                 return -EINVAL;
475         }
476
477         polkit_agent_open_if_enabled();
478
479         map = args[1];
480         toggle_map = n > 2 ? args[2] : "";
481         b = arg_convert;
482
483         return bus_method_call_with_reply(
484                         bus,
485                         "org.freedesktop.locale1",
486                         "/org/freedesktop/locale1",
487                         "org.freedesktop.locale1",
488                         "SetVConsoleKeyboard",
489                         &reply,
490                         NULL,
491                         DBUS_TYPE_STRING, &map,
492                         DBUS_TYPE_STRING, &toggle_map,
493                         DBUS_TYPE_BOOLEAN, &b,
494                         DBUS_TYPE_BOOLEAN, &interactive,
495                         DBUS_TYPE_INVALID);
496 }
497
498 static Set *keymaps = NULL;
499
500 static int nftw_cb(
501                 const char *fpath,
502                 const struct stat *sb,
503                 int tflag,
504                 struct FTW *ftwbuf) {
505
506         char *p, *e;
507         int r;
508
509         if (tflag != FTW_F)
510                 return 0;
511
512         if (!endswith(fpath, ".map") &&
513             !endswith(fpath, ".map.gz"))
514                 return 0;
515
516         p = strdup(path_get_file_name(fpath));
517         if (!p)
518                 return log_oom();
519
520         e = endswith(p, ".map");
521         if (e)
522                 *e = 0;
523
524         e = endswith(p, ".map.gz");
525         if (e)
526                 *e = 0;
527
528         r = set_put(keymaps, p);
529         if (r == -EEXIST)
530                 free(p);
531         else if (r < 0) {
532                 log_error("Can't add keymap: %s", strerror(-r));
533                 free(p);
534                 return r;
535         }
536
537         return 0;
538 }
539
540 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
541         char _cleanup_strv_free_ **l = NULL;
542         char **i;
543
544         keymaps = set_new(string_hash_func, string_compare_func);
545         if (!keymaps)
546                 return log_oom();
547
548         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
549         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
550         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
551
552         l = set_get_strv(keymaps);
553         if (!l) {
554                 set_free_free(keymaps);
555                 return log_oom();
556         }
557
558         set_free(keymaps);
559
560         if (strv_isempty(l)) {
561                 log_error("Couldn't find any console keymaps.");
562                 return -ENOENT;
563         }
564
565         strv_sort(l);
566
567         pager_open_if_enabled();
568
569         STRV_FOREACH(i, l)
570                 puts(*i);
571
572
573         return 0;
574 }
575
576 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
577         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
578         dbus_bool_t interactive = true, b;
579         const char *layout, *model, *variant, *options;
580
581         assert(bus);
582         assert(args);
583
584         if (n > 5) {
585                 log_error("Too many arguments.");
586                 return -EINVAL;
587         }
588
589         polkit_agent_open_if_enabled();
590
591         layout = args[1];
592         model = n > 2 ? args[2] : "";
593         variant = n > 3 ? args[3] : "";
594         options = n > 4 ? args[4] : "";
595         b = arg_convert;
596
597         return bus_method_call_with_reply(
598                         bus,
599                         "org.freedesktop.locale1",
600                         "/org/freedesktop/locale1",
601                         "org.freedesktop.locale1",
602                         "SetX11Keyboard",
603                         &reply,
604                         NULL,
605                         DBUS_TYPE_STRING, &layout,
606                         DBUS_TYPE_STRING, &model,
607                         DBUS_TYPE_STRING, &variant,
608                         DBUS_TYPE_STRING, &options,
609                         DBUS_TYPE_BOOLEAN, &b,
610                         DBUS_TYPE_BOOLEAN, &interactive,
611                         DBUS_TYPE_INVALID);
612 }
613
614 static int help(void) {
615
616         printf("%s [OPTIONS...] COMMAND ...\n\n"
617                "Query or change system time and date settings.\n\n"
618                "  -h --help              Show this help\n"
619                "     --version           Show package version\n"
620                "     --no-convert        Don't convert keyboard mappings\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\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                program_invocation_short_name);
633
634         return 0;
635 }
636
637 static int parse_argv(int argc, char *argv[]) {
638
639         enum {
640                 ARG_VERSION = 0x100,
641                 ARG_NO_PAGER,
642                 ARG_NO_CONVERT,
643                 ARG_NO_ASK_PASSWORD
644         };
645
646         static const struct option options[] = {
647                 { "help",                no_argument,       NULL, 'h'                     },
648                 { "version",             no_argument,       NULL, ARG_VERSION             },
649                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
650                 { "host",                required_argument, NULL, 'H'                     },
651                 { "privileged",          no_argument,       NULL, 'P'                     },
652                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
653                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
654                 { NULL,                  0,                 NULL, 0                       }
655         };
656
657         int c;
658
659         assert(argc >= 0);
660         assert(argv);
661
662         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
663
664                 switch (c) {
665
666                 case 'h':
667                         help();
668                         return 0;
669
670                 case ARG_VERSION:
671                         puts(PACKAGE_STRING);
672                         puts(SYSTEMD_FEATURES);
673                         return 0;
674
675                 case 'P':
676                         arg_transport = TRANSPORT_POLKIT;
677                         break;
678
679                 case 'H':
680                         arg_transport = TRANSPORT_SSH;
681                         arg_host = optarg;
682                         break;
683
684                 case ARG_NO_CONVERT:
685                         arg_convert = false;
686                         break;
687
688                 case ARG_NO_PAGER:
689                         arg_no_pager = true;
690                         break;
691
692                 case '?':
693                         return -EINVAL;
694
695                 default:
696                         log_error("Unknown option code %c", c);
697                         return -EINVAL;
698                 }
699         }
700
701         return 1;
702 }
703
704 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
705
706         static const struct {
707                 const char* verb;
708                 const enum {
709                         MORE,
710                         LESS,
711                         EQUAL
712                 } argc_cmp;
713                 const int argc;
714                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
715         } verbs[] = {
716                 { "status",         LESS,   1, show_status           },
717                 { "set-locale",     MORE,   2, set_locale            },
718                 { "list-locales",   EQUAL,  1, list_locales          },
719                 { "set-keymap",     MORE,   2, set_vconsole_keymap   },
720                 { "list-keymaps",   EQUAL,  1, list_vconsole_keymaps },
721                 { "set-x11-keymap", MORE,   2, set_x11_keymap        },
722         };
723
724         int left;
725         unsigned i;
726
727         assert(argc >= 0);
728         assert(argv);
729         assert(error);
730
731         left = argc - optind;
732
733         if (left <= 0)
734                 /* Special rule: no arguments means "status" */
735                 i = 0;
736         else {
737                 if (streq(argv[optind], "help")) {
738                         help();
739                         return 0;
740                 }
741
742                 for (i = 0; i < ELEMENTSOF(verbs); i++)
743                         if (streq(argv[optind], verbs[i].verb))
744                                 break;
745
746                 if (i >= ELEMENTSOF(verbs)) {
747                         log_error("Unknown operation %s", argv[optind]);
748                         return -EINVAL;
749                 }
750         }
751
752         switch (verbs[i].argc_cmp) {
753
754         case EQUAL:
755                 if (left != verbs[i].argc) {
756                         log_error("Invalid number of arguments.");
757                         return -EINVAL;
758                 }
759
760                 break;
761
762         case MORE:
763                 if (left < verbs[i].argc) {
764                         log_error("Too few arguments.");
765                         return -EINVAL;
766                 }
767
768                 break;
769
770         case LESS:
771                 if (left > verbs[i].argc) {
772                         log_error("Too many arguments.");
773                         return -EINVAL;
774                 }
775
776                 break;
777
778         default:
779                 assert_not_reached("Unknown comparison operator.");
780         }
781
782         if (!bus) {
783                 log_error("Failed to get D-Bus connection: %s", error->message);
784                 return -EIO;
785         }
786
787         return verbs[i].dispatch(bus, argv + optind, left);
788 }
789
790 int main(int argc, char *argv[]) {
791         int r, retval = EXIT_FAILURE;
792         DBusConnection *bus = NULL;
793         DBusError error;
794
795         dbus_error_init(&error);
796
797         setlocale(LC_ALL, "");
798         log_parse_environment();
799         log_open();
800
801         r = parse_argv(argc, argv);
802         if (r < 0)
803                 goto finish;
804         else if (r == 0) {
805                 retval = EXIT_SUCCESS;
806                 goto finish;
807         }
808
809         if (arg_transport == TRANSPORT_NORMAL)
810                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
811         else if (arg_transport == TRANSPORT_POLKIT)
812                 bus_connect_system_polkit(&bus, &error);
813         else if (arg_transport == TRANSPORT_SSH)
814                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
815         else
816                 assert_not_reached("Uh, invalid transport...");
817
818         r = localectl_main(bus, argc, argv, &error);
819         retval = r < 0 ? EXIT_FAILURE : r;
820
821 finish:
822         if (bus) {
823                 dbus_connection_flush(bus);
824                 dbus_connection_close(bus);
825                 dbus_connection_unref(bus);
826         }
827
828         dbus_error_free(&error);
829         dbus_shutdown();
830
831         pager_close();
832
833         return retval;
834 }