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