chiark / gitweb /
Use initalization instead of explicit zeroing
[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         dbus_message_iter_recurse(&iter, &sub);
186
187         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
188                 const char *name;
189
190                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
191                         log_error("Failed to parse reply.");
192                         return -EIO;
193                 }
194
195                 dbus_message_iter_recurse(&sub, &sub2);
196
197                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
198                         log_error("Failed to parse reply.");
199                         return -EIO;
200                 }
201
202                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
203                         log_error("Failed to parse reply.");
204                         return -EIO;
205                 }
206
207                 dbus_message_iter_recurse(&sub2, &sub3);
208
209                 r = status_property(name, &sub3, &info);
210                 if (r < 0) {
211                         log_error("Failed to parse reply.");
212                         return r;
213                 }
214
215                 dbus_message_iter_next(&sub);
216         }
217
218         print_status_info(&info);
219         strv_free(info.locale);
220         return 0;
221 }
222
223 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
224         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
225         dbus_bool_t interactive = true;
226         DBusError error;
227         DBusMessageIter iter;
228         int r;
229
230         assert(bus);
231         assert(args);
232
233         dbus_error_init(&error);
234
235         polkit_agent_open_if_enabled();
236
237         m = dbus_message_new_method_call(
238                         "org.freedesktop.locale1",
239                         "/org/freedesktop/locale1",
240                         "org.freedesktop.locale1",
241                         "SetLocale");
242         if (!m)
243                 return log_oom();
244
245         dbus_message_iter_init_append(m, &iter);
246
247         r = bus_append_strv_iter(&iter, args + 1);
248         if (r < 0)
249                 return log_oom();
250
251         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
252                 return log_oom();
253
254         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
255         if (!reply) {
256                 log_error("Failed to issue method call: %s", bus_error_message(&error));
257                 r = -EIO;
258                 goto finish;
259         }
260
261         r = 0;
262
263 finish:
264         dbus_error_free(&error);
265         return r;
266 }
267
268 static int add_locales_from_archive(Set *locales) {
269         /* Stolen from glibc... */
270
271         struct locarhead {
272                 uint32_t magic;
273                 /* Serial number.  */
274                 uint32_t serial;
275                 /* Name hash table.  */
276                 uint32_t namehash_offset;
277                 uint32_t namehash_used;
278                 uint32_t namehash_size;
279                 /* String table.  */
280                 uint32_t string_offset;
281                 uint32_t string_used;
282                 uint32_t string_size;
283                 /* Table with locale records.  */
284                 uint32_t locrectab_offset;
285                 uint32_t locrectab_used;
286                 uint32_t locrectab_size;
287                 /* MD5 sum hash table.  */
288                 uint32_t sumhash_offset;
289                 uint32_t sumhash_used;
290                 uint32_t sumhash_size;
291         };
292
293         struct namehashent {
294                 /* Hash value of the name.  */
295                 uint32_t hashval;
296                 /* Offset of the name in the string table.  */
297                 uint32_t name_offset;
298                 /* Offset of the locale record.  */
299                 uint32_t locrec_offset;
300         };
301
302         const struct locarhead *h;
303         const struct namehashent *e;
304         const void *p = MAP_FAILED;
305         _cleanup_close_ int fd = -1;
306         size_t sz = 0;
307         struct stat st;
308         unsigned i;
309         int r;
310
311         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
312         if (fd < 0) {
313                 if (errno != ENOENT)
314                         log_error("Failed to open locale archive: %m");
315                 r = -errno;
316                 goto finish;
317         }
318
319         if (fstat(fd, &st) < 0) {
320                 log_error("fstat() failed: %m");
321                 r = -errno;
322                 goto finish;
323         }
324
325         if (!S_ISREG(st.st_mode)) {
326                 log_error("Archive file is not regular");
327                 r = -EBADMSG;
328                 goto finish;
329         }
330
331         if (st.st_size < (off_t) sizeof(struct locarhead)) {
332                 log_error("Archive has invalid size");
333                 r = -EBADMSG;
334                 goto finish;
335         }
336
337         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
338         if (p == MAP_FAILED) {
339                 log_error("Failed to map archive: %m");
340                 r = -errno;
341                 goto finish;
342         }
343
344         h = (const struct locarhead *) p;
345         if (h->magic != 0xde020109 ||
346             h->namehash_offset + h->namehash_size > st.st_size ||
347             h->string_offset + h->string_size > st.st_size ||
348             h->locrectab_offset + h->locrectab_size > st.st_size ||
349             h->sumhash_offset + h->sumhash_size > st.st_size) {
350                 log_error("Invalid archive file.");
351                 r = -EBADMSG;
352                 goto finish;
353         }
354
355         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
356         for (i = 0; i < h->namehash_size; i++) {
357                 char *z;
358
359                 if (e[i].locrec_offset == 0)
360                         continue;
361
362                 z = strdup((char*) p + e[i].name_offset);
363                 if (!z) {
364                         r = log_oom();
365                         goto finish;
366                 }
367
368                 r = set_put(locales, z);
369                 if (r < 0) {
370                         free(z);
371                         log_error("Failed to add locale: %s", strerror(-r));
372                         goto finish;
373                 }
374         }
375
376         r = 0;
377
378  finish:
379         if (p != MAP_FAILED)
380                 munmap((void*) p, sz);
381
382         return r;
383 }
384
385 static int add_locales_from_libdir (Set *locales) {
386         DIR _cleanup_closedir_ *dir;
387         struct dirent *entry;
388         int r;
389
390         dir = opendir("/usr/lib/locale");
391         if (!dir) {
392                 log_error("Failed to open locale directory: %m");
393                 return -errno;
394         }
395
396         errno = 0;
397         while ((entry = readdir(dir))) {
398                 char *z;
399
400                 if (entry->d_type != DT_DIR)
401                         continue;
402
403                 if (ignore_file(entry->d_name))
404                         continue;
405
406                 z = strdup(entry->d_name);
407                 if (!z)
408                         return log_oom();
409
410                 r = set_put(locales, z);
411                 if (r < 0) {
412                         free(z);
413
414                         if (r != -EEXIST) {
415                                 log_error("Failed to add locale: %s", strerror(-r));
416                                 return r;
417                         }
418                 }
419
420                 errno = 0;
421         }
422
423         if (errno > 0) {
424                 log_error("Failed to read locale directory: %m");
425                 return -errno;
426         }
427
428         return 0;
429 }
430
431 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
432         _cleanup_set_free_ Set *locales;
433         _cleanup_strv_free_ char **l = NULL;
434         int r;
435
436         locales = set_new(string_hash_func, string_compare_func);
437         if (!locales)
438                 return log_oom();
439
440         r = add_locales_from_archive(locales);
441         if (r < 0 && r != -ENOENT)
442                 return r;
443
444         r = add_locales_from_libdir(locales);
445         if (r < 0)
446                 return r;
447
448         l = set_get_strv(locales);
449         if (!l)
450                 return log_oom();
451
452         strv_sort(l);
453
454         pager_open_if_enabled();
455
456         strv_print(l);
457
458         return 0;
459 }
460
461 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
462         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
463         dbus_bool_t interactive = true, b;
464         const char *map, *toggle_map;
465
466         assert(bus);
467         assert(args);
468
469         if (n > 3) {
470                 log_error("Too many arguments.");
471                 return -EINVAL;
472         }
473
474         polkit_agent_open_if_enabled();
475
476         map = args[1];
477         toggle_map = n > 2 ? args[2] : "";
478         b = arg_convert;
479
480         return bus_method_call_with_reply(
481                         bus,
482                         "org.freedesktop.locale1",
483                         "/org/freedesktop/locale1",
484                         "org.freedesktop.locale1",
485                         "SetVConsoleKeyboard",
486                         &reply,
487                         NULL,
488                         DBUS_TYPE_STRING, &map,
489                         DBUS_TYPE_STRING, &toggle_map,
490                         DBUS_TYPE_BOOLEAN, &b,
491                         DBUS_TYPE_BOOLEAN, &interactive,
492                         DBUS_TYPE_INVALID);
493 }
494
495 static Set *keymaps = NULL;
496
497 static int nftw_cb(
498                 const char *fpath,
499                 const struct stat *sb,
500                 int tflag,
501                 struct FTW *ftwbuf) {
502
503         char *p, *e;
504         int r;
505
506         if (tflag != FTW_F)
507                 return 0;
508
509         if (!endswith(fpath, ".map") &&
510             !endswith(fpath, ".map.gz"))
511                 return 0;
512
513         p = strdup(path_get_file_name(fpath));
514         if (!p)
515                 return log_oom();
516
517         e = endswith(p, ".map");
518         if (e)
519                 *e = 0;
520
521         e = endswith(p, ".map.gz");
522         if (e)
523                 *e = 0;
524
525         r = set_put(keymaps, p);
526         if (r == -EEXIST)
527                 free(p);
528         else if (r < 0) {
529                 log_error("Can't add keymap: %s", strerror(-r));
530                 free(p);
531                 return r;
532         }
533
534         return 0;
535 }
536
537 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
538         char _cleanup_strv_free_ **l = NULL;
539
540         keymaps = set_new(string_hash_func, string_compare_func);
541         if (!keymaps)
542                 return log_oom();
543
544         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
545         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
546         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
547
548         l = set_get_strv(keymaps);
549         if (!l) {
550                 set_free_free(keymaps);
551                 return log_oom();
552         }
553
554         set_free(keymaps);
555
556         if (strv_isempty(l)) {
557                 log_error("Couldn't find any console keymaps.");
558                 return -ENOENT;
559         }
560
561         strv_sort(l);
562
563         pager_open_if_enabled();
564
565         strv_print(l);
566
567         return 0;
568 }
569
570 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
571         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
572         dbus_bool_t interactive = true, b;
573         const char *layout, *model, *variant, *options;
574
575         assert(bus);
576         assert(args);
577
578         if (n > 5) {
579                 log_error("Too many arguments.");
580                 return -EINVAL;
581         }
582
583         polkit_agent_open_if_enabled();
584
585         layout = args[1];
586         model = n > 2 ? args[2] : "";
587         variant = n > 3 ? args[3] : "";
588         options = n > 4 ? args[4] : "";
589         b = arg_convert;
590
591         return bus_method_call_with_reply(
592                         bus,
593                         "org.freedesktop.locale1",
594                         "/org/freedesktop/locale1",
595                         "org.freedesktop.locale1",
596                         "SetX11Keyboard",
597                         &reply,
598                         NULL,
599                         DBUS_TYPE_STRING, &layout,
600                         DBUS_TYPE_STRING, &model,
601                         DBUS_TYPE_STRING, &variant,
602                         DBUS_TYPE_STRING, &options,
603                         DBUS_TYPE_BOOLEAN, &b,
604                         DBUS_TYPE_BOOLEAN, &interactive,
605                         DBUS_TYPE_INVALID);
606 }
607
608 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
609         _cleanup_fclose_ FILE *f = NULL;
610         char _cleanup_strv_free_ **list = NULL;
611         char line[LINE_MAX];
612         enum {
613                 NONE,
614                 MODELS,
615                 LAYOUTS,
616                 VARIANTS,
617                 OPTIONS
618         } state = NONE, look_for;
619         int r;
620
621         if (n > 2) {
622                 log_error("Too many arguments.");
623                 return -EINVAL;
624         }
625
626         f = fopen("/usr/share/X11/xkb/rules/xorg.lst", "re");
627         if (!f) {
628                 log_error("Failed to open keyboard mapping list. %m");
629                 return -errno;
630         }
631
632         if (streq(args[0], "list-x11-keymap-models"))
633                 look_for = MODELS;
634         else if (streq(args[0], "list-x11-keymap-layouts"))
635                 look_for = LAYOUTS;
636         else if (streq(args[0], "list-x11-keymap-variants"))
637                 look_for = VARIANTS;
638         else if (streq(args[0], "list-x11-keymap-options"))
639                 look_for = OPTIONS;
640         else
641                 assert_not_reached("Wrong parameter");
642
643         FOREACH_LINE(line, f, break) {
644                 char *l, *w;
645
646                 l = strstrip(line);
647
648                 if (isempty(l))
649                         continue;
650
651                 if (l[0] == '!') {
652                         if (startswith(l, "! model"))
653                                 state = MODELS;
654                         else if (startswith(l, "! layout"))
655                                 state = LAYOUTS;
656                         else if (startswith(l, "! variant"))
657                                 state = VARIANTS;
658                         else if (startswith(l, "! option"))
659                                 state = OPTIONS;
660                         else
661                                 state = NONE;
662
663                         continue;
664                 }
665
666                 if (state != look_for)
667                         continue;
668
669                 w = l + strcspn(l, WHITESPACE);
670
671                 if (n > 1) {
672                         char *e;
673
674                         if (*w == 0)
675                                 continue;
676
677                         *w = 0;
678                         w++;
679                         w += strspn(w, WHITESPACE);
680
681                         e = strchr(w, ':');
682                         if (!e)
683                                 continue;
684
685                         *e = 0;
686
687                         if (!streq(w, args[1]))
688                                 continue;
689                 } else
690                         *w = 0;
691
692                  r = strv_extend(&list, l);
693                  if (r < 0)
694                          return log_oom();
695         }
696
697         if (strv_isempty(list)) {
698                 log_error("Couldn't find any entries.");
699                 return -ENOENT;
700         }
701
702         strv_sort(list);
703         strv_uniq(list);
704
705         pager_open_if_enabled();
706
707         strv_print(list);
708         return 0;
709 }
710
711 static int help(void) {
712
713         printf("%s [OPTIONS...] COMMAND ...\n\n"
714                "Query or change system locale and keyboard settings.\n\n"
715                "  -h --help                Show this help\n"
716                "     --version             Show package version\n"
717                "     --no-convert          Don't convert keyboard mappings\n"
718                "     --no-pager            Do not pipe output into a pager\n"
719                "     --no-ask-password     Do not prompt for password\n"
720                "  -H --host=[USER@]HOST    Operate on remote host\n\n"
721                "Commands:\n"
722                "  status                   Show current locale settings\n"
723                "  set-locale LOCALE...     Set system locale\n"
724                "  list-locales             Show known locales\n"
725                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
726                "  list-keymaps             Show known virtual console keyboard mappings\n"
727                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
728                "                           Set X11 keyboard mapping\n"
729                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
730                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
731                "  list-x11-keymap-variants [LAYOUT]\n"
732                "                           Show known X11 keyboard mapping variants\n"
733                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
734                program_invocation_short_name);
735
736         return 0;
737 }
738
739 static int parse_argv(int argc, char *argv[]) {
740
741         enum {
742                 ARG_VERSION = 0x100,
743                 ARG_NO_PAGER,
744                 ARG_NO_CONVERT,
745                 ARG_NO_ASK_PASSWORD
746         };
747
748         static const struct option options[] = {
749                 { "help",                no_argument,       NULL, 'h'                     },
750                 { "version",             no_argument,       NULL, ARG_VERSION             },
751                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
752                 { "host",                required_argument, NULL, 'H'                     },
753                 { "privileged",          no_argument,       NULL, 'P'                     },
754                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
755                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
756                 { NULL,                  0,                 NULL, 0                       }
757         };
758
759         int c;
760
761         assert(argc >= 0);
762         assert(argv);
763
764         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
765
766                 switch (c) {
767
768                 case 'h':
769                         help();
770                         return 0;
771
772                 case ARG_VERSION:
773                         puts(PACKAGE_STRING);
774                         puts(SYSTEMD_FEATURES);
775                         return 0;
776
777                 case 'P':
778                         arg_transport = TRANSPORT_POLKIT;
779                         break;
780
781                 case 'H':
782                         arg_transport = TRANSPORT_SSH;
783                         arg_host = optarg;
784                         break;
785
786                 case ARG_NO_CONVERT:
787                         arg_convert = false;
788                         break;
789
790                 case ARG_NO_PAGER:
791                         arg_no_pager = true;
792                         break;
793
794                 case '?':
795                         return -EINVAL;
796
797                 default:
798                         log_error("Unknown option code %c", c);
799                         return -EINVAL;
800                 }
801         }
802
803         return 1;
804 }
805
806 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
807
808         static const struct {
809                 const char* verb;
810                 const enum {
811                         MORE,
812                         LESS,
813                         EQUAL
814                 } argc_cmp;
815                 const int argc;
816                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
817         } verbs[] = {
818                 { "status",                   LESS,   1, show_status           },
819                 { "set-locale",               MORE,   2, set_locale            },
820                 { "list-locales",             EQUAL,  1, list_locales          },
821                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
822                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
823                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
824                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
825                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
826                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
827                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
828         };
829
830         int left;
831         unsigned i;
832
833         assert(argc >= 0);
834         assert(argv);
835         assert(error);
836
837         left = argc - optind;
838
839         if (left <= 0)
840                 /* Special rule: no arguments means "status" */
841                 i = 0;
842         else {
843                 if (streq(argv[optind], "help")) {
844                         help();
845                         return 0;
846                 }
847
848                 for (i = 0; i < ELEMENTSOF(verbs); i++)
849                         if (streq(argv[optind], verbs[i].verb))
850                                 break;
851
852                 if (i >= ELEMENTSOF(verbs)) {
853                         log_error("Unknown operation %s", argv[optind]);
854                         return -EINVAL;
855                 }
856         }
857
858         switch (verbs[i].argc_cmp) {
859
860         case EQUAL:
861                 if (left != verbs[i].argc) {
862                         log_error("Invalid number of arguments.");
863                         return -EINVAL;
864                 }
865
866                 break;
867
868         case MORE:
869                 if (left < verbs[i].argc) {
870                         log_error("Too few arguments.");
871                         return -EINVAL;
872                 }
873
874                 break;
875
876         case LESS:
877                 if (left > verbs[i].argc) {
878                         log_error("Too many arguments.");
879                         return -EINVAL;
880                 }
881
882                 break;
883
884         default:
885                 assert_not_reached("Unknown comparison operator.");
886         }
887
888         if (!bus) {
889                 log_error("Failed to get D-Bus connection: %s", error->message);
890                 return -EIO;
891         }
892
893         return verbs[i].dispatch(bus, argv + optind, left);
894 }
895
896 int main(int argc, char *argv[]) {
897         int r, retval = EXIT_FAILURE;
898         DBusConnection *bus = NULL;
899         DBusError error;
900
901         dbus_error_init(&error);
902
903         setlocale(LC_ALL, "");
904         log_parse_environment();
905         log_open();
906
907         r = parse_argv(argc, argv);
908         if (r < 0)
909                 goto finish;
910         else if (r == 0) {
911                 retval = EXIT_SUCCESS;
912                 goto finish;
913         }
914
915         if (arg_transport == TRANSPORT_NORMAL)
916                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
917         else if (arg_transport == TRANSPORT_POLKIT)
918                 bus_connect_system_polkit(&bus, &error);
919         else if (arg_transport == TRANSPORT_SSH)
920                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
921         else
922                 assert_not_reached("Uh, invalid transport...");
923
924         r = localectl_main(bus, argc, argv, &error);
925         retval = r < 0 ? EXIT_FAILURE : r;
926
927 finish:
928         if (bus) {
929                 dbus_connection_flush(bus);
930                 dbus_connection_close(bus);
931                 dbus_connection_unref(bus);
932         }
933
934         dbus_error_free(&error);
935         dbus_shutdown();
936
937         pager_close();
938
939         return retval;
940 }