1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2016 Michal Soltys <soltys@ziu.info>
8 #include <linux/tiocl.h>
10 #include <sys/ioctl.h>
16 #include "alloc-util.h"
17 #include "creds-util.h"
19 #include "errno-util.h"
23 #include "locale-util.h"
25 #include "main-func.h"
26 #include "proc-cmdline.h"
27 #include "process-util.h"
28 #include "stdio-util.h"
29 #include "string-util.h"
31 #include "terminal-util.h"
33 typedef struct Context
{
41 static void context_done(Context
*c
) {
45 free(c
->keymap_toggle
);
51 #define context_merge(dst, src, src_compat, name) \
54 free_and_replace(dst->name, src->name); \
55 else if (src_compat && src_compat->name) \
56 free_and_replace(dst->name, src_compat->name); \
59 static void context_merge_config(
62 Context
*src_compat
) {
67 context_merge(dst
, src
, src_compat
, keymap
);
68 context_merge(dst
, src
, src_compat
, keymap_toggle
);
69 context_merge(dst
, src
, src_compat
, font
);
70 context_merge(dst
, src
, src_compat
, font_map
);
71 context_merge(dst
, src
, src_compat
, font_unimap
);
74 static int context_read_creds(Context
*c
) {
75 _cleanup_(context_done
) Context v
= {};
80 r
= read_credential_strings_many(
81 "vconsole.keymap", &v
.keymap
,
82 "vconsole.keymap_toggle", &v
.keymap_toggle
,
83 "vconsole.font", &v
.font
,
84 "vconsole.font_map", &v
.font_map
,
85 "vconsole.font_unimap", &v
.font_unimap
);
87 log_warning_errno(r
, "Failed to import credentials, ignoring: %m");
89 context_merge_config(c
, &v
, NULL
);
93 static int context_read_env(Context
*c
) {
94 _cleanup_(context_done
) Context v
= {};
100 NULL
, "/etc/vconsole.conf",
102 "KEYMAP_TOGGLE", &v
.keymap_toggle
,
104 "FONT_MAP", &v
.font_map
,
105 "FONT_UNIMAP", &v
.font_unimap
);
108 log_warning_errno(r
, "Failed to read /etc/vconsole.conf, ignoring: %m");
112 context_merge_config(c
, &v
, NULL
);
116 static int context_read_proc_cmdline(Context
*c
) {
117 _cleanup_(context_done
) Context v
= {}, w
= {};
122 r
= proc_cmdline_get_key_many(
123 PROC_CMDLINE_STRIP_RD_PREFIX
,
124 "vconsole.keymap", &v
.keymap
,
125 "vconsole.keymap_toggle", &v
.keymap_toggle
,
126 "vconsole.font", &v
.font
,
127 "vconsole.font_map", &v
.font_map
,
128 "vconsole.font_unimap", &v
.font_unimap
,
129 /* compatibility with obsolete multiple-dot scheme */
130 "vconsole.keymap.toggle", &w
.keymap_toggle
,
131 "vconsole.font.map", &w
.font_map
,
132 "vconsole.font.unimap", &w
.font_unimap
);
135 log_warning_errno(r
, "Failed to read /proc/cmdline, ignoring: %m");
139 context_merge_config(c
, &v
, &w
);
143 static void context_load_config(Context
*c
) {
146 /* Load data from credentials (lowest priority) */
147 (void) context_read_creds(c
);
149 /* Load data from configuration file (middle priority) */
150 (void) context_read_env(c
);
152 /* Let the kernel command line override /etc/vconsole.conf (highest priority) */
153 (void) context_read_proc_cmdline(c
);
156 static int verify_vc_device(int fd
) {
157 unsigned char data
[] = {
161 return RET_NERRNO(ioctl(fd
, TIOCLINUX
, data
));
164 static int verify_vc_allocation(unsigned idx
) {
165 char vcname
[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
167 xsprintf(vcname
, "/dev/vcs%u", idx
);
169 return RET_NERRNO(access(vcname
, F_OK
));
172 static int verify_vc_allocation_byfd(int fd
) {
173 struct vt_stat vcs
= {};
175 if (ioctl(fd
, VT_GETSTATE
, &vcs
) < 0)
178 return verify_vc_allocation(vcs
.v_active
);
181 static int verify_vc_kbmode(int fd
) {
187 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
188 * Otherwise we would (likely) interfere with X11's processing of the
191 * https://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
194 if (ioctl(fd
, KDGKBMODE
, &curr_mode
) < 0)
197 return IN_SET(curr_mode
, K_XLATE
, K_UNICODE
) ? 0 : -EBUSY
;
200 static int verify_vc_display_mode(int fd
) {
205 /* Similarly the vc is likely busy if it is in KD_GRAPHICS mode. If it's not the case and it's been
206 * left in graphics mode, the kernel will refuse to operate on the font settings anyway. */
208 if (ioctl(fd
, KDGETMODE
, &mode
) < 0)
211 return mode
!= KD_TEXT
? -EBUSY
: 0;
214 static int toggle_utf8_vc(const char *name
, int fd
, bool utf8
) {
216 struct termios tc
= {};
221 r
= ioctl(fd
, KDSKBMODE
, utf8
? K_UNICODE
: K_XLATE
);
223 return log_warning_errno(errno
, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8
), name
);
225 r
= loop_write(fd
, utf8
? "\033%G" : "\033%@", SIZE_MAX
);
227 return log_warning_errno(r
, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8
), name
);
229 r
= tcgetattr(fd
, &tc
);
231 SET_FLAG(tc
.c_iflag
, IUTF8
, utf8
);
232 r
= tcsetattr(fd
, TCSANOW
, &tc
);
235 return log_warning_errno(errno
, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8
), name
);
237 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8
), name
);
241 static int toggle_utf8_sysfs(bool utf8
) {
244 r
= write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8
), WRITE_STRING_FILE_DISABLE_BUFFER
);
246 return log_warning_errno(r
, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8
));
248 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8
));
252 /* SYSTEMD_DEFAULT_KEYMAP must not be empty */
253 assert_cc(STRLEN(SYSTEMD_DEFAULT_KEYMAP
) > 0);
255 static int keyboard_load_and_wait(const char *vc
, Context
*c
, bool utf8
) {
265 *keymap
= empty_to_null(c
->keymap
) ?: SYSTEMD_DEFAULT_KEYMAP
,
266 *keymap_toggle
= empty_to_null(c
->keymap_toggle
);
268 if (streq(keymap
, "@kernel"))
271 args
[i
++] = KBD_LOADKEYS
;
279 args
[i
++] = keymap_toggle
;
283 _cleanup_free_
char *cmd
= NULL
;
285 cmd
= strv_join((char**) args
, " ");
286 log_debug("Executing \"%s\"...", strnull(cmd
));
289 r
= safe_fork("(loadkeys)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
293 execv(args
[0], (char **) args
);
297 return wait_for_terminate_and_check(KBD_LOADKEYS
, pid
, WAIT_LOG
);
300 static int font_load_and_wait(const char *vc
, Context
*c
) {
310 *font
= empty_to_null(c
->font
),
311 *font_map
= empty_to_null(c
->font_map
),
312 *font_unimap
= empty_to_null(c
->font_unimap
);
314 /* Any part can be set independently */
315 if (!font
&& !font_map
&& !font_unimap
)
318 args
[i
++] = KBD_SETFONT
;
323 args
[i
++] = font_map
;
327 args
[i
++] = font_unimap
;
334 _cleanup_free_
char *cmd
= NULL
;
336 cmd
= strv_join((char**) args
, " ");
337 log_debug("Executing \"%s\"...", strnull(cmd
));
340 r
= safe_fork("(setfont)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
344 execv(args
[0], (char **) args
);
348 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails. This might mean various
349 * things, but in particular lack of a graphical console. Let's be generous and not treat this as an
351 r
= wait_for_terminate_and_check(KBD_SETFONT
, pid
, WAIT_LOG_ABNORMAL
);
353 log_notice(KBD_SETFONT
" failed with a \"system error\" (EX_OSERR), ignoring.");
354 else if (r
>= 0 && r
!= EXIT_SUCCESS
)
355 log_error(KBD_SETFONT
" failed with exit status %i.", r
);
361 * A newly allocated VT uses the font from the source VT. Here we update all possibly already allocated VTs
362 * with the configured font. It also allows systemd-vconsole-setup.service to be restarted to apply a new
365 * We also setup per-console utf8 related stuff: kbdmode, term processing, stty iutf8.
367 static void setup_remaining_vcs(int src_fd
, unsigned src_idx
, bool utf8
) {
368 struct console_font_op cfo
= {
369 .op
= KD_FONT_OP_GET
,
370 .width
= UINT_MAX
, .height
= UINT_MAX
,
371 .charcount
= UINT_MAX
,
373 struct unimapinit adv
= {};
374 struct unimapdesc unimapd
;
375 _cleanup_free_
struct unipair
* unipairs
= NULL
;
376 _cleanup_free_
void *fontbuf
= NULL
;
377 int log_level
= LOG_WARNING
;
380 unipairs
= new(struct unipair
, USHRT_MAX
);
382 return (void) log_oom();
384 /* get metadata of the current font (width, height, count) */
385 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
387 /* We might be called to operate on the dummy console (to setup keymap
388 * mainly) when fbcon deferred takeover is used for example. In such case,
389 * setting font is not supported and is expected to fail. */
391 log_level
= LOG_DEBUG
;
393 log_full_errno(log_level
, errno
,
394 "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
396 /* verify parameter sanity first */
397 if (cfo
.width
> 32 || cfo
.height
> 32 || cfo
.charcount
> 512)
398 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
399 cfo
.width
, cfo
.height
, cfo
.charcount
);
402 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
403 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
404 * requires 32 per glyph, regardless of the actual height - see the comment above #define
405 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
407 fontbuf
= malloc_multiply((cfo
.width
+ 7) / 8 * 32, cfo
.charcount
);
412 /* get fonts from the source console */
414 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
416 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to read the font data: %m");
418 unimapd
.entries
= unipairs
;
419 unimapd
.entry_ct
= USHRT_MAX
;
420 r
= ioctl(src_fd
, GIO_UNIMAP
, &unimapd
);
422 log_warning_errno(errno
, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
424 cfo
.op
= KD_FONT_OP_SET
;
429 if (cfo
.op
!= KD_FONT_OP_SET
)
430 log_full(log_level
, "Fonts will not be copied to remaining consoles");
432 for (unsigned i
= 1; i
<= 63; i
++) {
433 char ttyname
[sizeof("/dev/tty63")];
434 _cleanup_close_
int fd_d
= -EBADF
;
436 if (i
== src_idx
|| verify_vc_allocation(i
) < 0)
439 /* try to open terminal */
440 xsprintf(ttyname
, "/dev/tty%u", i
);
441 fd_d
= open_terminal(ttyname
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
443 log_warning_errno(fd_d
, "Unable to open tty%u, fonts will not be copied: %m", i
);
447 if (verify_vc_kbmode(fd_d
) < 0)
450 (void) toggle_utf8_vc(ttyname
, fd_d
, utf8
);
452 if (cfo
.op
!= KD_FONT_OP_SET
)
455 r
= verify_vc_display_mode(fd_d
);
457 log_debug_errno(r
, "KD_FONT_OP_SET skipped: tty%u is not in text mode", i
);
461 if (ioctl(fd_d
, KDFONTOP
, &cfo
) < 0) {
462 log_warning_errno(errno
, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i
);
466 /* Copy unicode translation table unimapd is a ushort count and a pointer
467 * to an array of struct unipair { ushort, ushort }. */
468 r
= ioctl(fd_d
, PIO_UNIMAPCLR
, &adv
);
470 log_warning_errno(errno
, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i
);
474 r
= ioctl(fd_d
, PIO_UNIMAP
, &unimapd
);
476 log_warning_errno(errno
, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i
);
480 log_debug("Font and unimap successfully copied to %s", ttyname
);
484 static int find_source_vc(char **ret_path
, unsigned *ret_idx
) {
490 /* This function returns an fd when it finds a candidate. When it fails, it returns the first error
491 * that occurred when the VC was being opened or -EBUSY when it finds some VCs but all are busy
492 * otherwise -ENOENT when there is no allocated VC. */
494 for (unsigned i
= 1; i
<= 63; i
++) {
495 _cleanup_close_
int fd
= -EBADF
;
496 _cleanup_free_
char *path
= NULL
;
498 /* We save the first error but we give less importance for the case where we previously fail
499 * due to the VCs being not allocated. Similarly errors on opening a device has a higher
500 * priority than errors due to devices either not allocated or busy. */
502 r
= verify_vc_allocation(i
);
504 RET_GATHER(err
, log_debug_errno(r
, "VC %u existence check failed, skipping: %m", i
));
508 if (asprintf(&path
, "/dev/tty%u", i
) < 0)
511 fd
= open_terminal(path
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
513 log_debug_errno(fd
, "Failed to open terminal %s, ignoring: %m", path
);
514 if (IN_SET(err
, 0, -EBUSY
, -ENOENT
))
519 r
= verify_vc_kbmode(fd
);
521 log_debug_errno(r
, "Failed to check VC %s keyboard mode: %m", path
);
522 if (IN_SET(err
, 0, -ENOENT
))
527 r
= verify_vc_display_mode(fd
);
529 log_debug_errno(r
, "Failed to check VC %s display mode: %m", path
);
530 if (IN_SET(err
, 0, -ENOENT
))
535 log_debug("Selecting %s as source console", path
);
537 /* all checks passed, return this one as a source console */
539 *ret_path
= TAKE_PTR(path
);
546 static int verify_source_vc(char **ret_path
, const char *src_vc
) {
547 _cleanup_close_
int fd
= -EBADF
;
551 fd
= open_terminal(src_vc
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
553 return log_error_errno(fd
, "Failed to open %s: %m", src_vc
);
555 r
= verify_vc_device(fd
);
557 return log_error_errno(r
, "Device %s is not a virtual console: %m", src_vc
);
559 r
= verify_vc_allocation_byfd(fd
);
561 return log_error_errno(r
, "Virtual console %s is not allocated: %m", src_vc
);
563 r
= verify_vc_kbmode(fd
);
565 return log_error_errno(r
, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc
);
567 /* setfont(8) silently ignores when the font can't be applied due to the vc being in
568 * KD_GRAPHICS. Hence we continue to accept this case however we now let the user know that the vc
569 * will be initialized only partially. */
570 r
= verify_vc_display_mode(fd
);
572 log_notice_errno(r
, "Virtual console %s is not in KD_TEXT, font settings likely won't be applied.", src_vc
);
574 path
= strdup(src_vc
);
582 static int run(int argc
, char **argv
) {
583 _cleanup_(context_done
) Context c
= {};
584 _cleanup_free_
char *vc
= NULL
;
585 _cleanup_close_
int fd
= -EBADF
, lock_fd
= -EBADF
;
586 bool utf8
, keyboard_ok
;
595 fd
= verify_source_vc(&vc
, argv
[1]);
599 fd
= find_source_vc(&vc
, &idx
);
600 if (fd
< 0 && fd
!= -EBUSY
)
601 return log_error_errno(fd
, "No virtual console that can be configured found: %m");
604 utf8
= is_locale_utf8();
605 (void) toggle_utf8_sysfs(utf8
);
608 /* We found only busy VCs, which might happen during the boot process when the boot splash is
609 * displayed on the only allocated VC. In this case we don't interfere and avoid initializing
610 * the VC partially as some operations are likely to fail. */
611 log_notice("All allocated virtual consoles are busy, will not configure key mapping and font.");
615 context_load_config(&c
);
617 /* Take lock around the remaining operation to avoid being interrupted by a tty reset operation
618 * performed for services with TTYVHangup=yes. */
619 lock_fd
= lock_dev_console();
620 if (ERRNO_IS_NEG_DEVICE_ABSENT(lock_fd
))
621 log_debug_errno(lock_fd
, "Device /dev/console does not exist, proceeding without lock: %m");
622 else if (lock_fd
< 0)
623 log_warning_errno(lock_fd
, "Failed to lock /dev/console, proceeding without lock: %m");
625 (void) toggle_utf8_vc(vc
, fd
, utf8
);
627 r
= font_load_and_wait(vc
, &c
);
628 keyboard_ok
= keyboard_load_and_wait(vc
, &c
, utf8
) == 0;
632 setup_remaining_vcs(fd
, idx
, utf8
);
634 log_full(r
== EX_OSERR
? LOG_NOTICE
: LOG_WARNING
,
635 "Configuration of first virtual console failed, ignoring remaining ones.");
638 return IN_SET(r
, 0, EX_OSERR
) && keyboard_ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
641 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);