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