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