]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <sys/stat.h> | |
4 | ||
5 | #include "sd-bus.h" | |
6 | #include "sd-event.h" | |
7 | ||
8 | #include "alloc-util.h" | |
9 | #include "bus-error.h" | |
10 | #include "bus-locator.h" | |
11 | #include "bus-log-control-api.h" | |
12 | #include "bus-object.h" | |
13 | #include "bus-polkit.h" | |
14 | #include "bus-unit-util.h" | |
15 | #include "bus-util.h" | |
16 | #include "constants.h" | |
17 | #include "daemon-util.h" | |
18 | #include "hashmap.h" | |
19 | #include "label-util.h" | |
20 | #include "localed-util.h" | |
21 | #include "log.h" | |
22 | #include "main-func.h" | |
23 | #include "service-util.h" | |
24 | #include "string-util.h" | |
25 | #include "strv.h" | |
26 | ||
27 | static int vconsole_reload(sd_bus *bus) { | |
28 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
29 | int r; | |
30 | ||
31 | assert(bus); | |
32 | ||
33 | r = bus_call_method(bus, bus_systemd_mgr, "RestartUnit", &error, NULL, "ss", "systemd-vconsole-setup.service", "replace"); | |
34 | if (r < 0) | |
35 | return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); | |
36 | return 0; | |
37 | } | |
38 | ||
39 | static int property_get_locale( | |
40 | sd_bus *bus, | |
41 | const char *path, | |
42 | const char *interface, | |
43 | const char *property, | |
44 | sd_bus_message *reply, | |
45 | void *userdata, | |
46 | sd_bus_error *error) { | |
47 | ||
48 | Context *c = ASSERT_PTR(userdata); | |
49 | _cleanup_strv_free_ char **l = NULL; | |
50 | int r; | |
51 | ||
52 | r = locale_read_data(c, reply); | |
53 | if (r < 0) | |
54 | return r; | |
55 | ||
56 | r = locale_context_build_env(&c->locale_context, &l, NULL); | |
57 | if (r < 0) | |
58 | return r; | |
59 | ||
60 | return sd_bus_message_append_strv(reply, l); | |
61 | } | |
62 | ||
63 | static int property_get_vconsole( | |
64 | sd_bus *bus, | |
65 | const char *path, | |
66 | const char *interface, | |
67 | const char *property, | |
68 | sd_bus_message *reply, | |
69 | void *userdata, | |
70 | sd_bus_error *error) { | |
71 | ||
72 | Context *c = ASSERT_PTR(userdata); | |
73 | int r; | |
74 | ||
75 | assert(property); | |
76 | ||
77 | r = vconsole_read_data(c, reply); | |
78 | if (r < 0) | |
79 | return r; | |
80 | ||
81 | if (streq(property, "VConsoleKeymap")) | |
82 | return sd_bus_message_append_basic(reply, 's', c->vc.keymap); | |
83 | if (streq(property, "VConsoleKeymapToggle")) | |
84 | return sd_bus_message_append_basic(reply, 's', c->vc.toggle); | |
85 | ||
86 | return -EINVAL; | |
87 | } | |
88 | ||
89 | static int property_get_xkb( | |
90 | sd_bus *bus, | |
91 | const char *path, | |
92 | const char *interface, | |
93 | const char *property, | |
94 | sd_bus_message *reply, | |
95 | void *userdata, | |
96 | sd_bus_error *error) { | |
97 | ||
98 | Context *c = ASSERT_PTR(userdata); | |
99 | const X11Context *xc; | |
100 | int r; | |
101 | ||
102 | assert(property); | |
103 | ||
104 | r = vconsole_read_data(c, reply); | |
105 | if (r < 0) | |
106 | return r; | |
107 | ||
108 | r = x11_read_data(c, reply); | |
109 | if (r < 0) | |
110 | return r; | |
111 | ||
112 | xc = context_get_x11_context(c); | |
113 | ||
114 | if (streq(property, "X11Layout")) | |
115 | return sd_bus_message_append_basic(reply, 's', xc->layout); | |
116 | if (streq(property, "X11Model")) | |
117 | return sd_bus_message_append_basic(reply, 's', xc->model); | |
118 | if (streq(property, "X11Variant")) | |
119 | return sd_bus_message_append_basic(reply, 's', xc->variant); | |
120 | if (streq(property, "X11Options")) | |
121 | return sd_bus_message_append_basic(reply, 's', xc->options); | |
122 | ||
123 | return -EINVAL; | |
124 | } | |
125 | ||
126 | static int process_locale_list_item( | |
127 | const char *assignment, | |
128 | char *new_locale[static _VARIABLE_LC_MAX], | |
129 | bool use_localegen, | |
130 | sd_bus_error *error) { | |
131 | ||
132 | assert(assignment); | |
133 | assert(new_locale); | |
134 | ||
135 | for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { | |
136 | const char *name, *e; | |
137 | ||
138 | assert_se(name = locale_variable_to_string(p)); | |
139 | ||
140 | e = startswith(assignment, name); | |
141 | if (!e) | |
142 | continue; | |
143 | ||
144 | if (*e != '=') | |
145 | continue; | |
146 | ||
147 | e++; | |
148 | ||
149 | if (!locale_is_valid(e)) | |
150 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s is not valid, refusing.", e); | |
151 | if (!use_localegen && locale_is_installed(e) <= 0) | |
152 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s not installed, refusing.", e); | |
153 | if (new_locale[p]) | |
154 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale variable %s set twice, refusing.", name); | |
155 | ||
156 | return strdup_to(&new_locale[p], e); | |
157 | } | |
158 | ||
159 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale assignment %s not valid, refusing.", assignment); | |
160 | } | |
161 | ||
162 | static int locale_gen_process_locale(char *new_locale[static _VARIABLE_LC_MAX], sd_bus_error *error) { | |
163 | int r; | |
164 | ||
165 | assert(new_locale); | |
166 | ||
167 | for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { | |
168 | if (p == VARIABLE_LANGUAGE) | |
169 | continue; | |
170 | if (isempty(new_locale[p])) | |
171 | continue; | |
172 | if (locale_is_installed(new_locale[p])) | |
173 | continue; | |
174 | ||
175 | r = locale_gen_enable_locale(new_locale[p]); | |
176 | if (r == -ENOEXEC) { | |
177 | log_error_errno(r, "Refused to enable locale for generation: %m"); | |
178 | return sd_bus_error_setf(error, | |
179 | SD_BUS_ERROR_INVALID_ARGS, | |
180 | "Specified locale is not installed and non-UTF-8 locale will not be auto-generated: %s", | |
181 | new_locale[p]); | |
182 | } | |
183 | if (r == -EINVAL) { | |
184 | log_error_errno(r, "Failed to enable invalid locale %s for generation.", new_locale[p]); | |
185 | return sd_bus_error_setf(error, | |
186 | SD_BUS_ERROR_INVALID_ARGS, | |
187 | "Cannot enable locale generation for invalid locale: %s", | |
188 | new_locale[p]); | |
189 | } | |
190 | if (r < 0) { | |
191 | log_error_errno(r, "Failed to enable locale for generation: %m"); | |
192 | return sd_bus_error_set_errnof(error, r, "Failed to enable locale generation: %m"); | |
193 | } | |
194 | ||
195 | r = locale_gen_run(); | |
196 | if (r < 0) { | |
197 | log_error_errno(r, "Failed to generate locale: %m"); | |
198 | return sd_bus_error_set_errnof(error, r, "Failed to generate locale: %m"); | |
199 | } | |
200 | } | |
201 | ||
202 | return 0; | |
203 | } | |
204 | ||
205 | static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { | |
206 | _cleanup_(locale_variables_freep) char *new_locale[_VARIABLE_LC_MAX] = {}; | |
207 | _cleanup_strv_free_ char **l = NULL, **l_set = NULL, **l_unset = NULL; | |
208 | Context *c = ASSERT_PTR(userdata); | |
209 | int interactive, r; | |
210 | bool use_localegen; | |
211 | ||
212 | assert(m); | |
213 | ||
214 | r = sd_bus_message_read_strv(m, &l); | |
215 | if (r < 0) | |
216 | return bus_log_parse_error(r); | |
217 | ||
218 | r = sd_bus_message_read_basic(m, 'b', &interactive); | |
219 | if (r < 0) | |
220 | return bus_log_parse_error(r); | |
221 | ||
222 | use_localegen = locale_gen_check_available(); | |
223 | ||
224 | /* If single locale without variable name is provided, then we assume it is LANG=. */ | |
225 | if (strv_length(l) == 1 && !strchr(l[0], '=')) { | |
226 | if (!locale_is_valid(l[0])) | |
227 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid locale specification: %s", l[0]); | |
228 | if (!use_localegen && locale_is_installed(l[0]) <= 0) | |
229 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified locale is not installed: %s", l[0]); | |
230 | ||
231 | new_locale[VARIABLE_LANG] = strdup(l[0]); | |
232 | if (!new_locale[VARIABLE_LANG]) | |
233 | return log_oom(); | |
234 | ||
235 | l = strv_free(l); | |
236 | } | |
237 | ||
238 | /* Check whether a variable is valid */ | |
239 | STRV_FOREACH(i, l) { | |
240 | r = process_locale_list_item(*i, new_locale, use_localegen, error); | |
241 | if (r < 0) | |
242 | return r; | |
243 | } | |
244 | ||
245 | /* If LANG was specified, but not LANGUAGE, check if we should | |
246 | * set it based on the language fallback table. */ | |
247 | if (!isempty(new_locale[VARIABLE_LANG]) && | |
248 | isempty(new_locale[VARIABLE_LANGUAGE])) { | |
249 | _cleanup_free_ char *language = NULL; | |
250 | ||
251 | (void) find_language_fallback(new_locale[VARIABLE_LANG], &language); | |
252 | if (language) { | |
253 | log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale[VARIABLE_LANG], language); | |
254 | free_and_replace(new_locale[VARIABLE_LANGUAGE], language); | |
255 | } | |
256 | } | |
257 | ||
258 | r = locale_read_data(c, m); | |
259 | if (r < 0) { | |
260 | log_error_errno(r, "Failed to read locale data: %m"); | |
261 | return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Failed to read locale data"); | |
262 | } | |
263 | ||
264 | /* Merge with the current settings */ | |
265 | r = locale_context_merge(&c->locale_context, new_locale); | |
266 | if (r < 0) | |
267 | return log_oom(); | |
268 | ||
269 | locale_variables_simplify(new_locale); | |
270 | ||
271 | if (locale_context_equal(&c->locale_context, new_locale)) { | |
272 | log_debug("Locale settings were not modified."); | |
273 | return sd_bus_reply_method_return(m, NULL); | |
274 | } | |
275 | ||
276 | r = bus_verify_polkit_async_full( | |
277 | m, | |
278 | "org.freedesktop.locale1.set-locale", | |
279 | /* details= */ NULL, | |
280 | /* good_user= */ UID_INVALID, | |
281 | interactive ? POLKIT_ALLOW_INTERACTIVE : 0, | |
282 | &c->polkit_registry, | |
283 | error); | |
284 | if (r < 0) | |
285 | return r; | |
286 | if (r == 0) | |
287 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
288 | ||
289 | /* Generate locale in case it is missing and the system is using locale-gen */ | |
290 | if (use_localegen) { | |
291 | r = locale_gen_process_locale(new_locale, error); | |
292 | if (r < 0) | |
293 | return r; | |
294 | } | |
295 | ||
296 | locale_context_take(&c->locale_context, new_locale); | |
297 | ||
298 | /* Write locale configuration */ | |
299 | r = locale_context_save(&c->locale_context, &l_set, &l_unset); | |
300 | if (r < 0) { | |
301 | log_error_errno(r, "Failed to set locale: %m"); | |
302 | return sd_bus_error_set_errnof(error, r, "Failed to set locale: %m"); | |
303 | } | |
304 | ||
305 | /* Since we just updated the locale configuration file, ask the system manager to read it again to | |
306 | * update its default locale settings. It's important to not use UnsetAndSetEnvironment or a similar | |
307 | * method because in this case unsetting variables means restoring them to PID1 default values, which | |
308 | * may be outdated, since locale.conf has just changed and PID1 hasn't read it */ | |
309 | (void) bus_service_manager_reload(sd_bus_message_get_bus(m)); | |
310 | ||
311 | if (!strv_isempty(l_set)) { | |
312 | _cleanup_free_ char *line = NULL; | |
313 | ||
314 | line = strv_join(l_set, ", "); | |
315 | log_info("Changed locale to %s.", strnull(line)); | |
316 | } else | |
317 | log_info("Changed locale to unset."); | |
318 | ||
319 | (void) sd_bus_emit_properties_changed( | |
320 | sd_bus_message_get_bus(m), | |
321 | "/org/freedesktop/locale1", | |
322 | "org.freedesktop.locale1", | |
323 | "Locale", NULL); | |
324 | ||
325 | return sd_bus_reply_method_return(m, NULL); | |
326 | } | |
327 | ||
328 | static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { | |
329 | _cleanup_(x11_context_clear) X11Context converted = {}; | |
330 | Context *c = ASSERT_PTR(userdata); | |
331 | int convert, interactive, r; | |
332 | bool x_needs_update; | |
333 | VCContext in; | |
334 | ||
335 | assert(m); | |
336 | ||
337 | r = sd_bus_message_read(m, "ssbb", &in.keymap, &in.toggle, &convert, &interactive); | |
338 | if (r < 0) | |
339 | return bus_log_parse_error(r); | |
340 | ||
341 | vc_context_empty_to_null(&in); | |
342 | ||
343 | r = vc_context_verify_and_warn(&in, LOG_ERR, error); | |
344 | if (r < 0) | |
345 | return r; | |
346 | ||
347 | r = vconsole_read_data(c, m); | |
348 | if (r < 0) { | |
349 | log_error_errno(r, "Failed to read virtual console keymap data: %m"); | |
350 | return sd_bus_error_set_errnof(error, r, "Failed to read virtual console keymap data: %m"); | |
351 | } | |
352 | ||
353 | r = x11_read_data(c, m); | |
354 | if (r < 0) { | |
355 | log_error_errno(r, "Failed to read X11 keyboard layout data: %m"); | |
356 | return sd_bus_error_set_errnof(error, r, "Failed to read X11 keyboard layout data: %m"); | |
357 | } | |
358 | ||
359 | if (convert) { | |
360 | r = vconsole_convert_to_x11(&in, x11_context_verify, &converted); | |
361 | if (r < 0) { | |
362 | log_error_errno(r, "Failed to convert keymap data: %m"); | |
363 | return sd_bus_error_set_errnof(error, r, "Failed to convert keymap data: %m"); | |
364 | } | |
365 | ||
366 | if (x11_context_isempty(&converted)) | |
367 | log_notice("No conversion found for virtual console keymap \"%s\".", strempty(in.keymap)); | |
368 | else | |
369 | log_info("The virtual console keymap '%s' is converted to X11 keyboard layout '%s' model '%s' variant '%s' options '%s'", | |
370 | in.keymap, strempty(converted.layout), strempty(converted.model), strempty(converted.variant), strempty(converted.options)); | |
371 | ||
372 | /* save the result of conversion to emit changed properties later. */ | |
373 | x_needs_update = !x11_context_equal(&c->x11_from_vc, &converted) || !x11_context_equal(&c->x11_from_xorg, &converted); | |
374 | } else | |
375 | x_needs_update = !x11_context_equal(&c->x11_from_vc, &c->x11_from_xorg); | |
376 | ||
377 | if (vc_context_equal(&c->vc, &in) && !x_needs_update) | |
378 | return sd_bus_reply_method_return(m, NULL); | |
379 | ||
380 | r = bus_verify_polkit_async_full( | |
381 | m, | |
382 | "org.freedesktop.locale1.set-keyboard", | |
383 | /* details= */ NULL, | |
384 | /* good_user= */ UID_INVALID, | |
385 | interactive ? POLKIT_ALLOW_INTERACTIVE : 0, | |
386 | &c->polkit_registry, | |
387 | error); | |
388 | if (r < 0) | |
389 | return r; | |
390 | if (r == 0) | |
391 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
392 | ||
393 | r = vc_context_copy(&c->vc, &in); | |
394 | if (r < 0) | |
395 | return log_oom(); | |
396 | ||
397 | if (x_needs_update) { | |
398 | if (convert) { | |
399 | r = x11_context_copy(&c->x11_from_vc, &converted); | |
400 | if (r < 0) | |
401 | return log_oom(); | |
402 | x11_context_replace(&c->x11_from_xorg, &converted); | |
403 | } else { | |
404 | const X11Context *xc = context_get_x11_context(c); | |
405 | ||
406 | /* Even if the conversion is not requested, sync the two X11 contexts. */ | |
407 | r = x11_context_copy(&c->x11_from_vc, xc); | |
408 | if (r < 0) | |
409 | return log_oom(); | |
410 | ||
411 | r = x11_context_copy(&c->x11_from_xorg, xc); | |
412 | if (r < 0) | |
413 | return log_oom(); | |
414 | } | |
415 | } | |
416 | ||
417 | r = vconsole_write_data(c); | |
418 | if (r < 0) | |
419 | log_warning_errno(r, "Failed to write virtual console keymap, ignoring: %m"); | |
420 | ||
421 | if (x_needs_update) { | |
422 | r = x11_write_data(c); | |
423 | if (r < 0) | |
424 | log_warning_errno(r, "Failed to write X11 keyboard layout, ignoring: %m"); | |
425 | } | |
426 | ||
427 | log_info("Changed virtual console keymap to '%s' toggle '%s'", | |
428 | strempty(c->vc.keymap), strempty(c->vc.toggle)); | |
429 | ||
430 | (void) vconsole_reload(sd_bus_message_get_bus(m)); | |
431 | ||
432 | (void) sd_bus_emit_properties_changed( | |
433 | sd_bus_message_get_bus(m), | |
434 | "/org/freedesktop/locale1", | |
435 | "org.freedesktop.locale1", | |
436 | "VConsoleKeymap", "VConsoleKeymapToggle", | |
437 | x_needs_update ? "X11Layout" : NULL, | |
438 | x_needs_update ? "X11Model" : NULL, | |
439 | x_needs_update ? "X11Variant" : NULL, | |
440 | x_needs_update ? "X11Options" : NULL, | |
441 | NULL); | |
442 | ||
443 | return sd_bus_reply_method_return(m, NULL); | |
444 | } | |
445 | ||
446 | static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { | |
447 | _cleanup_(vc_context_clear) VCContext converted = {}; | |
448 | Context *c = ASSERT_PTR(userdata); | |
449 | int convert, interactive, r; | |
450 | X11Context in; | |
451 | ||
452 | assert(m); | |
453 | ||
454 | r = sd_bus_message_read(m, "ssssbb", &in.layout, &in.model, &in.variant, &in.options, &convert, &interactive); | |
455 | if (r < 0) | |
456 | return bus_log_parse_error(r); | |
457 | ||
458 | x11_context_empty_to_null(&in); | |
459 | ||
460 | r = x11_context_verify_and_warn(&in, LOG_ERR, error); | |
461 | if (r < 0) | |
462 | return r; | |
463 | ||
464 | r = vconsole_read_data(c, m); | |
465 | if (r < 0) { | |
466 | log_error_errno(r, "Failed to read virtual console keymap data: %m"); | |
467 | return sd_bus_error_set_errnof(error, r, "Failed to read virtual console keymap data: %m"); | |
468 | } | |
469 | ||
470 | r = x11_read_data(c, m); | |
471 | if (r < 0) { | |
472 | log_error_errno(r, "Failed to read x11 keyboard layout data: %m"); | |
473 | return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Failed to read x11 keyboard layout data"); | |
474 | } | |
475 | ||
476 | if (convert) { | |
477 | r = x11_convert_to_vconsole(&in, &converted); | |
478 | if (r < 0) { | |
479 | log_error_errno(r, "Failed to convert keymap data: %m"); | |
480 | return sd_bus_error_set_errnof(error, r, "Failed to convert keymap data: %m"); | |
481 | } | |
482 | ||
483 | if (vc_context_isempty(&converted)) | |
484 | /* We search for layout-variant match first, but then we also look | |
485 | * for anything which matches just the layout. So it's accurate to say | |
486 | * that we couldn't find anything which matches the layout. */ | |
487 | log_notice("No conversion to virtual console map found for \"%s\".", strempty(in.layout)); | |
488 | else | |
489 | log_info("The X11 keyboard layout '%s' is converted to virtual console keymap '%s'", | |
490 | in.layout, converted.keymap); | |
491 | ||
492 | /* save the result of conversion to emit changed properties later. */ | |
493 | convert = !vc_context_equal(&c->vc, &converted); | |
494 | } | |
495 | ||
496 | if (x11_context_equal(&c->x11_from_vc, &in) && x11_context_equal(&c->x11_from_xorg, &in) && !convert) | |
497 | return sd_bus_reply_method_return(m, NULL); | |
498 | ||
499 | r = bus_verify_polkit_async_full( | |
500 | m, | |
501 | "org.freedesktop.locale1.set-keyboard", | |
502 | /* details= */ NULL, | |
503 | /* good_user= */ UID_INVALID, | |
504 | interactive ? POLKIT_ALLOW_INTERACTIVE : 0, | |
505 | &c->polkit_registry, | |
506 | error); | |
507 | if (r < 0) | |
508 | return r; | |
509 | if (r == 0) | |
510 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
511 | ||
512 | r = x11_context_copy(&c->x11_from_vc, &in); | |
513 | if (r < 0) | |
514 | return log_oom(); | |
515 | ||
516 | r = x11_context_copy(&c->x11_from_xorg, &in); | |
517 | if (r < 0) | |
518 | return log_oom(); | |
519 | ||
520 | if (convert) | |
521 | vc_context_replace(&c->vc, &converted); | |
522 | ||
523 | r = vconsole_write_data(c); | |
524 | if (r < 0) | |
525 | log_warning_errno(r, "Failed to update vconsole.conf, ignoring: %m"); | |
526 | ||
527 | r = x11_write_data(c); | |
528 | if (r < 0) | |
529 | log_warning_errno(r, "Failed to write X11 keyboard layout, ignoring: %m"); | |
530 | ||
531 | log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", | |
532 | strempty(in.layout), | |
533 | strempty(in.model), | |
534 | strempty(in.variant), | |
535 | strempty(in.options)); | |
536 | ||
537 | (void) sd_bus_emit_properties_changed( | |
538 | sd_bus_message_get_bus(m), | |
539 | "/org/freedesktop/locale1", | |
540 | "org.freedesktop.locale1", | |
541 | "X11Layout", "X11Model", "X11Variant", "X11Options", | |
542 | convert ? "VConsoleKeymap" : NULL, | |
543 | convert ? "VConsoleKeymapToggle" : NULL, | |
544 | NULL); | |
545 | ||
546 | if (convert) | |
547 | (void) vconsole_reload(sd_bus_message_get_bus(m)); | |
548 | ||
549 | return sd_bus_reply_method_return(m, NULL); | |
550 | } | |
551 | ||
552 | static const sd_bus_vtable locale_vtable[] = { | |
553 | SD_BUS_VTABLE_START(0), | |
554 | SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
555 | SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
556 | SD_BUS_PROPERTY("X11Model", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
557 | SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
558 | SD_BUS_PROPERTY("X11Options", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
559 | SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
560 | SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
561 | ||
562 | SD_BUS_METHOD_WITH_ARGS("SetLocale", | |
563 | SD_BUS_ARGS("as", locale, "b", interactive), | |
564 | SD_BUS_NO_RESULT, | |
565 | method_set_locale, | |
566 | SD_BUS_VTABLE_UNPRIVILEGED), | |
567 | SD_BUS_METHOD_WITH_ARGS("SetVConsoleKeyboard", | |
568 | SD_BUS_ARGS("s", keymap, "s", keymap_toggle, "b", convert, "b", interactive), | |
569 | SD_BUS_NO_RESULT, | |
570 | method_set_vc_keyboard, | |
571 | SD_BUS_VTABLE_UNPRIVILEGED), | |
572 | SD_BUS_METHOD_WITH_ARGS("SetX11Keyboard", | |
573 | SD_BUS_ARGS("s", layout, "s", model, "s", variant, "s", options, "b", convert, "b", interactive), | |
574 | SD_BUS_NO_RESULT, | |
575 | method_set_x11_keyboard, | |
576 | SD_BUS_VTABLE_UNPRIVILEGED), | |
577 | ||
578 | SD_BUS_VTABLE_END | |
579 | }; | |
580 | ||
581 | static const BusObjectImplementation manager_object = { | |
582 | "/org/freedesktop/locale1", | |
583 | "org.freedesktop.locale1", | |
584 | .vtables = BUS_VTABLES(locale_vtable), | |
585 | }; | |
586 | ||
587 | static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { | |
588 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
589 | int r; | |
590 | ||
591 | assert(c); | |
592 | assert(event); | |
593 | assert(_bus); | |
594 | ||
595 | r = sd_bus_default_system(&bus); | |
596 | if (r < 0) | |
597 | return log_error_errno(r, "Failed to get system bus connection: %m"); | |
598 | ||
599 | r = bus_add_implementation(bus, &manager_object, c); | |
600 | if (r < 0) | |
601 | return r; | |
602 | ||
603 | r = bus_log_control_api_register(bus); | |
604 | if (r < 0) | |
605 | return r; | |
606 | ||
607 | r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.locale1", 0, NULL, NULL); | |
608 | if (r < 0) | |
609 | return log_error_errno(r, "Failed to request name: %m"); | |
610 | ||
611 | r = sd_bus_attach_event(bus, event, 0); | |
612 | if (r < 0) | |
613 | return log_error_errno(r, "Failed to attach bus to event loop: %m"); | |
614 | ||
615 | *_bus = TAKE_PTR(bus); | |
616 | ||
617 | return 0; | |
618 | } | |
619 | ||
620 | static bool context_check_idle(void *userdata) { | |
621 | Context *c = ASSERT_PTR(userdata); | |
622 | ||
623 | return hashmap_isempty(c->polkit_registry); | |
624 | } | |
625 | ||
626 | static int run(int argc, char *argv[]) { | |
627 | _cleanup_(context_clear) Context context = {}; | |
628 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
629 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
630 | int r; | |
631 | ||
632 | log_setup(); | |
633 | ||
634 | r = service_parse_argv("systemd-localed.service", | |
635 | "Manage system locale settings and key mappings.", | |
636 | BUS_IMPLEMENTATIONS(&manager_object, | |
637 | &log_control_object), | |
638 | argc, argv); | |
639 | if (r <= 0) | |
640 | return r; | |
641 | ||
642 | umask(0022); | |
643 | ||
644 | r = mac_init(); | |
645 | if (r < 0) | |
646 | return r; | |
647 | ||
648 | r = sd_event_default(&event); | |
649 | if (r < 0) | |
650 | return log_error_errno(r, "Failed to allocate event loop: %m"); | |
651 | ||
652 | (void) sd_event_set_watchdog(event, true); | |
653 | ||
654 | r = sd_event_set_signal_exit(event, true); | |
655 | if (r < 0) | |
656 | return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); | |
657 | ||
658 | r = connect_bus(&context, event, &bus); | |
659 | if (r < 0) | |
660 | return r; | |
661 | ||
662 | r = sd_notify(false, NOTIFY_READY_MESSAGE); | |
663 | if (r < 0) | |
664 | log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); | |
665 | ||
666 | r = bus_event_loop_with_idle( | |
667 | event, | |
668 | bus, | |
669 | "org.freedesktop.locale1", | |
670 | DEFAULT_EXIT_USEC, | |
671 | context_check_idle, | |
672 | &context); | |
673 | if (r < 0) | |
674 | return log_error_errno(r, "Failed to run event loop: %m"); | |
675 | ||
676 | return 0; | |
677 | } | |
678 | ||
679 | DEFINE_MAIN_FUNCTION(run); |