chiark / gitweb /
localectl: support systems without locale-archive
[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 *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                 r = -errno;
395                 goto finish;
396         }
397
398         errno = 0;
399         while ((entry = readdir(dir))) {
400                 char *z;
401
402                 if (entry->d_type != DT_DIR)
403                         continue;
404
405                 if (ignore_file(entry->d_name))
406                         continue;
407
408                 z = strdup(entry->d_name);
409                 if (!z) {
410                         r = log_oom();
411                         goto finish;
412                 }
413
414                 r = set_put(locales, z);
415                 if (r < 0) {
416                         free(z);
417
418                         if (r != -EEXIST) {
419                                 log_error("Failed to add locale: %s", strerror(-r));
420                                 goto finish;
421                         }
422                 }
423
424                 errno = 0;
425         }
426
427         if (errno != 0) {
428                 log_error("Failed to read locale directory: %m");
429                 r = -errno;
430                 goto finish;
431         }
432
433         r = 0;
434
435  finish:
436         closedir(dir);
437         return r;
438 }
439
440 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
441         Set *locales;
442         _cleanup_strv_free_ char **l = NULL;
443         char **j;
444         int r;
445
446         locales = set_new(string_hash_func, string_compare_func);
447         if (!locales)
448                 return log_oom();
449
450         r = add_locales_from_archive(locales);
451         if (r < 0 && r != -ENOENT)
452                 goto finish;
453
454         r = add_locales_from_libdir(locales);
455         if (r < 0)
456                 goto finish;
457
458         l = set_get_strv(locales);
459         if (!l) {
460                 r = log_oom();
461                 goto finish;
462         }
463
464         strv_sort(l);
465
466         pager_open_if_enabled();
467
468         STRV_FOREACH(j, l)
469                 puts(*j);
470
471         r = 0;
472
473 finish:
474         set_free(locales);
475
476         return r;
477 }
478
479 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
480         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
481         dbus_bool_t interactive = true, b;
482         const char *map, *toggle_map;
483
484         assert(bus);
485         assert(args);
486
487         if (n > 3) {
488                 log_error("Too many arguments.");
489                 return -EINVAL;
490         }
491
492         polkit_agent_open_if_enabled();
493
494         map = args[1];
495         toggle_map = n > 2 ? args[2] : "";
496         b = arg_convert;
497
498         return bus_method_call_with_reply(
499                         bus,
500                         "org.freedesktop.locale1",
501                         "/org/freedesktop/locale1",
502                         "org.freedesktop.locale1",
503                         "SetVConsoleKeyboard",
504                         &reply,
505                         NULL,
506                         DBUS_TYPE_STRING, &map,
507                         DBUS_TYPE_STRING, &toggle_map,
508                         DBUS_TYPE_BOOLEAN, &b,
509                         DBUS_TYPE_BOOLEAN, &interactive,
510                         DBUS_TYPE_INVALID);
511 }
512
513 static Set *keymaps = NULL;
514
515 static int nftw_cb(
516                 const char *fpath,
517                 const struct stat *sb,
518                 int tflag,
519                 struct FTW *ftwbuf) {
520
521         char *p, *e;
522         int r;
523
524         if (tflag != FTW_F)
525                 return 0;
526
527         if (!endswith(fpath, ".map") &&
528             !endswith(fpath, ".map.gz"))
529                 return 0;
530
531         p = strdup(path_get_file_name(fpath));
532         if (!p)
533                 return log_oom();
534
535         e = endswith(p, ".map");
536         if (e)
537                 *e = 0;
538
539         e = endswith(p, ".map.gz");
540         if (e)
541                 *e = 0;
542
543         r = set_put(keymaps, p);
544         if (r == -EEXIST)
545                 free(p);
546         else if (r < 0) {
547                 log_error("Can't add keymap: %s", strerror(-r));
548                 free(p);
549                 return r;
550         }
551
552         return 0;
553 }
554
555 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
556         char _cleanup_strv_free_ **l = NULL;
557         char **i;
558
559         keymaps = set_new(string_hash_func, string_compare_func);
560         if (!keymaps)
561                 return log_oom();
562
563         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
564         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
565         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
566
567         l = set_get_strv(keymaps);
568         if (!l) {
569                 set_free_free(keymaps);
570                 return log_oom();
571         }
572
573         set_free(keymaps);
574
575         if (strv_isempty(l)) {
576                 log_error("Couldn't find any console keymaps.");
577                 return -ENOENT;
578         }
579
580         strv_sort(l);
581
582         pager_open_if_enabled();
583
584         STRV_FOREACH(i, l)
585                 puts(*i);
586
587
588         return 0;
589 }
590
591 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
592         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
593         dbus_bool_t interactive = true, b;
594         const char *layout, *model, *variant, *options;
595
596         assert(bus);
597         assert(args);
598
599         if (n > 5) {
600                 log_error("Too many arguments.");
601                 return -EINVAL;
602         }
603
604         polkit_agent_open_if_enabled();
605
606         layout = args[1];
607         model = n > 2 ? args[2] : "";
608         variant = n > 3 ? args[3] : "";
609         options = n > 4 ? args[4] : "";
610         b = arg_convert;
611
612         return bus_method_call_with_reply(
613                         bus,
614                         "org.freedesktop.locale1",
615                         "/org/freedesktop/locale1",
616                         "org.freedesktop.locale1",
617                         "SetX11Keyboard",
618                         &reply,
619                         NULL,
620                         DBUS_TYPE_STRING, &layout,
621                         DBUS_TYPE_STRING, &model,
622                         DBUS_TYPE_STRING, &variant,
623                         DBUS_TYPE_STRING, &options,
624                         DBUS_TYPE_BOOLEAN, &b,
625                         DBUS_TYPE_BOOLEAN, &interactive,
626                         DBUS_TYPE_INVALID);
627 }
628
629 static int help(void) {
630
631         printf("%s [OPTIONS...] COMMAND ...\n\n"
632                "Query or change system time and date settings.\n\n"
633                "  -h --help              Show this help\n"
634                "     --version           Show package version\n"
635                "     --no-convert        Don't convert keyboard mappings\n"
636                "     --no-pager          Do not pipe output into a pager\n"
637                "     --no-ask-password   Do not prompt for password\n"
638                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
639                "Commands:\n"
640                "  status                 Show current locale settings\n"
641                "  set-locale LOCALE...   Set system locale\n"
642                "  list-locales           Show known locales\n"
643                "  set-keymap MAP [MAP]   Set virtual console keyboard mapping\n"
644                "  list-keymaps           Show known virtual console keyboard mappings\n"
645                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
646                "                         Set X11 keyboard mapping\n",
647                program_invocation_short_name);
648
649         return 0;
650 }
651
652 static int parse_argv(int argc, char *argv[]) {
653
654         enum {
655                 ARG_VERSION = 0x100,
656                 ARG_NO_PAGER,
657                 ARG_NO_CONVERT,
658                 ARG_NO_ASK_PASSWORD
659         };
660
661         static const struct option options[] = {
662                 { "help",                no_argument,       NULL, 'h'                     },
663                 { "version",             no_argument,       NULL, ARG_VERSION             },
664                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
665                 { "host",                required_argument, NULL, 'H'                     },
666                 { "privileged",          no_argument,       NULL, 'P'                     },
667                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
668                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
669                 { NULL,                  0,                 NULL, 0                       }
670         };
671
672         int c;
673
674         assert(argc >= 0);
675         assert(argv);
676
677         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
678
679                 switch (c) {
680
681                 case 'h':
682                         help();
683                         return 0;
684
685                 case ARG_VERSION:
686                         puts(PACKAGE_STRING);
687                         puts(SYSTEMD_FEATURES);
688                         return 0;
689
690                 case 'P':
691                         arg_transport = TRANSPORT_POLKIT;
692                         break;
693
694                 case 'H':
695                         arg_transport = TRANSPORT_SSH;
696                         arg_host = optarg;
697                         break;
698
699                 case ARG_NO_CONVERT:
700                         arg_convert = false;
701                         break;
702
703                 case ARG_NO_PAGER:
704                         arg_no_pager = true;
705                         break;
706
707                 case '?':
708                         return -EINVAL;
709
710                 default:
711                         log_error("Unknown option code %c", c);
712                         return -EINVAL;
713                 }
714         }
715
716         return 1;
717 }
718
719 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
720
721         static const struct {
722                 const char* verb;
723                 const enum {
724                         MORE,
725                         LESS,
726                         EQUAL
727                 } argc_cmp;
728                 const int argc;
729                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
730         } verbs[] = {
731                 { "status",         LESS,   1, show_status           },
732                 { "set-locale",     MORE,   2, set_locale            },
733                 { "list-locales",   EQUAL,  1, list_locales          },
734                 { "set-keymap",     MORE,   2, set_vconsole_keymap   },
735                 { "list-keymaps",   EQUAL,  1, list_vconsole_keymaps },
736                 { "set-x11-keymap", MORE,   2, set_x11_keymap        },
737         };
738
739         int left;
740         unsigned i;
741
742         assert(argc >= 0);
743         assert(argv);
744         assert(error);
745
746         left = argc - optind;
747
748         if (left <= 0)
749                 /* Special rule: no arguments means "status" */
750                 i = 0;
751         else {
752                 if (streq(argv[optind], "help")) {
753                         help();
754                         return 0;
755                 }
756
757                 for (i = 0; i < ELEMENTSOF(verbs); i++)
758                         if (streq(argv[optind], verbs[i].verb))
759                                 break;
760
761                 if (i >= ELEMENTSOF(verbs)) {
762                         log_error("Unknown operation %s", argv[optind]);
763                         return -EINVAL;
764                 }
765         }
766
767         switch (verbs[i].argc_cmp) {
768
769         case EQUAL:
770                 if (left != verbs[i].argc) {
771                         log_error("Invalid number of arguments.");
772                         return -EINVAL;
773                 }
774
775                 break;
776
777         case MORE:
778                 if (left < verbs[i].argc) {
779                         log_error("Too few arguments.");
780                         return -EINVAL;
781                 }
782
783                 break;
784
785         case LESS:
786                 if (left > verbs[i].argc) {
787                         log_error("Too many arguments.");
788                         return -EINVAL;
789                 }
790
791                 break;
792
793         default:
794                 assert_not_reached("Unknown comparison operator.");
795         }
796
797         if (!bus) {
798                 log_error("Failed to get D-Bus connection: %s", error->message);
799                 return -EIO;
800         }
801
802         return verbs[i].dispatch(bus, argv + optind, left);
803 }
804
805 int main(int argc, char *argv[]) {
806         int r, retval = EXIT_FAILURE;
807         DBusConnection *bus = NULL;
808         DBusError error;
809
810         dbus_error_init(&error);
811
812         setlocale(LC_ALL, "");
813         log_parse_environment();
814         log_open();
815
816         r = parse_argv(argc, argv);
817         if (r < 0)
818                 goto finish;
819         else if (r == 0) {
820                 retval = EXIT_SUCCESS;
821                 goto finish;
822         }
823
824         if (arg_transport == TRANSPORT_NORMAL)
825                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
826         else if (arg_transport == TRANSPORT_POLKIT)
827                 bus_connect_system_polkit(&bus, &error);
828         else if (arg_transport == TRANSPORT_SSH)
829                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
830         else
831                 assert_not_reached("Uh, invalid transport...");
832
833         r = localectl_main(bus, argc, argv, &error);
834         retval = r < 0 ? EXIT_FAILURE : r;
835
836 finish:
837         if (bus) {
838                 dbus_connection_flush(bus);
839                 dbus_connection_close(bus);
840                 dbus_connection_unref(bus);
841         }
842
843         dbus_error_free(&error);
844         dbus_shutdown();
845
846         pager_close();
847
848         return retval;
849 }