chiark / gitweb /
Allow for the use of @ in remote host calls
[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 char *arg_host = NULL;
50 static char *arg_user = NULL;
51 static bool arg_convert = true;
52
53 static void pager_open_if_enabled(void) {
54
55         if (arg_no_pager)
56                 return;
57
58         pager_open(false);
59 }
60
61 static void polkit_agent_open_if_enabled(void) {
62
63         /* Open the polkit agent as a child process if necessary */
64
65         if (!arg_ask_password)
66                 return;
67
68         polkit_agent_open();
69 }
70
71 typedef struct StatusInfo {
72         char **locale;
73         const char *vconsole_keymap;
74         const char *vconsole_keymap_toggle;
75         const char *x11_layout;
76         const char *x11_model;
77         const char *x11_variant;
78         const char *x11_options;
79 } StatusInfo;
80
81 static void print_status_info(StatusInfo *i) {
82         assert(i);
83
84         if (strv_isempty(i->locale))
85                 puts("   System Locale: n/a\n");
86         else {
87                 char **j;
88
89                 printf("   System Locale: %s\n", i->locale[0]);
90                 STRV_FOREACH(j, i->locale + 1)
91                         printf("                  %s\n", *j);
92         }
93
94         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
95         if (!isempty(i->vconsole_keymap_toggle))
96                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
97
98         printf("      X11 Layout: %s\n", strna(i->x11_layout));
99         if (!isempty(i->x11_model))
100                 printf("       X11 Model: %s\n", i->x11_model);
101         if (!isempty(i->x11_variant))
102                 printf("     X11 Variant: %s\n", i->x11_variant);
103         if (!isempty(i->x11_options))
104                 printf("     X11 Options: %s\n", i->x11_options);
105 }
106
107 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
108         int r;
109
110         assert(name);
111         assert(iter);
112
113         switch (dbus_message_iter_get_arg_type(iter)) {
114
115         case DBUS_TYPE_STRING: {
116                 const char *s;
117
118                 dbus_message_iter_get_basic(iter, &s);
119                 if (!isempty(s)) {
120                         if (streq(name, "VConsoleKeymap"))
121                                 i->vconsole_keymap = s;
122                         else if (streq(name, "VConsoleKeymapToggle"))
123                                 i->vconsole_keymap_toggle = s;
124                         else if (streq(name, "X11Layout"))
125                                 i->x11_layout = s;
126                         else if (streq(name, "X11Model"))
127                                 i->x11_model = s;
128                         else if (streq(name, "X11Variant"))
129                                 i->x11_variant = s;
130                         else if (streq(name, "X11Options"))
131                                 i->x11_options = s;
132                 }
133                 break;
134         }
135
136         case DBUS_TYPE_ARRAY:
137
138                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
139                         char **l;
140
141                         r = bus_parse_strv_iter(iter, &l);
142                         if (r < 0)
143                                 return r;
144
145                         if (streq(name, "Locale")) {
146                                 strv_free(i->locale);
147                                 i->locale = l;
148                                 l = NULL;
149                         }
150
151                         strv_free(l);
152                 }
153         }
154
155         return 0;
156 }
157
158 static int show_status(DBusConnection *bus, char **args, unsigned n) {
159         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
160         const char *interface = "";
161         int r;
162         DBusMessageIter iter, sub, sub2, sub3;
163         StatusInfo info = {};
164
165         assert(args);
166
167         r = bus_method_call_with_reply(
168                         bus,
169                         "org.freedesktop.locale1",
170                         "/org/freedesktop/locale1",
171                         "org.freedesktop.DBus.Properties",
172                         "GetAll",
173                         &reply,
174                         NULL,
175                         DBUS_TYPE_STRING, &interface,
176                         DBUS_TYPE_INVALID);
177         if (r < 0)
178                 return r;
179
180         if (!dbus_message_iter_init(reply, &iter) ||
181             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
182             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
183                 log_error("Failed to parse reply.");
184                 return -EIO;
185         }
186
187         dbus_message_iter_recurse(&iter, &sub);
188
189         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
190                 const char *name;
191
192                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
193                         log_error("Failed to parse reply.");
194                         return -EIO;
195                 }
196
197                 dbus_message_iter_recurse(&sub, &sub2);
198
199                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
200                         log_error("Failed to parse reply.");
201                         return -EIO;
202                 }
203
204                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
205                         log_error("Failed to parse reply.");
206                         return -EIO;
207                 }
208
209                 dbus_message_iter_recurse(&sub2, &sub3);
210
211                 r = status_property(name, &sub3, &info);
212                 if (r < 0) {
213                         log_error("Failed to parse reply.");
214                         return r;
215                 }
216
217                 dbus_message_iter_next(&sub);
218         }
219
220         print_status_info(&info);
221         strv_free(info.locale);
222         return 0;
223 }
224
225 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
226         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
227         dbus_bool_t interactive = arg_ask_password;
228         DBusError error;
229         DBusMessageIter iter;
230         int r;
231
232         assert(bus);
233         assert(args);
234
235         dbus_error_init(&error);
236
237         polkit_agent_open_if_enabled();
238
239         m = dbus_message_new_method_call(
240                         "org.freedesktop.locale1",
241                         "/org/freedesktop/locale1",
242                         "org.freedesktop.locale1",
243                         "SetLocale");
244         if (!m)
245                 return log_oom();
246
247         dbus_message_iter_init_append(m, &iter);
248
249         r = bus_append_strv_iter(&iter, args + 1);
250         if (r < 0)
251                 return log_oom();
252
253         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
254                 return log_oom();
255
256         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
257         if (!reply) {
258                 log_error("Failed to issue method call: %s", bus_error_message(&error));
259                 r = -EIO;
260                 goto finish;
261         }
262
263         r = 0;
264
265 finish:
266         dbus_error_free(&error);
267         return r;
268 }
269
270 static int add_locales_from_archive(Set *locales) {
271         /* Stolen from glibc... */
272
273         struct locarhead {
274                 uint32_t magic;
275                 /* Serial number.  */
276                 uint32_t serial;
277                 /* Name hash table.  */
278                 uint32_t namehash_offset;
279                 uint32_t namehash_used;
280                 uint32_t namehash_size;
281                 /* String table.  */
282                 uint32_t string_offset;
283                 uint32_t string_used;
284                 uint32_t string_size;
285                 /* Table with locale records.  */
286                 uint32_t locrectab_offset;
287                 uint32_t locrectab_used;
288                 uint32_t locrectab_size;
289                 /* MD5 sum hash table.  */
290                 uint32_t sumhash_offset;
291                 uint32_t sumhash_used;
292                 uint32_t sumhash_size;
293         };
294
295         struct namehashent {
296                 /* Hash value of the name.  */
297                 uint32_t hashval;
298                 /* Offset of the name in the string table.  */
299                 uint32_t name_offset;
300                 /* Offset of the locale record.  */
301                 uint32_t locrec_offset;
302         };
303
304         const struct locarhead *h;
305         const struct namehashent *e;
306         const void *p = MAP_FAILED;
307         _cleanup_close_ int fd = -1;
308         size_t sz = 0;
309         struct stat st;
310         unsigned i;
311         int r;
312
313         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
314         if (fd < 0) {
315                 if (errno != ENOENT)
316                         log_error("Failed to open locale archive: %m");
317                 r = -errno;
318                 goto finish;
319         }
320
321         if (fstat(fd, &st) < 0) {
322                 log_error("fstat() failed: %m");
323                 r = -errno;
324                 goto finish;
325         }
326
327         if (!S_ISREG(st.st_mode)) {
328                 log_error("Archive file is not regular");
329                 r = -EBADMSG;
330                 goto finish;
331         }
332
333         if (st.st_size < (off_t) sizeof(struct locarhead)) {
334                 log_error("Archive has invalid size");
335                 r = -EBADMSG;
336                 goto finish;
337         }
338
339         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
340         if (p == MAP_FAILED) {
341                 log_error("Failed to map archive: %m");
342                 r = -errno;
343                 goto finish;
344         }
345
346         h = (const struct locarhead *) p;
347         if (h->magic != 0xde020109 ||
348             h->namehash_offset + h->namehash_size > st.st_size ||
349             h->string_offset + h->string_size > st.st_size ||
350             h->locrectab_offset + h->locrectab_size > st.st_size ||
351             h->sumhash_offset + h->sumhash_size > st.st_size) {
352                 log_error("Invalid archive file.");
353                 r = -EBADMSG;
354                 goto finish;
355         }
356
357         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
358         for (i = 0; i < h->namehash_size; i++) {
359                 char *z;
360
361                 if (e[i].locrec_offset == 0)
362                         continue;
363
364                 if (!utf8_is_valid((char*) p + e[i].name_offset))
365                         continue;
366
367                 z = strdup((char*) p + e[i].name_offset);
368                 if (!z) {
369                         r = log_oom();
370                         goto finish;
371                 }
372
373                 r = set_consume(locales, z);
374                 if (r < 0) {
375                         log_error("Failed to add locale: %s", strerror(-r));
376                         goto finish;
377                 }
378         }
379
380         r = 0;
381
382  finish:
383         if (p != MAP_FAILED)
384                 munmap((void*) p, sz);
385
386         return r;
387 }
388
389 static int add_locales_from_libdir (Set *locales) {
390         _cleanup_closedir_ DIR *dir;
391         struct dirent *entry;
392         int r;
393
394         dir = opendir("/usr/lib/locale");
395         if (!dir) {
396                 log_error("Failed to open locale directory: %m");
397                 return -errno;
398         }
399
400         errno = 0;
401         while ((entry = readdir(dir))) {
402                 char *z;
403
404                 if (entry->d_type != DT_DIR)
405                         continue;
406
407                 if (ignore_file(entry->d_name))
408                         continue;
409
410                 z = strdup(entry->d_name);
411                 if (!z)
412                         return log_oom();
413
414                 r = set_consume(locales, z);
415                 if (r < 0 && r != -EEXIST) {
416                         log_error("Failed to add locale: %s", strerror(-r));
417                         return r;
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 = arg_ask_password, 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_consume(keymaps, p);
526         if (r < 0 && r != -EEXIST) {
527                 log_error("Can't add keymap: %s", strerror(-r));
528                 return r;
529         }
530
531         return 0;
532 }
533
534 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
535         _cleanup_strv_free_ char **l = NULL;
536
537         keymaps = set_new(string_hash_func, string_compare_func);
538         if (!keymaps)
539                 return log_oom();
540
541         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
542         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
543         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
544
545         l = set_get_strv(keymaps);
546         if (!l) {
547                 set_free_free(keymaps);
548                 return log_oom();
549         }
550
551         set_free(keymaps);
552
553         if (strv_isempty(l)) {
554                 log_error("Couldn't find any console keymaps.");
555                 return -ENOENT;
556         }
557
558         strv_sort(l);
559
560         pager_open_if_enabled();
561
562         strv_print(l);
563
564         return 0;
565 }
566
567 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
568         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
569         dbus_bool_t interactive = arg_ask_password, b;
570         const char *layout, *model, *variant, *options;
571
572         assert(bus);
573         assert(args);
574
575         if (n > 5) {
576                 log_error("Too many arguments.");
577                 return -EINVAL;
578         }
579
580         polkit_agent_open_if_enabled();
581
582         layout = args[1];
583         model = n > 2 ? args[2] : "";
584         variant = n > 3 ? args[3] : "";
585         options = n > 4 ? args[4] : "";
586         b = arg_convert;
587
588         return bus_method_call_with_reply(
589                         bus,
590                         "org.freedesktop.locale1",
591                         "/org/freedesktop/locale1",
592                         "org.freedesktop.locale1",
593                         "SetX11Keyboard",
594                         &reply,
595                         NULL,
596                         DBUS_TYPE_STRING, &layout,
597                         DBUS_TYPE_STRING, &model,
598                         DBUS_TYPE_STRING, &variant,
599                         DBUS_TYPE_STRING, &options,
600                         DBUS_TYPE_BOOLEAN, &b,
601                         DBUS_TYPE_BOOLEAN, &interactive,
602                         DBUS_TYPE_INVALID);
603 }
604
605 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
606         _cleanup_fclose_ FILE *f = NULL;
607         _cleanup_strv_free_ char **list = NULL;
608         char line[LINE_MAX];
609         enum {
610                 NONE,
611                 MODELS,
612                 LAYOUTS,
613                 VARIANTS,
614                 OPTIONS
615         } state = NONE, look_for;
616         int r;
617
618         if (n > 2) {
619                 log_error("Too many arguments.");
620                 return -EINVAL;
621         }
622
623         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
624         if (!f) {
625                 log_error("Failed to open keyboard mapping list. %m");
626                 return -errno;
627         }
628
629         if (streq(args[0], "list-x11-keymap-models"))
630                 look_for = MODELS;
631         else if (streq(args[0], "list-x11-keymap-layouts"))
632                 look_for = LAYOUTS;
633         else if (streq(args[0], "list-x11-keymap-variants"))
634                 look_for = VARIANTS;
635         else if (streq(args[0], "list-x11-keymap-options"))
636                 look_for = OPTIONS;
637         else
638                 assert_not_reached("Wrong parameter");
639
640         FOREACH_LINE(line, f, break) {
641                 char *l, *w;
642
643                 l = strstrip(line);
644
645                 if (isempty(l))
646                         continue;
647
648                 if (l[0] == '!') {
649                         if (startswith(l, "! model"))
650                                 state = MODELS;
651                         else if (startswith(l, "! layout"))
652                                 state = LAYOUTS;
653                         else if (startswith(l, "! variant"))
654                                 state = VARIANTS;
655                         else if (startswith(l, "! option"))
656                                 state = OPTIONS;
657                         else
658                                 state = NONE;
659
660                         continue;
661                 }
662
663                 if (state != look_for)
664                         continue;
665
666                 w = l + strcspn(l, WHITESPACE);
667
668                 if (n > 1) {
669                         char *e;
670
671                         if (*w == 0)
672                                 continue;
673
674                         *w = 0;
675                         w++;
676                         w += strspn(w, WHITESPACE);
677
678                         e = strchr(w, ':');
679                         if (!e)
680                                 continue;
681
682                         *e = 0;
683
684                         if (!streq(w, args[1]))
685                                 continue;
686                 } else
687                         *w = 0;
688
689                  r = strv_extend(&list, l);
690                  if (r < 0)
691                          return log_oom();
692         }
693
694         if (strv_isempty(list)) {
695                 log_error("Couldn't find any entries.");
696                 return -ENOENT;
697         }
698
699         strv_sort(list);
700         strv_uniq(list);
701
702         pager_open_if_enabled();
703
704         strv_print(list);
705         return 0;
706 }
707
708 static int help(void) {
709
710         printf("%s [OPTIONS...] COMMAND ...\n\n"
711                "Query or change system locale and keyboard settings.\n\n"
712                "  -h --help                Show this help\n"
713                "     --version             Show package version\n"
714                "     --no-convert          Don't convert keyboard mappings\n"
715                "     --no-pager            Do not pipe output into a pager\n"
716                "  -P --privileged          Acquire privileges before execution\n"
717                "     --no-ask-password     Do not prompt for password\n"
718                "  -H --host=[USER@]HOST    Operate on remote host\n\n"
719                "Commands:\n"
720                "  status                   Show current locale settings\n"
721                "  set-locale LOCALE...     Set system locale\n"
722                "  list-locales             Show known locales\n"
723                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
724                "  list-keymaps             Show known virtual console keyboard mappings\n"
725                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
726                "                           Set X11 keyboard mapping\n"
727                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
728                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
729                "  list-x11-keymap-variants [LAYOUT]\n"
730                "                           Show known X11 keyboard mapping variants\n"
731                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
732                program_invocation_short_name);
733
734         return 0;
735 }
736
737 static int parse_argv(int argc, char *argv[]) {
738
739         enum {
740                 ARG_VERSION = 0x100,
741                 ARG_NO_PAGER,
742                 ARG_NO_CONVERT,
743                 ARG_NO_ASK_PASSWORD
744         };
745
746         static const struct option options[] = {
747                 { "help",                no_argument,       NULL, 'h'                     },
748                 { "version",             no_argument,       NULL, ARG_VERSION             },
749                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
750                 { "host",                required_argument, NULL, 'H'                     },
751                 { "privileged",          no_argument,       NULL, 'P'                     },
752                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
753                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
754                 { NULL,                  0,                 NULL, 0                       }
755         };
756
757         int c;
758
759         assert(argc >= 0);
760         assert(argv);
761
762         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
763
764                 switch (c) {
765
766                 case 'h':
767                         help();
768                         return 0;
769
770                 case ARG_VERSION:
771                         puts(PACKAGE_STRING);
772                         puts(SYSTEMD_FEATURES);
773                         return 0;
774
775                 case 'P':
776                         arg_transport = TRANSPORT_POLKIT;
777                         break;
778
779                 case 'H':
780                         arg_transport = TRANSPORT_SSH;
781                         parse_user_at_host(optarg, &arg_user, &arg_host);
782                         break;
783
784                 case ARG_NO_CONVERT:
785                         arg_convert = false;
786                         break;
787
788                 case ARG_NO_PAGER:
789                         arg_no_pager = true;
790                         break;
791
792                 case ARG_NO_ASK_PASSWORD:
793                         arg_ask_password = false;
794                         break;
795
796                 case '?':
797                         return -EINVAL;
798
799                 default:
800                         log_error("Unknown option code %c", c);
801                         return -EINVAL;
802                 }
803         }
804
805         return 1;
806 }
807
808 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
809
810         static const struct {
811                 const char* verb;
812                 const enum {
813                         MORE,
814                         LESS,
815                         EQUAL
816                 } argc_cmp;
817                 const int argc;
818                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
819         } verbs[] = {
820                 { "status",                   LESS,   1, show_status           },
821                 { "set-locale",               MORE,   2, set_locale            },
822                 { "list-locales",             EQUAL,  1, list_locales          },
823                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
824                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
825                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
826                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
827                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
828                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
829                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
830         };
831
832         int left;
833         unsigned i;
834
835         assert(argc >= 0);
836         assert(argv);
837         assert(error);
838
839         left = argc - optind;
840
841         if (left <= 0)
842                 /* Special rule: no arguments means "status" */
843                 i = 0;
844         else {
845                 if (streq(argv[optind], "help")) {
846                         help();
847                         return 0;
848                 }
849
850                 for (i = 0; i < ELEMENTSOF(verbs); i++)
851                         if (streq(argv[optind], verbs[i].verb))
852                                 break;
853
854                 if (i >= ELEMENTSOF(verbs)) {
855                         log_error("Unknown operation %s", argv[optind]);
856                         return -EINVAL;
857                 }
858         }
859
860         switch (verbs[i].argc_cmp) {
861
862         case EQUAL:
863                 if (left != verbs[i].argc) {
864                         log_error("Invalid number of arguments.");
865                         return -EINVAL;
866                 }
867
868                 break;
869
870         case MORE:
871                 if (left < verbs[i].argc) {
872                         log_error("Too few arguments.");
873                         return -EINVAL;
874                 }
875
876                 break;
877
878         case LESS:
879                 if (left > verbs[i].argc) {
880                         log_error("Too many arguments.");
881                         return -EINVAL;
882                 }
883
884                 break;
885
886         default:
887                 assert_not_reached("Unknown comparison operator.");
888         }
889
890         if (!bus) {
891                 log_error("Failed to get D-Bus connection: %s", error->message);
892                 return -EIO;
893         }
894
895         return verbs[i].dispatch(bus, argv + optind, left);
896 }
897
898 int main(int argc, char *argv[]) {
899         int r, retval = EXIT_FAILURE;
900         DBusConnection *bus = NULL;
901         DBusError error;
902
903         dbus_error_init(&error);
904
905         setlocale(LC_ALL, "");
906         log_parse_environment();
907         log_open();
908
909         r = parse_argv(argc, argv);
910         if (r < 0)
911                 goto finish;
912         else if (r == 0) {
913                 retval = EXIT_SUCCESS;
914                 goto finish;
915         }
916
917         if (arg_transport == TRANSPORT_NORMAL)
918                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
919         else if (arg_transport == TRANSPORT_POLKIT)
920                 bus_connect_system_polkit(&bus, &error);
921         else if (arg_transport == TRANSPORT_SSH)
922                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
923         else
924                 assert_not_reached("Uh, invalid transport...");
925
926         r = localectl_main(bus, argc, argv, &error);
927         retval = r < 0 ? EXIT_FAILURE : r;
928
929 finish:
930         if (bus) {
931                 dbus_connection_flush(bus);
932                 dbus_connection_close(bus);
933                 dbus_connection_unref(bus);
934         }
935
936         dbus_error_free(&error);
937         dbus_shutdown();
938
939         pager_close();
940
941         return retval;
942 }