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