1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright © 2016 Michal Soltys <soltys@ziu.info>
10 #include <linux/tiocl.h>
15 #include <sys/ioctl.h>
19 #include <sys/types.h>
22 #include "alloc-util.h"
27 #include "locale-util.h"
29 #include "proc-cmdline.h"
30 #include "process-util.h"
31 #include "signal-util.h"
32 #include "stdio-util.h"
33 #include "string-util.h"
35 #include "terminal-util.h"
39 static int verify_vc_device(int fd
) {
40 unsigned char data
[] = {
46 r
= ioctl(fd
, TIOCLINUX
, data
);
53 static int verify_vc_allocation(unsigned idx
) {
54 char vcname
[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
56 xsprintf(vcname
, "/dev/vcs%u", idx
);
58 if (access(vcname
, F_OK
) < 0)
64 static int verify_vc_allocation_byfd(int fd
) {
65 struct vt_stat vcs
= {};
67 if (ioctl(fd
, VT_GETSTATE
, &vcs
) < 0)
70 return verify_vc_allocation(vcs
.v_active
);
73 static int toggle_utf8(const char *name
, int fd
, bool utf8
) {
75 struct termios tc
= {};
79 r
= vt_verify_kbmode(fd
);
81 log_warning_errno(r
, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", name
);
84 return log_warning_errno(r
, "Failed to verify kbdmode on %s: %m", name
);
86 r
= ioctl(fd
, KDSKBMODE
, utf8
? K_UNICODE
: K_XLATE
);
88 return log_warning_errno(errno
, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8
), name
);
90 r
= loop_write(fd
, utf8
? "\033%G" : "\033%@", 3, false);
92 return log_warning_errno(r
, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8
), name
);
94 r
= tcgetattr(fd
, &tc
);
96 SET_FLAG(tc
.c_iflag
, IUTF8
, utf8
);
97 r
= tcsetattr(fd
, TCSANOW
, &tc
);
100 return log_warning_errno(errno
, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8
), name
);
102 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8
), name
);
106 static int toggle_utf8_sysfs(bool utf8
) {
109 r
= write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8
), WRITE_STRING_FILE_DISABLE_BUFFER
);
111 return log_warning_errno(r
, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8
));
113 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8
));
117 static int keyboard_load_and_wait(const char *vc
, const char *map
, const char *map_toggle
, bool utf8
) {
123 /* An empty map means kernel map */
127 args
[i
++] = KBD_LOADKEYS
;
135 args
[i
++] = map_toggle
;
139 _cleanup_free_
char *cmd
;
141 cmd
= strv_join((char**) args
, " ");
142 log_debug("Executing \"%s\"...", strnull(cmd
));
145 r
= safe_fork("(loadkeys)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
149 execv(args
[0], (char **) args
);
153 return wait_for_terminate_and_check(KBD_LOADKEYS
, pid
, WAIT_LOG
);
156 static int font_load_and_wait(const char *vc
, const char *font
, const char *map
, const char *unimap
) {
162 /* Any part can be set independently */
163 if (isempty(font
) && isempty(map
) && isempty(unimap
))
166 args
[i
++] = KBD_SETFONT
;
173 if (!isempty(unimap
)) {
182 _cleanup_free_
char *cmd
;
184 cmd
= strv_join((char**) args
, " ");
185 log_debug("Executing \"%s\"...", strnull(cmd
));
188 r
= safe_fork("(setfont)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
192 execv(args
[0], (char **) args
);
196 return wait_for_terminate_and_check(KBD_SETFONT
, pid
, WAIT_LOG
);
200 * A newly allocated VT uses the font from the source VT. Here
201 * we update all possibly already allocated VTs with the configured
202 * font. It also allows to restart systemd-vconsole-setup.service,
203 * to apply a new font to all VTs.
205 * We also setup per-console utf8 related stuff: kbdmode, term
206 * processing, stty iutf8.
208 static void setup_remaining_vcs(int src_fd
, unsigned src_idx
, bool utf8
) {
209 struct console_font_op cfo
= {
210 .op
= KD_FONT_OP_GET
,
211 .width
= UINT_MAX
, .height
= UINT_MAX
,
212 .charcount
= UINT_MAX
,
214 struct unimapinit adv
= {};
215 struct unimapdesc unimapd
;
216 _cleanup_free_
struct unipair
* unipairs
= NULL
;
217 _cleanup_free_
void *fontbuf
= NULL
;
221 unipairs
= new(struct unipair
, USHRT_MAX
);
227 /* get metadata of the current font (width, height, count) */
228 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
230 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
232 /* verify parameter sanity first */
233 if (cfo
.width
> 32 || cfo
.height
> 32 || cfo
.charcount
> 512)
234 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
235 cfo
.width
, cfo
.height
, cfo
.charcount
);
238 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
239 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
240 * requires 32 per glyph, regardless of the actual height - see the comment above #define
241 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
243 fontbuf
= malloc_multiply((cfo
.width
+ 7) / 8 * 32, cfo
.charcount
);
248 /* get fonts from the source console */
250 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
252 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to read the font data: %m");
254 unimapd
.entries
= unipairs
;
255 unimapd
.entry_ct
= USHRT_MAX
;
256 r
= ioctl(src_fd
, GIO_UNIMAP
, &unimapd
);
258 log_warning_errno(errno
, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
260 cfo
.op
= KD_FONT_OP_SET
;
265 if (cfo
.op
!= KD_FONT_OP_SET
)
266 log_warning("Fonts will not be copied to remaining consoles");
268 for (i
= 1; i
<= 63; i
++) {
269 char ttyname
[sizeof("/dev/tty63")];
270 _cleanup_close_
int fd_d
= -1;
272 if (i
== src_idx
|| verify_vc_allocation(i
) < 0)
275 /* try to open terminal */
276 xsprintf(ttyname
, "/dev/tty%u", i
);
277 fd_d
= open_terminal(ttyname
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
279 log_warning_errno(fd_d
, "Unable to open tty%u, fonts will not be copied: %m", i
);
283 if (vt_verify_kbmode(fd_d
) < 0)
286 toggle_utf8(ttyname
, fd_d
, utf8
);
288 if (cfo
.op
!= KD_FONT_OP_SET
)
291 r
= ioctl(fd_d
, KDFONTOP
, &cfo
);
293 int last_errno
, mode
;
295 /* The fonts couldn't have been copied. It might be due to the
296 * terminal being in graphical mode. In this case the kernel
297 * returns -EINVAL which is too generic for distinguishing this
298 * specific case. So we need to retrieve the terminal mode and if
299 * the graphical mode is in used, let's assume that something else
300 * is using the terminal and the failure was expected as we
301 * shouldn't have tried to copy the fonts. */
304 if (ioctl(fd_d
, KDGETMODE
, &mode
) >= 0 && mode
!= KD_TEXT
)
305 log_debug("KD_FONT_OP_SET skipped: tty%u is not in text mode", i
);
307 log_warning_errno(last_errno
, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i
);
313 * copy unicode translation table unimapd is a ushort count and a pointer
314 * to an array of struct unipair { ushort, ushort }
316 r
= ioctl(fd_d
, PIO_UNIMAPCLR
, &adv
);
318 log_warning_errno(errno
, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i
);
322 r
= ioctl(fd_d
, PIO_UNIMAP
, &unimapd
);
324 log_warning_errno(errno
, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i
);
328 log_debug("Font and unimap successfully copied to %s", ttyname
);
332 static int find_source_vc(char **ret_path
, unsigned *ret_idx
) {
333 _cleanup_free_
char *path
= NULL
;
337 path
= new(char, sizeof("/dev/tty63"));
341 for (i
= 1; i
<= 63; i
++) {
342 _cleanup_close_
int fd
= -1;
344 r
= verify_vc_allocation(i
);
351 sprintf(path
, "/dev/tty%u", i
);
352 fd
= open_terminal(path
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
358 r
= vt_verify_kbmode(fd
);
365 /* all checks passed, return this one as a source console */
367 *ret_path
= TAKE_PTR(path
);
371 return log_error_errno(err
, "No usable source console found: %m");
374 static int verify_source_vc(char **ret_path
, const char *src_vc
) {
375 _cleanup_close_
int fd
= -1;
379 fd
= open_terminal(src_vc
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
381 return log_error_errno(fd
, "Failed to open %s: %m", src_vc
);
383 r
= verify_vc_device(fd
);
385 return log_error_errno(r
, "Device %s is not a virtual console: %m", src_vc
);
387 r
= verify_vc_allocation_byfd(fd
);
389 return log_error_errno(r
, "Virtual console %s is not allocated: %m", src_vc
);
391 r
= vt_verify_kbmode(fd
);
393 return log_error_errno(r
, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc
);
395 path
= strdup(src_vc
);
403 int main(int argc
, char **argv
) {
406 *vc_keymap
= NULL
, *vc_keymap_toggle
= NULL
,
407 *vc_font
= NULL
, *vc_font_map
= NULL
, *vc_font_unimap
= NULL
;
408 _cleanup_close_
int fd
= -1;
409 bool utf8
, keyboard_ok
;
418 fd
= verify_source_vc(&vc
, argv
[1]);
420 fd
= find_source_vc(&vc
, &idx
);
424 utf8
= is_locale_utf8();
426 r
= parse_env_file(NULL
, "/etc/vconsole.conf",
427 "KEYMAP", &vc_keymap
,
428 "KEYMAP_TOGGLE", &vc_keymap_toggle
,
430 "FONT_MAP", &vc_font_map
,
431 "FONT_UNIMAP", &vc_font_unimap
);
432 if (r
< 0 && r
!= -ENOENT
)
433 log_warning_errno(r
, "Failed to read /etc/vconsole.conf: %m");
435 /* Let the kernel command line override /etc/vconsole.conf */
436 r
= proc_cmdline_get_key_many(
437 PROC_CMDLINE_STRIP_RD_PREFIX
,
438 "vconsole.keymap", &vc_keymap
,
439 "vconsole.keymap_toggle", &vc_keymap_toggle
,
440 "vconsole.font", &vc_font
,
441 "vconsole.font_map", &vc_font_map
,
442 "vconsole.font_unimap", &vc_font_unimap
,
443 /* compatibility with obsolete multiple-dot scheme */
444 "vconsole.keymap.toggle", &vc_keymap_toggle
,
445 "vconsole.font.map", &vc_font_map
,
446 "vconsole.font.unimap", &vc_font_unimap
);
447 if (r
< 0 && r
!= -ENOENT
)
448 log_warning_errno(r
, "Failed to read /proc/cmdline: %m");
450 (void) toggle_utf8_sysfs(utf8
);
451 (void) toggle_utf8(vc
, fd
, utf8
);
453 r
= font_load_and_wait(vc
, vc_font
, vc_font_map
, vc_font_unimap
);
454 keyboard_ok
= keyboard_load_and_wait(vc
, vc_keymap
, vc_keymap_toggle
, utf8
) == 0;
458 setup_remaining_vcs(fd
, idx
, utf8
);
459 else if (r
== EX_OSERR
)
460 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails.
461 * This might mean various things, but in particular lack of a graphical
462 * console. Let's be generous and not treat this as an error. */
463 log_notice("Setting fonts failed with a \"system error\", ignoring.");
465 log_warning("Setting source virtual console failed, ignoring remaining ones");
468 return IN_SET(r
, 0, EX_OSERR
) && keyboard_ok
? EXIT_SUCCESS
: EXIT_FAILURE
;