chiark / gitweb /
50250c4b476df5576713c1253aadd1a090a2f7a6
[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 #include "utf8.h"
41
42 static bool arg_no_pager = false;
43 static enum transport {
44         TRANSPORT_NORMAL,
45         TRANSPORT_SSH,
46         TRANSPORT_POLKIT
47 } arg_transport = TRANSPORT_NORMAL;
48 static bool arg_ask_password = true;
49 static const char *arg_host = NULL;
50 static bool arg_convert = true;
51
52 static void pager_open_if_enabled(void) {
53
54         if (arg_no_pager)
55                 return;
56
57         pager_open(false);
58 }
59
60 static void polkit_agent_open_if_enabled(void) {
61
62         /* Open the polkit agent as a child process if necessary */
63
64         if (!arg_ask_password)
65                 return;
66
67         polkit_agent_open();
68 }
69
70 typedef struct StatusInfo {
71         char **locale;
72         const char *vconsole_keymap;
73         const char *vconsole_keymap_toggle;
74         const char *x11_layout;
75         const char *x11_model;
76         const char *x11_variant;
77         const char *x11_options;
78 } StatusInfo;
79
80 static void print_status_info(StatusInfo *i) {
81         assert(i);
82
83         if (strv_isempty(i->locale))
84                 puts("   System Locale: n/a\n");
85         else {
86                 char **j;
87
88                 printf("   System Locale: %s\n", i->locale[0]);
89                 STRV_FOREACH(j, i->locale + 1)
90                         printf("                  %s\n", *j);
91         }
92
93         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
94         if (!isempty(i->vconsole_keymap_toggle))
95                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
96
97         printf("      X11 Layout: %s\n", strna(i->x11_layout));
98         if (!isempty(i->x11_model))
99                 printf("       X11 Model: %s\n", i->x11_model);
100         if (!isempty(i->x11_variant))
101                 printf("     X11 Variant: %s\n", i->x11_variant);
102         if (!isempty(i->x11_options))
103                 printf("     X11 Options: %s\n", i->x11_options);
104 }
105
106 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
107         int r;
108
109         assert(name);
110         assert(iter);
111
112         switch (dbus_message_iter_get_arg_type(iter)) {
113
114         case DBUS_TYPE_STRING: {
115                 const char *s;
116
117                 dbus_message_iter_get_basic(iter, &s);
118                 if (!isempty(s)) {
119                         if (streq(name, "VConsoleKeymap"))
120                                 i->vconsole_keymap = s;
121                         else if (streq(name, "VConsoleKeymapToggle"))
122                                 i->vconsole_keymap_toggle = s;
123                         else if (streq(name, "X11Layout"))
124                                 i->x11_layout = s;
125                         else if (streq(name, "X11Model"))
126                                 i->x11_model = s;
127                         else if (streq(name, "X11Variant"))
128                                 i->x11_variant = s;
129                         else if (streq(name, "X11Options"))
130                                 i->x11_options = s;
131                 }
132                 break;
133         }
134
135         case DBUS_TYPE_ARRAY:
136
137                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
138                         char **l;
139
140                         r = bus_parse_strv_iter(iter, &l);
141                         if (r < 0)
142                                 return r;
143
144                         if (streq(name, "Locale")) {
145                                 strv_free(i->locale);
146                                 i->locale = l;
147                                 l = NULL;
148                         }
149
150                         strv_free(l);
151                 }
152         }
153
154         return 0;
155 }
156
157 static int show_status(DBusConnection *bus, char **args, unsigned n) {
158         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
159         const char *interface = "";
160         int r;
161         DBusMessageIter iter, sub, sub2, sub3;
162         StatusInfo info = {};
163
164         assert(args);
165
166         r = bus_method_call_with_reply(
167                         bus,
168                         "org.freedesktop.locale1",
169                         "/org/freedesktop/locale1",
170                         "org.freedesktop.DBus.Properties",
171                         "GetAll",
172                         &reply,
173                         NULL,
174                         DBUS_TYPE_STRING, &interface,
175                         DBUS_TYPE_INVALID);
176         if (r < 0)
177                 return r;
178
179         if (!dbus_message_iter_init(reply, &iter) ||
180             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
181             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
182                 log_error("Failed to parse reply.");
183                 return -EIO;
184         }
185
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                 if (!utf8_is_valid((char*) p + e[i].name_offset))
364                         continue;
365
366                 z = strdup((char*) p + e[i].name_offset);
367                 if (!z) {
368                         r = log_oom();
369                         goto finish;
370                 }
371
372                 r = set_consume(locales, z);
373                 if (r < 0) {
374                         log_error("Failed to add locale: %s", strerror(-r));
375                         goto finish;
376                 }
377         }
378
379         r = 0;
380
381  finish:
382         if (p != MAP_FAILED)
383                 munmap((void*) p, sz);
384
385         return r;
386 }
387
388 static int add_locales_from_libdir (Set *locales) {
389         _cleanup_closedir_ DIR *dir;
390         struct dirent *entry;
391         int r;
392
393         dir = opendir("/usr/lib/locale");
394         if (!dir) {
395                 log_error("Failed to open locale directory: %m");
396                 return -errno;
397         }
398
399         errno = 0;
400         while ((entry = readdir(dir))) {
401                 char *z;
402
403                 if (entry->d_type != DT_DIR)
404                         continue;
405
406                 if (ignore_file(entry->d_name))
407                         continue;
408
409                 z = strdup(entry->d_name);
410                 if (!z)
411                         return log_oom();
412
413                 r = set_consume(locales, z);
414                 if (r < 0 && r != -EEXIST) {
415                         log_error("Failed to add locale: %s", strerror(-r));
416                         return r;
417                 }
418
419                 errno = 0;
420         }
421
422         if (errno > 0) {
423                 log_error("Failed to read locale directory: %m");
424                 return -errno;
425         }
426
427         return 0;
428 }
429
430 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
431         _cleanup_set_free_ Set *locales;
432         _cleanup_strv_free_ char **l = NULL;
433         int r;
434
435         locales = set_new(string_hash_func, string_compare_func);
436         if (!locales)
437                 return log_oom();
438
439         r = add_locales_from_archive(locales);
440         if (r < 0 && r != -ENOENT)
441                 return r;
442
443         r = add_locales_from_libdir(locales);
444         if (r < 0)
445                 return r;
446
447         l = set_get_strv(locales);
448         if (!l)
449                 return log_oom();
450
451         strv_sort(l);
452
453         pager_open_if_enabled();
454
455         strv_print(l);
456
457         return 0;
458 }
459
460 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
461         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
462         dbus_bool_t interactive = true, b;
463         const char *map, *toggle_map;
464
465         assert(bus);
466         assert(args);
467
468         if (n > 3) {
469                 log_error("Too many arguments.");
470                 return -EINVAL;
471         }
472
473         polkit_agent_open_if_enabled();
474
475         map = args[1];
476         toggle_map = n > 2 ? args[2] : "";
477         b = arg_convert;
478
479         return bus_method_call_with_reply(
480                         bus,
481                         "org.freedesktop.locale1",
482                         "/org/freedesktop/locale1",
483                         "org.freedesktop.locale1",
484                         "SetVConsoleKeyboard",
485                         &reply,
486                         NULL,
487                         DBUS_TYPE_STRING, &map,
488                         DBUS_TYPE_STRING, &toggle_map,
489                         DBUS_TYPE_BOOLEAN, &b,
490                         DBUS_TYPE_BOOLEAN, &interactive,
491                         DBUS_TYPE_INVALID);
492 }
493
494 static Set *keymaps = NULL;
495
496 static int nftw_cb(
497                 const char *fpath,
498                 const struct stat *sb,
499                 int tflag,
500                 struct FTW *ftwbuf) {
501
502         char *p, *e;
503         int r;
504
505         if (tflag != FTW_F)
506                 return 0;
507
508         if (!endswith(fpath, ".map") &&
509             !endswith(fpath, ".map.gz"))
510                 return 0;
511
512         p = strdup(path_get_file_name(fpath));
513         if (!p)
514                 return log_oom();
515
516         e = endswith(p, ".map");
517         if (e)
518                 *e = 0;
519
520         e = endswith(p, ".map.gz");
521         if (e)
522                 *e = 0;
523
524         r = set_consume(keymaps, p);
525         if (r < 0 && r != -EEXIST) {
526                 log_error("Can't add keymap: %s", strerror(-r));
527                 return r;
528         }
529
530         return 0;
531 }
532
533 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
534         _cleanup_strv_free_ char **l = NULL;
535
536         keymaps = set_new(string_hash_func, string_compare_func);
537         if (!keymaps)
538                 return log_oom();
539
540         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
541         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
542         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
543
544         l = set_get_strv(keymaps);
545         if (!l) {
546                 set_free_free(keymaps);
547                 return log_oom();
548         }
549
550         set_free(keymaps);
551
552         if (strv_isempty(l)) {
553                 log_error("Couldn't find any console keymaps.");
554                 return -ENOENT;
555         }
556
557         strv_sort(l);
558
559         pager_open_if_enabled();
560
561         strv_print(l);
562
563         return 0;
564 }
565
566 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
567         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
568         dbus_bool_t interactive = true, b;
569         const char *layout, *model, *variant, *options;
570
571         assert(bus);
572         assert(args);
573
574         if (n > 5) {
575                 log_error("Too many arguments.");
576                 return -EINVAL;
577         }
578
579         polkit_agent_open_if_enabled();
580
581         layout = args[1];
582         model = n > 2 ? args[2] : "";
583         variant = n > 3 ? args[3] : "";
584         options = n > 4 ? args[4] : "";
585         b = arg_convert;
586
587         return bus_method_call_with_reply(
588                         bus,
589                         "org.freedesktop.locale1",
590                         "/org/freedesktop/locale1",
591                         "org.freedesktop.locale1",
592                         "SetX11Keyboard",
593                         &reply,
594                         NULL,
595                         DBUS_TYPE_STRING, &layout,
596                         DBUS_TYPE_STRING, &model,
597                         DBUS_TYPE_STRING, &variant,
598                         DBUS_TYPE_STRING, &options,
599                         DBUS_TYPE_BOOLEAN, &b,
600                         DBUS_TYPE_BOOLEAN, &interactive,
601                         DBUS_TYPE_INVALID);
602 }
603
604 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
605         _cleanup_fclose_ FILE *f = NULL;
606         _cleanup_strv_free_ char **list = NULL;
607         char line[LINE_MAX];
608         enum {
609                 NONE,
610                 MODELS,
611                 LAYOUTS,
612                 VARIANTS,
613                 OPTIONS
614         } state = NONE, look_for;
615         int r;
616
617         if (n > 2) {
618                 log_error("Too many arguments.");
619                 return -EINVAL;
620         }
621
622         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
623         if (!f) {
624                 log_error("Failed to open keyboard mapping list. %m");
625                 return -errno;
626         }
627
628         if (streq(args[0], "list-x11-keymap-models"))
629                 look_for = MODELS;
630         else if (streq(args[0], "list-x11-keymap-layouts"))
631                 look_for = LAYOUTS;
632         else if (streq(args[0], "list-x11-keymap-variants"))
633                 look_for = VARIANTS;
634         else if (streq(args[0], "list-x11-keymap-options"))
635                 look_for = OPTIONS;
636         else
637                 assert_not_reached("Wrong parameter");
638
639         FOREACH_LINE(line, f, break) {
640                 char *l, *w;
641
642                 l = strstrip(line);
643
644                 if (isempty(l))
645                         continue;
646
647                 if (l[0] == '!') {
648                         if (startswith(l, "! model"))
649                                 state = MODELS;
650                         else if (startswith(l, "! layout"))
651                                 state = LAYOUTS;
652                         else if (startswith(l, "! variant"))
653                                 state = VARIANTS;
654                         else if (startswith(l, "! option"))
655                                 state = OPTIONS;
656                         else
657                                 state = NONE;
658
659                         continue;
660                 }
661
662                 if (state != look_for)
663                         continue;
664
665                 w = l + strcspn(l, WHITESPACE);
666
667                 if (n > 1) {
668                         char *e;
669
670                         if (*w == 0)
671                                 continue;
672
673                         *w = 0;
674                         w++;
675                         w += strspn(w, WHITESPACE);
676
677                         e = strchr(w, ':');
678                         if (!e)
679                                 continue;
680
681                         *e = 0;
682
683                         if (!streq(w, args[1]))
684                                 continue;
685                 } else
686                         *w = 0;
687
688                  r = strv_extend(&list, l);
689                  if (r < 0)
690                          return log_oom();
691         }
692
693         if (strv_isempty(list)) {
694                 log_error("Couldn't find any entries.");
695                 return -ENOENT;
696         }
697
698         strv_sort(list);
699         strv_uniq(list);
700
701         pager_open_if_enabled();
702
703         strv_print(list);
704         return 0;
705 }
706
707 static int help(void) {
708
709         printf("%s [OPTIONS...] COMMAND ...\n\n"
710                "Query or change system locale and keyboard settings.\n\n"
711                "  -h --help                Show this help\n"
712                "     --version             Show package version\n"
713                "     --no-convert          Don't convert keyboard mappings\n"
714                "     --no-pager            Do not pipe output into a pager\n"
715                "     --no-ask-password     Do not prompt for password\n"
716                "  -H --host=[USER@]HOST    Operate on remote host\n\n"
717                "Commands:\n"
718                "  status                   Show current locale settings\n"
719                "  set-locale LOCALE...     Set system locale\n"
720                "  list-locales             Show known locales\n"
721                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
722                "  list-keymaps             Show known virtual console keyboard mappings\n"
723                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
724                "                           Set X11 keyboard mapping\n"
725                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
726                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
727                "  list-x11-keymap-variants [LAYOUT]\n"
728                "                           Show known X11 keyboard mapping variants\n"
729                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
730                program_invocation_short_name);
731
732         return 0;
733 }
734
735 static int parse_argv(int argc, char *argv[]) {
736
737         enum {
738                 ARG_VERSION = 0x100,
739                 ARG_NO_PAGER,
740                 ARG_NO_CONVERT,
741                 ARG_NO_ASK_PASSWORD
742         };
743
744         static const struct option options[] = {
745                 { "help",                no_argument,       NULL, 'h'                     },
746                 { "version",             no_argument,       NULL, ARG_VERSION             },
747                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
748                 { "host",                required_argument, NULL, 'H'                     },
749                 { "privileged",          no_argument,       NULL, 'P'                     },
750                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
751                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
752                 { NULL,                  0,                 NULL, 0                       }
753         };
754
755         int c;
756
757         assert(argc >= 0);
758         assert(argv);
759
760         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
761
762                 switch (c) {
763
764                 case 'h':
765                         help();
766                         return 0;
767
768                 case ARG_VERSION:
769                         puts(PACKAGE_STRING);
770                         puts(SYSTEMD_FEATURES);
771                         return 0;
772
773                 case 'P':
774                         arg_transport = TRANSPORT_POLKIT;
775                         break;
776
777                 case 'H':
778                         arg_transport = TRANSPORT_SSH;
779                         arg_host = optarg;
780                         break;
781
782                 case ARG_NO_CONVERT:
783                         arg_convert = false;
784                         break;
785
786                 case ARG_NO_PAGER:
787                         arg_no_pager = true;
788                         break;
789
790                 case '?':
791                         return -EINVAL;
792
793                 default:
794                         log_error("Unknown option code %c", c);
795                         return -EINVAL;
796                 }
797         }
798
799         return 1;
800 }
801
802 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
803
804         static const struct {
805                 const char* verb;
806                 const enum {
807                         MORE,
808                         LESS,
809                         EQUAL
810                 } argc_cmp;
811                 const int argc;
812                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
813         } verbs[] = {
814                 { "status",                   LESS,   1, show_status           },
815                 { "set-locale",               MORE,   2, set_locale            },
816                 { "list-locales",             EQUAL,  1, list_locales          },
817                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
818                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
819                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
820                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
821                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
822                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
823                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
824         };
825
826         int left;
827         unsigned i;
828
829         assert(argc >= 0);
830         assert(argv);
831         assert(error);
832
833         left = argc - optind;
834
835         if (left <= 0)
836                 /* Special rule: no arguments means "status" */
837                 i = 0;
838         else {
839                 if (streq(argv[optind], "help")) {
840                         help();
841                         return 0;
842                 }
843
844                 for (i = 0; i < ELEMENTSOF(verbs); i++)
845                         if (streq(argv[optind], verbs[i].verb))
846                                 break;
847
848                 if (i >= ELEMENTSOF(verbs)) {
849                         log_error("Unknown operation %s", argv[optind]);
850                         return -EINVAL;
851                 }
852         }
853
854         switch (verbs[i].argc_cmp) {
855
856         case EQUAL:
857                 if (left != verbs[i].argc) {
858                         log_error("Invalid number of arguments.");
859                         return -EINVAL;
860                 }
861
862                 break;
863
864         case MORE:
865                 if (left < verbs[i].argc) {
866                         log_error("Too few arguments.");
867                         return -EINVAL;
868                 }
869
870                 break;
871
872         case LESS:
873                 if (left > verbs[i].argc) {
874                         log_error("Too many arguments.");
875                         return -EINVAL;
876                 }
877
878                 break;
879
880         default:
881                 assert_not_reached("Unknown comparison operator.");
882         }
883
884         if (!bus) {
885                 log_error("Failed to get D-Bus connection: %s", error->message);
886                 return -EIO;
887         }
888
889         return verbs[i].dispatch(bus, argv + optind, left);
890 }
891
892 int main(int argc, char *argv[]) {
893         int r, retval = EXIT_FAILURE;
894         DBusConnection *bus = NULL;
895         DBusError error;
896
897         dbus_error_init(&error);
898
899         setlocale(LC_ALL, "");
900         log_parse_environment();
901         log_open();
902
903         r = parse_argv(argc, argv);
904         if (r < 0)
905                 goto finish;
906         else if (r == 0) {
907                 retval = EXIT_SUCCESS;
908                 goto finish;
909         }
910
911         if (arg_transport == TRANSPORT_NORMAL)
912                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
913         else if (arg_transport == TRANSPORT_POLKIT)
914                 bus_connect_system_polkit(&bus, &error);
915         else if (arg_transport == TRANSPORT_SSH)
916                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
917         else
918                 assert_not_reached("Uh, invalid transport...");
919
920         r = localectl_main(bus, argc, argv, &error);
921         retval = r < 0 ? EXIT_FAILURE : r;
922
923 finish:
924         if (bus) {
925                 dbus_connection_flush(bus);
926                 dbus_connection_close(bus);
927                 dbus_connection_unref(bus);
928         }
929
930         dbus_error_free(&error);
931         dbus_shutdown();
932
933         pager_close();
934
935         return retval;
936 }