]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
hashmap: introduce hash_ops to make struct Hashmap smaller
[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 Copyright 2013 Kay Sievers
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
23 #include <locale.h>
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
33 #include "sd-bus.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "bus-message.h"
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"
44 #include "utf8.h"
45 #include "def.h"
46 #include "locale-util.h"
47
48 static bool arg_no_pager = false;
49 static bool arg_ask_password = true;
50 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
51 static char *arg_host = NULL;
52 static bool arg_convert = true;
53
54 static void pager_open_if_enabled(void) {
55
56 if (arg_no_pager)
57 return;
58
59 pager_open(false);
60 }
61
62 static void polkit_agent_open_if_enabled(void) {
63
64 /* Open the polkit agent as a child process if necessary */
65 if (!arg_ask_password)
66 return;
67
68 if (arg_transport != BUS_TRANSPORT_LOCAL)
69 return;
70
71 polkit_agent_open();
72 }
73
74 typedef 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
84 static 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
110 static int show_status(sd_bus *bus, char **args, unsigned n) {
111 StatusInfo info = {};
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) },
121 {}
122 };
123 int r;
124
125 assert(bus);
126
127 r = bus_map_all_properties(bus,
128 "org.freedesktop.locale1",
129 "/org/freedesktop/locale1",
130 map,
131 &info);
132 if (r < 0) {
133 log_error("Could not get properties: %s", strerror(-r));
134 goto fail;
135 }
136
137 print_status_info(&info);
138
139 fail:
140 strv_free(info.locale);
141 return r;
142 }
143
144 static int set_locale(sd_bus *bus, char **args, unsigned n) {
145 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
146 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
147 int r;
148
149 assert(bus);
150 assert(args);
151
152 polkit_agent_open_if_enabled();
153
154 r = sd_bus_message_new_method_call(
155 bus,
156 &m,
157 "org.freedesktop.locale1",
158 "/org/freedesktop/locale1",
159 "org.freedesktop.locale1",
160 "SetLocale");
161 if (r < 0)
162 return bus_log_create_error(r);
163
164 r = sd_bus_message_append_strv(m, args + 1);
165 if (r < 0)
166 return bus_log_create_error(r);
167
168 r = sd_bus_message_append(m, "b", arg_ask_password);
169 if (r < 0)
170 return bus_log_create_error(r);
171
172 r = sd_bus_call(bus, m, 0, &error, NULL);
173 if (r < 0) {
174 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
175 return r;
176 }
177
178 return 0;
179 }
180
181 static int list_locales(sd_bus *bus, char **args, unsigned n) {
182 _cleanup_strv_free_ char **l = NULL;
183 int r;
184
185 assert(args);
186
187 r = get_locales(&l);
188 if (r < 0) {
189 log_error("Failed to read list of locales: %s", strerror(-r));
190 return r;
191 }
192
193 pager_open_if_enabled();
194 strv_print(l);
195
196 return 0;
197 }
198
199 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
200 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
201 const char *map, *toggle_map;
202 int r;
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] : "";
216
217 r = sd_bus_call_method(
218 bus,
219 "org.freedesktop.locale1",
220 "/org/freedesktop/locale1",
221 "org.freedesktop.locale1",
222 "SetVConsoleKeyboard",
223 &error,
224 NULL,
225 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
226 if (r < 0)
227 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
228
229 return r;
230 }
231
232 static Set *keymaps = NULL;
233
234 static 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
250 p = strdup(basename(fpath));
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
262 r = set_consume(keymaps, p);
263 if (r < 0 && r != -EEXIST) {
264 log_error("Can't add keymap: %s", strerror(-r));
265 return r;
266 }
267
268 return 0;
269 }
270
271 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
272 _cleanup_strv_free_ char **l = NULL;
273 const char *dir;
274
275 keymaps = set_new(&string_hash_ops);
276 if (!keymaps)
277 return log_oom();
278
279 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
280 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
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
299 strv_print(l);
300
301 return 0;
302 }
303
304 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
305 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
306 const char *layout, *model, *variant, *options;
307 int r;
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] : "";
322 options = n > 4 ? args[4] : "";
323
324 r = sd_bus_call_method(
325 bus,
326 "org.freedesktop.locale1",
327 "/org/freedesktop/locale1",
328 "org.freedesktop.locale1",
329 "SetX11Keyboard",
330 &error,
331 NULL,
332 "ssssbb", layout, model, variant, options,
333 arg_convert, arg_ask_password);
334 if (r < 0)
335 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
336
337 return r;
338 }
339
340 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
341 _cleanup_fclose_ FILE *f = NULL;
342 _cleanup_strv_free_ char **list = NULL;
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
358 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
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
443 static void help(void) {
444 printf("%s [OPTIONS...] COMMAND ...\n\n"
445 "Query or change system locale and keyboard settings.\n\n"
446 " -h --help Show this help\n"
447 " --version Show package version\n"
448 " --no-pager Do not pipe output into a pager\n"
449 " --no-ask-password Do not prompt for password\n"
450 " -H --host=[USER@]HOST Operate on remote host\n"
451 " -M --machine=CONTAINER Operate on local container\n"
452 " --no-convert Don't convert keyboard mappings\n\n"
453 "Commands:\n"
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"
459 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
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"
465 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
466 , program_invocation_short_name);
467 }
468
469 static 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[] = {
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 },
486 {}
487 };
488
489 int c;
490
491 assert(argc >= 0);
492 assert(argv);
493
494 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
495
496 switch (c) {
497
498 case 'h':
499 help();
500 return 0;
501
502 case ARG_VERSION:
503 puts(PACKAGE_STRING);
504 puts(SYSTEMD_FEATURES);
505 return 0;
506
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
515 case ARG_NO_ASK_PASSWORD:
516 arg_ask_password = false;
517 break;
518
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
529 case '?':
530 return -EINVAL;
531
532 default:
533 assert_not_reached("Unhandled option");
534 }
535
536 return 1;
537 }
538
539 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
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;
549 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
550 } verbs[] = {
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 },
561 };
562
563 int left;
564 unsigned i;
565
566 assert(argc >= 0);
567 assert(argv);
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
620 return verbs[i].dispatch(bus, argv + optind, left);
621 }
622
623 int main(int argc, char*argv[]) {
624 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
625 int r;
626
627 setlocale(LC_ALL, "");
628 log_parse_environment();
629 log_open();
630
631 r = parse_argv(argc, argv);
632 if (r <= 0)
633 goto finish;
634
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));
638 goto finish;
639 }
640
641 r = localectl_main(bus, argc, argv);
642
643 finish:
644 pager_close();
645
646 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
647 }