]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
localctl: skip locale entries with non-UTF8 names
[thirdparty/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 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 DIR _cleanup_closedir_ *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 char _cleanup_strv_free_ **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 char _cleanup_strv_free_ **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/xorg.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 }