chiark / gitweb /
5739a1dcdd1b79967c8d97ae37b715648457939c
[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(false);
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         int r;
436
437         locales = set_new(string_hash_func, string_compare_func);
438         if (!locales)
439                 return log_oom();
440
441         r = add_locales_from_archive(locales);
442         if (r < 0 && r != -ENOENT)
443                 return r;
444
445         r = add_locales_from_libdir(locales);
446         if (r < 0)
447                 return r;
448
449         l = set_get_strv(locales);
450         if (!l)
451                 return log_oom();
452
453         strv_sort(l);
454
455         pager_open_if_enabled();
456
457         strv_print(l);
458
459         return 0;
460 }
461
462 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
463         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
464         dbus_bool_t interactive = true, b;
465         const char *map, *toggle_map;
466
467         assert(bus);
468         assert(args);
469
470         if (n > 3) {
471                 log_error("Too many arguments.");
472                 return -EINVAL;
473         }
474
475         polkit_agent_open_if_enabled();
476
477         map = args[1];
478         toggle_map = n > 2 ? args[2] : "";
479         b = arg_convert;
480
481         return bus_method_call_with_reply(
482                         bus,
483                         "org.freedesktop.locale1",
484                         "/org/freedesktop/locale1",
485                         "org.freedesktop.locale1",
486                         "SetVConsoleKeyboard",
487                         &reply,
488                         NULL,
489                         DBUS_TYPE_STRING, &map,
490                         DBUS_TYPE_STRING, &toggle_map,
491                         DBUS_TYPE_BOOLEAN, &b,
492                         DBUS_TYPE_BOOLEAN, &interactive,
493                         DBUS_TYPE_INVALID);
494 }
495
496 static Set *keymaps = NULL;
497
498 static int nftw_cb(
499                 const char *fpath,
500                 const struct stat *sb,
501                 int tflag,
502                 struct FTW *ftwbuf) {
503
504         char *p, *e;
505         int r;
506
507         if (tflag != FTW_F)
508                 return 0;
509
510         if (!endswith(fpath, ".map") &&
511             !endswith(fpath, ".map.gz"))
512                 return 0;
513
514         p = strdup(path_get_file_name(fpath));
515         if (!p)
516                 return log_oom();
517
518         e = endswith(p, ".map");
519         if (e)
520                 *e = 0;
521
522         e = endswith(p, ".map.gz");
523         if (e)
524                 *e = 0;
525
526         r = set_put(keymaps, p);
527         if (r == -EEXIST)
528                 free(p);
529         else if (r < 0) {
530                 log_error("Can't add keymap: %s", strerror(-r));
531                 free(p);
532                 return r;
533         }
534
535         return 0;
536 }
537
538 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
539         char _cleanup_strv_free_ **l = NULL;
540
541         keymaps = set_new(string_hash_func, string_compare_func);
542         if (!keymaps)
543                 return log_oom();
544
545         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
546         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
547         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
548
549         l = set_get_strv(keymaps);
550         if (!l) {
551                 set_free_free(keymaps);
552                 return log_oom();
553         }
554
555         set_free(keymaps);
556
557         if (strv_isempty(l)) {
558                 log_error("Couldn't find any console keymaps.");
559                 return -ENOENT;
560         }
561
562         strv_sort(l);
563
564         pager_open_if_enabled();
565
566         strv_print(l);
567
568         return 0;
569 }
570
571 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
572         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
573         dbus_bool_t interactive = true, b;
574         const char *layout, *model, *variant, *options;
575
576         assert(bus);
577         assert(args);
578
579         if (n > 5) {
580                 log_error("Too many arguments.");
581                 return -EINVAL;
582         }
583
584         polkit_agent_open_if_enabled();
585
586         layout = args[1];
587         model = n > 2 ? args[2] : "";
588         variant = n > 3 ? args[3] : "";
589         options = n > 4 ? args[4] : "";
590         b = arg_convert;
591
592         return bus_method_call_with_reply(
593                         bus,
594                         "org.freedesktop.locale1",
595                         "/org/freedesktop/locale1",
596                         "org.freedesktop.locale1",
597                         "SetX11Keyboard",
598                         &reply,
599                         NULL,
600                         DBUS_TYPE_STRING, &layout,
601                         DBUS_TYPE_STRING, &model,
602                         DBUS_TYPE_STRING, &variant,
603                         DBUS_TYPE_STRING, &options,
604                         DBUS_TYPE_BOOLEAN, &b,
605                         DBUS_TYPE_BOOLEAN, &interactive,
606                         DBUS_TYPE_INVALID);
607 }
608
609 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
610         _cleanup_fclose_ FILE *f = NULL;
611         char _cleanup_strv_free_ **list = NULL;
612         char line[LINE_MAX];
613         enum {
614                 NONE,
615                 MODELS,
616                 LAYOUTS,
617                 VARIANTS,
618                 OPTIONS
619         } state = NONE, look_for;
620         int r;
621
622         if (n > 2) {
623                 log_error("Too many arguments.");
624                 return -EINVAL;
625         }
626
627         f = fopen("/usr/share/X11/xkb/rules/xorg.lst", "re");
628         if (!f) {
629                 log_error("Failed to open keyboard mapping list. %m");
630                 return -errno;
631         }
632
633         if (streq(args[0], "list-x11-keymap-models"))
634                 look_for = MODELS;
635         else if (streq(args[0], "list-x11-keymap-layouts"))
636                 look_for = LAYOUTS;
637         else if (streq(args[0], "list-x11-keymap-variants"))
638                 look_for = VARIANTS;
639         else if (streq(args[0], "list-x11-keymap-options"))
640                 look_for = OPTIONS;
641         else
642                 assert_not_reached("Wrong parameter");
643
644         FOREACH_LINE(line, f, break) {
645                 char *l, *w;
646
647                 l = strstrip(line);
648
649                 if (isempty(l))
650                         continue;
651
652                 if (l[0] == '!') {
653                         if (startswith(l, "! model"))
654                                 state = MODELS;
655                         else if (startswith(l, "! layout"))
656                                 state = LAYOUTS;
657                         else if (startswith(l, "! variant"))
658                                 state = VARIANTS;
659                         else if (startswith(l, "! option"))
660                                 state = OPTIONS;
661                         else
662                                 state = NONE;
663
664                         continue;
665                 }
666
667                 if (state != look_for)
668                         continue;
669
670                 w = l + strcspn(l, WHITESPACE);
671
672                 if (n > 1) {
673                         char *e;
674
675                         if (*w == 0)
676                                 continue;
677
678                         *w = 0;
679                         w++;
680                         w += strspn(w, WHITESPACE);
681
682                         e = strchr(w, ':');
683                         if (!e)
684                                 continue;
685
686                         *e = 0;
687
688                         if (!streq(w, args[1]))
689                                 continue;
690                 } else
691                         *w = 0;
692
693                  r = strv_extend(&list, l);
694                  if (r < 0)
695                          return log_oom();
696         }
697
698         if (strv_isempty(list)) {
699                 log_error("Couldn't find any entries.");
700                 return -ENOENT;
701         }
702
703         strv_sort(list);
704         strv_uniq(list);
705
706         pager_open_if_enabled();
707
708         strv_print(list);
709         return 0;
710 }
711
712 static int help(void) {
713
714         printf("%s [OPTIONS...] COMMAND ...\n\n"
715                "Query or change system locale and keyboard settings.\n\n"
716                "  -h --help                Show this help\n"
717                "     --version             Show package version\n"
718                "     --no-convert          Don't convert keyboard mappings\n"
719                "     --no-pager            Do not pipe output into a pager\n"
720                "     --no-ask-password     Do not prompt for password\n"
721                "  -H --host=[USER@]HOST    Operate on remote host\n\n"
722                "Commands:\n"
723                "  status                   Show current locale settings\n"
724                "  set-locale LOCALE...     Set system locale\n"
725                "  list-locales             Show known locales\n"
726                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
727                "  list-keymaps             Show known virtual console keyboard mappings\n"
728                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
729                "                           Set X11 keyboard mapping\n"
730                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
731                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
732                "  list-x11-keymap-variants [LAYOUT]\n"
733                "                           Show known X11 keyboard mapping variants\n"
734                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
735                program_invocation_short_name);
736
737         return 0;
738 }
739
740 static int parse_argv(int argc, char *argv[]) {
741
742         enum {
743                 ARG_VERSION = 0x100,
744                 ARG_NO_PAGER,
745                 ARG_NO_CONVERT,
746                 ARG_NO_ASK_PASSWORD
747         };
748
749         static const struct option options[] = {
750                 { "help",                no_argument,       NULL, 'h'                     },
751                 { "version",             no_argument,       NULL, ARG_VERSION             },
752                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
753                 { "host",                required_argument, NULL, 'H'                     },
754                 { "privileged",          no_argument,       NULL, 'P'                     },
755                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
756                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
757                 { NULL,                  0,                 NULL, 0                       }
758         };
759
760         int c;
761
762         assert(argc >= 0);
763         assert(argv);
764
765         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
766
767                 switch (c) {
768
769                 case 'h':
770                         help();
771                         return 0;
772
773                 case ARG_VERSION:
774                         puts(PACKAGE_STRING);
775                         puts(SYSTEMD_FEATURES);
776                         return 0;
777
778                 case 'P':
779                         arg_transport = TRANSPORT_POLKIT;
780                         break;
781
782                 case 'H':
783                         arg_transport = TRANSPORT_SSH;
784                         arg_host = optarg;
785                         break;
786
787                 case ARG_NO_CONVERT:
788                         arg_convert = false;
789                         break;
790
791                 case ARG_NO_PAGER:
792                         arg_no_pager = true;
793                         break;
794
795                 case '?':
796                         return -EINVAL;
797
798                 default:
799                         log_error("Unknown option code %c", c);
800                         return -EINVAL;
801                 }
802         }
803
804         return 1;
805 }
806
807 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
808
809         static const struct {
810                 const char* verb;
811                 const enum {
812                         MORE,
813                         LESS,
814                         EQUAL
815                 } argc_cmp;
816                 const int argc;
817                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
818         } verbs[] = {
819                 { "status",                   LESS,   1, show_status           },
820                 { "set-locale",               MORE,   2, set_locale            },
821                 { "list-locales",             EQUAL,  1, list_locales          },
822                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
823                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
824                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
825                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
826                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
827                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
828                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
829         };
830
831         int left;
832         unsigned i;
833
834         assert(argc >= 0);
835         assert(argv);
836         assert(error);
837
838         left = argc - optind;
839
840         if (left <= 0)
841                 /* Special rule: no arguments means "status" */
842                 i = 0;
843         else {
844                 if (streq(argv[optind], "help")) {
845                         help();
846                         return 0;
847                 }
848
849                 for (i = 0; i < ELEMENTSOF(verbs); i++)
850                         if (streq(argv[optind], verbs[i].verb))
851                                 break;
852
853                 if (i >= ELEMENTSOF(verbs)) {
854                         log_error("Unknown operation %s", argv[optind]);
855                         return -EINVAL;
856                 }
857         }
858
859         switch (verbs[i].argc_cmp) {
860
861         case EQUAL:
862                 if (left != verbs[i].argc) {
863                         log_error("Invalid number of arguments.");
864                         return -EINVAL;
865                 }
866
867                 break;
868
869         case MORE:
870                 if (left < verbs[i].argc) {
871                         log_error("Too few arguments.");
872                         return -EINVAL;
873                 }
874
875                 break;
876
877         case LESS:
878                 if (left > verbs[i].argc) {
879                         log_error("Too many arguments.");
880                         return -EINVAL;
881                 }
882
883                 break;
884
885         default:
886                 assert_not_reached("Unknown comparison operator.");
887         }
888
889         if (!bus) {
890                 log_error("Failed to get D-Bus connection: %s", error->message);
891                 return -EIO;
892         }
893
894         return verbs[i].dispatch(bus, argv + optind, left);
895 }
896
897 int main(int argc, char *argv[]) {
898         int r, retval = EXIT_FAILURE;
899         DBusConnection *bus = NULL;
900         DBusError error;
901
902         dbus_error_init(&error);
903
904         setlocale(LC_ALL, "");
905         log_parse_environment();
906         log_open();
907
908         r = parse_argv(argc, argv);
909         if (r < 0)
910                 goto finish;
911         else if (r == 0) {
912                 retval = EXIT_SUCCESS;
913                 goto finish;
914         }
915
916         if (arg_transport == TRANSPORT_NORMAL)
917                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
918         else if (arg_transport == TRANSPORT_POLKIT)
919                 bus_connect_system_polkit(&bus, &error);
920         else if (arg_transport == TRANSPORT_SSH)
921                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
922         else
923                 assert_not_reached("Uh, invalid transport...");
924
925         r = localectl_main(bus, argc, argv, &error);
926         retval = r < 0 ? EXIT_FAILURE : r;
927
928 finish:
929         if (bus) {
930                 dbus_connection_flush(bus);
931                 dbus_connection_close(bus);
932                 dbus_connection_unref(bus);
933         }
934
935         dbus_error_free(&error);
936         dbus_shutdown();
937
938         pager_close();
939
940         return retval;
941 }