]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/vconsole/vconsole-setup.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / vconsole / vconsole-setup.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
97c4a07d
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2010 Kay Sievers
af7a5213 6 Copyright 2016 Michal Soltys <soltys@ziu.info>
97c4a07d
LP
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
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
97c4a07d
LP
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
5430f7f2 16 Lesser General Public License for more details.
97c4a07d 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
97c4a07d
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
97c4a07d 22#include <errno.h>
97c4a07d 23#include <fcntl.h>
97c4a07d 24#include <limits.h>
97c4a07d 25#include <linux/kd.h>
07630cea 26#include <linux/tiocl.h>
dd04aac9 27#include <linux/vt.h>
07630cea
LP
28#include <stdbool.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <sys/ioctl.h>
93c9a9d2 32#include <sysexits.h>
042d7f50 33#include <termios.h>
07630cea 34#include <unistd.h>
97c4a07d 35
b5efdb8a 36#include "alloc-util.h"
3ffd4af2 37#include "fd-util.h"
a5c32cff 38#include "fileio.h"
c004493c 39#include "io-util.h"
8752c575 40#include "locale-util.h"
07630cea 41#include "log.h"
0b452006 42#include "process-util.h"
ce30c8dc 43#include "signal-util.h"
d054f0a4 44#include "stdio-util.h"
07630cea 45#include "string-util.h"
3d623780 46#include "strv.h"
07630cea
LP
47#include "terminal-util.h"
48#include "util.h"
49#include "virt.h"
97c4a07d 50
1142bed2 51static int verify_vc_device(int fd) {
97c4a07d 52 unsigned char data[1];
1142bed2 53 int r;
97c4a07d
LP
54
55 data[0] = TIOCL_GETFGCONSOLE;
1142bed2
MS
56 r = ioctl(fd, TIOCLINUX, data);
57 return r < 0 ? -errno : r;
97c4a07d
LP
58}
59
1142bed2
MS
60static int verify_vc_allocation(unsigned idx) {
61 char vcname[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
62 int r;
03044059 63
1142bed2
MS
64 xsprintf(vcname, "/dev/vcs%u", idx);
65 r = access(vcname, F_OK);
66 return r < 0 ? -errno : r;
03044059
MS
67}
68
1142bed2 69static int verify_vc_allocation_byfd(int fd) {
03044059 70 struct vt_stat vcs = {};
1142bed2 71 int r;
03044059 72
1142bed2
MS
73 r = ioctl(fd, VT_GETSTATE, &vcs);
74 return r < 0 ? -errno : verify_vc_allocation(vcs.v_active);
03044059
MS
75}
76
1142bed2 77static int verify_vc_kbmode(int fd) {
03044059
MS
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.
d23a0044 83 * Otherwise we would (likely) interfere with X11's processing of the
03044059
MS
84 * key events.
85 *
86 * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
87 */
1142bed2
MS
88 if (r < 0)
89 return -errno;
90
91 return IN_SET(curr_mode, K_XLATE, K_UNICODE) ? 0 : -EBUSY;
03044059
MS
92}
93
aaa709bb 94static int toggle_utf8(const char *name, int fd, bool utf8) {
042d7f50
MS
95 int r;
96 struct termios tc = {};
97c4a07d 97
aaa709bb
ZJS
98 assert(name);
99
042d7f50 100 r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE);
97c4a07d 101 if (r < 0)
aaa709bb 102 return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name);
97c4a07d 103
042d7f50
MS
104 r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false);
105 if (r < 0)
aaa709bb 106 return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name);
042d7f50
MS
107
108 r = tcgetattr(fd, &tc);
109 if (r >= 0) {
ab8ee0f2 110 SET_FLAG(tc.c_iflag, IUTF8, utf8);
042d7f50 111 r = tcsetattr(fd, TCSANOW, &tc);
a25d4d0e 112 }
042d7f50 113 if (r < 0)
aaa709bb 114 return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name);
d305a67b 115
aaa709bb 116 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name);
042d7f50
MS
117 return 0;
118}
d305a67b 119
042d7f50
MS
120static int toggle_utf8_sysfs(bool utf8) {
121 int r;
d305a67b 122
042d7f50 123 r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), 0);
d305a67b 124 if (r < 0)
aaa709bb
ZJS
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;
d305a67b
TG
129}
130
aecb6fcb 131static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
3d623780 132 _cleanup_free_ char *cmd = NULL;
5b396b06 133 const char *args[8];
1142bed2 134 unsigned i = 0;
97c4a07d
LP
135 pid_t pid;
136
8931278c
LDM
137 /* An empty map means kernel map */
138 if (isempty(map))
c9d2b3d0 139 return 0;
944d4c91 140
9841e8e3 141 args[i++] = KBD_LOADKEYS;
97c4a07d
LP
142 args[i++] = "-q";
143 args[i++] = "-C";
144 args[i++] = vc;
145 if (utf8)
146 args[i++] = "-u";
147 args[i++] = map;
5b396b06
AB
148 if (map_toggle)
149 args[i++] = map_toggle;
97c4a07d
LP
150 args[i++] = NULL;
151
3d623780
ZJS
152 log_debug("Executing \"%s\"...",
153 strnull((cmd = strv_join((char**) args, " "))));
154
741f8cf6 155 pid = fork();
aecb6fcb
LP
156 if (pid < 0)
157 return log_error_errno(errno, "Failed to fork: %m");
158 else if (pid == 0) {
ce30c8dc
LP
159
160 (void) reset_all_signal_handlers();
161 (void) reset_signal_mask();
162
97c4a07d
LP
163 execv(args[0], (char **) args);
164 _exit(EXIT_FAILURE);
165 }
166
c9d2b3d0 167 return wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true);
97c4a07d
LP
168}
169
aecb6fcb 170static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
3d623780 171 _cleanup_free_ char *cmd = NULL;
97c4a07d 172 const char *args[9];
1142bed2 173 unsigned i = 0;
97c4a07d
LP
174 pid_t pid;
175
c9d2b3d0
MS
176 /* Any part can be set independently */
177 if (isempty(font) && isempty(map) && isempty(unimap))
178 return 0;
944d4c91 179
9841e8e3 180 args[i++] = KBD_SETFONT;
97c4a07d
LP
181 args[i++] = "-C";
182 args[i++] = vc;
c9d2b3d0 183 if (!isempty(map)) {
97c4a07d
LP
184 args[i++] = "-m";
185 args[i++] = map;
186 }
c9d2b3d0 187 if (!isempty(unimap)) {
97c4a07d
LP
188 args[i++] = "-u";
189 args[i++] = unimap;
190 }
c9d2b3d0
MS
191 if (!isempty(font))
192 args[i++] = font;
97c4a07d
LP
193 args[i++] = NULL;
194
3d623780
ZJS
195 log_debug("Executing \"%s\"...",
196 strnull((cmd = strv_join((char**) args, " "))));
197
741f8cf6 198 pid = fork();
aecb6fcb
LP
199 if (pid < 0)
200 return log_error_errno(errno, "Failed to fork: %m");
201 else if (pid == 0) {
ce30c8dc
LP
202
203 (void) reset_all_signal_handlers();
204 (void) reset_signal_mask();
205
97c4a07d
LP
206 execv(args[0], (char **) args);
207 _exit(EXIT_FAILURE);
208 }
209
c9d2b3d0 210 return wait_for_terminate_and_warn(KBD_SETFONT, pid, true);
97c4a07d
LP
211}
212
d3b37e84 213/*
1142bed2 214 * A newly allocated VT uses the font from the source VT. Here
d3b37e84
KS
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.
eb22d84b
MS
218 *
219 * We also setup per-console utf8 related stuff: kbdmode, term
220 * processing, stty iutf8.
d3b37e84 221 */
1142bed2 222static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
eb22d84b 223 struct console_font_op cfo = {
5297577f
MS
224 .op = KD_FONT_OP_GET,
225 .width = UINT_MAX, .height = UINT_MAX,
226 .charcount = UINT_MAX,
eb22d84b 227 };
eb22d84b 228 struct unimapinit adv = {};
ff452e76 229 struct unimapdesc unimapd;
6a08e1b0 230 _cleanup_free_ struct unipair* unipairs = NULL;
eb22d84b 231 _cleanup_free_ void *fontbuf = NULL;
1142bed2
MS
232 unsigned i;
233 int r;
dd04aac9 234
6a08e1b0 235 unipairs = new(struct unipair, USHRT_MAX);
e2c9192a
LP
236 if (!unipairs) {
237 log_oom();
6a08e1b0
KR
238 return;
239 }
240
5297577f 241 /* get metadata of the current font (width, height, count) */
1142bed2 242 r = ioctl(src_fd, KDFONTOP, &cfo);
eb22d84b 243 if (r < 0)
5297577f 244 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
eb22d84b 245 else {
a5eebcff 246 /* verify parameter sanity first */
5297577f
MS
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)",
a5eebcff 249 cfo.width, cfo.height, cfo.charcount);
5297577f
MS
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 }
1142bed2 262 /* get fonts from the source console */
5297577f 263 cfo.data = fontbuf;
1142bed2 264 r = ioctl(src_fd, KDFONTOP, &cfo);
5297577f
MS
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;
1142bed2 270 r = ioctl(src_fd, GIO_UNIMAP, &unimapd);
5297577f
MS
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 }
eb22d84b
MS
277 }
278
a5eebcff 279 if (cfo.op != KD_FONT_OP_SET)
5297577f 280 log_warning("Fonts will not be copied to remaining consoles");
eb22d84b 281
9fa71843 282 for (i = 1; i <= 63; i++) {
1142bed2 283 char ttyname[sizeof("/dev/tty63")];
eb22d84b 284 _cleanup_close_ int fd_d = -1;
dd04aac9 285
1142bed2 286 if (i == src_idx || verify_vc_allocation(i) < 0)
dd04aac9
KS
287 continue;
288
eb22d84b 289 /* try to open terminal */
1142bed2
MS
290 xsprintf(ttyname, "/dev/tty%u", i);
291 fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC|O_NOCTTY);
eb22d84b 292 if (fd_d < 0) {
1142bed2 293 log_warning_errno(fd_d, "Unable to open tty%u, fonts will not be copied: %m", i);
dd04aac9 294 continue;
eb22d84b 295 }
dd04aac9 296
1142bed2 297 if (verify_vc_kbmode(fd_d) < 0)
dd04aac9
KS
298 continue;
299
aaa709bb 300 toggle_utf8(ttyname, fd_d, utf8);
eb22d84b
MS
301
302 if (cfo.op != KD_FONT_OP_SET)
303 continue;
304
305 r = ioctl(fd_d, KDFONTOP, &cfo);
306 if (r < 0) {
1142bed2 307 log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i);
eb22d84b
MS
308 continue;
309 }
ff452e76 310
1142bed2
MS
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 */
eb22d84b 316 r = ioctl(fd_d, PIO_UNIMAPCLR, &adv);
aaa709bb 317 if (r < 0) {
1142bed2 318 log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i);
aaa709bb 319 continue;
ff452e76 320 }
aaa709bb
ZJS
321
322 r = ioctl(fd_d, PIO_UNIMAP, &unimapd);
323 if (r < 0) {
1142bed2 324 log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i);
aaa709bb
ZJS
325 continue;
326 }
327
328 log_debug("Font and unimap successfully copied to %s", ttyname);
dd04aac9
KS
329 }
330}
331
1142bed2
MS
332static 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
377static 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
97c4a07d 408int main(int argc, char **argv) {
abee28c5 409 _cleanup_free_ char
1142bed2 410 *vc = NULL,
abee28c5
ZJS
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;
1142bed2
MS
414 bool utf8, keyboard_ok;
415 unsigned idx = 0;
93c9a9d2 416 int r;
97c4a07d 417
944d4c91 418 log_set_target(LOG_TARGET_AUTO);
97c4a07d
LP
419 log_parse_environment();
420 log_open();
421
4c12626c
LP
422 umask(0022);
423
97c4a07d 424 if (argv[1])
1142bed2
MS
425 fd = verify_source_vc(&vc, argv[1]);
426 else
427 fd = find_source_vc(&vc, &idx);
97c4a07d 428
1142bed2 429 if (fd < 0)
abee28c5 430 return EXIT_FAILURE;
03044059 431
653ab83b 432 utf8 = is_locale_utf8();
97c4a07d 433
2034ec42
MS
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);
2034ec42 441 if (r < 0 && r != -ENOENT)
da927ba9 442 log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m");
2034ec42
MS
443
444 /* Let the kernel command line override /etc/vconsole.conf */
75f86906 445 if (detect_container() <= 0) {
741f8cf6
LP
446 r = parse_env_file("/proc/cmdline", WHITESPACE,
447 "vconsole.keymap", &vc_keymap,
9e303250 448 "vconsole.keymap_toggle", &vc_keymap_toggle,
741f8cf6 449 "vconsole.font", &vc_font,
9e303250
MS
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,
741f8cf6
LP
454 "vconsole.font.map", &vc_font_map,
455 "vconsole.font.unimap", &vc_font_unimap,
456 NULL);
741f8cf6 457 if (r < 0 && r != -ENOENT)
da927ba9 458 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
741f8cf6 459 }
1ebdf5b6 460
042d7f50 461 toggle_utf8_sysfs(utf8);
aaa709bb 462 toggle_utf8(vc, fd, utf8);
93c9a9d2
ZJS
463
464 r = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap);
c9d2b3d0 465 keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
97c4a07d 466
1142bed2 467 if (idx > 0) {
93c9a9d2 468 if (r == 0)
1142bed2 469 setup_remaining_vcs(fd, idx, utf8);
93c9a9d2
ZJS
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.");
eb22d84b 475 else
aaa709bb 476 log_warning("Setting source virtual console failed, ignoring remaining ones");
eb22d84b 477 }
97c4a07d 478
93c9a9d2 479 return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
97c4a07d 480}