chiark / gitweb /
b7a536b983a46ff3666323022d643efc40fc1866
[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_errno(r, "Failed to disable UTF-8: %m");
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_errno(r, "Failed to enable UTF-8: %m");
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                 return log_error_errno(errno, "Failed to fork: %m");
127         else if (pid == 0) {
128                 execv(args[0], (char **) args);
129                 _exit(EXIT_FAILURE);
130         }
131
132         *_pid = pid;
133         return 0;
134 }
135
136 static int font_load(const char *vc, const char *font, const char *map, const char *unimap, pid_t *_pid) {
137         const char *args[9];
138         int i = 0;
139         pid_t pid;
140
141         if (isempty(font)) {
142                 /* An empty font means kernel font */
143                 *_pid = 0;
144                 return 0;
145         }
146
147         args[i++] = KBD_SETFONT;
148         args[i++] = "-C";
149         args[i++] = vc;
150         args[i++] = font;
151         if (map) {
152                 args[i++] = "-m";
153                 args[i++] = map;
154         }
155         if (unimap) {
156                 args[i++] = "-u";
157                 args[i++] = unimap;
158         }
159         args[i++] = NULL;
160
161         pid = fork();
162         if (pid < 0)
163                 return log_error_errno(errno, "Failed to fork: %m");
164         else if (pid == 0) {
165                 execv(args[0], (char **) args);
166                 _exit(EXIT_FAILURE);
167         }
168
169         *_pid = pid;
170         return 0;
171 }
172
173 /*
174  * A newly allocated VT uses the font from the active VT. Here
175  * we update all possibly already allocated VTs with the configured
176  * font. It also allows to restart systemd-vconsole-setup.service,
177  * to apply a new font to all VTs.
178  */
179 static void font_copy_to_all_vcs(int fd) {
180         struct vt_stat vcs = {};
181         unsigned char map8[E_TABSZ];
182         unsigned short map16[E_TABSZ];
183         struct unimapdesc unimapd;
184         struct unipair unipairs[USHRT_MAX];
185         int i, r;
186
187         /* get active, and 16 bit mask of used VT numbers */
188         r = ioctl(fd, VT_GETSTATE, &vcs);
189         if (r < 0)
190                 return;
191
192         for (i = 1; i <= 15; i++) {
193                 char vcname[16];
194                 _cleanup_close_ int vcfd = -1;
195                 struct console_font_op cfo = {};
196
197                 if (i == vcs.v_active)
198                         continue;
199
200                 /* skip non-allocated ttys */
201                 snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i);
202                 if (access(vcname, F_OK) < 0)
203                         continue;
204
205                 snprintf(vcname, sizeof(vcname), "/dev/tty%i", i);
206                 vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
207                 if (vcfd < 0)
208                         continue;
209
210                 /* copy font from active VT, where the font was uploaded to */
211                 cfo.op = KD_FONT_OP_COPY;
212                 cfo.height = vcs.v_active-1; /* tty1 == index 0 */
213                 (void) ioctl(vcfd, KDFONTOP, &cfo);
214
215                 /* copy map of 8bit chars */
216                 if (ioctl(fd, GIO_SCRNMAP, map8) >= 0)
217                     (void) ioctl(vcfd, PIO_SCRNMAP, map8);
218
219                 /* copy map of 8bit chars -> 16bit Unicode values */
220                 if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0)
221                     (void) ioctl(vcfd, PIO_UNISCRNMAP, map16);
222
223                 /* copy unicode translation table */
224                 /* unimapd is a ushort count and a pointer to an
225                    array of struct unipair { ushort, ushort } */
226                 unimapd.entries  = unipairs;
227                 unimapd.entry_ct = USHRT_MAX;
228                 if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) {
229                         struct unimapinit adv = { 0, 0, 0 };
230
231                         (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv);
232                         (void) ioctl(vcfd, PIO_UNIMAP, &unimapd);
233                 }
234         }
235 }
236
237 int main(int argc, char **argv) {
238         const char *vc;
239         _cleanup_free_ char
240                 *vc_keymap = NULL, *vc_keymap_toggle = NULL,
241                 *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
242         _cleanup_close_ int fd = -1;
243         bool utf8;
244         pid_t font_pid = 0, keymap_pid = 0;
245         bool font_copy = false;
246         int r = EXIT_FAILURE;
247
248         log_set_target(LOG_TARGET_AUTO);
249         log_parse_environment();
250         log_open();
251
252         umask(0022);
253
254         if (argv[1])
255                 vc = argv[1];
256         else {
257                 vc = "/dev/tty0";
258                 font_copy = true;
259         }
260
261         fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
262         if (fd < 0) {
263                 log_error_errno(errno, "Failed to open %s: %m", vc);
264                 return EXIT_FAILURE;
265         }
266
267         if (!is_vconsole(fd)) {
268                 log_error("Device %s is not a virtual console.", vc);
269                 return EXIT_FAILURE;
270         }
271
272         utf8 = is_locale_utf8();
273
274         r = parse_env_file("/etc/vconsole.conf", NEWLINE,
275                            "KEYMAP", &vc_keymap,
276                            "KEYMAP_TOGGLE", &vc_keymap_toggle,
277                            "FONT", &vc_font,
278                            "FONT_MAP", &vc_font_map,
279                            "FONT_UNIMAP", &vc_font_unimap,
280                            NULL);
281
282         if (r < 0 && r != -ENOENT)
283                 log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m");
284
285         /* Let the kernel command line override /etc/vconsole.conf */
286         if (detect_container(NULL) <= 0) {
287                 r = parse_env_file("/proc/cmdline", WHITESPACE,
288                                    "vconsole.keymap", &vc_keymap,
289                                    "vconsole.keymap.toggle", &vc_keymap_toggle,
290                                    "vconsole.font", &vc_font,
291                                    "vconsole.font.map", &vc_font_map,
292                                    "vconsole.font.unimap", &vc_font_unimap,
293                                    NULL);
294
295                 if (r < 0 && r != -ENOENT)
296                         log_warning_errno(r, "Failed to read /proc/cmdline: %m");
297         }
298
299         if (utf8)
300                 enable_utf8(fd);
301         else
302                 disable_utf8(fd);
303
304         r = font_load(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid);
305         if (r < 0) {
306                 log_error_errno(r, "Failed to start " KBD_SETFONT ": %m");
307                 return EXIT_FAILURE;
308         }
309
310         if (font_pid > 0)
311                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid, true);
312
313         r = keymap_load(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid);
314         if (r < 0) {
315                 log_error_errno(r, "Failed to start " KBD_LOADKEYS ": %m");
316                 return EXIT_FAILURE;
317         }
318
319         if (keymap_pid > 0)
320                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid, true);
321
322         /* Only copy the font when we started setfont successfully */
323         if (font_copy && font_pid > 0)
324                 font_copy_to_all_vcs(fd);
325
326         return EXIT_SUCCESS;
327 }