]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/vconsole/vconsole-setup.c
Merge pull request #8314 from poettering/rearrange-stdio
[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 135 pid_t pid;
4c253ed1 136 int r;
97c4a07d 137
8931278c
LDM
138 /* An empty map means kernel map */
139 if (isempty(map))
c9d2b3d0 140 return 0;
944d4c91 141
9841e8e3 142 args[i++] = KBD_LOADKEYS;
97c4a07d
LP
143 args[i++] = "-q";
144 args[i++] = "-C";
145 args[i++] = vc;
146 if (utf8)
147 args[i++] = "-u";
148 args[i++] = map;
5b396b06
AB
149 if (map_toggle)
150 args[i++] = map_toggle;
97c4a07d
LP
151 args[i++] = NULL;
152
3d623780
ZJS
153 log_debug("Executing \"%s\"...",
154 strnull((cmd = strv_join((char**) args, " "))));
155
b6e1fff1 156 r = safe_fork("(loadkeys)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_LOG, &pid);
4c253ed1 157 if (r < 0)
b6e1fff1 158 return r;
4c253ed1 159 if (r == 0) {
97c4a07d
LP
160 execv(args[0], (char **) args);
161 _exit(EXIT_FAILURE);
162 }
163
7d4904fe 164 return wait_for_terminate_and_check(KBD_LOADKEYS, pid, WAIT_LOG);
97c4a07d
LP
165}
166
aecb6fcb 167static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
3d623780 168 _cleanup_free_ char *cmd = NULL;
97c4a07d 169 const char *args[9];
1142bed2 170 unsigned i = 0;
97c4a07d 171 pid_t pid;
4c253ed1 172 int r;
97c4a07d 173
c9d2b3d0
MS
174 /* Any part can be set independently */
175 if (isempty(font) && isempty(map) && isempty(unimap))
176 return 0;
944d4c91 177
9841e8e3 178 args[i++] = KBD_SETFONT;
97c4a07d
LP
179 args[i++] = "-C";
180 args[i++] = vc;
c9d2b3d0 181 if (!isempty(map)) {
97c4a07d
LP
182 args[i++] = "-m";
183 args[i++] = map;
184 }
c9d2b3d0 185 if (!isempty(unimap)) {
97c4a07d
LP
186 args[i++] = "-u";
187 args[i++] = unimap;
188 }
c9d2b3d0
MS
189 if (!isempty(font))
190 args[i++] = font;
97c4a07d
LP
191 args[i++] = NULL;
192
3d623780
ZJS
193 log_debug("Executing \"%s\"...",
194 strnull((cmd = strv_join((char**) args, " "))));
195
b6e1fff1 196 r = safe_fork("(setfont)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_LOG, &pid);
4c253ed1 197 if (r < 0)
b6e1fff1 198 return r;
4c253ed1 199 if (r == 0) {
97c4a07d
LP
200 execv(args[0], (char **) args);
201 _exit(EXIT_FAILURE);
202 }
203
7d4904fe 204 return wait_for_terminate_and_check(KBD_SETFONT, pid, WAIT_LOG);
97c4a07d
LP
205}
206
d3b37e84 207/*
1142bed2 208 * A newly allocated VT uses the font from the source VT. Here
d3b37e84
KS
209 * we update all possibly already allocated VTs with the configured
210 * font. It also allows to restart systemd-vconsole-setup.service,
211 * to apply a new font to all VTs.
eb22d84b
MS
212 *
213 * We also setup per-console utf8 related stuff: kbdmode, term
214 * processing, stty iutf8.
d3b37e84 215 */
1142bed2 216static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
eb22d84b 217 struct console_font_op cfo = {
5297577f
MS
218 .op = KD_FONT_OP_GET,
219 .width = UINT_MAX, .height = UINT_MAX,
220 .charcount = UINT_MAX,
eb22d84b 221 };
eb22d84b 222 struct unimapinit adv = {};
ff452e76 223 struct unimapdesc unimapd;
6a08e1b0 224 _cleanup_free_ struct unipair* unipairs = NULL;
eb22d84b 225 _cleanup_free_ void *fontbuf = NULL;
1142bed2
MS
226 unsigned i;
227 int r;
dd04aac9 228
6a08e1b0 229 unipairs = new(struct unipair, USHRT_MAX);
e2c9192a
LP
230 if (!unipairs) {
231 log_oom();
6a08e1b0
KR
232 return;
233 }
234
5297577f 235 /* get metadata of the current font (width, height, count) */
1142bed2 236 r = ioctl(src_fd, KDFONTOP, &cfo);
eb22d84b 237 if (r < 0)
5297577f 238 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
eb22d84b 239 else {
a5eebcff 240 /* verify parameter sanity first */
5297577f
MS
241 if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512)
242 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
a5eebcff 243 cfo.width, cfo.height, cfo.charcount);
5297577f
MS
244 else {
245 /*
246 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
247 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
248 * requries 32 per glyph, regardless of the actual height - see the comment above #define
249 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
250 */
8419d457 251 fontbuf = malloc_multiply((cfo.width + 7) / 8 * 32, cfo.charcount);
5297577f
MS
252 if (!fontbuf) {
253 log_oom();
254 return;
255 }
1142bed2 256 /* get fonts from the source console */
5297577f 257 cfo.data = fontbuf;
1142bed2 258 r = ioctl(src_fd, KDFONTOP, &cfo);
5297577f
MS
259 if (r < 0)
260 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to read the font data: %m");
261 else {
262 unimapd.entries = unipairs;
263 unimapd.entry_ct = USHRT_MAX;
1142bed2 264 r = ioctl(src_fd, GIO_UNIMAP, &unimapd);
5297577f
MS
265 if (r < 0)
266 log_warning_errno(errno, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
267 else
268 cfo.op = KD_FONT_OP_SET;
269 }
270 }
eb22d84b
MS
271 }
272
a5eebcff 273 if (cfo.op != KD_FONT_OP_SET)
5297577f 274 log_warning("Fonts will not be copied to remaining consoles");
eb22d84b 275
9fa71843 276 for (i = 1; i <= 63; i++) {
1142bed2 277 char ttyname[sizeof("/dev/tty63")];
eb22d84b 278 _cleanup_close_ int fd_d = -1;
dd04aac9 279
1142bed2 280 if (i == src_idx || verify_vc_allocation(i) < 0)
dd04aac9
KS
281 continue;
282
eb22d84b 283 /* try to open terminal */
1142bed2
MS
284 xsprintf(ttyname, "/dev/tty%u", i);
285 fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC|O_NOCTTY);
eb22d84b 286 if (fd_d < 0) {
1142bed2 287 log_warning_errno(fd_d, "Unable to open tty%u, fonts will not be copied: %m", i);
dd04aac9 288 continue;
eb22d84b 289 }
dd04aac9 290
1142bed2 291 if (verify_vc_kbmode(fd_d) < 0)
dd04aac9
KS
292 continue;
293
aaa709bb 294 toggle_utf8(ttyname, fd_d, utf8);
eb22d84b
MS
295
296 if (cfo.op != KD_FONT_OP_SET)
297 continue;
298
299 r = ioctl(fd_d, KDFONTOP, &cfo);
300 if (r < 0) {
1142bed2 301 log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i);
eb22d84b
MS
302 continue;
303 }
ff452e76 304
1142bed2
MS
305 /*
306 * copy unicode translation table
307 * unimapd is a ushort count and a pointer to an
308 * array of struct unipair { ushort, ushort }
309 */
eb22d84b 310 r = ioctl(fd_d, PIO_UNIMAPCLR, &adv);
aaa709bb 311 if (r < 0) {
1142bed2 312 log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i);
aaa709bb 313 continue;
ff452e76 314 }
aaa709bb
ZJS
315
316 r = ioctl(fd_d, PIO_UNIMAP, &unimapd);
317 if (r < 0) {
1142bed2 318 log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i);
aaa709bb
ZJS
319 continue;
320 }
321
322 log_debug("Font and unimap successfully copied to %s", ttyname);
dd04aac9
KS
323 }
324}
325
1142bed2
MS
326static int find_source_vc(char **ret_path, unsigned *ret_idx) {
327 _cleanup_free_ char *path = NULL;
328 unsigned i;
329 int ret_fd, r, err = 0;
330
331 path = new(char, sizeof("/dev/tty63"));
234519ae 332 if (!path)
1142bed2
MS
333 return log_oom();
334
335 for (i = 1; i <= 63; i++) {
336 _cleanup_close_ int fd = -1;
337
338 r = verify_vc_allocation(i);
339 if (r < 0) {
340 if (!err)
341 err = -r;
342 continue;
343 }
344
345 sprintf(path, "/dev/tty%u", i);
346 fd = open_terminal(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
347 if (fd < 0) {
348 if (!err)
349 err = -fd;
350 continue;
351 }
352 r = verify_vc_kbmode(fd);
353 if (r < 0) {
354 if (!err)
355 err = -r;
356 continue;
357 }
358
359 /* all checks passed, return this one as a source console */
360 *ret_idx = i;
361 *ret_path = path;
362 path = NULL;
363 ret_fd = fd;
364 fd = -1;
365 return ret_fd;
366 }
367
368 return log_error_errno(err, "No usable source console found: %m");
369}
370
371static int verify_source_vc(char **ret_path, const char *src_vc) {
372 char *path;
373 _cleanup_close_ int fd = -1;
374 int ret_fd, r;
375
376 fd = open_terminal(src_vc, O_RDWR|O_CLOEXEC|O_NOCTTY);
377 if (fd < 0)
378 return log_error_errno(fd, "Failed to open %s: %m", src_vc);
379
380 r = verify_vc_device(fd);
381 if (r < 0)
382 return log_error_errno(r, "Device %s is not a virtual console: %m", src_vc);
383
384 r = verify_vc_allocation_byfd(fd);
385 if (r < 0)
386 return log_error_errno(r, "Virtual console %s is not allocated: %m", src_vc);
387
388 r = verify_vc_kbmode(fd);
389 if (r < 0)
390 return log_error_errno(r, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc);
391
392 path = strdup(src_vc);
234519ae 393 if (!path)
1142bed2
MS
394 return log_oom();
395
396 *ret_path = path;
397 ret_fd = fd;
398 fd = -1;
399 return ret_fd;
400}
401
97c4a07d 402int main(int argc, char **argv) {
abee28c5 403 _cleanup_free_ char
1142bed2 404 *vc = NULL,
abee28c5
ZJS
405 *vc_keymap = NULL, *vc_keymap_toggle = NULL,
406 *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
407 _cleanup_close_ int fd = -1;
1142bed2
MS
408 bool utf8, keyboard_ok;
409 unsigned idx = 0;
93c9a9d2 410 int r;
97c4a07d 411
944d4c91 412 log_set_target(LOG_TARGET_AUTO);
97c4a07d
LP
413 log_parse_environment();
414 log_open();
415
4c12626c
LP
416 umask(0022);
417
97c4a07d 418 if (argv[1])
1142bed2
MS
419 fd = verify_source_vc(&vc, argv[1]);
420 else
421 fd = find_source_vc(&vc, &idx);
97c4a07d 422
1142bed2 423 if (fd < 0)
abee28c5 424 return EXIT_FAILURE;
03044059 425
653ab83b 426 utf8 = is_locale_utf8();
97c4a07d 427
2034ec42
MS
428 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
429 "KEYMAP", &vc_keymap,
430 "KEYMAP_TOGGLE", &vc_keymap_toggle,
431 "FONT", &vc_font,
432 "FONT_MAP", &vc_font_map,
433 "FONT_UNIMAP", &vc_font_unimap,
434 NULL);
2034ec42 435 if (r < 0 && r != -ENOENT)
da927ba9 436 log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m");
2034ec42
MS
437
438 /* Let the kernel command line override /etc/vconsole.conf */
75f86906 439 if (detect_container() <= 0) {
741f8cf6
LP
440 r = parse_env_file("/proc/cmdline", WHITESPACE,
441 "vconsole.keymap", &vc_keymap,
9e303250 442 "vconsole.keymap_toggle", &vc_keymap_toggle,
741f8cf6 443 "vconsole.font", &vc_font,
9e303250
MS
444 "vconsole.font_map", &vc_font_map,
445 "vconsole.font_unimap", &vc_font_unimap,
446 /* compatibility with obsolete multiple-dot scheme */
447 "vconsole.keymap.toggle", &vc_keymap_toggle,
741f8cf6
LP
448 "vconsole.font.map", &vc_font_map,
449 "vconsole.font.unimap", &vc_font_unimap,
450 NULL);
741f8cf6 451 if (r < 0 && r != -ENOENT)
da927ba9 452 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
741f8cf6 453 }
1ebdf5b6 454
31390963
LP
455 (void) toggle_utf8_sysfs(utf8);
456 (void) toggle_utf8(vc, fd, utf8);
93c9a9d2
ZJS
457
458 r = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap);
c9d2b3d0 459 keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
97c4a07d 460
1142bed2 461 if (idx > 0) {
93c9a9d2 462 if (r == 0)
1142bed2 463 setup_remaining_vcs(fd, idx, utf8);
93c9a9d2
ZJS
464 else if (r == EX_OSERR)
465 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails.
466 * This might mean various things, but in particular lack of a graphical
467 * console. Let's be generous and not treat this as an error. */
468 log_notice("Setting fonts failed with a \"system error\", ignoring.");
eb22d84b 469 else
aaa709bb 470 log_warning("Setting source virtual console failed, ignoring remaining ones");
eb22d84b 471 }
97c4a07d 472
93c9a9d2 473 return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
97c4a07d 474}