chiark / gitweb /
vconsole-setup: run setfont before loadkeys
[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 #include <linux/vt.h>
37
38 #include "util.h"
39 #include "log.h"
40 #include "macro.h"
41 #include "virt.h"
42 #include "fileio.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 int disable_utf8(int fd) {
52         int r = 0, k;
53
54         if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
55                 r = -errno;
56
57         if (loop_write(fd, "\033%@", 3, false) < 0)
58                 r = -errno;
59
60         k = write_string_file("/sys/module/vt/parameters/default_utf8", "0");
61         if (k < 0)
62                 r = k;
63
64         if (r < 0)
65                 log_warning("Failed to disable UTF-8: %s", strerror(-r));
66
67         return r;
68 }
69
70 static int enable_utf8(int fd) {
71         int r = 0, k;
72         long current = 0;
73
74         if (ioctl(fd, KDGKBMODE, &current) < 0 || current == K_XLATE) {
75                 /*
76                  * Change the current keyboard to unicode, unless it
77                  * is currently in raw or off mode anyway. We
78                  * shouldn't interfere with X11's processing of the
79                  * key events.
80                  *
81                  * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
82                  *
83                  */
84
85                 if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0)
86                         r = -errno;
87         }
88
89         if (loop_write(fd, "\033%G", 3, false) < 0)
90                 r = -errno;
91
92         k = write_string_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 keymap_load(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 font_load(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 /*
176  * A newly allocated VT uses the font from the active VT. Here
177  * we update all possibly already allocated VTs with the configured
178  * font. It also allows to restart systemd-vconsole-setup.service,
179  * to apply a new font to all VTs.
180  */
181 static void font_copy_to_all_vcs(int fd) {
182         struct vt_stat vcs = {};
183         unsigned char map8[E_TABSZ];
184         unsigned short map16[E_TABSZ];
185         struct unimapdesc unimapd;
186         struct unipair unipairs[USHRT_MAX];
187         int i, r;
188
189         /* get active, and 16 bit mask of used VT numbers */
190         r = ioctl(fd, VT_GETSTATE, &vcs);
191         if (r < 0)
192                 return;
193
194         for (i = 1; i <= 15; i++) {
195                 char vcname[16];
196                 _cleanup_close_ int vcfd = -1;
197                 struct console_font_op cfo = {};
198
199                 if (i == vcs.v_active)
200                         continue;
201
202                 /* skip non-allocated ttys */
203                 snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i);
204                 if (access(vcname, F_OK) < 0)
205                         continue;
206
207                 snprintf(vcname, sizeof(vcname), "/dev/tty%i", i);
208                 vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
209                 if (vcfd < 0)
210                         continue;
211
212                 /* copy font from active VT, where the font was uploaded to */
213                 cfo.op = KD_FONT_OP_COPY;
214                 cfo.height = vcs.v_active-1; /* tty1 == index 0 */
215                 ioctl(vcfd, KDFONTOP, &cfo);
216
217                 /* copy map of 8bit chars */
218                 if (ioctl(fd, GIO_SCRNMAP, map8) >= 0)
219                     ioctl(vcfd, PIO_SCRNMAP, map8);
220
221                 /* copy map of 8bit chars -> 16bit Unicode values */
222                 if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0)
223                     ioctl(vcfd, PIO_UNISCRNMAP, map16);
224
225                 /* copy unicode translation table */
226                 /* unimapd is a ushort count and a pointer to an
227                    array of struct unipair { ushort, ushort } */
228                 unimapd.entries  = unipairs;
229                 unimapd.entry_ct = USHRT_MAX;
230                 if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) {
231                         struct unimapinit adv = { 0, 0, 0 };
232
233                         ioctl(vcfd, PIO_UNIMAPCLR, &adv);
234                         ioctl(vcfd, PIO_UNIMAP, &unimapd);
235                 }
236         }
237 }
238
239 int main(int argc, char **argv) {
240         const char *vc;
241         _cleanup_free_ char
242                 *vc_keymap = NULL, *vc_keymap_toggle = NULL,
243                 *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
244         _cleanup_close_ int fd = -1;
245         bool utf8;
246         pid_t font_pid = 0, keymap_pid = 0;
247         bool font_copy = false;
248         int r = EXIT_FAILURE;
249
250         log_set_target(LOG_TARGET_AUTO);
251         log_parse_environment();
252         log_open();
253
254         umask(0022);
255
256         if (argv[1])
257                 vc = argv[1];
258         else {
259                 vc = "/dev/tty0";
260                 font_copy = true;
261         }
262
263         fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
264         if (fd < 0) {
265                 log_error("Failed to open %s: %m", vc);
266                 return EXIT_FAILURE;
267         }
268
269         if (!is_vconsole(fd)) {
270                 log_error("Device %s is not a virtual console.", vc);
271                 return EXIT_FAILURE;
272         }
273
274         utf8 = is_locale_utf8();
275
276         r = parse_env_file("/etc/vconsole.conf", NEWLINE,
277                            "KEYMAP", &vc_keymap,
278                            "KEYMAP_TOGGLE", &vc_keymap_toggle,
279                            "FONT", &vc_font,
280                            "FONT_MAP", &vc_font_map,
281                            "FONT_UNIMAP", &vc_font_unimap,
282                            NULL);
283
284         if (r < 0 && r != -ENOENT)
285                 log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
286
287         /* Let the kernel command line override /etc/vconsole.conf */
288         if (detect_container(NULL) <= 0) {
289                 r = parse_env_file("/proc/cmdline", WHITESPACE,
290                                    "vconsole.keymap", &vc_keymap,
291                                    "vconsole.keymap.toggle", &vc_keymap_toggle,
292                                    "vconsole.font", &vc_font,
293                                    "vconsole.font.map", &vc_font_map,
294                                    "vconsole.font.unimap", &vc_font_unimap,
295                                    NULL);
296
297                 if (r < 0 && r != -ENOENT)
298                         log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
299         }
300
301         if (utf8)
302                 enable_utf8(fd);
303         else
304                 disable_utf8(fd);
305
306         r = font_load(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid);
307         if (r < 0) {
308                 log_error("Failed to start " KBD_LOADKEYS ": %s", strerror(-r));
309                 return EXIT_FAILURE;
310         }
311
312         if (font_pid > 0)
313                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
314
315         r = keymap_load(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid);
316         if (r < 0) {
317                 log_error("Failed to start " KBD_SETFONT ": %s", strerror(-r));
318                 return EXIT_FAILURE;
319         }
320
321         if (keymap_pid > 0)
322                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
323
324         /* Only copy the font when we started setfont successfully */
325         if (font_copy && font_pid > 0)
326                 font_copy_to_all_vcs(fd);
327
328         return EXIT_SUCCESS;
329 }