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