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