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