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