chiark / gitweb /
3096d060842d440ce9a7112a36dc1ea8a51d5128
[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                "  -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, "has:H: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 '?':
792                         return -EINVAL;
793
794                 default:
795                         log_error("Unknown option code %c", c);
796                         return -EINVAL;
797                 }
798         }
799
800         return 1;
801 }
802
803 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
804
805         static const struct {
806                 const char* verb;
807                 const enum {
808                         MORE,
809                         LESS,
810                         EQUAL
811                 } argc_cmp;
812                 const int argc;
813                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
814         } verbs[] = {
815                 { "status",                   LESS,   1, show_status           },
816                 { "set-locale",               MORE,   2, set_locale            },
817                 { "list-locales",             EQUAL,  1, list_locales          },
818                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
819                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
820                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
821                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
822                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
823                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
824                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
825         };
826
827         int left;
828         unsigned i;
829
830         assert(argc >= 0);
831         assert(argv);
832         assert(error);
833
834         left = argc - optind;
835
836         if (left <= 0)
837                 /* Special rule: no arguments means "status" */
838                 i = 0;
839         else {
840                 if (streq(argv[optind], "help")) {
841                         help();
842                         return 0;
843                 }
844
845                 for (i = 0; i < ELEMENTSOF(verbs); i++)
846                         if (streq(argv[optind], verbs[i].verb))
847                                 break;
848
849                 if (i >= ELEMENTSOF(verbs)) {
850                         log_error("Unknown operation %s", argv[optind]);
851                         return -EINVAL;
852                 }
853         }
854
855         switch (verbs[i].argc_cmp) {
856
857         case EQUAL:
858                 if (left != verbs[i].argc) {
859                         log_error("Invalid number of arguments.");
860                         return -EINVAL;
861                 }
862
863                 break;
864
865         case MORE:
866                 if (left < verbs[i].argc) {
867                         log_error("Too few arguments.");
868                         return -EINVAL;
869                 }
870
871                 break;
872
873         case LESS:
874                 if (left > verbs[i].argc) {
875                         log_error("Too many arguments.");
876                         return -EINVAL;
877                 }
878
879                 break;
880
881         default:
882                 assert_not_reached("Unknown comparison operator.");
883         }
884
885         if (!bus) {
886                 log_error("Failed to get D-Bus connection: %s", error->message);
887                 return -EIO;
888         }
889
890         return verbs[i].dispatch(bus, argv + optind, left);
891 }
892
893 int main(int argc, char *argv[]) {
894         int r, retval = EXIT_FAILURE;
895         DBusConnection *bus = NULL;
896         DBusError error;
897
898         dbus_error_init(&error);
899
900         setlocale(LC_ALL, "");
901         log_parse_environment();
902         log_open();
903
904         r = parse_argv(argc, argv);
905         if (r < 0)
906                 goto finish;
907         else if (r == 0) {
908                 retval = EXIT_SUCCESS;
909                 goto finish;
910         }
911
912         if (arg_transport == TRANSPORT_NORMAL)
913                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
914         else if (arg_transport == TRANSPORT_POLKIT)
915                 bus_connect_system_polkit(&bus, &error);
916         else if (arg_transport == TRANSPORT_SSH)
917                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
918         else
919                 assert_not_reached("Uh, invalid transport...");
920
921         r = localectl_main(bus, argc, argv, &error);
922         retval = r < 0 ? EXIT_FAILURE : r;
923
924 finish:
925         if (bus) {
926                 dbus_connection_flush(bus);
927                 dbus_connection_close(bus);
928                 dbus_connection_unref(bus);
929         }
930
931         dbus_error_free(&error);
932         dbus_shutdown();
933
934         pager_close();
935
936         return retval;
937 }