chiark / gitweb /
TODO
[elogind.git] / src / 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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU 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         if ((k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "0")) < 0)
74                 r = k;
75
76         if (r < 0)
77                 log_warning("Failed to disable UTF-8: %s", strerror(errno));
78
79         return r;
80 }
81
82 static int load_keymap(const char *vc, const char *map, const char *map_toggle, bool utf8, pid_t *_pid) {
83         const char *args[8];
84         int i = 0;
85         pid_t pid;
86
87         if (isempty(map)) {
88                 /* An empty map means kernel map */
89                 *_pid = 0;
90                 return 0;
91         }
92
93         args[i++] = KBD_LOADKEYS;
94         args[i++] = "-q";
95         args[i++] = "-C";
96         args[i++] = vc;
97         if (utf8)
98                 args[i++] = "-u";
99         args[i++] = map;
100         if (map_toggle)
101                 args[i++] = map_toggle;
102         args[i++] = NULL;
103
104         if ((pid = fork()) < 0) {
105                 log_error("Failed to fork: %m");
106                 return -errno;
107         } else if (pid == 0) {
108                 execv(args[0], (char **) args);
109                 _exit(EXIT_FAILURE);
110         }
111
112         *_pid = pid;
113         return 0;
114 }
115
116 static int load_font(const char *vc, const char *font, const char *map, const char *unimap, pid_t *_pid) {
117         const char *args[9];
118         int i = 0;
119         pid_t pid;
120
121         if (isempty(font)) {
122                 /* An empty font means kernel font */
123                 *_pid = 0;
124                 return 0;
125         }
126
127         args[i++] = KBD_SETFONT;
128         args[i++] = "-C";
129         args[i++] = vc;
130         args[i++] = font;
131         if (map) {
132                 args[i++] = "-m";
133                 args[i++] = map;
134         }
135         if (unimap) {
136                 args[i++] = "-u";
137                 args[i++] = unimap;
138         }
139         args[i++] = NULL;
140
141         if ((pid = fork()) < 0) {
142                 log_error("Failed to fork: %m");
143                 return -errno;
144         } else if (pid == 0) {
145                 execv(args[0], (char **) args);
146                 _exit(EXIT_FAILURE);
147         }
148
149         *_pid = pid;
150         return 0;
151 }
152
153 int main(int argc, char **argv) {
154         const char *vc;
155         char *vc_keymap = NULL;
156         char *vc_keymap_toggle = NULL;
157         char *vc_font = NULL;
158         char *vc_font_map = NULL;
159         char *vc_font_unimap = NULL;
160 #ifdef TARGET_GENTOO
161         char *vc_unicode = NULL;
162 #endif
163 #ifdef TARGET_MANDRIVA
164         char *vc_keytable = NULL;
165 #endif
166         int fd = -1;
167         bool utf8;
168         int r = EXIT_FAILURE;
169         pid_t font_pid = 0, keymap_pid = 0;
170
171         log_set_target(LOG_TARGET_AUTO);
172         log_parse_environment();
173         log_open();
174
175         umask(0022);
176
177         if (argv[1])
178                 vc = argv[1];
179         else
180                 vc = "/dev/tty0";
181
182         if ((fd = open_terminal(vc, O_RDWR|O_CLOEXEC)) < 0) {
183                 log_error("Failed to open %s: %m", vc);
184                 goto finish;
185         }
186
187         if (!is_vconsole(fd)) {
188                 log_error("Device %s is not a virtual console.", vc);
189                 goto finish;
190         }
191
192         utf8 = is_locale_utf8();
193
194         vc_keymap = strdup("us");
195         vc_font = strdup(DEFAULT_FONT);
196
197         if (!vc_keymap || !vc_font) {
198                 log_error("Failed to allocate strings.");
199                 goto finish;
200         }
201
202         r = 0;
203
204         if (detect_container(NULL) <= 0)
205                 if ((r = parse_env_file("/proc/cmdline", WHITESPACE,
206                                         "vconsole.keymap", &vc_keymap,
207                                         "vconsole.keymap.toggle", &vc_keymap_toggle,
208                                         "vconsole.font", &vc_font,
209                                         "vconsole.font.map", &vc_font_map,
210                                         "vconsole.font.unimap", &vc_font_unimap,
211                                         NULL)) < 0) {
212
213                         if (r != -ENOENT)
214                                 log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
215                 }
216
217         /* Hmm, nothing set on the kernel cmd line? Then let's
218          * try /etc/vconsole.conf */
219         if (r <= 0 &&
220             (r = parse_env_file("/etc/vconsole.conf", NEWLINE,
221                                 "KEYMAP", &vc_keymap,
222                                 "KEYMAP_TOGGLE", &vc_keymap_toggle,
223                                 "FONT", &vc_font,
224                                 "FONT_MAP", &vc_font_map,
225                                 "FONT_UNIMAP", &vc_font_unimap,
226                                 NULL)) < 0) {
227
228                 if (r != -ENOENT)
229                         log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
230         }
231
232         if (r <= 0) {
233 #if defined(TARGET_FEDORA) || defined(TARGET_MEEGO)
234                 if ((r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
235                                         "SYSFONT", &vc_font,
236                                         "SYSFONTACM", &vc_font_map,
237                                         "UNIMAP", &vc_font_unimap,
238                                         NULL)) < 0) {
239
240                         if (r != -ENOENT)
241                                 log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
242                 }
243
244                 if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
245                                         "KEYTABLE", &vc_keymap,
246                                         "KEYMAP", &vc_keymap,
247                                         NULL)) < 0) {
248
249                         if (r != -ENOENT)
250                                 log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
251                 }
252
253                 if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
254                         char *t;
255
256                         if (!(t = strdup("/etc/sysconfig/console/default.kmap"))) {
257                                 log_error("Out of memory.");
258                                 goto finish;
259                         }
260
261                         free(vc_keymap);
262                         vc_keymap = t;
263                 }
264
265 #elif defined(TARGET_SUSE)
266                 if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
267                                         "KEYTABLE", &vc_keymap,
268                                         NULL)) < 0) {
269
270                         if (r != -ENOENT)
271                                 log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
272                 }
273
274                 if ((r = parse_env_file("/etc/sysconfig/console", NEWLINE,
275                                         "CONSOLE_FONT", &vc_font,
276                                         "CONSOLE_SCREENMAP", &vc_font_map,
277                                         "CONSOLE_UNICODEMAP", &vc_font_unimap,
278                                         NULL)) < 0) {
279
280                         if (r != -ENOENT)
281                                 log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r));
282                 }
283
284 #elif defined(TARGET_ARCH)
285                 if ((r = parse_env_file("/etc/rc.conf", NEWLINE,
286                                         "KEYMAP", &vc_keymap,
287                                         "CONSOLEFONT", &vc_font,
288                                         "CONSOLEMAP", &vc_font_map,
289                                         NULL)) < 0) {
290
291                         if (r != -ENOENT)
292                                 log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
293                 }
294
295 #elif defined(TARGET_FRUGALWARE)
296                 if ((r = parse_env_file("/etc/sysconfig/keymap", NEWLINE,
297                                         "keymap", &vc_keymap,
298                                         NULL)) < 0) {
299                         if (r != -ENOENT)
300                                 log_warning("Failed to read /etc/sysconfig/keymap: %s", strerror(-r));
301                 }
302                 if ((r = parse_env_file("/etc/sysconfig/font", NEWLINE,
303                                         "font", &vc_font,
304                                         NULL)) < 0) {
305                         if (r != -ENOENT)
306                                 log_warning("Failed to read /etc/sysconfig/font: %s", strerror(-r));
307                 }
308
309 #elif defined(TARGET_ALTLINUX)
310                 if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
311                                         "KEYTABLE", &vc_keymap,
312                                         NULL)) < 0) {
313
314                         if (r != -ENOENT)
315                                 log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
316                 }
317
318                 if ((r = parse_env_file("/etc/sysconfig/consolefont", NEWLINE,
319                                         "SYSFONT", &vc_font,
320                                         NULL)) < 0) {
321
322                         if (r != -ENOENT)
323                                 log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r));
324                 }
325
326 #elif defined(TARGET_GENTOO)
327                 if ((r = parse_env_file("/etc/rc.conf", NEWLINE,
328                                         "unicode", &vc_unicode,
329                                         NULL)) < 0) {
330                         if (r != -ENOENT)
331                                 log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
332                 }
333
334                 if (vc_unicode) {
335                         int rc_unicode;
336
337                         if ((rc_unicode = parse_boolean(vc_unicode)) < 0)
338                                 log_error("Unknown value for /etc/rc.conf unicode=%s", vc_unicode);
339                         else {
340                                 if (rc_unicode && !utf8)
341                                         log_warning("/etc/rc.conf wants unicode, but current locale is not UTF-8 capable!");
342                                 else if (!rc_unicode && utf8) {
343                                         log_debug("/etc/rc.conf does not want unicode, leave it on in kernel but does not apply to vconsole.");
344                                         utf8 = false;
345                                 }
346                         }
347                 }
348
349                 /* /etc/conf.d/consolefont comments and gentoo
350                  * documentation mention uppercase, but the actual
351                  * contents are lowercase.  the existing
352                  * /etc/init.d/consolefont tries both
353                  */
354                 if ((r = parse_env_file("/etc/conf.d/consolefont", NEWLINE,
355                                         "CONSOLEFONT", &vc_font,
356                                         "consolefont", &vc_font,
357                                         "consoletranslation", &vc_font_map,
358                                         "CONSOLETRANSLATION", &vc_font_map,
359                                         "unicodemap", &vc_font_unimap,
360                                         "UNICODEMAP", &vc_font_unimap,
361                                         NULL)) < 0) {
362                         if (r != -ENOENT)
363                                 log_warning("Failed to read /etc/conf.d/consolefont: %s", strerror(-r));
364                 }
365
366                 if ((r = parse_env_file("/etc/conf.d/keymaps", NEWLINE,
367                                         "keymap", &vc_keymap,
368                                         "KEYMAP", &vc_keymap,
369                                         NULL)) < 0) {
370                         if (r != -ENOENT)
371                                 log_warning("Failed to read /etc/conf.d/keymaps: %s", strerror(-r));
372                 }
373
374 #elif defined(TARGET_MANDRIVA)
375
376                 if ((r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
377                                         "SYSFONT", &vc_font,
378                                         "SYSFONTACM", &vc_font_map,
379                                         "UNIMAP", &vc_font_unimap,
380                                         NULL)) < 0) {
381
382                         if (r != -ENOENT)
383                                 log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
384                 }
385
386                 if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
387                                         "KEYTABLE", &vc_keytable,
388                                         "KEYMAP", &vc_keymap,
389                                         "UNIKEYTABLE", &vc_keymap,
390                                         "GRP_TOGGLE", &vc_keymap_toggle,
391                                         NULL)) < 0) {
392
393                         if (r != -ENOENT)
394                                 log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
395                 }
396
397                 if (vc_keytable) {
398                         if (vc_keymap)
399                                 free(vc_keymap);
400                         if (utf8) {
401                                 if (endswith(vc_keytable, ".uni") || strstr(vc_keytable, ".uni."))
402                                         vc_keymap = strdup(vc_keytable);
403                                 else {
404                                         char *s;
405                                         if ((s = strstr(vc_keytable, ".map")))
406                                                 vc_keytable[s-vc_keytable+1] = '\0';
407                                         vc_keymap = strappend(vc_keytable, ".uni");
408                                 }
409                         } else
410                                 vc_keymap = strdup(vc_keytable);
411
412                         free(vc_keytable);
413
414                         if (!vc_keymap) {
415                                 log_error("Out of memory.");
416                                 goto finish;
417                         }
418                 }
419
420                 if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
421                         char *t;
422
423                         if (!(t = strdup("/etc/sysconfig/console/default.kmap"))) {
424                                 log_error("Out of memory.");
425                                 goto finish;
426                         }
427
428                         free(vc_keymap);
429                         vc_keymap = t;
430                 }
431 #endif
432         }
433
434         r = EXIT_FAILURE;
435
436         if (!utf8)
437                 disable_utf8(fd);
438
439         if (load_keymap(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
440             load_font(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
441                 r = EXIT_SUCCESS;
442
443 finish:
444         if (keymap_pid > 0)
445                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
446
447         if (font_pid > 0)
448                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
449
450         free(vc_keymap);
451         free(vc_font);
452         free(vc_font_map);
453         free(vc_font_unimap);
454
455         if (fd >= 0)
456                 close_nointr_nofail(fd);
457
458         return r;
459 }