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