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