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