chiark / gitweb /
1208aeb524b89c6df87cb195ed60d0cfa07f1900
[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
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 int disable_utf8(int fd) {
51         int r = 0, k;
52
53         if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
54                 r = -errno;
55
56         if (loop_write(fd, "\033%@", 3, false) < 0)
57                 r = -errno;
58
59         k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "0");
60         if (k < 0)
61                 r = k;
62
63         if (r < 0)
64                 log_warning("Failed to disable UTF-8: %s", strerror(-r));
65
66         return r;
67 }
68
69 static int enable_utf8(int fd) {
70         int r = 0, k;
71         long current = 0;
72
73         if (ioctl(fd, KDGKBMODE, &current) < 0 || current == K_XLATE) {
74                 /*
75                  * Change the current keyboard to unicode, unless it
76                  * is currently in raw or off mode anyway. We
77                  * shouldn't interfere with X11's processing of the
78                  * key events.
79                  *
80                  * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
81                  *
82                  */
83
84                 if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0)
85                         r = -errno;
86         }
87
88         if (loop_write(fd, "\033%G", 3, false) < 0)
89                 r = -errno;
90
91         k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "1");
92         if (k < 0)
93                 r = k;
94
95         if (r < 0)
96                 log_warning("Failed to enable UTF-8: %s", strerror(-r));
97
98         return r;
99 }
100
101 static int keymap_load(const char *vc, const char *map, const char *map_toggle, bool utf8, pid_t *_pid) {
102         const char *args[8];
103         int i = 0;
104         pid_t pid;
105
106         if (isempty(map)) {
107                 /* An empty map means kernel map */
108                 *_pid = 0;
109                 return 0;
110         }
111
112         args[i++] = KBD_LOADKEYS;
113         args[i++] = "-q";
114         args[i++] = "-C";
115         args[i++] = vc;
116         if (utf8)
117                 args[i++] = "-u";
118         args[i++] = map;
119         if (map_toggle)
120                 args[i++] = map_toggle;
121         args[i++] = NULL;
122
123         pid = fork();
124         if (pid < 0) {
125                 log_error("Failed to fork: %m");
126                 return -errno;
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                 log_error("Failed to fork: %m");
164                 return -errno;
165         } else if (pid == 0) {
166                 execv(args[0], (char **) args);
167                 _exit(EXIT_FAILURE);
168         }
169
170         *_pid = pid;
171         return 0;
172 }
173
174 /*
175  * A newly allocated VT uses the font from the active VT. Here
176  * we update all possibly already allocated VTs with the configured
177  * font. It also allows to restart systemd-vconsole-setup.service,
178  * to apply a new font to all VTs.
179  */
180 static void font_copy_to_all_vcs(int fd) {
181         struct vt_stat vcs;
182         int i;
183         int r;
184
185         /* get active, and 16 bit mask of used VT numbers */
186         zero(vcs);
187         r = ioctl(fd, VT_GETSTATE, &vcs);
188         if (r < 0)
189                 return;
190
191         for (i = 1; i <= 15; i++) {
192                 char vcname[16];
193                 int vcfd;
194                 struct console_font_op cfo;
195
196                 if (i == vcs.v_active)
197                         continue;
198
199                 /* skip non-allocated ttys */
200                 snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i);
201                 if (access(vcname, F_OK) < 0)
202                         continue;
203
204                 snprintf(vcname, sizeof(vcname), "/dev/tty%i", i);
205                 vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
206                 if (vcfd < 0)
207                         continue;
208
209                 /* copy font from active VT, where the font was uploaded to */
210                 zero(cfo);
211                 cfo.op = KD_FONT_OP_COPY;
212                 cfo.height = vcs.v_active-1; /* tty1 == index 0 */
213                 ioctl(vcfd, KDFONTOP, &cfo);
214
215                 close_nointr_nofail(vcfd);
216         }
217 }
218
219 int main(int argc, char **argv) {
220         const char *vc;
221         char *vc_keymap = NULL;
222         char *vc_keymap_toggle = NULL;
223         char *vc_font = NULL;
224         char *vc_font_map = NULL;
225         char *vc_font_unimap = NULL;
226         int fd = -1;
227         bool utf8;
228         pid_t font_pid = 0, keymap_pid = 0;
229         bool font_copy = false;
230         int r = EXIT_FAILURE;
231
232         log_set_target(LOG_TARGET_AUTO);
233         log_parse_environment();
234         log_open();
235
236         umask(0022);
237
238         if (argv[1])
239                 vc = argv[1];
240         else {
241                 vc = "/dev/tty0";
242                 font_copy = true;
243         }
244
245         fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
246         if (fd < 0) {
247                 log_error("Failed to open %s: %m", vc);
248                 goto finish;
249         }
250
251         if (!is_vconsole(fd)) {
252                 log_error("Device %s is not a virtual console.", vc);
253                 goto finish;
254         }
255
256         utf8 = is_locale_utf8();
257
258         r = 0;
259
260         if (detect_container(NULL) <= 0) {
261                 r = parse_env_file("/proc/cmdline", WHITESPACE,
262                                    "vconsole.keymap", &vc_keymap,
263                                    "vconsole.keymap.toggle", &vc_keymap_toggle,
264                                    "vconsole.font", &vc_font,
265                                    "vconsole.font.map", &vc_font_map,
266                                    "vconsole.font.unimap", &vc_font_unimap,
267                                    NULL);
268
269                 if (r < 0 && r != -ENOENT)
270                         log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
271         }
272
273         /* Hmm, nothing set on the kernel cmd line? Then let's
274          * try /etc/vconsole.conf */
275         if (r <= 0) {
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
288         if (utf8)
289                 enable_utf8(fd);
290         else
291                 disable_utf8(fd);
292
293         r = EXIT_FAILURE;
294         if (keymap_load(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
295             font_load(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
296                 r = EXIT_SUCCESS;
297
298 finish:
299         if (keymap_pid > 0)
300                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
301
302         if (font_pid > 0) {
303                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
304                 if (font_copy)
305                         font_copy_to_all_vcs(fd);
306         }
307
308         free(vc_keymap);
309         free(vc_font);
310         free(vc_font_map);
311         free(vc_font_unimap);
312
313         if (fd >= 0)
314                 close_nointr_nofail(fd);
315
316         return r;
317 }