]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/vconsole/vconsole-setup.c
vconsole-setup: handle the case where the vc is in KD_GRAPHICS mode more gracefully
[thirdparty/systemd.git] / src / vconsole / vconsole-setup.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
97c4a07d 2/***
96b2fb93 3 Copyright © 2016 Michal Soltys <soltys@ziu.info>
97c4a07d
LP
4***/
5
97c4a07d 6#include <errno.h>
97c4a07d 7#include <fcntl.h>
97c4a07d 8#include <limits.h>
97c4a07d 9#include <linux/kd.h>
07630cea 10#include <linux/tiocl.h>
dd04aac9 11#include <linux/vt.h>
07630cea 12#include <stdbool.h>
07630cea 13#include <stdlib.h>
ac8db36c 14#include <sys/file.h>
07630cea 15#include <sys/ioctl.h>
93c9a9d2 16#include <sysexits.h>
042d7f50 17#include <termios.h>
ca78ad1d
ZJS
18#include <sys/stat.h>
19#include <sys/types.h>
07630cea 20#include <unistd.h>
97c4a07d 21
b5efdb8a 22#include "alloc-util.h"
ea575e17 23#include "creds-util.h"
af189d7b 24#include "dev-setup.h"
686d13b9 25#include "env-file.h"
7c248223 26#include "errno-util.h"
3ffd4af2 27#include "fd-util.h"
a5c32cff 28#include "fileio.h"
c004493c 29#include "io-util.h"
8752c575 30#include "locale-util.h"
07630cea 31#include "log.h"
7686f3f1 32#include "main-func.h"
01771226 33#include "proc-cmdline.h"
0b452006 34#include "process-util.h"
ce30c8dc 35#include "signal-util.h"
d054f0a4 36#include "stdio-util.h"
07630cea 37#include "string-util.h"
3d623780 38#include "strv.h"
07630cea 39#include "terminal-util.h"
07630cea 40#include "virt.h"
97c4a07d 41
8886ca62
YW
42typedef enum VCMeta {
43 VC_KEYMAP,
44 VC_KEYMAP_TOGGLE,
45 VC_FONT,
46 VC_FONT_MAP,
47 VC_FONT_UNIMAP,
48 _VC_META_MAX,
49 _VC_META_INVALID = -EINVAL,
50} VCMeta;
51
52typedef struct Context {
53 char *config[_VC_META_MAX];
54} Context;
55
56static const char * const vc_meta_names[_VC_META_MAX] = {
64fe2aea
ZJS
57 [VC_KEYMAP] = "vconsole.keymap",
58 [VC_KEYMAP_TOGGLE] = "vconsole.keymap_toggle",
59 [VC_FONT] = "vconsole.font",
60 [VC_FONT_MAP] = "vconsole.font_map",
61 [VC_FONT_UNIMAP] = "vconsole.font_unimap",
8886ca62
YW
62};
63
64/* compatibility with obsolete multiple-dot scheme */
65static const char * const vc_meta_compat_names[_VC_META_MAX] = {
66 [VC_KEYMAP_TOGGLE] = "vconsole.keymap.toggle",
67 [VC_FONT_MAP] = "vconsole.font.map",
68 [VC_FONT_UNIMAP] = "vconsole.font.unimap",
69};
70
71static const char * const vc_env_names[_VC_META_MAX] = {
72 [VC_KEYMAP] = "KEYMAP",
73 [VC_KEYMAP_TOGGLE] = "KEYMAP_TOGGLE",
74 [VC_FONT] = "FONT",
75 [VC_FONT_MAP] = "FONT_MAP",
76 [VC_FONT_UNIMAP] = "FONT_UNIMAP",
77};
78
79static void context_done(Context *c) {
80 assert(c);
81
bc605984
LP
82 FOREACH_ARRAY(cc, c->config, _VC_META_MAX)
83 free(*cc);
8886ca62
YW
84}
85
86static void context_merge_config(
87 Context *dst,
88 Context *src,
89 Context *src_compat) {
90
91 assert(dst);
92 assert(src);
93
94 for (VCMeta i = 0; i < _VC_META_MAX; i++)
95 if (src->config[i])
96 free_and_replace(dst->config[i], src->config[i]);
97 else if (src_compat && src_compat->config[i])
98 free_and_replace(dst->config[i], src_compat->config[i]);
99}
100
dfc55e34
YW
101static const char* context_get_config(Context *c, VCMeta meta) {
102 assert(c);
103 assert(meta >= 0 && meta < _VC_META_MAX);
104
105 if (meta == VC_KEYMAP)
106 return isempty(c->config[VC_KEYMAP]) ? SYSTEMD_DEFAULT_KEYMAP : c->config[VC_KEYMAP];
107
108 return empty_to_null(c->config[meta]);
109}
110
8886ca62
YW
111static int context_read_creds(Context *c) {
112 _cleanup_(context_done) Context v = {};
113 int r;
114
115 assert(c);
116
117 r = read_credential_strings_many(
118 vc_meta_names[VC_KEYMAP], &v.config[VC_KEYMAP],
119 vc_meta_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
120 vc_meta_names[VC_FONT], &v.config[VC_FONT],
121 vc_meta_names[VC_FONT_MAP], &v.config[VC_FONT_MAP],
122 vc_meta_names[VC_FONT_UNIMAP], &v.config[VC_FONT_UNIMAP]);
55ace8e5
ZJS
123 if (r < 0)
124 log_warning_errno(r, "Failed to import credentials, ignoring: %m");
8886ca62
YW
125
126 context_merge_config(c, &v, NULL);
127 return 0;
128}
129
130static int context_read_env(Context *c) {
131 _cleanup_(context_done) Context v = {};
132 int r;
133
134 assert(c);
135
136 r = parse_env_file(
137 NULL, "/etc/vconsole.conf",
138 vc_env_names[VC_KEYMAP], &v.config[VC_KEYMAP],
139 vc_env_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
140 vc_env_names[VC_FONT], &v.config[VC_FONT],
141 vc_env_names[VC_FONT_MAP], &v.config[VC_FONT_MAP],
142 vc_env_names[VC_FONT_UNIMAP], &v.config[VC_FONT_UNIMAP]);
143 if (r < 0) {
144 if (r != -ENOENT)
145 log_warning_errno(r, "Failed to read /etc/vconsole.conf, ignoring: %m");
146 return r;
147 }
148
149 context_merge_config(c, &v, NULL);
150 return 0;
151}
152
153static int context_read_proc_cmdline(Context *c) {
154 _cleanup_(context_done) Context v = {}, w = {};
155 int r;
156
157 assert(c);
158
159 r = proc_cmdline_get_key_many(
160 PROC_CMDLINE_STRIP_RD_PREFIX,
161 vc_meta_names[VC_KEYMAP], &v.config[VC_KEYMAP],
162 vc_meta_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
163 vc_meta_names[VC_FONT], &v.config[VC_FONT],
164 vc_meta_names[VC_FONT_MAP], &v.config[VC_FONT_MAP],
165 vc_meta_names[VC_FONT_UNIMAP], &v.config[VC_FONT_UNIMAP],
166 vc_meta_compat_names[VC_KEYMAP_TOGGLE], &w.config[VC_KEYMAP_TOGGLE],
167 vc_meta_compat_names[VC_FONT_MAP], &w.config[VC_FONT_MAP],
168 vc_meta_compat_names[VC_FONT_UNIMAP], &w.config[VC_FONT_UNIMAP]);
169 if (r < 0) {
170 if (r != -ENOENT)
171 log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
172 return r;
173 }
174
175 context_merge_config(c, &v, &w);
176 return 0;
177}
178
179static void context_load_config(Context *c) {
180 assert(c);
181
182 /* Load data from credentials (lowest priority) */
183 (void) context_read_creds(c);
184
185 /* Load data from configuration file (middle priority) */
186 (void) context_read_env(c);
187
188 /* Let the kernel command line override /etc/vconsole.conf (highest priority) */
189 (void) context_read_proc_cmdline(c);
190}
191
1142bed2 192static int verify_vc_device(int fd) {
b53f3386
LP
193 unsigned char data[] = {
194 TIOCL_GETFGCONSOLE,
195 };
196
7c248223 197 return RET_NERRNO(ioctl(fd, TIOCLINUX, data));
97c4a07d
LP
198}
199
1142bed2
MS
200static int verify_vc_allocation(unsigned idx) {
201 char vcname[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
03044059 202
1142bed2 203 xsprintf(vcname, "/dev/vcs%u", idx);
b53f3386 204
7c248223 205 return RET_NERRNO(access(vcname, F_OK));
03044059
MS
206}
207
1142bed2 208static int verify_vc_allocation_byfd(int fd) {
03044059
MS
209 struct vt_stat vcs = {};
210
b53f3386
LP
211 if (ioctl(fd, VT_GETSTATE, &vcs) < 0)
212 return -errno;
213
214 return verify_vc_allocation(vcs.v_active);
03044059
MS
215}
216
26382cab
LP
217static int verify_vc_kbmode(int fd) {
218 int curr_mode;
219
58161db6
FB
220 assert(fd >= 0);
221
26382cab
LP
222 /*
223 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
224 * Otherwise we would (likely) interfere with X11's processing of the
225 * key events.
226 *
41d6f3bf 227 * https://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
26382cab
LP
228 */
229
230 if (ioctl(fd, KDGKBMODE, &curr_mode) < 0)
231 return -errno;
232
233 return IN_SET(curr_mode, K_XLATE, K_UNICODE) ? 0 : -EBUSY;
234}
235
58161db6
FB
236static int verify_vc_display_mode(int fd) {
237 int mode;
238
239 assert(fd >= 0);
240
241 /* Similarly the vc is likely busy if it is in KD_GRAPHICS mode. If it's not the case and it's been
242 * left in graphics mode, the kernel will refuse to operate on the font settings anyway. */
243
244 if (ioctl(fd, KDGETMODE, &mode) < 0)
245 return -errno;
246
247 return mode != KD_TEXT ? -EBUSY : 0;
248}
249
e8d1d6e7 250static int toggle_utf8_vc(const char *name, int fd, bool utf8) {
042d7f50
MS
251 int r;
252 struct termios tc = {};
97c4a07d 253
aaa709bb 254 assert(name);
e8d1d6e7 255 assert(fd >= 0);
aaa709bb 256
042d7f50 257 r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE);
97c4a07d 258 if (r < 0)
aaa709bb 259 return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name);
97c4a07d 260
e22c60a9 261 r = loop_write(fd, utf8 ? "\033%G" : "\033%@", SIZE_MAX);
042d7f50 262 if (r < 0)
aaa709bb 263 return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name);
042d7f50
MS
264
265 r = tcgetattr(fd, &tc);
266 if (r >= 0) {
ab8ee0f2 267 SET_FLAG(tc.c_iflag, IUTF8, utf8);
042d7f50 268 r = tcsetattr(fd, TCSANOW, &tc);
a25d4d0e 269 }
042d7f50 270 if (r < 0)
aaa709bb 271 return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name);
d305a67b 272
aaa709bb 273 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name);
042d7f50
MS
274 return 0;
275}
d305a67b 276
042d7f50
MS
277static int toggle_utf8_sysfs(bool utf8) {
278 int r;
d305a67b 279
57512c89 280 r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), WRITE_STRING_FILE_DISABLE_BUFFER);
d305a67b 281 if (r < 0)
aaa709bb
ZJS
282 return log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8));
283
284 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8));
285 return 0;
d305a67b
TG
286}
287
dfc55e34
YW
288static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) {
289 const char *map, *map_toggle, *args[8];
1142bed2 290 unsigned i = 0;
97c4a07d 291 pid_t pid;
4c253ed1 292 int r;
97c4a07d 293
dfc55e34
YW
294 assert(vc);
295 assert(c);
296
297 map = context_get_config(c, VC_KEYMAP);
298 map_toggle = context_get_config(c, VC_KEYMAP_TOGGLE);
299
8931278c 300 /* An empty map means kernel map */
bf77d597 301 if (isempty(map) || streq(map, "@kernel"))
c9d2b3d0 302 return 0;
944d4c91 303
9841e8e3 304 args[i++] = KBD_LOADKEYS;
97c4a07d
LP
305 args[i++] = "-q";
306 args[i++] = "-C";
307 args[i++] = vc;
308 if (utf8)
309 args[i++] = "-u";
310 args[i++] = map;
5b396b06
AB
311 if (map_toggle)
312 args[i++] = map_toggle;
97c4a07d
LP
313 args[i++] = NULL;
314
16dbd110 315 if (DEBUG_LOGGING) {
c2b2df60 316 _cleanup_free_ char *cmd = NULL;
16dbd110
LP
317
318 cmd = strv_join((char**) args, " ");
319 log_debug("Executing \"%s\"...", strnull(cmd));
320 }
3d623780 321
0672e2c6 322 r = safe_fork("(loadkeys)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
4c253ed1 323 if (r < 0)
b6e1fff1 324 return r;
4c253ed1 325 if (r == 0) {
97c4a07d
LP
326 execv(args[0], (char **) args);
327 _exit(EXIT_FAILURE);
328 }
329
7d4904fe 330 return wait_for_terminate_and_check(KBD_LOADKEYS, pid, WAIT_LOG);
97c4a07d
LP
331}
332
dfc55e34
YW
333static int font_load_and_wait(const char *vc, Context *c) {
334 const char *font, *map, *unimap, *args[9];
1142bed2 335 unsigned i = 0;
97c4a07d 336 pid_t pid;
4c253ed1 337 int r;
97c4a07d 338
dfc55e34
YW
339 assert(vc);
340 assert(c);
341
342 font = context_get_config(c, VC_FONT);
343 map = context_get_config(c, VC_FONT_MAP);
344 unimap = context_get_config(c, VC_FONT_UNIMAP);
345
c9d2b3d0 346 /* Any part can be set independently */
dfc55e34 347 if (!font && !map && !unimap)
c9d2b3d0 348 return 0;
944d4c91 349
9841e8e3 350 args[i++] = KBD_SETFONT;
97c4a07d
LP
351 args[i++] = "-C";
352 args[i++] = vc;
dfc55e34 353 if (map) {
97c4a07d
LP
354 args[i++] = "-m";
355 args[i++] = map;
356 }
dfc55e34 357 if (unimap) {
97c4a07d
LP
358 args[i++] = "-u";
359 args[i++] = unimap;
360 }
dfc55e34 361 if (font)
c9d2b3d0 362 args[i++] = font;
97c4a07d
LP
363 args[i++] = NULL;
364
16dbd110 365 if (DEBUG_LOGGING) {
c2b2df60 366 _cleanup_free_ char *cmd = NULL;
16dbd110
LP
367
368 cmd = strv_join((char**) args, " ");
369 log_debug("Executing \"%s\"...", strnull(cmd));
370 }
3d623780 371
0672e2c6 372 r = safe_fork("(setfont)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
4c253ed1 373 if (r < 0)
b6e1fff1 374 return r;
4c253ed1 375 if (r == 0) {
97c4a07d
LP
376 execv(args[0], (char **) args);
377 _exit(EXIT_FAILURE);
378 }
379
2f26c211
FB
380 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails. This might mean various
381 * things, but in particular lack of a graphical console. Let's be generous and not treat this as an
382 * error. */
383 r = wait_for_terminate_and_check(KBD_SETFONT, pid, WAIT_LOG_ABNORMAL);
384 if (r == EX_OSERR)
385 log_notice(KBD_SETFONT " failed with a \"system error\" (EX_OSERR), ignoring.");
386 else if (r >= 0 && r != EXIT_SUCCESS)
387 log_error(KBD_SETFONT " failed with exit status %i.", r);
388
389 return r;
97c4a07d
LP
390}
391
d3b37e84 392/*
1142bed2 393 * A newly allocated VT uses the font from the source VT. Here
d3b37e84
KS
394 * we update all possibly already allocated VTs with the configured
395 * font. It also allows to restart systemd-vconsole-setup.service,
396 * to apply a new font to all VTs.
eb22d84b
MS
397 *
398 * We also setup per-console utf8 related stuff: kbdmode, term
399 * processing, stty iutf8.
d3b37e84 400 */
1142bed2 401static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
eb22d84b 402 struct console_font_op cfo = {
5297577f
MS
403 .op = KD_FONT_OP_GET,
404 .width = UINT_MAX, .height = UINT_MAX,
405 .charcount = UINT_MAX,
eb22d84b 406 };
eb22d84b 407 struct unimapinit adv = {};
ff452e76 408 struct unimapdesc unimapd;
6a08e1b0 409 _cleanup_free_ struct unipair* unipairs = NULL;
eb22d84b 410 _cleanup_free_ void *fontbuf = NULL;
64fe2aea 411 int log_level = LOG_WARNING;
1142bed2 412 int r;
dd04aac9 413
6a08e1b0 414 unipairs = new(struct unipair, USHRT_MAX);
64fe2aea
ZJS
415 if (!unipairs)
416 return (void) log_oom();
0ef1adf5 417
5297577f 418 /* get metadata of the current font (width, height, count) */
1142bed2 419 r = ioctl(src_fd, KDFONTOP, &cfo);
0ef1adf5
FB
420 if (r < 0) {
421 /* We might be called to operate on the dummy console (to setup keymap
422 * mainly) when fbcon deferred takeover is used for example. In such case,
423 * setting font is not supported and is expected to fail. */
424 if (errno == ENOSYS)
425 log_level = LOG_DEBUG;
426
427 log_full_errno(log_level, errno,
428 "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
429 } else {
a5eebcff 430 /* verify parameter sanity first */
5297577f
MS
431 if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512)
432 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
a5eebcff 433 cfo.width, cfo.height, cfo.charcount);
5297577f
MS
434 else {
435 /*
436 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
437 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
5238e957 438 * requires 32 per glyph, regardless of the actual height - see the comment above #define
5297577f
MS
439 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
440 */
8419d457 441 fontbuf = malloc_multiply((cfo.width + 7) / 8 * 32, cfo.charcount);
5297577f
MS
442 if (!fontbuf) {
443 log_oom();
444 return;
445 }
1142bed2 446 /* get fonts from the source console */
5297577f 447 cfo.data = fontbuf;
1142bed2 448 r = ioctl(src_fd, KDFONTOP, &cfo);
5297577f
MS
449 if (r < 0)
450 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to read the font data: %m");
451 else {
452 unimapd.entries = unipairs;
453 unimapd.entry_ct = USHRT_MAX;
1142bed2 454 r = ioctl(src_fd, GIO_UNIMAP, &unimapd);
5297577f
MS
455 if (r < 0)
456 log_warning_errno(errno, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
457 else
458 cfo.op = KD_FONT_OP_SET;
459 }
460 }
eb22d84b
MS
461 }
462
a5eebcff 463 if (cfo.op != KD_FONT_OP_SET)
0ef1adf5 464 log_full(log_level, "Fonts will not be copied to remaining consoles");
eb22d84b 465
64fe2aea 466 for (unsigned i = 1; i <= 63; i++) {
1142bed2 467 char ttyname[sizeof("/dev/tty63")];
254d1313 468 _cleanup_close_ int fd_d = -EBADF;
dd04aac9 469
1142bed2 470 if (i == src_idx || verify_vc_allocation(i) < 0)
dd04aac9
KS
471 continue;
472
eb22d84b 473 /* try to open terminal */
1142bed2
MS
474 xsprintf(ttyname, "/dev/tty%u", i);
475 fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC|O_NOCTTY);
eb22d84b 476 if (fd_d < 0) {
1142bed2 477 log_warning_errno(fd_d, "Unable to open tty%u, fonts will not be copied: %m", i);
dd04aac9 478 continue;
eb22d84b 479 }
dd04aac9 480
26382cab 481 if (verify_vc_kbmode(fd_d) < 0)
dd04aac9
KS
482 continue;
483
e8d1d6e7 484 (void) toggle_utf8_vc(ttyname, fd_d, utf8);
eb22d84b
MS
485
486 if (cfo.op != KD_FONT_OP_SET)
487 continue;
488
58161db6 489 r = verify_vc_display_mode(fd_d);
eb22d84b 490 if (r < 0) {
58161db6
FB
491 log_debug_errno(r, "KD_FONT_OP_SET skipped: tty%u is not in text mode", i);
492 continue;
493 }
d610d201 494
58161db6
FB
495 if (ioctl(fd_d, KDFONTOP, &cfo) < 0) {
496 log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i);
eb22d84b
MS
497 continue;
498 }
ff452e76 499
64fe2aea
ZJS
500 /* Copy unicode translation table unimapd is a ushort count and a pointer
501 * to an array of struct unipair { ushort, ushort }. */
eb22d84b 502 r = ioctl(fd_d, PIO_UNIMAPCLR, &adv);
aaa709bb 503 if (r < 0) {
1142bed2 504 log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i);
aaa709bb 505 continue;
ff452e76 506 }
aaa709bb
ZJS
507
508 r = ioctl(fd_d, PIO_UNIMAP, &unimapd);
509 if (r < 0) {
1142bed2 510 log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i);
aaa709bb
ZJS
511 continue;
512 }
513
514 log_debug("Font and unimap successfully copied to %s", ttyname);
dd04aac9
KS
515 }
516}
517
1142bed2 518static int find_source_vc(char **ret_path, unsigned *ret_idx) {
c10d6bdb 519 int r, err = 0;
1142bed2 520
7b218ef9
LP
521 assert(ret_path);
522 assert(ret_idx);
1142bed2 523
64fe2aea 524 for (unsigned i = 1; i <= 63; i++) {
254d1313 525 _cleanup_close_ int fd = -EBADF;
7b218ef9 526 _cleanup_free_ char *path = NULL;
1142bed2
MS
527
528 r = verify_vc_allocation(i);
529 if (r < 0) {
fcdd21ec 530 log_debug_errno(r, "VC %u existence check failed, skipping: %m", i);
6433f1f6 531 RET_GATHER(err, r);
1142bed2
MS
532 continue;
533 }
534
7b218ef9
LP
535 if (asprintf(&path, "/dev/tty%u", i) < 0)
536 return log_oom();
537
1142bed2
MS
538 fd = open_terminal(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
539 if (fd < 0) {
6433f1f6
LP
540 log_debug_errno(fd, "Failed to open terminal %s, ignoring: %m", path);
541 RET_GATHER(err, r);
1142bed2
MS
542 continue;
543 }
58161db6 544
26382cab 545 r = verify_vc_kbmode(fd);
1142bed2 546 if (r < 0) {
6433f1f6
LP
547 log_debug_errno(r, "Failed to check VC %s keyboard mode: %m", path);
548 RET_GATHER(err, r);
1142bed2
MS
549 continue;
550 }
551
58161db6
FB
552 r = verify_vc_display_mode(fd);
553 if (r < 0) {
554 log_debug_errno(r, "Failed to check VC %s display mode: %m", path);
555 RET_GATHER(err, r);
556 continue;
557 }
558
1142bed2
MS
559 /* all checks passed, return this one as a source console */
560 *ret_idx = i;
ae2a15bc 561 *ret_path = TAKE_PTR(path);
c10d6bdb 562 return TAKE_FD(fd);
1142bed2
MS
563 }
564
565 return log_error_errno(err, "No usable source console found: %m");
566}
567
568static int verify_source_vc(char **ret_path, const char *src_vc) {
254d1313 569 _cleanup_close_ int fd = -EBADF;
c10d6bdb
LP
570 char *path;
571 int r;
1142bed2
MS
572
573 fd = open_terminal(src_vc, O_RDWR|O_CLOEXEC|O_NOCTTY);
574 if (fd < 0)
575 return log_error_errno(fd, "Failed to open %s: %m", src_vc);
576
577 r = verify_vc_device(fd);
578 if (r < 0)
579 return log_error_errno(r, "Device %s is not a virtual console: %m", src_vc);
580
581 r = verify_vc_allocation_byfd(fd);
582 if (r < 0)
583 return log_error_errno(r, "Virtual console %s is not allocated: %m", src_vc);
584
26382cab 585 r = verify_vc_kbmode(fd);
1142bed2
MS
586 if (r < 0)
587 return log_error_errno(r, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc);
588
58161db6
FB
589 /* setfont(8) silently ignores when the font can't be applied due to the vc being in
590 * KD_GRAPHICS. Hence we continue to accept this case however we now let the user know that the vc
591 * will be initialized only partially.*/
592 r = verify_vc_display_mode(fd);
593 if (r < 0)
594 log_notice_errno(r, "Virtual console %s is not in KD_TEXT, font settings likely won't be applied.", src_vc);
595
1142bed2 596 path = strdup(src_vc);
234519ae 597 if (!path)
1142bed2
MS
598 return log_oom();
599
600 *ret_path = path;
c10d6bdb 601 return TAKE_FD(fd);
1142bed2
MS
602}
603
7686f3f1 604static int run(int argc, char **argv) {
8886ca62
YW
605 _cleanup_(context_done) Context c = {};
606 _cleanup_free_ char *vc = NULL;
af189d7b 607 _cleanup_close_ int fd = -EBADF, lock_fd = -EBADF;
1142bed2
MS
608 bool utf8, keyboard_ok;
609 unsigned idx = 0;
93c9a9d2 610 int r;
97c4a07d 611
d2acb93d 612 log_setup();
97c4a07d 613
4c12626c
LP
614 umask(0022);
615
97c4a07d 616 if (argv[1])
1142bed2
MS
617 fd = verify_source_vc(&vc, argv[1]);
618 else
619 fd = find_source_vc(&vc, &idx);
1142bed2 620 if (fd < 0)
7686f3f1 621 return fd;
03044059 622
653ab83b 623 utf8 = is_locale_utf8();
97c4a07d 624
8886ca62 625 context_load_config(&c);
2034ec42 626
a0043bfa
ZJS
627 /* Take lock around the remaining operation to avoid being interrupted by a tty reset operation
628 * performed for services with TTYVHangup=yes. */
af189d7b
ZJS
629 lock_fd = lock_dev_console();
630 if (lock_fd < 0) {
631 log_full_errno(lock_fd == -ENOENT ? LOG_DEBUG : LOG_ERR,
632 lock_fd,
633 "Failed to lock /dev/console%s: %m",
634 lock_fd == -ENOENT ? ", ignoring" : "");
635 if (lock_fd != -ENOENT)
636 return lock_fd;
637 }
a0043bfa 638
31390963 639 (void) toggle_utf8_sysfs(utf8);
e8d1d6e7 640 (void) toggle_utf8_vc(vc, fd, utf8);
93c9a9d2 641
dfc55e34
YW
642 r = font_load_and_wait(vc, &c);
643 keyboard_ok = keyboard_load_and_wait(vc, &c, utf8) == 0;
97c4a07d 644
1142bed2 645 if (idx > 0) {
93c9a9d2 646 if (r == 0)
1142bed2 647 setup_remaining_vcs(fd, idx, utf8);
eb22d84b 648 else
2f26c211
FB
649 log_full(r == EX_OSERR ? LOG_NOTICE : LOG_WARNING,
650 "Setting source virtual console failed, ignoring remaining ones.");
eb22d84b 651 }
97c4a07d 652
93c9a9d2 653 return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
97c4a07d 654}
7686f3f1
LP
655
656DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);