]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
1822350d | 2 | /*** |
8d451309 | 3 | Copyright 2013 Kay Sievers |
1822350d KS |
4 | ***/ |
5 | ||
1822350d KS |
6 | #include <errno.h> |
7 | #include <string.h> | |
8 | #include <unistd.h> | |
9 | ||
349cc4a5 | 10 | #if HAVE_XKBCOMMON |
3ffd4af2 | 11 | #include <xkbcommon/xkbcommon.h> |
5de34470 | 12 | #include <dlfcn.h> |
3ffd4af2 LP |
13 | #endif |
14 | ||
8d451309 KS |
15 | #include "sd-bus.h" |
16 | ||
b5efdb8a | 17 | #include "alloc-util.h" |
8d451309 KS |
18 | #include "bus-error.h" |
19 | #include "bus-message.h" | |
bb15fafe LP |
20 | #include "bus-util.h" |
21 | #include "def.h" | |
4897d1dc | 22 | #include "keymap-util.h" |
75683450 | 23 | #include "locale-util.h" |
4897d1dc | 24 | #include "macro.h" |
bb15fafe | 25 | #include "path-util.h" |
d7b8eec7 | 26 | #include "selinux-util.h" |
4897d1dc | 27 | #include "string-util.h" |
bb15fafe | 28 | #include "strv.h" |
ee104e11 | 29 | #include "user-util.h" |
d4f5a1f4 | 30 | |
4897d1dc | 31 | static Hashmap *polkit_registry = NULL; |
1822350d | 32 | |
8d451309 KS |
33 | static int locale_update_system_manager(Context *c, sd_bus *bus) { |
34 | _cleanup_free_ char **l_unset = NULL; | |
35 | _cleanup_strv_free_ char **l_set = NULL; | |
4afd3348 | 36 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
8d451309 KS |
37 | sd_bus_error error = SD_BUS_ERROR_NULL; |
38 | unsigned c_set, c_unset, p; | |
39 | int r; | |
1822350d KS |
40 | |
41 | assert(bus); | |
42 | ||
78c97cbe | 43 | l_unset = new0(char*, _VARIABLE_LC_MAX); |
8d451309 KS |
44 | if (!l_unset) |
45 | return -ENOMEM; | |
1822350d | 46 | |
78c97cbe | 47 | l_set = new0(char*, _VARIABLE_LC_MAX); |
8d451309 KS |
48 | if (!l_set) |
49 | return -ENOMEM; | |
50 | ||
78c97cbe ZJS |
51 | for (p = 0, c_set = 0, c_unset = 0; p < _VARIABLE_LC_MAX; p++) { |
52 | const char *name; | |
53 | ||
54 | name = locale_variable_to_string(p); | |
55 | assert(name); | |
1822350d | 56 | |
8d451309 | 57 | if (isempty(c->locale[p])) |
78c97cbe | 58 | l_unset[c_set++] = (char*) name; |
1822350d KS |
59 | else { |
60 | char *s; | |
61 | ||
78c97cbe | 62 | if (asprintf(&s, "%s=%s", name, c->locale[p]) < 0) |
8d451309 | 63 | return -ENOMEM; |
1822350d KS |
64 | |
65 | l_set[c_unset++] = s; | |
66 | } | |
67 | } | |
68 | ||
78c97cbe | 69 | assert(c_set + c_unset == _VARIABLE_LC_MAX); |
151b9b96 | 70 | r = sd_bus_message_new_method_call(bus, &m, |
8d451309 KS |
71 | "org.freedesktop.systemd1", |
72 | "/org/freedesktop/systemd1", | |
73 | "org.freedesktop.systemd1.Manager", | |
151b9b96 | 74 | "UnsetAndSetEnvironment"); |
8d451309 KS |
75 | if (r < 0) |
76 | return r; | |
1822350d | 77 | |
8d451309 KS |
78 | r = sd_bus_message_append_strv(m, l_unset); |
79 | if (r < 0) | |
80 | return r; | |
1822350d | 81 | |
8d451309 KS |
82 | r = sd_bus_message_append_strv(m, l_set); |
83 | if (r < 0) | |
84 | return r; | |
1822350d | 85 | |
c49b30a2 | 86 | r = sd_bus_call(bus, m, 0, &error, NULL); |
8d451309 | 87 | if (r < 0) |
df4fd2c7 | 88 | log_error_errno(r, "Failed to update the manager environment, ignoring: %m"); |
1822350d | 89 | |
8d451309 | 90 | return 0; |
1822350d KS |
91 | } |
92 | ||
8d451309 | 93 | static int vconsole_reload(sd_bus *bus) { |
4afd3348 | 94 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
1822350d | 95 | int r; |
1822350d KS |
96 | |
97 | assert(bus); | |
98 | ||
8d451309 | 99 | r = sd_bus_call_method(bus, |
1822350d KS |
100 | "org.freedesktop.systemd1", |
101 | "/org/freedesktop/systemd1", | |
102 | "org.freedesktop.systemd1.Manager", | |
8d451309 KS |
103 | "RestartUnit", |
104 | &error, | |
105 | NULL, | |
106 | "ss", "systemd-vconsole-setup.service", "replace"); | |
1822350d | 107 | |
8d451309 KS |
108 | if (r < 0) |
109 | log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); | |
1822350d KS |
110 | return r; |
111 | } | |
112 | ||
df4fd2c7 | 113 | static int vconsole_convert_to_x11_and_emit(Context *c, sd_bus_message *m) { |
78bd12a0 | 114 | int r; |
0732ef7a | 115 | |
df4fd2c7 YW |
116 | assert(m); |
117 | ||
118 | r = x11_read_data(c, m); | |
119 | if (r < 0) | |
120 | return r; | |
4e829d21 | 121 | |
4897d1dc ZJS |
122 | r = vconsole_convert_to_x11(c); |
123 | if (r <= 0) | |
124 | return r; | |
4e829d21 | 125 | |
4897d1dc ZJS |
126 | /* modified */ |
127 | r = x11_write_data(c); | |
128 | if (r < 0) | |
129 | return log_error_errno(r, "Failed to write X11 keyboard layout: %m"); | |
4e829d21 | 130 | |
df4fd2c7 | 131 | sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), |
4897d1dc ZJS |
132 | "/org/freedesktop/locale1", |
133 | "org.freedesktop.locale1", | |
134 | "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); | |
4e829d21 | 135 | |
4897d1dc | 136 | return 1; |
4e829d21 ZJS |
137 | } |
138 | ||
df4fd2c7 | 139 | static int x11_convert_to_vconsole_and_emit(Context *c, sd_bus_message *m) { |
0732ef7a | 140 | int r; |
1822350d | 141 | |
df4fd2c7 YW |
142 | assert(m); |
143 | ||
144 | r = vconsole_read_data(c, m); | |
145 | if (r < 0) | |
146 | return r; | |
1822350d | 147 | |
4897d1dc ZJS |
148 | r = x11_convert_to_vconsole(c); |
149 | if (r <= 0) | |
150 | return r; | |
502f9614 | 151 | |
4897d1dc ZJS |
152 | /* modified */ |
153 | r = vconsole_write_data(c); | |
154 | if (r < 0) | |
155 | log_error_errno(r, "Failed to save virtual console keymap: %m"); | |
1822350d | 156 | |
df4fd2c7 | 157 | sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), |
4897d1dc ZJS |
158 | "/org/freedesktop/locale1", |
159 | "org.freedesktop.locale1", | |
160 | "VConsoleKeymap", "VConsoleKeymapToggle", NULL); | |
1822350d | 161 | |
df4fd2c7 | 162 | return vconsole_reload(sd_bus_message_get_bus(m)); |
1822350d KS |
163 | } |
164 | ||
ebcf1f97 LP |
165 | static int property_get_locale( |
166 | sd_bus *bus, | |
167 | const char *path, | |
168 | const char *interface, | |
169 | const char *property, | |
170 | sd_bus_message *reply, | |
171 | void *userdata, | |
172 | sd_bus_error *error) { | |
173 | ||
8d451309 | 174 | Context *c = userdata; |
b47d419c | 175 | _cleanup_strv_free_ char **l = NULL; |
df4fd2c7 YW |
176 | int p, q, r; |
177 | ||
178 | r = locale_read_data(c, reply); | |
179 | if (r < 0) | |
180 | return r; | |
1822350d | 181 | |
78c97cbe | 182 | l = new0(char*, _VARIABLE_LC_MAX+1); |
1822350d KS |
183 | if (!l) |
184 | return -ENOMEM; | |
185 | ||
78c97cbe | 186 | for (p = 0, q = 0; p < _VARIABLE_LC_MAX; p++) { |
1822350d | 187 | char *t; |
78c97cbe ZJS |
188 | const char *name; |
189 | ||
190 | name = locale_variable_to_string(p); | |
191 | assert(name); | |
1822350d | 192 | |
8d451309 | 193 | if (isempty(c->locale[p])) |
1822350d KS |
194 | continue; |
195 | ||
78c97cbe | 196 | if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0) |
1822350d | 197 | return -ENOMEM; |
1822350d | 198 | |
8d451309 | 199 | l[q++] = t; |
1822350d KS |
200 | } |
201 | ||
8d451309 | 202 | return sd_bus_message_append_strv(reply, l); |
1822350d KS |
203 | } |
204 | ||
df4fd2c7 YW |
205 | static int property_get_vconsole( |
206 | sd_bus *bus, | |
207 | const char *path, | |
208 | const char *interface, | |
209 | const char *property, | |
210 | sd_bus_message *reply, | |
211 | void *userdata, | |
212 | sd_bus_error *error) { | |
213 | ||
214 | Context *c = userdata; | |
215 | int r; | |
216 | ||
217 | r = vconsole_read_data(c, reply); | |
218 | if (r < 0) | |
219 | return r; | |
220 | ||
221 | if (streq(property, "VConsoleKeymap")) | |
222 | return sd_bus_message_append_basic(reply, 's', c->vc_keymap); | |
223 | else if (streq(property, "VConsoleKeymapToggle")) | |
224 | return sd_bus_message_append_basic(reply, 's', c->vc_keymap_toggle); | |
225 | ||
226 | return -EINVAL; | |
227 | } | |
228 | ||
229 | static int property_get_xkb( | |
230 | sd_bus *bus, | |
231 | const char *path, | |
232 | const char *interface, | |
233 | const char *property, | |
234 | sd_bus_message *reply, | |
235 | void *userdata, | |
236 | sd_bus_error *error) { | |
237 | ||
238 | Context *c = userdata; | |
239 | int r; | |
240 | ||
241 | r = x11_read_data(c, reply); | |
242 | if (r < 0) | |
243 | return r; | |
244 | ||
245 | if (streq(property, "X11Layout")) | |
246 | return sd_bus_message_append_basic(reply, 's', c->x11_layout); | |
247 | else if (streq(property, "X11Model")) | |
248 | return sd_bus_message_append_basic(reply, 's', c->x11_model); | |
249 | else if (streq(property, "X11Variant")) | |
250 | return sd_bus_message_append_basic(reply, 's', c->x11_variant); | |
251 | else if (streq(property, "X11Options")) | |
252 | return sd_bus_message_append_basic(reply, 's', c->x11_options); | |
253 | ||
254 | return -EINVAL; | |
255 | } | |
256 | ||
257 | static void locale_free(char ***l) { | |
258 | int p; | |
259 | ||
260 | for (p = 0; p < _VARIABLE_LC_MAX; p++) | |
261 | (*l)[p] = mfree((*l)[p]); | |
262 | } | |
263 | ||
19070062 | 264 | static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
8d451309 | 265 | Context *c = userdata; |
df4fd2c7 | 266 | _cleanup_strv_free_ char **settings = NULL, **l = NULL; |
60091993 YW |
267 | char *new_locale[_VARIABLE_LC_MAX] = {}, **i; |
268 | _cleanup_(locale_free) _unused_ char **dummy = new_locale; | |
8d451309 | 269 | bool modified = false; |
df4fd2c7 | 270 | int interactive, p, r; |
1822350d | 271 | |
19070062 LP |
272 | assert(m); |
273 | assert(c); | |
274 | ||
8d451309 KS |
275 | r = bus_message_read_strv_extend(m, &l); |
276 | if (r < 0) | |
ebcf1f97 | 277 | return r; |
1822350d | 278 | |
8d451309 KS |
279 | r = sd_bus_message_read_basic(m, 'b', &interactive); |
280 | if (r < 0) | |
ebcf1f97 | 281 | return r; |
1822350d | 282 | |
4156e767 YW |
283 | /* If single locale without variable name is provided, then we assume it is LANG=. */ |
284 | if (strv_length(l) == 1 && !strchr(*l, '=')) { | |
285 | if (!locale_is_valid(*l)) | |
286 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); | |
287 | ||
288 | new_locale[VARIABLE_LANG] = strdup(*l); | |
289 | if (!new_locale[VARIABLE_LANG]) | |
290 | return -ENOMEM; | |
291 | ||
292 | l = strv_free(l); | |
293 | } | |
294 | ||
df4fd2c7 | 295 | /* Check whether a variable is valid */ |
8d451309 KS |
296 | STRV_FOREACH(i, l) { |
297 | bool valid = false; | |
1822350d | 298 | |
78c97cbe | 299 | for (p = 0; p < _VARIABLE_LC_MAX; p++) { |
8d451309 | 300 | size_t k; |
78c97cbe | 301 | const char *name; |
1822350d | 302 | |
78c97cbe ZJS |
303 | name = locale_variable_to_string(p); |
304 | assert(name); | |
305 | ||
306 | k = strlen(name); | |
307 | if (startswith(*i, name) && | |
8d451309 | 308 | (*i)[k] == '=' && |
75683450 | 309 | locale_is_valid((*i) + k + 1)) { |
8d451309 | 310 | valid = true; |
1822350d | 311 | |
df4fd2c7 YW |
312 | new_locale[p] = strdup((*i) + k + 1); |
313 | if (!new_locale[p]) | |
314 | return -ENOMEM; | |
1822350d | 315 | |
8d451309 KS |
316 | break; |
317 | } | |
1822350d KS |
318 | } |
319 | ||
8d451309 | 320 | if (!valid) |
ebcf1f97 | 321 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); |
8d451309 | 322 | } |
1822350d | 323 | |
4e829d21 ZJS |
324 | /* If LANG was specified, but not LANGUAGE, check if we should |
325 | * set it based on the language fallback table. */ | |
df4fd2c7 YW |
326 | if (!isempty(new_locale[VARIABLE_LANG]) && |
327 | isempty(new_locale[VARIABLE_LANGUAGE])) { | |
4e829d21 ZJS |
328 | _cleanup_free_ char *language = NULL; |
329 | ||
df4fd2c7 | 330 | (void) find_language_fallback(new_locale[VARIABLE_LANG], &language); |
4e829d21 | 331 | if (language) { |
df4fd2c7 YW |
332 | log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale[VARIABLE_LANG], language); |
333 | free_and_replace(new_locale[VARIABLE_LANGUAGE], language); | |
4e829d21 ZJS |
334 | } |
335 | } | |
336 | ||
df4fd2c7 YW |
337 | r = locale_read_data(c, m); |
338 | if (r < 0) { | |
339 | log_error_errno(r, "Failed to read locale data: %m"); | |
340 | return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Failed to read locale data"); | |
341 | } | |
8d451309 | 342 | |
df4fd2c7 YW |
343 | /* Merge with the current settings */ |
344 | for (p = 0; p < _VARIABLE_LC_MAX; p++) | |
345 | if (!isempty(c->locale[p]) && isempty(new_locale[p])) { | |
346 | new_locale[p] = strdup(c->locale[p]); | |
347 | if (!new_locale[p]) | |
348 | return -ENOMEM; | |
349 | } | |
1822350d | 350 | |
df4fd2c7 | 351 | locale_simplify(new_locale); |
1822350d | 352 | |
df4fd2c7 YW |
353 | for (p = 0; p < _VARIABLE_LC_MAX; p++) |
354 | if (!streq_ptr(c->locale[p], new_locale[p])) { | |
355 | modified = true; | |
356 | break; | |
8d451309 | 357 | } |
1822350d | 358 | |
df4fd2c7 YW |
359 | if (!modified) { |
360 | log_debug("Locale settings were not modified."); | |
361 | return sd_bus_reply_method_return(m, NULL); | |
362 | } | |
1822350d | 363 | |
df4fd2c7 YW |
364 | r = bus_verify_polkit_async( |
365 | m, | |
366 | CAP_SYS_ADMIN, | |
367 | "org.freedesktop.locale1.set-locale", | |
368 | NULL, | |
369 | interactive, | |
370 | UID_INVALID, | |
371 | &polkit_registry, | |
372 | error); | |
373 | if (r < 0) | |
374 | return r; | |
375 | if (r == 0) | |
376 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
1822350d | 377 | |
df4fd2c7 YW |
378 | for (p = 0; p < _VARIABLE_LC_MAX; p++) |
379 | free_and_replace(c->locale[p], new_locale[p]); | |
1822350d | 380 | |
df4fd2c7 YW |
381 | r = locale_write_data(c, &settings); |
382 | if (r < 0) { | |
383 | log_error_errno(r, "Failed to set locale: %m"); | |
384 | return sd_bus_error_set_errnof(error, r, "Failed to set locale: %m"); | |
385 | } | |
502f9614 | 386 | |
df4fd2c7 | 387 | (void) locale_update_system_manager(c, sd_bus_message_get_bus(m)); |
1822350d | 388 | |
df4fd2c7 YW |
389 | if (settings) { |
390 | _cleanup_free_ char *line; | |
391 | ||
392 | line = strv_join(settings, ", "); | |
393 | log_info("Changed locale to %s.", strnull(line)); | |
502f9614 | 394 | } else |
df4fd2c7 YW |
395 | log_info("Changed locale to unset."); |
396 | ||
397 | (void) sd_bus_emit_properties_changed( | |
398 | sd_bus_message_get_bus(m), | |
399 | "/org/freedesktop/locale1", | |
400 | "org.freedesktop.locale1", | |
401 | "Locale", NULL); | |
502f9614 | 402 | |
df2d202e | 403 | return sd_bus_reply_method_return(m, NULL); |
8d451309 | 404 | } |
1822350d | 405 | |
19070062 | 406 | static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
8d451309 | 407 | Context *c = userdata; |
8d451309 | 408 | const char *keymap, *keymap_toggle; |
b47ff73b | 409 | int convert, interactive, r; |
f2cc3753 | 410 | |
19070062 LP |
411 | assert(m); |
412 | assert(c); | |
413 | ||
8d451309 KS |
414 | r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive); |
415 | if (r < 0) | |
ebcf1f97 | 416 | return r; |
1822350d | 417 | |
3c6f7c34 LP |
418 | keymap = empty_to_null(keymap); |
419 | keymap_toggle = empty_to_null(keymap_toggle); | |
1822350d | 420 | |
df4fd2c7 YW |
421 | r = vconsole_read_data(c, m); |
422 | if (r < 0) { | |
423 | log_error_errno(r, "Failed to read virtual console keymap data: %m"); | |
424 | return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Failed to read virtual console keymap data"); | |
425 | } | |
426 | ||
b47ff73b YW |
427 | if (streq_ptr(keymap, c->vc_keymap) && |
428 | streq_ptr(keymap_toggle, c->vc_keymap_toggle)) | |
429 | return sd_bus_reply_method_return(m, NULL); | |
1822350d | 430 | |
b47ff73b YW |
431 | if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) || |
432 | (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle)))) | |
433 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keymap data"); | |
1822350d | 434 | |
b47ff73b YW |
435 | r = bus_verify_polkit_async( |
436 | m, | |
437 | CAP_SYS_ADMIN, | |
438 | "org.freedesktop.locale1.set-keyboard", | |
439 | NULL, | |
440 | interactive, | |
441 | UID_INVALID, | |
442 | &polkit_registry, | |
443 | error); | |
444 | if (r < 0) | |
445 | return r; | |
446 | if (r == 0) | |
447 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
1822350d | 448 | |
b47ff73b YW |
449 | if (free_and_strdup(&c->vc_keymap, keymap) < 0 || |
450 | free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0) | |
451 | return -ENOMEM; | |
0b507b17 | 452 | |
b47ff73b YW |
453 | r = vconsole_write_data(c); |
454 | if (r < 0) { | |
455 | log_error_errno(r, "Failed to set virtual console keymap: %m"); | |
456 | return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %m"); | |
457 | } | |
1822350d | 458 | |
b47ff73b YW |
459 | log_info("Changed virtual console keymap to '%s' toggle '%s'", |
460 | strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); | |
1822350d | 461 | |
b47ff73b YW |
462 | r = vconsole_reload(sd_bus_message_get_bus(m)); |
463 | if (r < 0) | |
464 | log_error_errno(r, "Failed to request keymap reload: %m"); | |
1822350d | 465 | |
b47ff73b YW |
466 | (void) sd_bus_emit_properties_changed( |
467 | sd_bus_message_get_bus(m), | |
468 | "/org/freedesktop/locale1", | |
469 | "org.freedesktop.locale1", | |
470 | "VConsoleKeymap", "VConsoleKeymapToggle", NULL); | |
1822350d | 471 | |
b47ff73b | 472 | if (convert) { |
df4fd2c7 | 473 | r = vconsole_convert_to_x11_and_emit(c, m); |
b47ff73b YW |
474 | if (r < 0) |
475 | log_error_errno(r, "Failed to convert keymap data: %m"); | |
8d451309 | 476 | } |
1822350d | 477 | |
df2d202e | 478 | return sd_bus_reply_method_return(m, NULL); |
8d451309 | 479 | } |
1822350d | 480 | |
349cc4a5 | 481 | #if HAVE_XKBCOMMON |
5de34470 | 482 | |
3cb063fd | 483 | _printf_(3, 0) |
d4f5a1f4 | 484 | static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { |
8433e339 JS |
485 | const char *fmt; |
486 | ||
63c372cb | 487 | fmt = strjoina("libxkbcommon: ", format); |
032b7541 ZJS |
488 | #pragma GCC diagnostic push |
489 | #pragma GCC diagnostic ignored "-Wformat-nonliteral" | |
8433e339 | 490 | log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args); |
032b7541 | 491 | #pragma GCC diagnostic pop |
d4f5a1f4 DH |
492 | } |
493 | ||
5de34470 LP |
494 | #define LOAD_SYMBOL(symbol, dl, name) \ |
495 | ({ \ | |
496 | (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \ | |
497 | (symbol) ? 0 : -EOPNOTSUPP; \ | |
498 | }) | |
499 | ||
d4f5a1f4 | 500 | static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { |
5de34470 LP |
501 | |
502 | /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge | |
503 | * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function | |
504 | * pointers to the shared library are below: */ | |
505 | ||
506 | struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL; | |
507 | void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL; | |
508 | void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL; | |
509 | struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL; | |
510 | void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL; | |
511 | ||
d4f5a1f4 DH |
512 | const struct xkb_rule_names rmlvo = { |
513 | .model = model, | |
514 | .layout = layout, | |
515 | .variant = variant, | |
516 | .options = options, | |
517 | }; | |
518 | struct xkb_context *ctx = NULL; | |
519 | struct xkb_keymap *km = NULL; | |
5de34470 | 520 | void *dl; |
d4f5a1f4 DH |
521 | int r; |
522 | ||
5de34470 LP |
523 | /* Compile keymap from RMLVO information to check out its validity */ |
524 | ||
525 | dl = dlopen("libxkbcommon.so.0", RTLD_LAZY); | |
526 | if (!dl) | |
527 | return -EOPNOTSUPP; | |
528 | ||
529 | r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new"); | |
530 | if (r < 0) | |
531 | goto finish; | |
532 | ||
533 | r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref"); | |
534 | if (r < 0) | |
535 | goto finish; | |
536 | ||
537 | r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn"); | |
538 | if (r < 0) | |
539 | goto finish; | |
d4f5a1f4 | 540 | |
5de34470 LP |
541 | r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names"); |
542 | if (r < 0) | |
543 | goto finish; | |
544 | ||
545 | r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref"); | |
546 | if (r < 0) | |
547 | goto finish; | |
548 | ||
549 | ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES); | |
d4f5a1f4 DH |
550 | if (!ctx) { |
551 | r = -ENOMEM; | |
5de34470 | 552 | goto finish; |
d4f5a1f4 DH |
553 | } |
554 | ||
5de34470 | 555 | symbol_xkb_context_set_log_fn(ctx, log_xkb); |
d4f5a1f4 | 556 | |
5de34470 | 557 | km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS); |
d4f5a1f4 DH |
558 | if (!km) { |
559 | r = -EINVAL; | |
5de34470 | 560 | goto finish; |
d4f5a1f4 DH |
561 | } |
562 | ||
563 | r = 0; | |
564 | ||
5de34470 LP |
565 | finish: |
566 | if (symbol_xkb_keymap_unref && km) | |
567 | symbol_xkb_keymap_unref(km); | |
568 | ||
569 | if (symbol_xkb_context_unref && ctx) | |
570 | symbol_xkb_context_unref(ctx); | |
571 | ||
572 | (void) dlclose(dl); | |
d4f5a1f4 DH |
573 | return r; |
574 | } | |
5de34470 | 575 | |
d4f5a1f4 | 576 | #else |
5de34470 | 577 | |
d4f5a1f4 DH |
578 | static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { |
579 | return 0; | |
580 | } | |
5de34470 | 581 | |
d4f5a1f4 DH |
582 | #endif |
583 | ||
19070062 | 584 | static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
8d451309 | 585 | Context *c = userdata; |
8d451309 | 586 | const char *layout, *model, *variant, *options; |
b47ff73b | 587 | int convert, interactive, r; |
1822350d | 588 | |
19070062 LP |
589 | assert(m); |
590 | assert(c); | |
591 | ||
8d451309 KS |
592 | r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive); |
593 | if (r < 0) | |
ebcf1f97 | 594 | return r; |
1822350d | 595 | |
3c6f7c34 LP |
596 | layout = empty_to_null(layout); |
597 | model = empty_to_null(model); | |
598 | variant = empty_to_null(variant); | |
599 | options = empty_to_null(options); | |
1822350d | 600 | |
df4fd2c7 YW |
601 | r = x11_read_data(c, m); |
602 | if (r < 0) { | |
603 | log_error_errno(r, "Failed to read x11 keyboard layout data: %m"); | |
604 | return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Failed to read x11 keyboard layout data"); | |
605 | } | |
606 | ||
b47ff73b YW |
607 | if (streq_ptr(layout, c->x11_layout) && |
608 | streq_ptr(model, c->x11_model) && | |
609 | streq_ptr(variant, c->x11_variant) && | |
610 | streq_ptr(options, c->x11_options)) | |
611 | return sd_bus_reply_method_return(m, NULL); | |
612 | ||
613 | if ((layout && !string_is_safe(layout)) || | |
614 | (model && !string_is_safe(model)) || | |
615 | (variant && !string_is_safe(variant)) || | |
616 | (options && !string_is_safe(options))) | |
617 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keyboard data"); | |
618 | ||
fe28d887 YW |
619 | r = verify_xkb_rmlvo(model, layout, variant, options); |
620 | if (r < 0) { | |
621 | log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m", | |
622 | strempty(model), strempty(layout), strempty(variant), strempty(options)); | |
623 | ||
624 | if (r == -EOPNOTSUPP) | |
625 | return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Local keyboard configuration not supported on this system."); | |
626 | ||
627 | return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified keymap cannot be compiled, refusing as invalid."); | |
628 | } | |
629 | ||
b47ff73b YW |
630 | r = bus_verify_polkit_async( |
631 | m, | |
632 | CAP_SYS_ADMIN, | |
633 | "org.freedesktop.locale1.set-keyboard", | |
634 | NULL, | |
635 | interactive, | |
636 | UID_INVALID, | |
637 | &polkit_registry, | |
638 | error); | |
639 | if (r < 0) | |
640 | return r; | |
641 | if (r == 0) | |
642 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
1822350d | 643 | |
b47ff73b YW |
644 | if (free_and_strdup(&c->x11_layout, layout) < 0 || |
645 | free_and_strdup(&c->x11_model, model) < 0 || | |
646 | free_and_strdup(&c->x11_variant, variant) < 0 || | |
647 | free_and_strdup(&c->x11_options, options) < 0) | |
648 | return -ENOMEM; | |
1822350d | 649 | |
b47ff73b YW |
650 | r = x11_write_data(c); |
651 | if (r < 0) { | |
652 | log_error_errno(r, "Failed to set X11 keyboard layout: %m"); | |
653 | return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %m"); | |
654 | } | |
1822350d | 655 | |
b47ff73b YW |
656 | log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", |
657 | strempty(c->x11_layout), | |
658 | strempty(c->x11_model), | |
659 | strempty(c->x11_variant), | |
660 | strempty(c->x11_options)); | |
1822350d | 661 | |
b47ff73b YW |
662 | (void) sd_bus_emit_properties_changed( |
663 | sd_bus_message_get_bus(m), | |
664 | "/org/freedesktop/locale1", | |
665 | "org.freedesktop.locale1", | |
666 | "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); | |
1822350d | 667 | |
b47ff73b | 668 | if (convert) { |
df4fd2c7 | 669 | r = x11_convert_to_vconsole_and_emit(c, m); |
b47ff73b YW |
670 | if (r < 0) |
671 | log_error_errno(r, "Failed to convert keymap data: %m"); | |
1822350d KS |
672 | } |
673 | ||
df2d202e | 674 | return sd_bus_reply_method_return(m, NULL); |
1822350d KS |
675 | } |
676 | ||
8d451309 KS |
677 | static const sd_bus_vtable locale_vtable[] = { |
678 | SD_BUS_VTABLE_START(0), | |
6d1bd3b2 | 679 | SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), |
df4fd2c7 YW |
680 | SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), |
681 | SD_BUS_PROPERTY("X11Model", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
682 | SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
683 | SD_BUS_PROPERTY("X11Options", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
684 | SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
685 | SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
adacb957 LP |
686 | SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED), |
687 | SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), | |
688 | SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), | |
8d451309 KS |
689 | SD_BUS_VTABLE_END |
690 | }; | |
691 | ||
692 | static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { | |
4afd3348 | 693 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; |
1822350d KS |
694 | int r; |
695 | ||
8d451309 KS |
696 | assert(c); |
697 | assert(event); | |
1822350d KS |
698 | assert(_bus); |
699 | ||
76b54375 | 700 | r = sd_bus_default_system(&bus); |
f647962d MS |
701 | if (r < 0) |
702 | return log_error_errno(r, "Failed to get system bus connection: %m"); | |
1822350d | 703 | |
19befb2d | 704 | r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c); |
f647962d MS |
705 | if (r < 0) |
706 | return log_error_errno(r, "Failed to register object: %m"); | |
1822350d | 707 | |
0c0b9306 | 708 | r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.locale1", 0, NULL, NULL); |
f647962d | 709 | if (r < 0) |
0c0b9306 | 710 | return log_error_errno(r, "Failed to request name: %m"); |
1822350d | 711 | |
8d451309 | 712 | r = sd_bus_attach_event(bus, event, 0); |
f647962d MS |
713 | if (r < 0) |
714 | return log_error_errno(r, "Failed to attach bus to event loop: %m"); | |
1822350d | 715 | |
1cc6c93a | 716 | *_bus = TAKE_PTR(bus); |
1822350d | 717 | |
8d451309 | 718 | return 0; |
1822350d KS |
719 | } |
720 | ||
721 | int main(int argc, char *argv[]) { | |
df4fd2c7 YW |
722 | _cleanup_(context_free) Context context = { |
723 | .locale_mtime = USEC_INFINITY, | |
724 | .vc_mtime = USEC_INFINITY, | |
725 | .x11_mtime = USEC_INFINITY, | |
726 | }; | |
4afd3348 LP |
727 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; |
728 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
1822350d | 729 | int r; |
1822350d KS |
730 | |
731 | log_set_target(LOG_TARGET_AUTO); | |
732 | log_parse_environment(); | |
733 | log_open(); | |
8d451309 | 734 | |
1822350d | 735 | umask(0022); |
c3dacc8b | 736 | mac_selinux_init(); |
1822350d | 737 | |
1822350d KS |
738 | if (argc != 1) { |
739 | log_error("This program takes no arguments."); | |
740 | r = -EINVAL; | |
741 | goto finish; | |
742 | } | |
743 | ||
afc6adb5 | 744 | r = sd_event_default(&event); |
1822350d | 745 | if (r < 0) { |
da927ba9 | 746 | log_error_errno(r, "Failed to allocate event loop: %m"); |
1822350d KS |
747 | goto finish; |
748 | } | |
749 | ||
cde93897 LP |
750 | sd_event_set_watchdog(event, true); |
751 | ||
8d451309 | 752 | r = connect_bus(&context, event, &bus); |
1822350d KS |
753 | if (r < 0) |
754 | goto finish; | |
755 | ||
37224a5f | 756 | r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL); |
4897d1dc | 757 | if (r < 0) |
da927ba9 | 758 | log_error_errno(r, "Failed to run event loop: %m"); |
1822350d | 759 | |
1822350d | 760 | finish: |
4897d1dc ZJS |
761 | bus_verify_polkit_async_registry_free(polkit_registry); |
762 | ||
1822350d KS |
763 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; |
764 | } |