chiark / gitweb /
b5cd344397c859d5b92555ca26d161898cefd9df
[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 = arg_ask_password;
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 = arg_ask_password, 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 = arg_ask_password, 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                "  -P --privileged          Acquire privileges before execution\n"
716                "     --no-ask-password     Do not prompt for password\n"
717                "  -H --host=[USER@]HOST    Operate on remote host\n\n"
718                "Commands:\n"
719                "  status                   Show current locale settings\n"
720                "  set-locale LOCALE...     Set system locale\n"
721                "  list-locales             Show known locales\n"
722                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
723                "  list-keymaps             Show known virtual console keyboard mappings\n"
724                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
725                "                           Set X11 keyboard mapping\n"
726                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
727                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
728                "  list-x11-keymap-variants [LAYOUT]\n"
729                "                           Show known X11 keyboard mapping variants\n"
730                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
731                program_invocation_short_name);
732
733         return 0;
734 }
735
736 static int parse_argv(int argc, char *argv[]) {
737
738         enum {
739                 ARG_VERSION = 0x100,
740                 ARG_NO_PAGER,
741                 ARG_NO_CONVERT,
742                 ARG_NO_ASK_PASSWORD
743         };
744
745         static const struct option options[] = {
746                 { "help",                no_argument,       NULL, 'h'                     },
747                 { "version",             no_argument,       NULL, ARG_VERSION             },
748                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
749                 { "host",                required_argument, NULL, 'H'                     },
750                 { "privileged",          no_argument,       NULL, 'P'                     },
751                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
752                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
753                 { NULL,                  0,                 NULL, 0                       }
754         };
755
756         int c;
757
758         assert(argc >= 0);
759         assert(argv);
760
761         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
762
763                 switch (c) {
764
765                 case 'h':
766                         help();
767                         return 0;
768
769                 case ARG_VERSION:
770                         puts(PACKAGE_STRING);
771                         puts(SYSTEMD_FEATURES);
772                         return 0;
773
774                 case 'P':
775                         arg_transport = TRANSPORT_POLKIT;
776                         break;
777
778                 case 'H':
779                         arg_transport = TRANSPORT_SSH;
780                         arg_host = optarg;
781                         break;
782
783                 case ARG_NO_CONVERT:
784                         arg_convert = false;
785                         break;
786
787                 case ARG_NO_PAGER:
788                         arg_no_pager = true;
789                         break;
790
791                 case ARG_NO_ASK_PASSWORD:
792                         arg_ask_password = false;
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 }