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