chiark / gitweb /
vconsole: default to the kernel compiled-in keymap
[elogind.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         r = 0;
218
219         if (detect_container(NULL) <= 0) {
220                 r = parse_env_file("/proc/cmdline", WHITESPACE,
221                                    "vconsole.keymap", &vc_keymap,
222                                    "vconsole.keymap.toggle", &vc_keymap_toggle,
223                                    "vconsole.font", &vc_font,
224                                    "vconsole.font.map", &vc_font_map,
225                                    "vconsole.font.unimap", &vc_font_unimap,
226                                    NULL);
227
228                 if (r < 0 && r != -ENOENT)
229                         log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
230         }
231
232         /* Hmm, nothing set on the kernel cmd line? Then let's
233          * try /etc/vconsole.conf */
234         if (r <= 0) {
235                 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
236                                    "KEYMAP", &vc_keymap,
237                                    "KEYMAP_TOGGLE", &vc_keymap_toggle,
238                                    "FONT", &vc_font,
239                                    "FONT_MAP", &vc_font_map,
240                                    "FONT_UNIMAP", &vc_font_unimap,
241                                    NULL);
242
243                 if (r < 0 && r != -ENOENT)
244                         log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
245         }
246
247         if (r <= 0) {
248 #if defined(TARGET_FEDORA)
249                 r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
250                                    "SYSFONT", &vc_font,
251                                    "SYSFONTACM", &vc_font_map,
252                                    "UNIMAP", &vc_font_unimap,
253                                    NULL);
254                 if (r < 0 && r != -ENOENT)
255                         log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
256
257                 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
258                                    "KEYTABLE", &vc_keymap,
259                                    "KEYMAP", &vc_keymap,
260                                    NULL);
261                 if (r < 0 && r != -ENOENT)
262                         log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
263
264                 if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
265                         char *t;
266
267                         t = strdup("/etc/sysconfig/console/default.kmap");
268                         if (!t) {
269                                 log_oom();
270                                 goto finish;
271                         }
272
273                         free(vc_keymap);
274                         vc_keymap = t;
275                 }
276
277 #elif defined(TARGET_SUSE)
278                 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
279                                    "KEYTABLE", &vc_keymap,
280                                    NULL);
281                 if (r < 0 && r != -ENOENT)
282                         log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
283
284                 r = parse_env_file("/etc/sysconfig/console", NEWLINE,
285                                    "CONSOLE_FONT", &vc_font,
286                                    "CONSOLE_SCREENMAP", &vc_font_map,
287                                    "CONSOLE_UNICODEMAP", &vc_font_unimap,
288                                    NULL);
289                 if (r < 0 && r != -ENOENT)
290                         log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r));
291
292 #elif defined(TARGET_ARCH)
293                 r = parse_env_file("/etc/rc.conf", NEWLINE,
294                                    "KEYMAP", &vc_keymap,
295                                    "CONSOLEFONT", &vc_font,
296                                    "CONSOLEMAP", &vc_font_map,
297                                    NULL);
298                 if (r < 0 && r != -ENOENT)
299                         log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
300
301 #elif defined(TARGET_FRUGALWARE)
302                 r = parse_env_file("/etc/sysconfig/keymap", NEWLINE,
303                                    "keymap", &vc_keymap,
304                                    NULL);
305                 if (r < 0 && r != -ENOENT)
306                         log_warning("Failed to read /etc/sysconfig/keymap: %s", strerror(-r));
307
308                 r = parse_env_file("/etc/sysconfig/font", NEWLINE,
309                                    "font", &vc_font,
310                                    NULL);
311                 if (r < 0 && r != -ENOENT)
312                         log_warning("Failed to read /etc/sysconfig/font: %s", strerror(-r));
313
314 #elif defined(TARGET_ALTLINUX)
315                 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
316                                    "KEYTABLE", &vc_keymap,
317                                    NULL)
318                 if (r < 0 && r != -ENOENT)
319                         log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
320
321                 r = parse_env_file("/etc/sysconfig/consolefont", NEWLINE,
322                                    "SYSFONT", &vc_font,
323                                    NULL);
324                 if (r < 0 && r != -ENOENT)
325                         log_warning("Failed to read /etc/sysconfig/consolefont: %s", strerror(-r));
326
327 #elif defined(TARGET_GENTOO)
328                 r = parse_env_file("/etc/rc.conf", NEWLINE,
329                                    "unicode", &vc_unicode,
330                                    NULL);
331                 if (r < 0 && r != -ENOENT)
332                         log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
333
334                 if (vc_unicode) {
335                         int rc_unicode;
336
337                         rc_unicode = parse_boolean(vc_unicode);
338                         if (rc_unicode < 0)
339                                 log_warning("Unknown value for /etc/rc.conf unicode=%s", vc_unicode);
340                         else {
341                                 if (rc_unicode && !utf8)
342                                         log_warning("/etc/rc.conf wants unicode, but current locale is not UTF-8 capable!");
343                                 else if (!rc_unicode && utf8) {
344                                         log_debug("/etc/rc.conf does not want unicode, leave it on in kernel but does not apply to vconsole.");
345                                         utf8 = false;
346                                 }
347                         }
348                 }
349
350                 /* /etc/conf.d/consolefont comments and gentoo
351                  * documentation mention uppercase, but the actual
352                  * contents are lowercase.  the existing
353                  * /etc/init.d/consolefont tries both
354                  */
355                 r = parse_env_file("/etc/conf.d/consolefont", NEWLINE,
356                                    "CONSOLEFONT", &vc_font,
357                                    "consolefont", &vc_font,
358                                    "consoletranslation", &vc_font_map,
359                                    "CONSOLETRANSLATION", &vc_font_map,
360                                    "unicodemap", &vc_font_unimap,
361                                    "UNICODEMAP", &vc_font_unimap,
362                                    NULL);
363                 if (r < 0 && r != -ENOENT)
364                         log_warning("Failed to read /etc/conf.d/consolefont: %s", strerror(-r));
365
366                 r = parse_env_file("/etc/conf.d/keymaps", NEWLINE,
367                                    "keymap", &vc_keymap,
368                                    "KEYMAP", &vc_keymap,
369                                    NULL);
370                 if (r < 0 && r != -ENOENT)
371                         log_warning("Failed to read /etc/conf.d/keymaps: %s", strerror(-r));
372
373 #elif defined(TARGET_MANDRIVA) || defined (TARGET_MAGEIA)
374
375                 r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
376                                    "SYSFONT", &vc_font,
377                                    "SYSFONTACM", &vc_font_map,
378                                    "UNIMAP", &vc_font_unimap,
379                                    NULL);
380                 if (r < 0 && r != -ENOENT)
381                         log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
382
383                 r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
384                                    "KEYTABLE", &vc_keytable,
385                                    "KEYMAP", &vc_keymap,
386                                    "UNIKEYTABLE", &vc_keymap,
387                                    "GRP_TOGGLE", &vc_keymap_toggle,
388                                    NULL);
389                 if (r < 0 && r != -ENOENT)
390                         log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
391
392                 if (vc_keytable) {
393                         free(vc_keymap);
394                         if (utf8) {
395                                 if (endswith(vc_keytable, ".uni") || strstr(vc_keytable, ".uni."))
396                                         vc_keymap = strdup(vc_keytable);
397                                 else {
398                                         char *s;
399                                         s = strstr(vc_keytable, ".map");
400                                         if (s)
401                                                 vc_keytable[s-vc_keytable+1] = '\0';
402                                         vc_keymap = strappend(vc_keytable, ".uni");
403                                 }
404                         } else
405                                 vc_keymap = strdup(vc_keytable);
406
407                         free(vc_keytable);
408
409                         if (!vc_keymap) {
410                                 log_oom();
411                                 goto finish;
412                         }
413                 }
414
415                 if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
416                         char *t;
417
418                         t = strdup("/etc/sysconfig/console/default.kmap");
419                         if (!t) {
420                                 log_oom();
421                                 goto finish;
422                         }
423
424                         free(vc_keymap);
425                         vc_keymap = t;
426                 }
427 #endif
428         }
429
430         r = EXIT_FAILURE;
431
432         if (utf8)
433                 enable_utf8(fd);
434         else
435                 disable_utf8(fd);
436
437
438         if (load_keymap(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
439             load_font(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
440                 r = EXIT_SUCCESS;
441
442 finish:
443         if (keymap_pid > 0)
444                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
445
446         if (font_pid > 0)
447                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
448
449         free(vc_keymap);
450         free(vc_font);
451         free(vc_font_map);
452         free(vc_font_unimap);
453
454         if (fd >= 0)
455                 close_nointr_nofail(fd);
456
457         return r;
458 }