]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/vconsole/vconsole-setup.c
e7fced247d3a325f657ee1cedeb8d78b30d4be02
[thirdparty/systemd.git] / src / vconsole / vconsole-setup.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Kay Sievers
6 Copyright 2016 Michal Soltys <soltys@ziu.info>
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 <errno.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <linux/kd.h>
26 #include <linux/tiocl.h>
27 #include <linux/vt.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/ioctl.h>
32 #include <sysexits.h>
33 #include <termios.h>
34 #include <unistd.h>
35
36 #include "alloc-util.h"
37 #include "fd-util.h"
38 #include "fileio.h"
39 #include "io-util.h"
40 #include "locale-util.h"
41 #include "log.h"
42 #include "process-util.h"
43 #include "signal-util.h"
44 #include "stdio-util.h"
45 #include "string-util.h"
46 #include "strv.h"
47 #include "terminal-util.h"
48 #include "util.h"
49 #include "virt.h"
50
51 static int verify_vc_device(int fd) {
52 unsigned char data[] = {
53 TIOCL_GETFGCONSOLE,
54 };
55
56 int r;
57
58 r = ioctl(fd, TIOCLINUX, data);
59 if (r < 0)
60 return -errno;
61
62 return r;
63 }
64
65 static int verify_vc_allocation(unsigned idx) {
66 char vcname[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
67
68 xsprintf(vcname, "/dev/vcs%u", idx);
69
70 if (access(vcname, F_OK) < 0)
71 return -errno;
72
73 return 0;
74 }
75
76 static int verify_vc_allocation_byfd(int fd) {
77 struct vt_stat vcs = {};
78
79 if (ioctl(fd, VT_GETSTATE, &vcs) < 0)
80 return -errno;
81
82 return verify_vc_allocation(vcs.v_active);
83 }
84
85 static int verify_vc_kbmode(int fd) {
86 int curr_mode;
87
88 /*
89 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
90 * Otherwise we would (likely) interfere with X11's processing of the
91 * key events.
92 *
93 * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
94 */
95
96 if (ioctl(fd, KDGKBMODE, &curr_mode) < 0)
97 return -errno;
98
99 return IN_SET(curr_mode, K_XLATE, K_UNICODE) ? 0 : -EBUSY;
100 }
101
102 static int toggle_utf8(const char *name, int fd, bool utf8) {
103 int r;
104 struct termios tc = {};
105
106 assert(name);
107
108 r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE);
109 if (r < 0)
110 return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name);
111
112 r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false);
113 if (r < 0)
114 return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name);
115
116 r = tcgetattr(fd, &tc);
117 if (r >= 0) {
118 SET_FLAG(tc.c_iflag, IUTF8, utf8);
119 r = tcsetattr(fd, TCSANOW, &tc);
120 }
121 if (r < 0)
122 return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name);
123
124 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name);
125 return 0;
126 }
127
128 static int toggle_utf8_sysfs(bool utf8) {
129 int r;
130
131 r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), 0);
132 if (r < 0)
133 return log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8));
134
135 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8));
136 return 0;
137 }
138
139 static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
140 const char *args[8];
141 unsigned i = 0;
142 pid_t pid;
143 int r;
144
145 /* An empty map means kernel map */
146 if (isempty(map))
147 return 0;
148
149 args[i++] = KBD_LOADKEYS;
150 args[i++] = "-q";
151 args[i++] = "-C";
152 args[i++] = vc;
153 if (utf8)
154 args[i++] = "-u";
155 args[i++] = map;
156 if (map_toggle)
157 args[i++] = map_toggle;
158 args[i++] = NULL;
159
160 if (DEBUG_LOGGING) {
161 _cleanup_free_ char *cmd;
162
163 cmd = strv_join((char**) args, " ");
164 log_debug("Executing \"%s\"...", strnull(cmd));
165 }
166
167 r = safe_fork("(loadkeys)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_LOG, &pid);
168 if (r < 0)
169 return r;
170 if (r == 0) {
171 execv(args[0], (char **) args);
172 _exit(EXIT_FAILURE);
173 }
174
175 return wait_for_terminate_and_check(KBD_LOADKEYS, pid, WAIT_LOG);
176 }
177
178 static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
179 const char *args[9];
180 unsigned i = 0;
181 pid_t pid;
182 int r;
183
184 /* Any part can be set independently */
185 if (isempty(font) && isempty(map) && isempty(unimap))
186 return 0;
187
188 args[i++] = KBD_SETFONT;
189 args[i++] = "-C";
190 args[i++] = vc;
191 if (!isempty(map)) {
192 args[i++] = "-m";
193 args[i++] = map;
194 }
195 if (!isempty(unimap)) {
196 args[i++] = "-u";
197 args[i++] = unimap;
198 }
199 if (!isempty(font))
200 args[i++] = font;
201 args[i++] = NULL;
202
203 if (DEBUG_LOGGING) {
204 _cleanup_free_ char *cmd;
205
206 cmd = strv_join((char**) args, " ");
207 log_debug("Executing \"%s\"...", strnull(cmd));
208 }
209
210 r = safe_fork("(setfont)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_LOG, &pid);
211 if (r < 0)
212 return r;
213 if (r == 0) {
214 execv(args[0], (char **) args);
215 _exit(EXIT_FAILURE);
216 }
217
218 return wait_for_terminate_and_check(KBD_SETFONT, pid, WAIT_LOG);
219 }
220
221 /*
222 * A newly allocated VT uses the font from the source VT. Here
223 * we update all possibly already allocated VTs with the configured
224 * font. It also allows to restart systemd-vconsole-setup.service,
225 * to apply a new font to all VTs.
226 *
227 * We also setup per-console utf8 related stuff: kbdmode, term
228 * processing, stty iutf8.
229 */
230 static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
231 struct console_font_op cfo = {
232 .op = KD_FONT_OP_GET,
233 .width = UINT_MAX, .height = UINT_MAX,
234 .charcount = UINT_MAX,
235 };
236 struct unimapinit adv = {};
237 struct unimapdesc unimapd;
238 _cleanup_free_ struct unipair* unipairs = NULL;
239 _cleanup_free_ void *fontbuf = NULL;
240 unsigned i;
241 int r;
242
243 unipairs = new(struct unipair, USHRT_MAX);
244 if (!unipairs) {
245 log_oom();
246 return;
247 }
248
249 /* get metadata of the current font (width, height, count) */
250 r = ioctl(src_fd, KDFONTOP, &cfo);
251 if (r < 0)
252 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
253 else {
254 /* verify parameter sanity first */
255 if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512)
256 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
257 cfo.width, cfo.height, cfo.charcount);
258 else {
259 /*
260 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
261 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
262 * requries 32 per glyph, regardless of the actual height - see the comment above #define
263 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
264 */
265 fontbuf = malloc_multiply((cfo.width + 7) / 8 * 32, cfo.charcount);
266 if (!fontbuf) {
267 log_oom();
268 return;
269 }
270 /* get fonts from the source console */
271 cfo.data = fontbuf;
272 r = ioctl(src_fd, KDFONTOP, &cfo);
273 if (r < 0)
274 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to read the font data: %m");
275 else {
276 unimapd.entries = unipairs;
277 unimapd.entry_ct = USHRT_MAX;
278 r = ioctl(src_fd, GIO_UNIMAP, &unimapd);
279 if (r < 0)
280 log_warning_errno(errno, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
281 else
282 cfo.op = KD_FONT_OP_SET;
283 }
284 }
285 }
286
287 if (cfo.op != KD_FONT_OP_SET)
288 log_warning("Fonts will not be copied to remaining consoles");
289
290 for (i = 1; i <= 63; i++) {
291 char ttyname[sizeof("/dev/tty63")];
292 _cleanup_close_ int fd_d = -1;
293
294 if (i == src_idx || verify_vc_allocation(i) < 0)
295 continue;
296
297 /* try to open terminal */
298 xsprintf(ttyname, "/dev/tty%u", i);
299 fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC|O_NOCTTY);
300 if (fd_d < 0) {
301 log_warning_errno(fd_d, "Unable to open tty%u, fonts will not be copied: %m", i);
302 continue;
303 }
304
305 if (verify_vc_kbmode(fd_d) < 0)
306 continue;
307
308 toggle_utf8(ttyname, fd_d, utf8);
309
310 if (cfo.op != KD_FONT_OP_SET)
311 continue;
312
313 r = ioctl(fd_d, KDFONTOP, &cfo);
314 if (r < 0) {
315 log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i);
316 continue;
317 }
318
319 /*
320 * copy unicode translation table
321 * unimapd is a ushort count and a pointer to an
322 * array of struct unipair { ushort, ushort }
323 */
324 r = ioctl(fd_d, PIO_UNIMAPCLR, &adv);
325 if (r < 0) {
326 log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i);
327 continue;
328 }
329
330 r = ioctl(fd_d, PIO_UNIMAP, &unimapd);
331 if (r < 0) {
332 log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i);
333 continue;
334 }
335
336 log_debug("Font and unimap successfully copied to %s", ttyname);
337 }
338 }
339
340 static int find_source_vc(char **ret_path, unsigned *ret_idx) {
341 _cleanup_free_ char *path = NULL;
342 int r, err = 0;
343 unsigned i;
344
345 path = new(char, sizeof("/dev/tty63"));
346 if (!path)
347 return log_oom();
348
349 for (i = 1; i <= 63; i++) {
350 _cleanup_close_ int fd = -1;
351
352 r = verify_vc_allocation(i);
353 if (r < 0) {
354 if (!err)
355 err = -r;
356 continue;
357 }
358
359 sprintf(path, "/dev/tty%u", i);
360 fd = open_terminal(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
361 if (fd < 0) {
362 if (!err)
363 err = -fd;
364 continue;
365 }
366 r = verify_vc_kbmode(fd);
367 if (r < 0) {
368 if (!err)
369 err = -r;
370 continue;
371 }
372
373 /* all checks passed, return this one as a source console */
374 *ret_idx = i;
375 *ret_path = TAKE_PTR(path);
376 return TAKE_FD(fd);
377 }
378
379 return log_error_errno(err, "No usable source console found: %m");
380 }
381
382 static int verify_source_vc(char **ret_path, const char *src_vc) {
383 _cleanup_close_ int fd = -1;
384 char *path;
385 int r;
386
387 fd = open_terminal(src_vc, O_RDWR|O_CLOEXEC|O_NOCTTY);
388 if (fd < 0)
389 return log_error_errno(fd, "Failed to open %s: %m", src_vc);
390
391 r = verify_vc_device(fd);
392 if (r < 0)
393 return log_error_errno(r, "Device %s is not a virtual console: %m", src_vc);
394
395 r = verify_vc_allocation_byfd(fd);
396 if (r < 0)
397 return log_error_errno(r, "Virtual console %s is not allocated: %m", src_vc);
398
399 r = verify_vc_kbmode(fd);
400 if (r < 0)
401 return log_error_errno(r, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc);
402
403 path = strdup(src_vc);
404 if (!path)
405 return log_oom();
406
407 *ret_path = path;
408 return TAKE_FD(fd);
409 }
410
411 int main(int argc, char **argv) {
412 _cleanup_free_ char
413 *vc = NULL,
414 *vc_keymap = NULL, *vc_keymap_toggle = NULL,
415 *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
416 _cleanup_close_ int fd = -1;
417 bool utf8, keyboard_ok;
418 unsigned idx = 0;
419 int r;
420
421 log_set_target(LOG_TARGET_AUTO);
422 log_parse_environment();
423 log_open();
424
425 umask(0022);
426
427 if (argv[1])
428 fd = verify_source_vc(&vc, argv[1]);
429 else
430 fd = find_source_vc(&vc, &idx);
431 if (fd < 0)
432 return EXIT_FAILURE;
433
434 utf8 = is_locale_utf8();
435
436 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
437 "KEYMAP", &vc_keymap,
438 "KEYMAP_TOGGLE", &vc_keymap_toggle,
439 "FONT", &vc_font,
440 "FONT_MAP", &vc_font_map,
441 "FONT_UNIMAP", &vc_font_unimap,
442 NULL);
443 if (r < 0 && r != -ENOENT)
444 log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m");
445
446 /* Let the kernel command line override /etc/vconsole.conf */
447 if (detect_container() <= 0) {
448 r = parse_env_file("/proc/cmdline", WHITESPACE,
449 "vconsole.keymap", &vc_keymap,
450 "vconsole.keymap_toggle", &vc_keymap_toggle,
451 "vconsole.font", &vc_font,
452 "vconsole.font_map", &vc_font_map,
453 "vconsole.font_unimap", &vc_font_unimap,
454 /* compatibility with obsolete multiple-dot scheme */
455 "vconsole.keymap.toggle", &vc_keymap_toggle,
456 "vconsole.font.map", &vc_font_map,
457 "vconsole.font.unimap", &vc_font_unimap,
458 NULL);
459 if (r < 0 && r != -ENOENT)
460 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
461 }
462
463 (void) toggle_utf8_sysfs(utf8);
464 (void) toggle_utf8(vc, fd, utf8);
465
466 r = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap);
467 keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
468
469 if (idx > 0) {
470 if (r == 0)
471 setup_remaining_vcs(fd, idx, utf8);
472 else if (r == EX_OSERR)
473 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails.
474 * This might mean various things, but in particular lack of a graphical
475 * console. Let's be generous and not treat this as an error. */
476 log_notice("Setting fonts failed with a \"system error\", ignoring.");
477 else
478 log_warning("Setting source virtual console failed, ignoring remaining ones");
479 }
480
481 return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
482 }