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 verify_vc_kbmode(int fd
) {
77 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
78 * Otherwise we would (likely) interfere with X11's processing of the
81 * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
84 if (ioctl(fd
, KDGKBMODE
, &curr_mode
) < 0)
87 return IN_SET(curr_mode
, K_XLATE
, K_UNICODE
) ? 0 : -EBUSY
;
90 static int toggle_utf8_vc(const char *name
, int fd
, bool utf8
) {
92 struct termios tc
= {};
97 r
= ioctl(fd
, KDSKBMODE
, utf8
? K_UNICODE
: K_XLATE
);
99 return log_warning_errno(errno
, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8
), name
);
101 r
= loop_write(fd
, utf8
? "\033%G" : "\033%@", 3, false);
103 return log_warning_errno(r
, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8
), name
);
105 r
= tcgetattr(fd
, &tc
);
107 SET_FLAG(tc
.c_iflag
, IUTF8
, utf8
);
108 r
= tcsetattr(fd
, TCSANOW
, &tc
);
111 return log_warning_errno(errno
, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8
), name
);
113 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8
), name
);
117 static int toggle_utf8_sysfs(bool utf8
) {
120 r
= write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8
), WRITE_STRING_FILE_DISABLE_BUFFER
);
122 return log_warning_errno(r
, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8
));
124 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8
));
128 static int keyboard_load_and_wait(const char *vc
, const char *map
, const char *map_toggle
, bool utf8
) {
134 /* An empty map means kernel map */
138 args
[i
++] = KBD_LOADKEYS
;
146 args
[i
++] = map_toggle
;
150 _cleanup_free_
char *cmd
;
152 cmd
= strv_join((char**) args
, " ");
153 log_debug("Executing \"%s\"...", strnull(cmd
));
156 r
= safe_fork("(loadkeys)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
160 execv(args
[0], (char **) args
);
164 return wait_for_terminate_and_check(KBD_LOADKEYS
, pid
, WAIT_LOG
);
167 static int font_load_and_wait(const char *vc
, const char *font
, const char *map
, const char *unimap
) {
173 /* Any part can be set independently */
174 if (isempty(font
) && isempty(map
) && isempty(unimap
))
177 args
[i
++] = KBD_SETFONT
;
184 if (!isempty(unimap
)) {
193 _cleanup_free_
char *cmd
;
195 cmd
= strv_join((char**) args
, " ");
196 log_debug("Executing \"%s\"...", strnull(cmd
));
199 r
= safe_fork("(setfont)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
203 execv(args
[0], (char **) args
);
207 return wait_for_terminate_and_check(KBD_SETFONT
, pid
, WAIT_LOG
);
211 * A newly allocated VT uses the font from the source VT. Here
212 * we update all possibly already allocated VTs with the configured
213 * font. It also allows to restart systemd-vconsole-setup.service,
214 * to apply a new font to all VTs.
216 * We also setup per-console utf8 related stuff: kbdmode, term
217 * processing, stty iutf8.
219 static void setup_remaining_vcs(int src_fd
, unsigned src_idx
, bool utf8
) {
220 struct console_font_op cfo
= {
221 .op
= KD_FONT_OP_GET
,
222 .width
= UINT_MAX
, .height
= UINT_MAX
,
223 .charcount
= UINT_MAX
,
225 struct unimapinit adv
= {};
226 struct unimapdesc unimapd
;
227 _cleanup_free_
struct unipair
* unipairs
= NULL
;
228 _cleanup_free_
void *fontbuf
= NULL
;
232 unipairs
= new(struct unipair
, USHRT_MAX
);
238 /* get metadata of the current font (width, height, count) */
239 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
241 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
243 /* verify parameter sanity first */
244 if (cfo
.width
> 32 || cfo
.height
> 32 || cfo
.charcount
> 512)
245 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
246 cfo
.width
, cfo
.height
, cfo
.charcount
);
249 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
250 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
251 * requires 32 per glyph, regardless of the actual height - see the comment above #define
252 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
254 fontbuf
= malloc_multiply((cfo
.width
+ 7) / 8 * 32, cfo
.charcount
);
259 /* get fonts from the source console */
261 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
263 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to read the font data: %m");
265 unimapd
.entries
= unipairs
;
266 unimapd
.entry_ct
= USHRT_MAX
;
267 r
= ioctl(src_fd
, GIO_UNIMAP
, &unimapd
);
269 log_warning_errno(errno
, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
271 cfo
.op
= KD_FONT_OP_SET
;
276 if (cfo
.op
!= KD_FONT_OP_SET
)
277 log_warning("Fonts will not be copied to remaining consoles");
279 for (i
= 1; i
<= 63; i
++) {
280 char ttyname
[sizeof("/dev/tty63")];
281 _cleanup_close_
int fd_d
= -1;
283 if (i
== src_idx
|| verify_vc_allocation(i
) < 0)
286 /* try to open terminal */
287 xsprintf(ttyname
, "/dev/tty%u", i
);
288 fd_d
= open_terminal(ttyname
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
290 log_warning_errno(fd_d
, "Unable to open tty%u, fonts will not be copied: %m", i
);
294 if (verify_vc_kbmode(fd_d
) < 0)
297 (void) toggle_utf8_vc(ttyname
, fd_d
, utf8
);
299 if (cfo
.op
!= KD_FONT_OP_SET
)
302 r
= ioctl(fd_d
, KDFONTOP
, &cfo
);
304 int last_errno
, mode
;
306 /* The fonts couldn't have been copied. It might be due to the
307 * terminal being in graphical mode. In this case the kernel
308 * returns -EINVAL which is too generic for distinguishing this
309 * specific case. So we need to retrieve the terminal mode and if
310 * the graphical mode is in used, let's assume that something else
311 * is using the terminal and the failure was expected as we
312 * shouldn't have tried to copy the fonts. */
315 if (ioctl(fd_d
, KDGETMODE
, &mode
) >= 0 && mode
!= KD_TEXT
)
316 log_debug("KD_FONT_OP_SET skipped: tty%u is not in text mode", i
);
318 log_warning_errno(last_errno
, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i
);
324 * copy unicode translation table unimapd is a ushort count and a pointer
325 * to an array of struct unipair { ushort, ushort }
327 r
= ioctl(fd_d
, PIO_UNIMAPCLR
, &adv
);
329 log_warning_errno(errno
, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i
);
333 r
= ioctl(fd_d
, PIO_UNIMAP
, &unimapd
);
335 log_warning_errno(errno
, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i
);
339 log_debug("Font and unimap successfully copied to %s", ttyname
);
343 static int find_source_vc(char **ret_path
, unsigned *ret_idx
) {
344 _cleanup_free_
char *path
= NULL
;
348 path
= new(char, sizeof("/dev/tty63"));
352 for (i
= 1; i
<= 63; i
++) {
353 _cleanup_close_
int fd
= -1;
355 r
= verify_vc_allocation(i
);
362 sprintf(path
, "/dev/tty%u", i
);
363 fd
= open_terminal(path
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
369 r
= verify_vc_kbmode(fd
);
376 /* all checks passed, return this one as a source console */
378 *ret_path
= TAKE_PTR(path
);
382 return log_error_errno(err
, "No usable source console found: %m");
385 static int verify_source_vc(char **ret_path
, const char *src_vc
) {
386 _cleanup_close_
int fd
= -1;
390 fd
= open_terminal(src_vc
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
392 return log_error_errno(fd
, "Failed to open %s: %m", src_vc
);
394 r
= verify_vc_device(fd
);
396 return log_error_errno(r
, "Device %s is not a virtual console: %m", src_vc
);
398 r
= verify_vc_allocation_byfd(fd
);
400 return log_error_errno(r
, "Virtual console %s is not allocated: %m", src_vc
);
402 r
= verify_vc_kbmode(fd
);
404 return log_error_errno(r
, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc
);
406 path
= strdup(src_vc
);
414 int main(int argc
, char **argv
) {
417 *vc_keymap
= NULL
, *vc_keymap_toggle
= NULL
,
418 *vc_font
= NULL
, *vc_font_map
= NULL
, *vc_font_unimap
= NULL
;
419 _cleanup_close_
int fd
= -1;
420 bool utf8
, keyboard_ok
;
429 fd
= verify_source_vc(&vc
, argv
[1]);
431 fd
= find_source_vc(&vc
, &idx
);
435 utf8
= is_locale_utf8();
437 r
= parse_env_file(NULL
, "/etc/vconsole.conf",
438 "KEYMAP", &vc_keymap
,
439 "KEYMAP_TOGGLE", &vc_keymap_toggle
,
441 "FONT_MAP", &vc_font_map
,
442 "FONT_UNIMAP", &vc_font_unimap
);
443 if (r
< 0 && r
!= -ENOENT
)
444 log_warning_errno(r
, "Failed to read /etc/vconsole.conf: %m");
446 /* Let the kernel command line override /etc/vconsole.conf */
447 r
= proc_cmdline_get_key_many(
448 PROC_CMDLINE_STRIP_RD_PREFIX
,
449 "vconsole.keymap", &vc_keymap
,
450 "vconsole.keymap_toggle", &vc_keymap_toggle
,
451 "vconsole.font", &vc_font
,
452 "vconsole.font_map", &vc_font_map
,
453 "vconsole.font_unimap", &vc_font_unimap
,
454 /* compatibility with obsolete multiple-dot scheme */
455 "vconsole.keymap.toggle", &vc_keymap_toggle
,
456 "vconsole.font.map", &vc_font_map
,
457 "vconsole.font.unimap", &vc_font_unimap
);
458 if (r
< 0 && r
!= -ENOENT
)
459 log_warning_errno(r
, "Failed to read /proc/cmdline: %m");
461 (void) toggle_utf8_sysfs(utf8
);
462 (void) toggle_utf8_vc(vc
, fd
, utf8
);
464 r
= font_load_and_wait(vc
, vc_font
, vc_font_map
, vc_font_unimap
);
465 keyboard_ok
= keyboard_load_and_wait(vc
, vc_keymap
, vc_keymap_toggle
, utf8
) == 0;
469 setup_remaining_vcs(fd
, idx
, utf8
);
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.");
476 log_warning("Setting source virtual console failed, ignoring remaining ones");
479 return IN_SET(r
, 0, EX_OSERR
) && keyboard_ok
? EXIT_SUCCESS
: EXIT_FAILURE
;