]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
tree-wide: drop license boilerplate
[thirdparty/systemd.git] / src / locale / localectl.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6 Copyright 2013 Kay Sievers
7 ***/
8
9 #include <ftw.h>
10 #include <getopt.h>
11 #include <locale.h>
12 #include <stdbool.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include "sd-bus.h"
17
18 #include "bus-error.h"
19 #include "bus-util.h"
20 #include "def.h"
21 #include "fd-util.h"
22 #include "fileio.h"
23 #include "locale-util.h"
24 #include "pager.h"
25 #include "set.h"
26 #include "spawn-polkit-agent.h"
27 #include "strv.h"
28 #include "util.h"
29 #include "verbs.h"
30 #include "virt.h"
31
32 static bool arg_no_pager = false;
33 static bool arg_ask_password = true;
34 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
35 static char *arg_host = NULL;
36 static bool arg_convert = true;
37
38 typedef struct StatusInfo {
39 char **locale;
40 const char *vconsole_keymap;
41 const char *vconsole_keymap_toggle;
42 const char *x11_layout;
43 const char *x11_model;
44 const char *x11_variant;
45 const char *x11_options;
46 } StatusInfo;
47
48 static void status_info_clear(StatusInfo *info) {
49 if (info) {
50 strv_free(info->locale);
51 zero(*info);
52 }
53 }
54
55 static void print_overridden_variables(void) {
56 int r;
57 char *variables[_VARIABLE_LC_MAX] = {};
58 LocaleVariable j;
59 bool print_warning = true;
60
61 if (detect_container() > 0 || arg_host)
62 return;
63
64 r = parse_env_file("/proc/cmdline", WHITESPACE,
65 "locale.LANG", &variables[VARIABLE_LANG],
66 "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
67 "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
68 "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
69 "locale.LC_TIME", &variables[VARIABLE_LC_TIME],
70 "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
71 "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
72 "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
73 "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER],
74 "locale.LC_NAME", &variables[VARIABLE_LC_NAME],
75 "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
76 "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
77 "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
78 "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
79 NULL);
80
81 if (r < 0 && r != -ENOENT) {
82 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
83 goto finish;
84 }
85
86 for (j = 0; j < _VARIABLE_LC_MAX; j++)
87 if (variables[j]) {
88 if (print_warning) {
89 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
90 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
91
92 print_warning = false;
93 } else
94 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
95 }
96 finish:
97 for (j = 0; j < _VARIABLE_LC_MAX; j++)
98 free(variables[j]);
99 }
100
101 static void print_status_info(StatusInfo *i) {
102 assert(i);
103
104 if (strv_isempty(i->locale))
105 puts(" System Locale: n/a");
106 else {
107 char **j;
108
109 printf(" System Locale: %s\n", i->locale[0]);
110 STRV_FOREACH(j, i->locale + 1)
111 printf(" %s\n", *j);
112 }
113
114 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
115 if (!isempty(i->vconsole_keymap_toggle))
116 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
117
118 printf(" X11 Layout: %s\n", strna(i->x11_layout));
119 if (!isempty(i->x11_model))
120 printf(" X11 Model: %s\n", i->x11_model);
121 if (!isempty(i->x11_variant))
122 printf(" X11 Variant: %s\n", i->x11_variant);
123 if (!isempty(i->x11_options))
124 printf(" X11 Options: %s\n", i->x11_options);
125 }
126
127 static int show_status(int argc, char **argv, void *userdata) {
128 _cleanup_(status_info_clear) StatusInfo info = {};
129 static const struct bus_properties_map map[] = {
130 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
131 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
132 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
133 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
134 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
135 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
136 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
137 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
138 {}
139 };
140
141 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
142 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
143 sd_bus *bus = userdata;
144 int r;
145
146 assert(bus);
147
148 r = bus_map_all_properties(bus,
149 "org.freedesktop.locale1",
150 "/org/freedesktop/locale1",
151 map,
152 0,
153 &error,
154 &m,
155 &info);
156 if (r < 0)
157 return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
158
159 print_overridden_variables();
160 print_status_info(&info);
161
162 return r;
163 }
164
165 static int set_locale(int argc, char **argv, void *userdata) {
166 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
167 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
168 sd_bus *bus = userdata;
169 int r;
170
171 assert(bus);
172
173 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
174
175 r = sd_bus_message_new_method_call(
176 bus,
177 &m,
178 "org.freedesktop.locale1",
179 "/org/freedesktop/locale1",
180 "org.freedesktop.locale1",
181 "SetLocale");
182 if (r < 0)
183 return bus_log_create_error(r);
184
185 r = sd_bus_message_append_strv(m, argv + 1);
186 if (r < 0)
187 return bus_log_create_error(r);
188
189 r = sd_bus_message_append(m, "b", arg_ask_password);
190 if (r < 0)
191 return bus_log_create_error(r);
192
193 r = sd_bus_call(bus, m, 0, &error, NULL);
194 if (r < 0) {
195 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
196 return r;
197 }
198
199 return 0;
200 }
201
202 static int list_locales(int argc, char **argv, void *userdata) {
203 _cleanup_strv_free_ char **l = NULL;
204 int r;
205
206 r = get_locales(&l);
207 if (r < 0)
208 return log_error_errno(r, "Failed to read list of locales: %m");
209
210 (void) pager_open(arg_no_pager, false);
211 strv_print(l);
212
213 return 0;
214 }
215
216 static int set_vconsole_keymap(int argc, char **argv, void *userdata) {
217 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
218 const char *map, *toggle_map;
219 sd_bus *bus = userdata;
220 int r;
221
222 assert(bus);
223
224 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
225
226 map = argv[1];
227 toggle_map = argc > 2 ? argv[2] : "";
228
229 r = sd_bus_call_method(
230 bus,
231 "org.freedesktop.locale1",
232 "/org/freedesktop/locale1",
233 "org.freedesktop.locale1",
234 "SetVConsoleKeyboard",
235 &error,
236 NULL,
237 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
238 if (r < 0)
239 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
240
241 return r;
242 }
243
244 static int list_vconsole_keymaps(int argc, char **argv, void *userdata) {
245 _cleanup_strv_free_ char **l = NULL;
246 int r;
247
248 r = get_keymaps(&l);
249 if (r < 0)
250 return log_error_errno(r, "Failed to read list of keymaps: %m");
251
252 (void) pager_open(arg_no_pager, false);
253
254 strv_print(l);
255
256 return 0;
257 }
258
259 static int set_x11_keymap(int argc, char **argv, void *userdata) {
260 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
261 const char *layout, *model, *variant, *options;
262 sd_bus *bus = userdata;
263 int r;
264
265 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
266
267 layout = argv[1];
268 model = argc > 2 ? argv[2] : "";
269 variant = argc > 3 ? argv[3] : "";
270 options = argc > 4 ? argv[4] : "";
271
272 r = sd_bus_call_method(
273 bus,
274 "org.freedesktop.locale1",
275 "/org/freedesktop/locale1",
276 "org.freedesktop.locale1",
277 "SetX11Keyboard",
278 &error,
279 NULL,
280 "ssssbb", layout, model, variant, options,
281 arg_convert, arg_ask_password);
282 if (r < 0)
283 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
284
285 return r;
286 }
287
288 static int list_x11_keymaps(int argc, char **argv, void *userdata) {
289 _cleanup_fclose_ FILE *f = NULL;
290 _cleanup_strv_free_ char **list = NULL;
291 char line[LINE_MAX];
292 enum {
293 NONE,
294 MODELS,
295 LAYOUTS,
296 VARIANTS,
297 OPTIONS
298 } state = NONE, look_for;
299 int r;
300
301 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
302 if (!f)
303 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
304
305 if (streq(argv[0], "list-x11-keymap-models"))
306 look_for = MODELS;
307 else if (streq(argv[0], "list-x11-keymap-layouts"))
308 look_for = LAYOUTS;
309 else if (streq(argv[0], "list-x11-keymap-variants"))
310 look_for = VARIANTS;
311 else if (streq(argv[0], "list-x11-keymap-options"))
312 look_for = OPTIONS;
313 else
314 assert_not_reached("Wrong parameter");
315
316 FOREACH_LINE(line, f, break) {
317 char *l, *w;
318
319 l = strstrip(line);
320
321 if (isempty(l))
322 continue;
323
324 if (l[0] == '!') {
325 if (startswith(l, "! model"))
326 state = MODELS;
327 else if (startswith(l, "! layout"))
328 state = LAYOUTS;
329 else if (startswith(l, "! variant"))
330 state = VARIANTS;
331 else if (startswith(l, "! option"))
332 state = OPTIONS;
333 else
334 state = NONE;
335
336 continue;
337 }
338
339 if (state != look_for)
340 continue;
341
342 w = l + strcspn(l, WHITESPACE);
343
344 if (argc > 1) {
345 char *e;
346
347 if (*w == 0)
348 continue;
349
350 *w = 0;
351 w++;
352 w += strspn(w, WHITESPACE);
353
354 e = strchr(w, ':');
355 if (!e)
356 continue;
357
358 *e = 0;
359
360 if (!streq(w, argv[1]))
361 continue;
362 } else
363 *w = 0;
364
365 r = strv_extend(&list, l);
366 if (r < 0)
367 return log_oom();
368 }
369
370 if (strv_isempty(list)) {
371 log_error("Couldn't find any entries.");
372 return -ENOENT;
373 }
374
375 strv_sort(list);
376 strv_uniq(list);
377
378 (void) pager_open(arg_no_pager, false);
379
380 strv_print(list);
381 return 0;
382 }
383
384 static int help(void) {
385 printf("%s [OPTIONS...] COMMAND ...\n\n"
386 "Query or change system locale and keyboard settings.\n\n"
387 " -h --help Show this help\n"
388 " --version Show package version\n"
389 " --no-pager Do not pipe output into a pager\n"
390 " --no-ask-password Do not prompt for password\n"
391 " -H --host=[USER@]HOST Operate on remote host\n"
392 " -M --machine=CONTAINER Operate on local container\n"
393 " --no-convert Don't convert keyboard mappings\n\n"
394 "Commands:\n"
395 " status Show current locale settings\n"
396 " set-locale LOCALE... Set system locale\n"
397 " list-locales Show known locales\n"
398 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
399 " list-keymaps Show known virtual console keyboard mappings\n"
400 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
401 " Set X11 and console keyboard mappings\n"
402 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
403 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
404 " list-x11-keymap-variants [LAYOUT]\n"
405 " Show known X11 keyboard mapping variants\n"
406 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
407 , program_invocation_short_name);
408
409 return 0;
410 }
411
412 static int verb_help(int argc, char **argv, void *userdata) {
413 return help();
414 }
415
416 static int parse_argv(int argc, char *argv[]) {
417
418 enum {
419 ARG_VERSION = 0x100,
420 ARG_NO_PAGER,
421 ARG_NO_CONVERT,
422 ARG_NO_ASK_PASSWORD
423 };
424
425 static const struct option options[] = {
426 { "help", no_argument, NULL, 'h' },
427 { "version", no_argument, NULL, ARG_VERSION },
428 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
429 { "host", required_argument, NULL, 'H' },
430 { "machine", required_argument, NULL, 'M' },
431 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
432 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
433 {}
434 };
435
436 int c;
437
438 assert(argc >= 0);
439 assert(argv);
440
441 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
442
443 switch (c) {
444
445 case 'h':
446 return help();
447
448 case ARG_VERSION:
449 return version();
450
451 case ARG_NO_CONVERT:
452 arg_convert = false;
453 break;
454
455 case ARG_NO_PAGER:
456 arg_no_pager = true;
457 break;
458
459 case ARG_NO_ASK_PASSWORD:
460 arg_ask_password = false;
461 break;
462
463 case 'H':
464 arg_transport = BUS_TRANSPORT_REMOTE;
465 arg_host = optarg;
466 break;
467
468 case 'M':
469 arg_transport = BUS_TRANSPORT_MACHINE;
470 arg_host = optarg;
471 break;
472
473 case '?':
474 return -EINVAL;
475
476 default:
477 assert_not_reached("Unhandled option");
478 }
479
480 return 1;
481 }
482
483 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
484
485 static const Verb verbs[] = {
486 { "status", VERB_ANY, 1, VERB_DEFAULT, show_status },
487 { "set-locale", 2, VERB_ANY, 0, set_locale },
488 { "list-locales", VERB_ANY, 1, 0, list_locales },
489 { "set-keymap", 2, 3, 0, set_vconsole_keymap },
490 { "list-keymaps", VERB_ANY, 1, 0, list_vconsole_keymaps },
491 { "set-x11-keymap", 2, 5, 0, set_x11_keymap },
492 { "list-x11-keymap-models", VERB_ANY, 1, 0, list_x11_keymaps },
493 { "list-x11-keymap-layouts", VERB_ANY, 1, 0, list_x11_keymaps },
494 { "list-x11-keymap-variants", VERB_ANY, 2, 0, list_x11_keymaps },
495 { "list-x11-keymap-options", VERB_ANY, 1, 0, list_x11_keymaps },
496 { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */
497 {}
498 };
499
500 return dispatch_verb(argc, argv, verbs, bus);
501 }
502
503 int main(int argc, char*argv[]) {
504 sd_bus *bus = NULL;
505 int r;
506
507 setlocale(LC_ALL, "");
508 log_parse_environment();
509 log_open();
510
511 r = parse_argv(argc, argv);
512 if (r <= 0)
513 goto finish;
514
515 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
516 if (r < 0) {
517 log_error_errno(r, "Failed to create bus connection: %m");
518 goto finish;
519 }
520
521 r = localectl_main(bus, argc, argv);
522
523 finish:
524 /* make sure we terminate the bus connection first, and then close the
525 * pager, see issue #3543 for the details. */
526 sd_bus_flush_close_unref(bus);
527 pager_close();
528
529 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
530 }