chiark / gitweb /
move _cleanup_ attribute in front of the type
[elogind.git] / src / locale / localectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <locale.h>
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <string.h>
28 #include <ftw.h>
29 #include <sys/mman.h>
30 #include <fcntl.h>
31
32 #include "dbus-common.h"
33 #include "util.h"
34 #include "spawn-polkit-agent.h"
35 #include "build.h"
36 #include "strv.h"
37 #include "pager.h"
38 #include "set.h"
39 #include "path-util.h"
40 #include "utf8.h"
41
42 static bool arg_no_pager = false;
43 static enum transport {
44         TRANSPORT_NORMAL,
45         TRANSPORT_SSH,
46         TRANSPORT_POLKIT
47 } arg_transport = TRANSPORT_NORMAL;
48 static bool arg_ask_password = true;
49 static const char *arg_host = NULL;
50 static bool arg_convert = true;
51
52 static void pager_open_if_enabled(void) {
53
54         if (arg_no_pager)
55                 return;
56
57         pager_open(false);
58 }
59
60 static void polkit_agent_open_if_enabled(void) {
61
62         /* Open the polkit agent as a child process if necessary */
63
64         if (!arg_ask_password)
65                 return;
66
67         polkit_agent_open();
68 }
69
70 typedef struct StatusInfo {
71         char **locale;
72         const char *vconsole_keymap;
73         const char *vconsole_keymap_toggle;
74         const char *x11_layout;
75         const char *x11_model;
76         const char *x11_variant;
77         const char *x11_options;
78 } StatusInfo;
79
80 static void print_status_info(StatusInfo *i) {
81         assert(i);
82
83         if (strv_isempty(i->locale))
84                 puts("   System Locale: n/a\n");
85         else {
86                 char **j;
87
88                 printf("   System Locale: %s\n", i->locale[0]);
89                 STRV_FOREACH(j, i->locale + 1)
90                         printf("                  %s\n", *j);
91         }
92
93         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
94         if (!isempty(i->vconsole_keymap_toggle))
95                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
96
97         printf("      X11 Layout: %s\n", strna(i->x11_layout));
98         if (!isempty(i->x11_model))
99                 printf("       X11 Model: %s\n", i->x11_model);
100         if (!isempty(i->x11_variant))
101                 printf("     X11 Variant: %s\n", i->x11_variant);
102         if (!isempty(i->x11_options))
103                 printf("     X11 Options: %s\n", i->x11_options);
104 }
105
106 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
107         int r;
108
109         assert(name);
110         assert(iter);
111
112         switch (dbus_message_iter_get_arg_type(iter)) {
113
114         case DBUS_TYPE_STRING: {
115                 const char *s;
116
117                 dbus_message_iter_get_basic(iter, &s);
118                 if (!isempty(s)) {
119                         if (streq(name, "VConsoleKeymap"))
120                                 i->vconsole_keymap = s;
121                         else if (streq(name, "VConsoleKeymapToggle"))
122                                 i->vconsole_keymap_toggle = s;
123                         else if (streq(name, "X11Layout"))
124                                 i->x11_layout = s;
125                         else if (streq(name, "X11Model"))
126                                 i->x11_model = s;
127                         else if (streq(name, "X11Variant"))
128                                 i->x11_variant = s;
129                         else if (streq(name, "X11Options"))
130                                 i->x11_options = s;
131                 }
132                 break;
133         }
134
135         case DBUS_TYPE_ARRAY:
136
137                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
138                         char **l;
139
140                         r = bus_parse_strv_iter(iter, &l);
141                         if (r < 0)
142                                 return r;
143
144                         if (streq(name, "Locale")) {
145                                 strv_free(i->locale);
146                                 i->locale = l;
147                                 l = NULL;
148                         }
149
150                         strv_free(l);
151                 }
152         }
153
154         return 0;
155 }
156
157 static int show_status(DBusConnection *bus, char **args, unsigned n) {
158         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
159         const char *interface = "";
160         int r;
161         DBusMessageIter iter, sub, sub2, sub3;
162         StatusInfo info = {};
163
164         assert(args);
165
166         r = bus_method_call_with_reply(
167                         bus,
168                         "org.freedesktop.locale1",
169                         "/org/freedesktop/locale1",
170                         "org.freedesktop.DBus.Properties",
171                         "GetAll",
172                         &reply,
173                         NULL,
174                         DBUS_TYPE_STRING, &interface,
175                         DBUS_TYPE_INVALID);
176         if (r < 0)
177                 return r;
178
179         if (!dbus_message_iter_init(reply, &iter) ||
180             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
181             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
182                 log_error("Failed to parse reply.");
183                 return -EIO;
184         }
185
186         dbus_message_iter_recurse(&iter, &sub);
187
188         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
189                 const char *name;
190
191                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
192                         log_error("Failed to parse reply.");
193                         return -EIO;
194                 }
195
196                 dbus_message_iter_recurse(&sub, &sub2);
197
198                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
199                         log_error("Failed to parse reply.");
200                         return -EIO;
201                 }
202
203                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
204                         log_error("Failed to parse reply.");
205                         return -EIO;
206                 }
207
208                 dbus_message_iter_recurse(&sub2, &sub3);
209
210                 r = status_property(name, &sub3, &info);
211                 if (r < 0) {
212                         log_error("Failed to parse reply.");
213                         return r;
214                 }
215
216                 dbus_message_iter_next(&sub);
217         }
218
219         print_status_info(&info);
220         strv_free(info.locale);
221         return 0;
222 }
223
224 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
225         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
226         dbus_bool_t interactive = true;
227         DBusError error;
228         DBusMessageIter iter;
229         int r;
230
231         assert(bus);
232         assert(args);
233
234         dbus_error_init(&error);
235
236         polkit_agent_open_if_enabled();
237
238         m = dbus_message_new_method_call(
239                         "org.freedesktop.locale1",
240                         "/org/freedesktop/locale1",
241                         "org.freedesktop.locale1",
242                         "SetLocale");
243         if (!m)
244                 return log_oom();
245
246         dbus_message_iter_init_append(m, &iter);
247
248         r = bus_append_strv_iter(&iter, args + 1);
249         if (r < 0)
250                 return log_oom();
251
252         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
253                 return log_oom();
254
255         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
256         if (!reply) {
257                 log_error("Failed to issue method call: %s", bus_error_message(&error));
258                 r = -EIO;
259                 goto finish;
260         }
261
262         r = 0;
263
264 finish:
265         dbus_error_free(&error);
266         return r;
267 }
268
269 static int add_locales_from_archive(Set *locales) {
270         /* Stolen from glibc... */
271
272         struct locarhead {
273                 uint32_t magic;
274                 /* Serial number.  */
275                 uint32_t serial;
276                 /* Name hash table.  */
277                 uint32_t namehash_offset;
278                 uint32_t namehash_used;
279                 uint32_t namehash_size;
280                 /* String table.  */
281                 uint32_t string_offset;
282                 uint32_t string_used;
283                 uint32_t string_size;
284                 /* Table with locale records.  */
285                 uint32_t locrectab_offset;
286                 uint32_t locrectab_used;
287                 uint32_t locrectab_size;
288                 /* MD5 sum hash table.  */
289                 uint32_t sumhash_offset;
290                 uint32_t sumhash_used;
291                 uint32_t sumhash_size;
292         };
293
294         struct namehashent {
295                 /* Hash value of the name.  */
296                 uint32_t hashval;
297                 /* Offset of the name in the string table.  */
298                 uint32_t name_offset;
299                 /* Offset of the locale record.  */
300                 uint32_t locrec_offset;
301         };
302
303         const struct locarhead *h;
304         const struct namehashent *e;
305         const void *p = MAP_FAILED;
306         _cleanup_close_ int fd = -1;
307         size_t sz = 0;
308         struct stat st;
309         unsigned i;
310         int r;
311
312         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
313         if (fd < 0) {
314                 if (errno != ENOENT)
315                         log_error("Failed to open locale archive: %m");
316                 r = -errno;
317                 goto finish;
318         }
319
320         if (fstat(fd, &st) < 0) {
321                 log_error("fstat() failed: %m");
322                 r = -errno;
323                 goto finish;
324         }
325
326         if (!S_ISREG(st.st_mode)) {
327                 log_error("Archive file is not regular");
328                 r = -EBADMSG;
329                 goto finish;
330         }
331
332         if (st.st_size < (off_t) sizeof(struct locarhead)) {
333                 log_error("Archive has invalid size");
334                 r = -EBADMSG;
335                 goto finish;
336         }
337
338         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
339         if (p == MAP_FAILED) {
340                 log_error("Failed to map archive: %m");
341                 r = -errno;
342                 goto finish;
343         }
344
345         h = (const struct locarhead *) p;
346         if (h->magic != 0xde020109 ||
347             h->namehash_offset + h->namehash_size > st.st_size ||
348             h->string_offset + h->string_size > st.st_size ||
349             h->locrectab_offset + h->locrectab_size > st.st_size ||
350             h->sumhash_offset + h->sumhash_size > st.st_size) {
351                 log_error("Invalid archive file.");
352                 r = -EBADMSG;
353                 goto finish;
354         }
355
356         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
357         for (i = 0; i < h->namehash_size; i++) {
358                 char *z;
359
360                 if (e[i].locrec_offset == 0)
361                         continue;
362
363                 if (!utf8_is_valid((char*) p + e[i].name_offset))
364                         continue;
365
366                 z = strdup((char*) p + e[i].name_offset);
367                 if (!z) {
368                         r = log_oom();
369                         goto finish;
370                 }
371
372                 r = set_put(locales, z);
373                 if (r < 0) {
374                         free(z);
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_put(locales, z);
415                 if (r < 0) {
416                         free(z);
417
418                         if (r != -EEXIST) {
419                                 log_error("Failed to add locale: %s", strerror(-r));
420                                 return r;
421                         }
422                 }
423
424                 errno = 0;
425         }
426
427         if (errno > 0) {
428                 log_error("Failed to read locale directory: %m");
429                 return -errno;
430         }
431
432         return 0;
433 }
434
435 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
436         _cleanup_set_free_ Set *locales;
437         _cleanup_strv_free_ char **l = NULL;
438         int r;
439
440         locales = set_new(string_hash_func, string_compare_func);
441         if (!locales)
442                 return log_oom();
443
444         r = add_locales_from_archive(locales);
445         if (r < 0 && r != -ENOENT)
446                 return r;
447
448         r = add_locales_from_libdir(locales);
449         if (r < 0)
450                 return r;
451
452         l = set_get_strv(locales);
453         if (!l)
454                 return log_oom();
455
456         strv_sort(l);
457
458         pager_open_if_enabled();
459
460         strv_print(l);
461
462         return 0;
463 }
464
465 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
466         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
467         dbus_bool_t interactive = true, b;
468         const char *map, *toggle_map;
469
470         assert(bus);
471         assert(args);
472
473         if (n > 3) {
474                 log_error("Too many arguments.");
475                 return -EINVAL;
476         }
477
478         polkit_agent_open_if_enabled();
479
480         map = args[1];
481         toggle_map = n > 2 ? args[2] : "";
482         b = arg_convert;
483
484         return bus_method_call_with_reply(
485                         bus,
486                         "org.freedesktop.locale1",
487                         "/org/freedesktop/locale1",
488                         "org.freedesktop.locale1",
489                         "SetVConsoleKeyboard",
490                         &reply,
491                         NULL,
492                         DBUS_TYPE_STRING, &map,
493                         DBUS_TYPE_STRING, &toggle_map,
494                         DBUS_TYPE_BOOLEAN, &b,
495                         DBUS_TYPE_BOOLEAN, &interactive,
496                         DBUS_TYPE_INVALID);
497 }
498
499 static Set *keymaps = NULL;
500
501 static int nftw_cb(
502                 const char *fpath,
503                 const struct stat *sb,
504                 int tflag,
505                 struct FTW *ftwbuf) {
506
507         char *p, *e;
508         int r;
509
510         if (tflag != FTW_F)
511                 return 0;
512
513         if (!endswith(fpath, ".map") &&
514             !endswith(fpath, ".map.gz"))
515                 return 0;
516
517         p = strdup(path_get_file_name(fpath));
518         if (!p)
519                 return log_oom();
520
521         e = endswith(p, ".map");
522         if (e)
523                 *e = 0;
524
525         e = endswith(p, ".map.gz");
526         if (e)
527                 *e = 0;
528
529         r = set_put(keymaps, p);
530         if (r == -EEXIST)
531                 free(p);
532         else if (r < 0) {
533                 log_error("Can't add keymap: %s", strerror(-r));
534                 free(p);
535                 return r;
536         }
537
538         return 0;
539 }
540
541 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
542         _cleanup_strv_free_ char **l = NULL;
543
544         keymaps = set_new(string_hash_func, string_compare_func);
545         if (!keymaps)
546                 return log_oom();
547
548         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
549         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
550         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
551
552         l = set_get_strv(keymaps);
553         if (!l) {
554                 set_free_free(keymaps);
555                 return log_oom();
556         }
557
558         set_free(keymaps);
559
560         if (strv_isempty(l)) {
561                 log_error("Couldn't find any console keymaps.");
562                 return -ENOENT;
563         }
564
565         strv_sort(l);
566
567         pager_open_if_enabled();
568
569         strv_print(l);
570
571         return 0;
572 }
573
574 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
575         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
576         dbus_bool_t interactive = true, b;
577         const char *layout, *model, *variant, *options;
578
579         assert(bus);
580         assert(args);
581
582         if (n > 5) {
583                 log_error("Too many arguments.");
584                 return -EINVAL;
585         }
586
587         polkit_agent_open_if_enabled();
588
589         layout = args[1];
590         model = n > 2 ? args[2] : "";
591         variant = n > 3 ? args[3] : "";
592         options = n > 4 ? args[4] : "";
593         b = arg_convert;
594
595         return bus_method_call_with_reply(
596                         bus,
597                         "org.freedesktop.locale1",
598                         "/org/freedesktop/locale1",
599                         "org.freedesktop.locale1",
600                         "SetX11Keyboard",
601                         &reply,
602                         NULL,
603                         DBUS_TYPE_STRING, &layout,
604                         DBUS_TYPE_STRING, &model,
605                         DBUS_TYPE_STRING, &variant,
606                         DBUS_TYPE_STRING, &options,
607                         DBUS_TYPE_BOOLEAN, &b,
608                         DBUS_TYPE_BOOLEAN, &interactive,
609                         DBUS_TYPE_INVALID);
610 }
611
612 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
613         _cleanup_fclose_ FILE *f = NULL;
614         _cleanup_strv_free_ char **list = NULL;
615         char line[LINE_MAX];
616         enum {
617                 NONE,
618                 MODELS,
619                 LAYOUTS,
620                 VARIANTS,
621                 OPTIONS
622         } state = NONE, look_for;
623         int r;
624
625         if (n > 2) {
626                 log_error("Too many arguments.");
627                 return -EINVAL;
628         }
629
630         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
631         if (!f) {
632                 log_error("Failed to open keyboard mapping list. %m");
633                 return -errno;
634         }
635
636         if (streq(args[0], "list-x11-keymap-models"))
637                 look_for = MODELS;
638         else if (streq(args[0], "list-x11-keymap-layouts"))
639                 look_for = LAYOUTS;
640         else if (streq(args[0], "list-x11-keymap-variants"))
641                 look_for = VARIANTS;
642         else if (streq(args[0], "list-x11-keymap-options"))
643                 look_for = OPTIONS;
644         else
645                 assert_not_reached("Wrong parameter");
646
647         FOREACH_LINE(line, f, break) {
648                 char *l, *w;
649
650                 l = strstrip(line);
651
652                 if (isempty(l))
653                         continue;
654
655                 if (l[0] == '!') {
656                         if (startswith(l, "! model"))
657                                 state = MODELS;
658                         else if (startswith(l, "! layout"))
659                                 state = LAYOUTS;
660                         else if (startswith(l, "! variant"))
661                                 state = VARIANTS;
662                         else if (startswith(l, "! option"))
663                                 state = OPTIONS;
664                         else
665                                 state = NONE;
666
667                         continue;
668                 }
669
670                 if (state != look_for)
671                         continue;
672
673                 w = l + strcspn(l, WHITESPACE);
674
675                 if (n > 1) {
676                         char *e;
677
678                         if (*w == 0)
679                                 continue;
680
681                         *w = 0;
682                         w++;
683                         w += strspn(w, WHITESPACE);
684
685                         e = strchr(w, ':');
686                         if (!e)
687                                 continue;
688
689                         *e = 0;
690
691                         if (!streq(w, args[1]))
692                                 continue;
693                 } else
694                         *w = 0;
695
696                  r = strv_extend(&list, l);
697                  if (r < 0)
698                          return log_oom();
699         }
700
701         if (strv_isempty(list)) {
702                 log_error("Couldn't find any entries.");
703                 return -ENOENT;
704         }
705
706         strv_sort(list);
707         strv_uniq(list);
708
709         pager_open_if_enabled();
710
711         strv_print(list);
712         return 0;
713 }
714
715 static int help(void) {
716
717         printf("%s [OPTIONS...] COMMAND ...\n\n"
718                "Query or change system locale and keyboard settings.\n\n"
719                "  -h --help                Show this help\n"
720                "     --version             Show package version\n"
721                "     --no-convert          Don't convert keyboard mappings\n"
722                "     --no-pager            Do not pipe output into a pager\n"
723                "     --no-ask-password     Do not prompt for password\n"
724                "  -H --host=[USER@]HOST    Operate on remote host\n\n"
725                "Commands:\n"
726                "  status                   Show current locale settings\n"
727                "  set-locale LOCALE...     Set system locale\n"
728                "  list-locales             Show known locales\n"
729                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
730                "  list-keymaps             Show known virtual console keyboard mappings\n"
731                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
732                "                           Set X11 keyboard mapping\n"
733                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
734                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
735                "  list-x11-keymap-variants [LAYOUT]\n"
736                "                           Show known X11 keyboard mapping variants\n"
737                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
738                program_invocation_short_name);
739
740         return 0;
741 }
742
743 static int parse_argv(int argc, char *argv[]) {
744
745         enum {
746                 ARG_VERSION = 0x100,
747                 ARG_NO_PAGER,
748                 ARG_NO_CONVERT,
749                 ARG_NO_ASK_PASSWORD
750         };
751
752         static const struct option options[] = {
753                 { "help",                no_argument,       NULL, 'h'                     },
754                 { "version",             no_argument,       NULL, ARG_VERSION             },
755                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
756                 { "host",                required_argument, NULL, 'H'                     },
757                 { "privileged",          no_argument,       NULL, 'P'                     },
758                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
759                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
760                 { NULL,                  0,                 NULL, 0                       }
761         };
762
763         int c;
764
765         assert(argc >= 0);
766         assert(argv);
767
768         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
769
770                 switch (c) {
771
772                 case 'h':
773                         help();
774                         return 0;
775
776                 case ARG_VERSION:
777                         puts(PACKAGE_STRING);
778                         puts(SYSTEMD_FEATURES);
779                         return 0;
780
781                 case 'P':
782                         arg_transport = TRANSPORT_POLKIT;
783                         break;
784
785                 case 'H':
786                         arg_transport = TRANSPORT_SSH;
787                         arg_host = optarg;
788                         break;
789
790                 case ARG_NO_CONVERT:
791                         arg_convert = false;
792                         break;
793
794                 case ARG_NO_PAGER:
795                         arg_no_pager = true;
796                         break;
797
798                 case '?':
799                         return -EINVAL;
800
801                 default:
802                         log_error("Unknown option code %c", c);
803                         return -EINVAL;
804                 }
805         }
806
807         return 1;
808 }
809
810 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
811
812         static const struct {
813                 const char* verb;
814                 const enum {
815                         MORE,
816                         LESS,
817                         EQUAL
818                 } argc_cmp;
819                 const int argc;
820                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
821         } verbs[] = {
822                 { "status",                   LESS,   1, show_status           },
823                 { "set-locale",               MORE,   2, set_locale            },
824                 { "list-locales",             EQUAL,  1, list_locales          },
825                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
826                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
827                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
828                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
829                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
830                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
831                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
832         };
833
834         int left;
835         unsigned i;
836
837         assert(argc >= 0);
838         assert(argv);
839         assert(error);
840
841         left = argc - optind;
842
843         if (left <= 0)
844                 /* Special rule: no arguments means "status" */
845                 i = 0;
846         else {
847                 if (streq(argv[optind], "help")) {
848                         help();
849                         return 0;
850                 }
851
852                 for (i = 0; i < ELEMENTSOF(verbs); i++)
853                         if (streq(argv[optind], verbs[i].verb))
854                                 break;
855
856                 if (i >= ELEMENTSOF(verbs)) {
857                         log_error("Unknown operation %s", argv[optind]);
858                         return -EINVAL;
859                 }
860         }
861
862         switch (verbs[i].argc_cmp) {
863
864         case EQUAL:
865                 if (left != verbs[i].argc) {
866                         log_error("Invalid number of arguments.");
867                         return -EINVAL;
868                 }
869
870                 break;
871
872         case MORE:
873                 if (left < verbs[i].argc) {
874                         log_error("Too few arguments.");
875                         return -EINVAL;
876                 }
877
878                 break;
879
880         case LESS:
881                 if (left > verbs[i].argc) {
882                         log_error("Too many arguments.");
883                         return -EINVAL;
884                 }
885
886                 break;
887
888         default:
889                 assert_not_reached("Unknown comparison operator.");
890         }
891
892         if (!bus) {
893                 log_error("Failed to get D-Bus connection: %s", error->message);
894                 return -EIO;
895         }
896
897         return verbs[i].dispatch(bus, argv + optind, left);
898 }
899
900 int main(int argc, char *argv[]) {
901         int r, retval = EXIT_FAILURE;
902         DBusConnection *bus = NULL;
903         DBusError error;
904
905         dbus_error_init(&error);
906
907         setlocale(LC_ALL, "");
908         log_parse_environment();
909         log_open();
910
911         r = parse_argv(argc, argv);
912         if (r < 0)
913                 goto finish;
914         else if (r == 0) {
915                 retval = EXIT_SUCCESS;
916                 goto finish;
917         }
918
919         if (arg_transport == TRANSPORT_NORMAL)
920                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
921         else if (arg_transport == TRANSPORT_POLKIT)
922                 bus_connect_system_polkit(&bus, &error);
923         else if (arg_transport == TRANSPORT_SSH)
924                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
925         else
926                 assert_not_reached("Uh, invalid transport...");
927
928         r = localectl_main(bus, argc, argv, &error);
929         retval = r < 0 ? EXIT_FAILURE : r;
930
931 finish:
932         if (bus) {
933                 dbus_connection_flush(bus);
934                 dbus_connection_close(bus);
935                 dbus_connection_unref(bus);
936         }
937
938         dbus_error_free(&error);
939         dbus_shutdown();
940
941         pager_close();
942
943         return retval;
944 }