]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/vconsole/vconsole-setup.c
vconsole-setup: enable utf-8 mode explicitly
[thirdparty/systemd.git] / src / vconsole / vconsole-setup.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010 Kay Sievers
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <fcntl.h>
28 #include <ctype.h>
29 #include <stdbool.h>
30 #include <stdarg.h>
31 #include <limits.h>
32 #include <locale.h>
33 #include <langinfo.h>
34 #include <sys/ioctl.h>
35 #include <sys/wait.h>
36 #include <linux/tiocl.h>
37 #include <linux/kd.h>
38
39 #include "util.h"
40 #include "log.h"
41 #include "macro.h"
42 #include "virt.h"
43
44 static bool is_vconsole(int fd) {
45 unsigned char data[1];
46
47 data[0] = TIOCL_GETFGCONSOLE;
48 return ioctl(fd, TIOCLINUX, data) >= 0;
49 }
50
51 static bool is_locale_utf8(void) {
52 const char *set;
53
54 if (!setlocale(LC_ALL, ""))
55 return true;
56
57 set = nl_langinfo(CODESET);
58 if (!set)
59 return true;
60
61 return streq(set, "UTF-8");
62 }
63
64 static int disable_utf8(int fd) {
65 int r = 0, k;
66
67 if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
68 r = -errno;
69
70 if (loop_write(fd, "\033%@", 3, false) < 0)
71 r = -errno;
72
73 k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "0");
74 if (k < 0)
75 r = k;
76
77 if (r < 0)
78 log_warning("Failed to disable UTF-8: %s", strerror(-r));
79
80 return r;
81 }
82
83 static int enable_utf8(int fd) {
84 int r = 0, k;
85
86 if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0)
87 r = -errno;
88
89 if (loop_write(fd, "\033%G", 3, false) < 0)
90 r = -errno;
91
92 k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "1");
93 if (k < 0)
94 r = k;
95
96 if (r < 0)
97 log_warning("Failed to enable UTF-8: %s", strerror(-r));
98
99 return r;
100 }
101
102 static int load_keymap(const char *vc, const char *map, const char *map_toggle, bool utf8, pid_t *_pid) {
103 const char *args[8];
104 int i = 0;
105 pid_t pid;
106
107 if (isempty(map)) {
108 /* An empty map means kernel map */
109 *_pid = 0;
110 return 0;
111 }
112
113 args[i++] = KBD_LOADKEYS;
114 args[i++] = "-q";
115 args[i++] = "-C";
116 args[i++] = vc;
117 if (utf8)
118 args[i++] = "-u";
119 args[i++] = map;
120 if (map_toggle)
121 args[i++] = map_toggle;
122 args[i++] = NULL;
123
124 pid = fork();
125 if (pid < 0) {
126 log_error("Failed to fork: %m");
127 return -errno;
128 } else if (pid == 0) {
129 execv(args[0], (char **) args);
130 _exit(EXIT_FAILURE);
131 }
132
133 *_pid = pid;
134 return 0;
135 }
136
137 static int load_font(const char *vc, const char *font, const char *map, const char *unimap, pid_t *_pid) {
138 const char *args[9];
139 int i = 0;
140 pid_t pid;
141
142 if (isempty(font)) {
143 /* An empty font means kernel font */
144 *_pid = 0;
145 return 0;
146 }
147
148 args[i++] = KBD_SETFONT;
149 args[i++] = "-C";
150 args[i++] = vc;
151 args[i++] = font;
152 if (map) {
153 args[i++] = "-m";
154 args[i++] = map;
155 }
156 if (unimap) {
157 args[i++] = "-u";
158 args[i++] = unimap;
159 }
160 args[i++] = NULL;
161
162 pid = fork();
163 if (pid < 0) {
164 log_error("Failed to fork: %m");
165 return -errno;
166 } else if (pid == 0) {
167 execv(args[0], (char **) args);
168 _exit(EXIT_FAILURE);
169 }
170
171 *_pid = pid;
172 return 0;
173 }
174
175 int main(int argc, char **argv) {
176 const char *vc;
177 char *vc_keymap = NULL;
178 char *vc_keymap_toggle = NULL;
179 char *vc_font = NULL;
180 char *vc_font_map = NULL;
181 char *vc_font_unimap = NULL;
182 #ifdef TARGET_GENTOO
183 char *vc_unicode = NULL;
184 #endif
185 #if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
186 char *vc_keytable = NULL;
187 #endif
188 int fd = -1;
189 bool utf8;
190 int r = EXIT_FAILURE;
191 pid_t font_pid = 0, keymap_pid = 0;
192
193 log_set_target(LOG_TARGET_AUTO);
194 log_parse_environment();
195 log_open();
196
197 umask(0022);
198
199 if (argv[1])
200 vc = argv[1];
201 else
202 vc = "/dev/tty0";
203
204 fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
205 if (fd < 0) {
206 log_error("Failed to open %s: %m", vc);
207 goto finish;
208 }
209
210 if (!is_vconsole(fd)) {
211 log_error("Device %s is not a virtual console.", vc);
212 goto finish;
213 }
214
215 utf8 = is_locale_utf8();
216
217 vc_keymap = strdup("us");
218 vc_font = strdup(DEFAULT_FONT);
219
220 if (!vc_keymap || !vc_font) {
221 log_error("Failed to allocate strings.");
222 goto finish;
223 }
224
225 r = 0;
226
227 if (detect_container(NULL) <= 0) {
228 r = parse_env_file("/proc/cmdline", WHITESPACE,
229 "vconsole.keymap", &vc_keymap,
230 "vconsole.keymap.toggle", &vc_keymap_toggle,
231 "vconsole.font", &vc_font,
232 "vconsole.font.map", &vc_font_map,
233 "vconsole.font.unimap", &vc_font_unimap,
234 NULL);
235
236 if (r < 0 && r != -ENOENT)
237 log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
238 }
239
240 /* Hmm, nothing set on the kernel cmd line? Then let's
241 * try /etc/vconsole.conf */
242 if (r <= 0) {
243 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
244 "KEYMAP", &vc_keymap,
245 "KEYMAP_TOGGLE", &vc_keymap_toggle,
246 "FONT", &vc_font,
247 "FONT_MAP", &vc_font_map,
248 "FONT_UNIMAP", &vc_font_unimap,
249 NULL);
250
251 if (r < 0 && r != -ENOENT)
252 log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
253 }
254
255 if (r <= 0) {
256 #if defined(TARGET_FEDORA) || defined(TARGET_MEEGO)
257 r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
258 "SYSFONT", &vc_font,
259 "SYSFONTACM", &vc_font_map,
260 "UNIMAP", &vc_font_unimap,
261 NULL);
262 if (r < 0 && r != -ENOENT)
263 log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
264
265 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
266 "KEYTABLE", &vc_keymap,
267 "KEYMAP", &vc_keymap,
268 NULL);
269 if (r < 0 && r != -ENOENT)
270 log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
271
272 if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
273 char *t;
274
275 t = strdup("/etc/sysconfig/console/default.kmap");
276 if (!t) {
277 log_error("Out of memory.");
278 goto finish;
279 }
280
281 free(vc_keymap);
282 vc_keymap = t;
283 }
284
285 #elif defined(TARGET_SUSE)
286 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
287 "KEYTABLE", &vc_keymap,
288 NULL);
289 if (r < 0 && r != -ENOENT)
290 log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
291
292 r = parse_env_file("/etc/sysconfig/console", NEWLINE,
293 "CONSOLE_FONT", &vc_font,
294 "CONSOLE_SCREENMAP", &vc_font_map,
295 "CONSOLE_UNICODEMAP", &vc_font_unimap,
296 NULL);
297 if (r < 0 && r != -ENOENT)
298 log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r));
299
300 #elif defined(TARGET_ARCH)
301 r = parse_env_file("/etc/rc.conf", NEWLINE,
302 "KEYMAP", &vc_keymap,
303 "CONSOLEFONT", &vc_font,
304 "CONSOLEMAP", &vc_font_map,
305 NULL);
306 if (r < 0 && r != -ENOENT)
307 log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
308
309 #elif defined(TARGET_FRUGALWARE)
310 r = parse_env_file("/etc/sysconfig/keymap", NEWLINE,
311 "keymap", &vc_keymap,
312 NULL);
313 if (r < 0 && r != -ENOENT)
314 log_warning("Failed to read /etc/sysconfig/keymap: %s", strerror(-r));
315
316 r = parse_env_file("/etc/sysconfig/font", NEWLINE,
317 "font", &vc_font,
318 NULL);
319 if (r < 0 && r != -ENOENT)
320 log_warning("Failed to read /etc/sysconfig/font: %s", strerror(-r));
321
322 #elif defined(TARGET_ALTLINUX)
323 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
324 "KEYTABLE", &vc_keymap,
325 NULL)
326 if (r < 0 && r != -ENOENT)
327 log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
328
329 r = parse_env_file("/etc/sysconfig/consolefont", NEWLINE,
330 "SYSFONT", &vc_font,
331 NULL);
332 if (r < 0 && r != -ENOENT)
333 log_warning("Failed to read /etc/sysconfig/consolefont: %s", strerror(-r));
334
335 #elif defined(TARGET_GENTOO)
336 r = parse_env_file("/etc/rc.conf", NEWLINE,
337 "unicode", &vc_unicode,
338 NULL);
339 if (r < 0 && r != -ENOENT)
340 log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
341
342 if (vc_unicode) {
343 int rc_unicode;
344
345 rc_unicode = parse_boolean(vc_unicode);
346 if (rc_unicode < 0)
347 log_warning("Unknown value for /etc/rc.conf unicode=%s", vc_unicode);
348 else {
349 if (rc_unicode && !utf8)
350 log_warning("/etc/rc.conf wants unicode, but current locale is not UTF-8 capable!");
351 else if (!rc_unicode && utf8) {
352 log_debug("/etc/rc.conf does not want unicode, leave it on in kernel but does not apply to vconsole.");
353 utf8 = false;
354 }
355 }
356 }
357
358 /* /etc/conf.d/consolefont comments and gentoo
359 * documentation mention uppercase, but the actual
360 * contents are lowercase. the existing
361 * /etc/init.d/consolefont tries both
362 */
363 r = parse_env_file("/etc/conf.d/consolefont", NEWLINE,
364 "CONSOLEFONT", &vc_font,
365 "consolefont", &vc_font,
366 "consoletranslation", &vc_font_map,
367 "CONSOLETRANSLATION", &vc_font_map,
368 "unicodemap", &vc_font_unimap,
369 "UNICODEMAP", &vc_font_unimap,
370 NULL);
371 if (r < 0 && r != -ENOENT)
372 log_warning("Failed to read /etc/conf.d/consolefont: %s", strerror(-r));
373
374 r = parse_env_file("/etc/conf.d/keymaps", NEWLINE,
375 "keymap", &vc_keymap,
376 "KEYMAP", &vc_keymap,
377 NULL);
378 if (r < 0 && r != -ENOENT)
379 log_warning("Failed to read /etc/conf.d/keymaps: %s", strerror(-r));
380
381 #elif defined(TARGET_MANDRIVA) || defined (TARGET_MAGEIA)
382
383 r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
384 "SYSFONT", &vc_font,
385 "SYSFONTACM", &vc_font_map,
386 "UNIMAP", &vc_font_unimap,
387 NULL);
388 if (r < 0 && r != -ENOENT)
389 log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
390
391 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
392 "KEYTABLE", &vc_keytable,
393 "KEYMAP", &vc_keymap,
394 "UNIKEYTABLE", &vc_keymap,
395 "GRP_TOGGLE", &vc_keymap_toggle,
396 NULL);
397 if (r < 0 && r != -ENOENT)
398 log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
399
400 if (vc_keytable) {
401 free(vc_keymap);
402 if (utf8) {
403 if (endswith(vc_keytable, ".uni") || strstr(vc_keytable, ".uni."))
404 vc_keymap = strdup(vc_keytable);
405 else {
406 char *s;
407 s = strstr(vc_keytable, ".map");
408 if (s)
409 vc_keytable[s-vc_keytable+1] = '\0';
410 vc_keymap = strappend(vc_keytable, ".uni");
411 }
412 } else
413 vc_keymap = strdup(vc_keytable);
414
415 free(vc_keytable);
416
417 if (!vc_keymap) {
418 log_error("Out of memory.");
419 goto finish;
420 }
421 }
422
423 if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
424 char *t;
425
426 t = strdup("/etc/sysconfig/console/default.kmap");
427 if (!t) {
428 log_error("Out of memory.");
429 goto finish;
430 }
431
432 free(vc_keymap);
433 vc_keymap = t;
434 }
435 #endif
436 }
437
438 r = EXIT_FAILURE;
439
440 if (utf8)
441 enable_utf8(fd);
442 else
443 disable_utf8(fd);
444
445
446 if (load_keymap(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
447 load_font(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
448 r = EXIT_SUCCESS;
449
450 finish:
451 if (keymap_pid > 0)
452 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
453
454 if (font_pid > 0)
455 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
456
457 free(vc_keymap);
458 free(vc_font);
459 free(vc_font_map);
460 free(vc_font_unimap);
461
462 if (fd >= 0)
463 close_nointr_nofail(fd);
464
465 return r;
466 }