]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localectl.c
Unify parse_argv style
[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"
0732ef7a 45#include "def.h"
75683450 46#include "locale-util.h"
2087a7af
LP
47
48static bool arg_no_pager = false;
2087a7af 49static bool arg_ask_password = true;
4d7859d1 50static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
7085053a 51static char *arg_host = NULL;
2087a7af
LP
52static bool arg_convert = true;
53
54static void pager_open_if_enabled(void) {
55
56 if (arg_no_pager)
57 return;
58
1b12a7b5 59 pager_open(false);
2087a7af
LP
60}
61
62static void polkit_agent_open_if_enabled(void) {
63
64 /* Open the polkit agent as a child process if necessary */
2087a7af
LP
65 if (!arg_ask_password)
66 return;
67
46e65dcc
LP
68 if (arg_transport != BUS_TRANSPORT_LOCAL)
69 return;
70
2087a7af
LP
71 polkit_agent_open();
72}
73
74typedef struct StatusInfo {
75 char **locale;
76 const char *vconsole_keymap;
77 const char *vconsole_keymap_toggle;
78 const char *x11_layout;
79 const char *x11_model;
80 const char *x11_variant;
81 const char *x11_options;
82} StatusInfo;
83
84static void print_status_info(StatusInfo *i) {
85 assert(i);
86
87 if (strv_isempty(i->locale))
88 puts(" System Locale: n/a\n");
89 else {
90 char **j;
91
92 printf(" System Locale: %s\n", i->locale[0]);
93 STRV_FOREACH(j, i->locale + 1)
94 printf(" %s\n", *j);
95 }
96
97 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
98 if (!isempty(i->vconsole_keymap_toggle))
99 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
100
101 printf(" X11 Layout: %s\n", strna(i->x11_layout));
102 if (!isempty(i->x11_model))
103 printf(" X11 Model: %s\n", i->x11_model);
104 if (!isempty(i->x11_variant))
105 printf(" X11 Variant: %s\n", i->x11_variant);
106 if (!isempty(i->x11_options))
107 printf(" X11 Options: %s\n", i->x11_options);
108}
109
4d7859d1 110static int show_status(sd_bus *bus, char **args, unsigned n) {
b92bea5d 111 StatusInfo info = {};
9f6eb1cd
KS
112 static const struct bus_properties_map map[] = {
113 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
114 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
115 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
116 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
117 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
118 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
119 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
120 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
ffc06c35
KS
121 {}
122 };
123 int r;
2087a7af 124
ffc06c35 125 assert(bus);
2087a7af 126
ffc06c35
KS
127 r = bus_map_all_properties(bus,
128 "org.freedesktop.locale1",
129 "/org/freedesktop/locale1",
9f6eb1cd
KS
130 map,
131 &info);
6046278f
DH
132 if (r < 0) {
133 log_error("Could not get properties: %s", strerror(-r));
4d7859d1 134 goto fail;
6046278f 135 }
2087a7af 136
2087a7af 137 print_status_info(&info);
4d7859d1
KS
138
139fail:
2087a7af 140 strv_free(info.locale);
4d7859d1 141 return r;
2087a7af
LP
142}
143
4d7859d1
KS
144static int set_locale(sd_bus *bus, char **args, unsigned n) {
145 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
4d7859d1 146 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af
LP
147 int r;
148
149 assert(bus);
150 assert(args);
151
2087a7af
LP
152 polkit_agent_open_if_enabled();
153
151b9b96
LP
154 r = sd_bus_message_new_method_call(
155 bus,
156 &m,
2087a7af
LP
157 "org.freedesktop.locale1",
158 "/org/freedesktop/locale1",
159 "org.freedesktop.locale1",
151b9b96 160 "SetLocale");
4d7859d1 161 if (r < 0)
94676f3e 162 return bus_log_create_error(r);
2087a7af 163
4d7859d1
KS
164 r = sd_bus_message_append_strv(m, args + 1);
165 if (r < 0)
94676f3e 166 return bus_log_create_error(r);
2087a7af 167
4d7859d1 168 r = sd_bus_message_append(m, "b", arg_ask_password);
2087a7af 169 if (r < 0)
94676f3e 170 return bus_log_create_error(r);
2087a7af 171
c49b30a2 172 r = sd_bus_call(bus, m, 0, &error, NULL);
4d7859d1 173 if (r < 0) {
e1636421 174 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
4d7859d1 175 return r;
2087a7af
LP
176 }
177
4d7859d1 178 return 0;
2087a7af
LP
179}
180
4d7859d1 181static int list_locales(sd_bus *bus, char **args, unsigned n) {
17d33cec 182 _cleanup_strv_free_ char **l = NULL;
17d33cec
GC
183 int r;
184
75683450 185 assert(args);
17d33cec 186
75683450
LP
187 r = get_locales(&l);
188 if (r < 0) {
189 log_error("Failed to read list of locales: %s", strerror(-r));
bac3c8ee 190 return r;
75683450 191 }
2087a7af
LP
192
193 pager_open_if_enabled();
7c2d8094 194 strv_print(l);
2087a7af 195
bac3c8ee 196 return 0;
2087a7af
LP
197}
198
4d7859d1 199static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
4d7859d1 200 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af 201 const char *map, *toggle_map;
e1636421 202 int r;
2087a7af
LP
203
204 assert(bus);
205 assert(args);
206
207 if (n > 3) {
208 log_error("Too many arguments.");
209 return -EINVAL;
210 }
211
212 polkit_agent_open_if_enabled();
213
214 map = args[1];
215 toggle_map = n > 2 ? args[2] : "";
2087a7af 216
e1636421
LP
217 r = sd_bus_call_method(
218 bus,
2087a7af
LP
219 "org.freedesktop.locale1",
220 "/org/freedesktop/locale1",
221 "org.freedesktop.locale1",
222 "SetVConsoleKeyboard",
4d7859d1 223 &error,
2087a7af 224 NULL,
4d7859d1 225 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
e1636421
LP
226 if (r < 0)
227 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
228
229 return r;
2087a7af
LP
230}
231
232static Set *keymaps = NULL;
233
234static int nftw_cb(
235 const char *fpath,
236 const struct stat *sb,
237 int tflag,
238 struct FTW *ftwbuf) {
239
240 char *p, *e;
241 int r;
242
243 if (tflag != FTW_F)
244 return 0;
245
246 if (!endswith(fpath, ".map") &&
247 !endswith(fpath, ".map.gz"))
248 return 0;
249
2b6bf07d 250 p = strdup(basename(fpath));
2087a7af
LP
251 if (!p)
252 return log_oom();
253
254 e = endswith(p, ".map");
255 if (e)
256 *e = 0;
257
258 e = endswith(p, ".map.gz");
259 if (e)
260 *e = 0;
261
ef42202a
ZJS
262 r = set_consume(keymaps, p);
263 if (r < 0 && r != -EEXIST) {
2087a7af 264 log_error("Can't add keymap: %s", strerror(-r));
2087a7af
LP
265 return r;
266 }
267
268 return 0;
269}
270
4d7859d1 271static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
7fd1b19b 272 _cleanup_strv_free_ char **l = NULL;
0732ef7a 273 const char *dir;
2087a7af
LP
274
275 keymaps = set_new(string_hash_func, string_compare_func);
276 if (!keymaps)
277 return log_oom();
278
0732ef7a
ZJS
279 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
280 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
2087a7af
LP
281
282 l = set_get_strv(keymaps);
283 if (!l) {
284 set_free_free(keymaps);
285 return log_oom();
286 }
287
288 set_free(keymaps);
289
290 if (strv_isempty(l)) {
291 log_error("Couldn't find any console keymaps.");
292 return -ENOENT;
293 }
294
295 strv_sort(l);
296
297 pager_open_if_enabled();
298
7c2d8094 299 strv_print(l);
2087a7af
LP
300
301 return 0;
302}
303
4d7859d1 304static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
4d7859d1 305 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af 306 const char *layout, *model, *variant, *options;
e1636421 307 int r;
2087a7af
LP
308
309 assert(bus);
310 assert(args);
311
312 if (n > 5) {
313 log_error("Too many arguments.");
314 return -EINVAL;
315 }
316
317 polkit_agent_open_if_enabled();
318
319 layout = args[1];
320 model = n > 2 ? args[2] : "";
321 variant = n > 3 ? args[3] : "";
6b2b6f30 322 options = n > 4 ? args[4] : "";
2087a7af 323
e1636421
LP
324 r = sd_bus_call_method(
325 bus,
2087a7af
LP
326 "org.freedesktop.locale1",
327 "/org/freedesktop/locale1",
328 "org.freedesktop.locale1",
329 "SetX11Keyboard",
4d7859d1 330 &error,
2087a7af 331 NULL,
4d7859d1
KS
332 "ssssbb", layout, model, variant, options,
333 arg_convert, arg_ask_password);
e1636421
LP
334 if (r < 0)
335 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
336
337 return r;
2087a7af
LP
338}
339
4d7859d1 340static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
50cfc579 341 _cleanup_fclose_ FILE *f = NULL;
7fd1b19b 342 _cleanup_strv_free_ char **list = NULL;
50cfc579
LP
343 char line[LINE_MAX];
344 enum {
345 NONE,
346 MODELS,
347 LAYOUTS,
348 VARIANTS,
349 OPTIONS
350 } state = NONE, look_for;
351 int r;
352
353 if (n > 2) {
354 log_error("Too many arguments.");
355 return -EINVAL;
356 }
357
c62e11ce 358 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
50cfc579
LP
359 if (!f) {
360 log_error("Failed to open keyboard mapping list. %m");
361 return -errno;
362 }
363
364 if (streq(args[0], "list-x11-keymap-models"))
365 look_for = MODELS;
366 else if (streq(args[0], "list-x11-keymap-layouts"))
367 look_for = LAYOUTS;
368 else if (streq(args[0], "list-x11-keymap-variants"))
369 look_for = VARIANTS;
370 else if (streq(args[0], "list-x11-keymap-options"))
371 look_for = OPTIONS;
372 else
373 assert_not_reached("Wrong parameter");
374
375 FOREACH_LINE(line, f, break) {
376 char *l, *w;
377
378 l = strstrip(line);
379
380 if (isempty(l))
381 continue;
382
383 if (l[0] == '!') {
384 if (startswith(l, "! model"))
385 state = MODELS;
386 else if (startswith(l, "! layout"))
387 state = LAYOUTS;
388 else if (startswith(l, "! variant"))
389 state = VARIANTS;
390 else if (startswith(l, "! option"))
391 state = OPTIONS;
392 else
393 state = NONE;
394
395 continue;
396 }
397
398 if (state != look_for)
399 continue;
400
401 w = l + strcspn(l, WHITESPACE);
402
403 if (n > 1) {
404 char *e;
405
406 if (*w == 0)
407 continue;
408
409 *w = 0;
410 w++;
411 w += strspn(w, WHITESPACE);
412
413 e = strchr(w, ':');
414 if (!e)
415 continue;
416
417 *e = 0;
418
419 if (!streq(w, args[1]))
420 continue;
421 } else
422 *w = 0;
423
424 r = strv_extend(&list, l);
425 if (r < 0)
426 return log_oom();
427 }
428
429 if (strv_isempty(list)) {
430 log_error("Couldn't find any entries.");
431 return -ENOENT;
432 }
433
434 strv_sort(list);
435 strv_uniq(list);
436
437 pager_open_if_enabled();
438
439 strv_print(list);
440 return 0;
441}
442
601185b4 443static void help(void) {
2087a7af 444 printf("%s [OPTIONS...] COMMAND ...\n\n"
82c1d8f4 445 "Query or change system locale and keyboard settings.\n\n"
50cfc579
LP
446 " -h --help Show this help\n"
447 " --version Show package version\n"
50cfc579
LP
448 " --no-pager Do not pipe output into a pager\n"
449 " --no-ask-password Do not prompt for password\n"
4d7859d1 450 " -H --host=[USER@]HOST Operate on remote host\n"
a86a47ce
LP
451 " -M --machine=CONTAINER Operate on local container\n"
452 " --no-convert Don't convert keyboard mappings\n\n"
2087a7af 453 "Commands:\n"
50cfc579
LP
454 " status Show current locale settings\n"
455 " set-locale LOCALE... Set system locale\n"
456 " list-locales Show known locales\n"
457 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
458 " list-keymaps Show known virtual console keyboard mappings\n"
2087a7af 459 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
50cfc579
LP
460 " Set X11 keyboard mapping\n"
461 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
462 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
463 " list-x11-keymap-variants [LAYOUT]\n"
464 " Show known X11 keyboard mapping variants\n"
601185b4
ZJS
465 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
466 , program_invocation_short_name);
2087a7af
LP
467}
468
469static int parse_argv(int argc, char *argv[]) {
470
471 enum {
472 ARG_VERSION = 0x100,
473 ARG_NO_PAGER,
474 ARG_NO_CONVERT,
475 ARG_NO_ASK_PASSWORD
476 };
477
478 static const struct option options[] = {
4d7859d1
KS
479 { "help", no_argument, NULL, 'h' },
480 { "version", no_argument, NULL, ARG_VERSION },
481 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
482 { "host", required_argument, NULL, 'H' },
483 { "machine", required_argument, NULL, 'M' },
484 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
485 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
eb9da376 486 {}
2087a7af
LP
487 };
488
489 int c;
490
491 assert(argc >= 0);
492 assert(argv);
493
601185b4 494 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
2087a7af
LP
495
496 switch (c) {
497
498 case 'h':
601185b4
ZJS
499 help();
500 return 0;
2087a7af
LP
501
502 case ARG_VERSION:
503 puts(PACKAGE_STRING);
2087a7af
LP
504 puts(SYSTEMD_FEATURES);
505 return 0;
506
2087a7af
LP
507 case ARG_NO_CONVERT:
508 arg_convert = false;
509 break;
510
511 case ARG_NO_PAGER:
512 arg_no_pager = true;
513 break;
514
546158bc
JJ
515 case ARG_NO_ASK_PASSWORD:
516 arg_ask_password = false;
517 break;
518
4d7859d1
KS
519 case 'H':
520 arg_transport = BUS_TRANSPORT_REMOTE;
521 arg_host = optarg;
522 break;
523
524 case 'M':
525 arg_transport = BUS_TRANSPORT_CONTAINER;
526 arg_host = optarg;
527 break;
528
2087a7af
LP
529 case '?':
530 return -EINVAL;
531
532 default:
eb9da376 533 assert_not_reached("Unhandled option");
2087a7af 534 }
2087a7af
LP
535
536 return 1;
537}
538
4d7859d1 539static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
2087a7af
LP
540
541 static const struct {
542 const char* verb;
543 const enum {
544 MORE,
545 LESS,
546 EQUAL
547 } argc_cmp;
548 const int argc;
4d7859d1 549 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
2087a7af 550 } verbs[] = {
50cfc579
LP
551 { "status", LESS, 1, show_status },
552 { "set-locale", MORE, 2, set_locale },
553 { "list-locales", EQUAL, 1, list_locales },
554 { "set-keymap", MORE, 2, set_vconsole_keymap },
555 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
556 { "set-x11-keymap", MORE, 2, set_x11_keymap },
557 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
558 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
559 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
560 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
2087a7af
LP
561 };
562
563 int left;
564 unsigned i;
565
566 assert(argc >= 0);
567 assert(argv);
2087a7af
LP
568
569 left = argc - optind;
570
571 if (left <= 0)
572 /* Special rule: no arguments means "status" */
573 i = 0;
574 else {
575 if (streq(argv[optind], "help")) {
576 help();
577 return 0;
578 }
579
580 for (i = 0; i < ELEMENTSOF(verbs); i++)
581 if (streq(argv[optind], verbs[i].verb))
582 break;
583
584 if (i >= ELEMENTSOF(verbs)) {
585 log_error("Unknown operation %s", argv[optind]);
586 return -EINVAL;
587 }
588 }
589
590 switch (verbs[i].argc_cmp) {
591
592 case EQUAL:
593 if (left != verbs[i].argc) {
594 log_error("Invalid number of arguments.");
595 return -EINVAL;
596 }
597
598 break;
599
600 case MORE:
601 if (left < verbs[i].argc) {
602 log_error("Too few arguments.");
603 return -EINVAL;
604 }
605
606 break;
607
608 case LESS:
609 if (left > verbs[i].argc) {
610 log_error("Too many arguments.");
611 return -EINVAL;
612 }
613
614 break;
615
616 default:
617 assert_not_reached("Unknown comparison operator.");
618 }
619
2087a7af
LP
620 return verbs[i].dispatch(bus, argv + optind, left);
621}
622
4d7859d1 623int main(int argc, char*argv[]) {
4d7859d1 624 _cleanup_bus_unref_ sd_bus *bus = NULL;
84f6181c 625 int r;
2087a7af 626
a9cdc94f 627 setlocale(LC_ALL, "");
2087a7af
LP
628 log_parse_environment();
629 log_open();
630
631 r = parse_argv(argc, argv);
84f6181c 632 if (r <= 0)
2087a7af 633 goto finish;
2087a7af 634
4d7859d1
KS
635 r = bus_open_transport(arg_transport, arg_host, false, &bus);
636 if (r < 0) {
637 log_error("Failed to create bus connection: %s", strerror(-r));
4d7859d1 638 goto finish;
2087a7af
LP
639 }
640
4d7859d1 641 r = localectl_main(bus, argc, argv);
2087a7af 642
4d7859d1 643finish:
2087a7af
LP
644 pager_close();
645
84f6181c 646 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
2087a7af 647}