]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
Allow for the use of @ in remote host calls
[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 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/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
542 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
543 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
544
545 l = set_get_strv(keymaps);
546 if (!l) {
547 set_free_free(keymaps);
548 return log_oom();
549 }
550
551 set_free(keymaps);
552
553 if (strv_isempty(l)) {
554 log_error("Couldn't find any console keymaps.");
555 return -ENOENT;
556 }
557
558 strv_sort(l);
559
560 pager_open_if_enabled();
561
562 strv_print(l);
563
564 return 0;
565 }
566
567 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
568 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
569 dbus_bool_t interactive = arg_ask_password, b;
570 const char *layout, *model, *variant, *options;
571
572 assert(bus);
573 assert(args);
574
575 if (n > 5) {
576 log_error("Too many arguments.");
577 return -EINVAL;
578 }
579
580 polkit_agent_open_if_enabled();
581
582 layout = args[1];
583 model = n > 2 ? args[2] : "";
584 variant = n > 3 ? args[3] : "";
585 options = n > 4 ? args[4] : "";
586 b = arg_convert;
587
588 return bus_method_call_with_reply(
589 bus,
590 "org.freedesktop.locale1",
591 "/org/freedesktop/locale1",
592 "org.freedesktop.locale1",
593 "SetX11Keyboard",
594 &reply,
595 NULL,
596 DBUS_TYPE_STRING, &layout,
597 DBUS_TYPE_STRING, &model,
598 DBUS_TYPE_STRING, &variant,
599 DBUS_TYPE_STRING, &options,
600 DBUS_TYPE_BOOLEAN, &b,
601 DBUS_TYPE_BOOLEAN, &interactive,
602 DBUS_TYPE_INVALID);
603 }
604
605 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
606 _cleanup_fclose_ FILE *f = NULL;
607 _cleanup_strv_free_ char **list = NULL;
608 char line[LINE_MAX];
609 enum {
610 NONE,
611 MODELS,
612 LAYOUTS,
613 VARIANTS,
614 OPTIONS
615 } state = NONE, look_for;
616 int r;
617
618 if (n > 2) {
619 log_error("Too many arguments.");
620 return -EINVAL;
621 }
622
623 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
624 if (!f) {
625 log_error("Failed to open keyboard mapping list. %m");
626 return -errno;
627 }
628
629 if (streq(args[0], "list-x11-keymap-models"))
630 look_for = MODELS;
631 else if (streq(args[0], "list-x11-keymap-layouts"))
632 look_for = LAYOUTS;
633 else if (streq(args[0], "list-x11-keymap-variants"))
634 look_for = VARIANTS;
635 else if (streq(args[0], "list-x11-keymap-options"))
636 look_for = OPTIONS;
637 else
638 assert_not_reached("Wrong parameter");
639
640 FOREACH_LINE(line, f, break) {
641 char *l, *w;
642
643 l = strstrip(line);
644
645 if (isempty(l))
646 continue;
647
648 if (l[0] == '!') {
649 if (startswith(l, "! model"))
650 state = MODELS;
651 else if (startswith(l, "! layout"))
652 state = LAYOUTS;
653 else if (startswith(l, "! variant"))
654 state = VARIANTS;
655 else if (startswith(l, "! option"))
656 state = OPTIONS;
657 else
658 state = NONE;
659
660 continue;
661 }
662
663 if (state != look_for)
664 continue;
665
666 w = l + strcspn(l, WHITESPACE);
667
668 if (n > 1) {
669 char *e;
670
671 if (*w == 0)
672 continue;
673
674 *w = 0;
675 w++;
676 w += strspn(w, WHITESPACE);
677
678 e = strchr(w, ':');
679 if (!e)
680 continue;
681
682 *e = 0;
683
684 if (!streq(w, args[1]))
685 continue;
686 } else
687 *w = 0;
688
689 r = strv_extend(&list, l);
690 if (r < 0)
691 return log_oom();
692 }
693
694 if (strv_isempty(list)) {
695 log_error("Couldn't find any entries.");
696 return -ENOENT;
697 }
698
699 strv_sort(list);
700 strv_uniq(list);
701
702 pager_open_if_enabled();
703
704 strv_print(list);
705 return 0;
706 }
707
708 static int help(void) {
709
710 printf("%s [OPTIONS...] COMMAND ...\n\n"
711 "Query or change system locale and keyboard settings.\n\n"
712 " -h --help Show this help\n"
713 " --version Show package version\n"
714 " --no-convert Don't convert keyboard mappings\n"
715 " --no-pager Do not pipe output into a pager\n"
716 " -P --privileged Acquire privileges before execution\n"
717 " --no-ask-password Do not prompt for password\n"
718 " -H --host=[USER@]HOST Operate on remote host\n\n"
719 "Commands:\n"
720 " status Show current locale settings\n"
721 " set-locale LOCALE... Set system locale\n"
722 " list-locales Show known locales\n"
723 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
724 " list-keymaps Show known virtual console keyboard mappings\n"
725 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
726 " Set X11 keyboard mapping\n"
727 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
728 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
729 " list-x11-keymap-variants [LAYOUT]\n"
730 " Show known X11 keyboard mapping variants\n"
731 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
732 program_invocation_short_name);
733
734 return 0;
735 }
736
737 static int parse_argv(int argc, char *argv[]) {
738
739 enum {
740 ARG_VERSION = 0x100,
741 ARG_NO_PAGER,
742 ARG_NO_CONVERT,
743 ARG_NO_ASK_PASSWORD
744 };
745
746 static const struct option options[] = {
747 { "help", no_argument, NULL, 'h' },
748 { "version", no_argument, NULL, ARG_VERSION },
749 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
750 { "host", required_argument, NULL, 'H' },
751 { "privileged", no_argument, NULL, 'P' },
752 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
753 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
754 { NULL, 0, NULL, 0 }
755 };
756
757 int c;
758
759 assert(argc >= 0);
760 assert(argv);
761
762 while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
763
764 switch (c) {
765
766 case 'h':
767 help();
768 return 0;
769
770 case ARG_VERSION:
771 puts(PACKAGE_STRING);
772 puts(SYSTEMD_FEATURES);
773 return 0;
774
775 case 'P':
776 arg_transport = TRANSPORT_POLKIT;
777 break;
778
779 case 'H':
780 arg_transport = TRANSPORT_SSH;
781 parse_user_at_host(optarg, &arg_user, &arg_host);
782 break;
783
784 case ARG_NO_CONVERT:
785 arg_convert = false;
786 break;
787
788 case ARG_NO_PAGER:
789 arg_no_pager = true;
790 break;
791
792 case ARG_NO_ASK_PASSWORD:
793 arg_ask_password = false;
794 break;
795
796 case '?':
797 return -EINVAL;
798
799 default:
800 log_error("Unknown option code %c", c);
801 return -EINVAL;
802 }
803 }
804
805 return 1;
806 }
807
808 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
809
810 static const struct {
811 const char* verb;
812 const enum {
813 MORE,
814 LESS,
815 EQUAL
816 } argc_cmp;
817 const int argc;
818 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
819 } verbs[] = {
820 { "status", LESS, 1, show_status },
821 { "set-locale", MORE, 2, set_locale },
822 { "list-locales", EQUAL, 1, list_locales },
823 { "set-keymap", MORE, 2, set_vconsole_keymap },
824 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
825 { "set-x11-keymap", MORE, 2, set_x11_keymap },
826 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
827 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
828 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
829 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
830 };
831
832 int left;
833 unsigned i;
834
835 assert(argc >= 0);
836 assert(argv);
837 assert(error);
838
839 left = argc - optind;
840
841 if (left <= 0)
842 /* Special rule: no arguments means "status" */
843 i = 0;
844 else {
845 if (streq(argv[optind], "help")) {
846 help();
847 return 0;
848 }
849
850 for (i = 0; i < ELEMENTSOF(verbs); i++)
851 if (streq(argv[optind], verbs[i].verb))
852 break;
853
854 if (i >= ELEMENTSOF(verbs)) {
855 log_error("Unknown operation %s", argv[optind]);
856 return -EINVAL;
857 }
858 }
859
860 switch (verbs[i].argc_cmp) {
861
862 case EQUAL:
863 if (left != verbs[i].argc) {
864 log_error("Invalid number of arguments.");
865 return -EINVAL;
866 }
867
868 break;
869
870 case MORE:
871 if (left < verbs[i].argc) {
872 log_error("Too few arguments.");
873 return -EINVAL;
874 }
875
876 break;
877
878 case LESS:
879 if (left > verbs[i].argc) {
880 log_error("Too many arguments.");
881 return -EINVAL;
882 }
883
884 break;
885
886 default:
887 assert_not_reached("Unknown comparison operator.");
888 }
889
890 if (!bus) {
891 log_error("Failed to get D-Bus connection: %s", error->message);
892 return -EIO;
893 }
894
895 return verbs[i].dispatch(bus, argv + optind, left);
896 }
897
898 int main(int argc, char *argv[]) {
899 int r, retval = EXIT_FAILURE;
900 DBusConnection *bus = NULL;
901 DBusError error;
902
903 dbus_error_init(&error);
904
905 setlocale(LC_ALL, "");
906 log_parse_environment();
907 log_open();
908
909 r = parse_argv(argc, argv);
910 if (r < 0)
911 goto finish;
912 else if (r == 0) {
913 retval = EXIT_SUCCESS;
914 goto finish;
915 }
916
917 if (arg_transport == TRANSPORT_NORMAL)
918 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
919 else if (arg_transport == TRANSPORT_POLKIT)
920 bus_connect_system_polkit(&bus, &error);
921 else if (arg_transport == TRANSPORT_SSH)
922 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
923 else
924 assert_not_reached("Uh, invalid transport...");
925
926 r = localectl_main(bus, argc, argv, &error);
927 retval = r < 0 ? EXIT_FAILURE : r;
928
929 finish:
930 if (bus) {
931 dbus_connection_flush(bus);
932 dbus_connection_close(bus);
933 dbus_connection_unref(bus);
934 }
935
936 dbus_error_free(&error);
937 dbus_shutdown();
938
939 pager_close();
940
941 return retval;
942 }