1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright © 2016 Michal Soltys <soltys@ziu.info>
10 #include <linux/tiocl.h>
14 #include <sys/ioctl.h>
18 #include <sys/types.h>
21 #include "alloc-util.h"
26 #include "locale-util.h"
28 #include "proc-cmdline.h"
29 #include "process-util.h"
30 #include "signal-util.h"
31 #include "stdio-util.h"
32 #include "string-util.h"
34 #include "terminal-util.h"
38 static int verify_vc_device(int fd
) {
39 unsigned char data
[] = {
45 r
= ioctl(fd
, TIOCLINUX
, data
);
52 static int verify_vc_allocation(unsigned idx
) {
53 char vcname
[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
55 xsprintf(vcname
, "/dev/vcs%u", idx
);
57 if (access(vcname
, F_OK
) < 0)
63 static int verify_vc_allocation_byfd(int fd
) {
64 struct vt_stat vcs
= {};
66 if (ioctl(fd
, VT_GETSTATE
, &vcs
) < 0)
69 return verify_vc_allocation(vcs
.v_active
);
72 static int verify_vc_kbmode(int fd
) {
76 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
77 * Otherwise we would (likely) interfere with X11's processing of the
80 * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
83 if (ioctl(fd
, KDGKBMODE
, &curr_mode
) < 0)
86 return IN_SET(curr_mode
, K_XLATE
, K_UNICODE
) ? 0 : -EBUSY
;
89 static int toggle_utf8_vc(const char *name
, int fd
, bool utf8
) {
91 struct termios tc
= {};
96 r
= ioctl(fd
, KDSKBMODE
, utf8
? K_UNICODE
: K_XLATE
);
98 return log_warning_errno(errno
, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8
), name
);
100 r
= loop_write(fd
, utf8
? "\033%G" : "\033%@", 3, false);
102 return log_warning_errno(r
, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8
), name
);
104 r
= tcgetattr(fd
, &tc
);
106 SET_FLAG(tc
.c_iflag
, IUTF8
, utf8
);
107 r
= tcsetattr(fd
, TCSANOW
, &tc
);
110 return log_warning_errno(errno
, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8
), name
);
112 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8
), name
);
116 static int toggle_utf8_sysfs(bool utf8
) {
119 r
= write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8
), WRITE_STRING_FILE_DISABLE_BUFFER
);
121 return log_warning_errno(r
, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8
));
123 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8
));
127 static int keyboard_load_and_wait(const char *vc
, const char *map
, const char *map_toggle
, bool utf8
) {
133 /* An empty map means kernel map */
137 args
[i
++] = KBD_LOADKEYS
;
145 args
[i
++] = map_toggle
;
149 _cleanup_free_
char *cmd
;
151 cmd
= strv_join((char**) args
, " ");
152 log_debug("Executing \"%s\"...", strnull(cmd
));
155 r
= safe_fork("(loadkeys)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
159 execv(args
[0], (char **) args
);
163 return wait_for_terminate_and_check(KBD_LOADKEYS
, pid
, WAIT_LOG
);
166 static int font_load_and_wait(const char *vc
, const char *font
, const char *map
, const char *unimap
) {
172 /* Any part can be set independently */
173 if (isempty(font
) && isempty(map
) && isempty(unimap
))
176 args
[i
++] = KBD_SETFONT
;
183 if (!isempty(unimap
)) {
192 _cleanup_free_
char *cmd
;
194 cmd
= strv_join((char**) args
, " ");
195 log_debug("Executing \"%s\"...", strnull(cmd
));
198 r
= safe_fork("(setfont)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
202 execv(args
[0], (char **) args
);
206 return wait_for_terminate_and_check(KBD_SETFONT
, pid
, WAIT_LOG
);
210 * A newly allocated VT uses the font from the source VT. Here
211 * we update all possibly already allocated VTs with the configured
212 * font. It also allows to restart systemd-vconsole-setup.service,
213 * to apply a new font to all VTs.
215 * We also setup per-console utf8 related stuff: kbdmode, term
216 * processing, stty iutf8.
218 static void setup_remaining_vcs(int src_fd
, unsigned src_idx
, bool utf8
) {
219 struct console_font_op cfo
= {
220 .op
= KD_FONT_OP_GET
,
221 .width
= UINT_MAX
, .height
= UINT_MAX
,
222 .charcount
= UINT_MAX
,
224 struct unimapinit adv
= {};
225 struct unimapdesc unimapd
;
226 _cleanup_free_
struct unipair
* unipairs
= NULL
;
227 _cleanup_free_
void *fontbuf
= NULL
;
231 unipairs
= new(struct unipair
, USHRT_MAX
);
237 /* get metadata of the current font (width, height, count) */
238 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
240 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
242 /* verify parameter sanity first */
243 if (cfo
.width
> 32 || cfo
.height
> 32 || cfo
.charcount
> 512)
244 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
245 cfo
.width
, cfo
.height
, cfo
.charcount
);
248 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
249 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
250 * requires 32 per glyph, regardless of the actual height - see the comment above #define
251 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
253 fontbuf
= malloc_multiply((cfo
.width
+ 7) / 8 * 32, cfo
.charcount
);
258 /* get fonts from the source console */
260 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
262 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to read the font data: %m");
264 unimapd
.entries
= unipairs
;
265 unimapd
.entry_ct
= USHRT_MAX
;
266 r
= ioctl(src_fd
, GIO_UNIMAP
, &unimapd
);
268 log_warning_errno(errno
, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
270 cfo
.op
= KD_FONT_OP_SET
;
275 if (cfo
.op
!= KD_FONT_OP_SET
)
276 log_warning("Fonts will not be copied to remaining consoles");
278 for (i
= 1; i
<= 63; i
++) {
279 char ttyname
[sizeof("/dev/tty63")];
280 _cleanup_close_
int fd_d
= -1;
282 if (i
== src_idx
|| verify_vc_allocation(i
) < 0)
285 /* try to open terminal */
286 xsprintf(ttyname
, "/dev/tty%u", i
);
287 fd_d
= open_terminal(ttyname
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
289 log_warning_errno(fd_d
, "Unable to open tty%u, fonts will not be copied: %m", i
);
293 if (verify_vc_kbmode(fd_d
) < 0)
296 (void) toggle_utf8_vc(ttyname
, fd_d
, utf8
);
298 if (cfo
.op
!= KD_FONT_OP_SET
)
301 r
= ioctl(fd_d
, KDFONTOP
, &cfo
);
303 int last_errno
, mode
;
305 /* The fonts couldn't have been copied. It might be due to the
306 * terminal being in graphical mode. In this case the kernel
307 * returns -EINVAL which is too generic for distinguishing this
308 * specific case. So we need to retrieve the terminal mode and if
309 * the graphical mode is in used, let's assume that something else
310 * is using the terminal and the failure was expected as we
311 * shouldn't have tried to copy the fonts. */
314 if (ioctl(fd_d
, KDGETMODE
, &mode
) >= 0 && mode
!= KD_TEXT
)
315 log_debug("KD_FONT_OP_SET skipped: tty%u is not in text mode", i
);
317 log_warning_errno(last_errno
, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i
);
323 * copy unicode translation table unimapd is a ushort count and a pointer
324 * to an array of struct unipair { ushort, ushort }
326 r
= ioctl(fd_d
, PIO_UNIMAPCLR
, &adv
);
328 log_warning_errno(errno
, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i
);
332 r
= ioctl(fd_d
, PIO_UNIMAP
, &unimapd
);
334 log_warning_errno(errno
, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i
);
338 log_debug("Font and unimap successfully copied to %s", ttyname
);
342 static int find_source_vc(char **ret_path
, unsigned *ret_idx
) {
343 _cleanup_free_
char *path
= NULL
;
347 path
= new(char, sizeof("/dev/tty63"));
351 for (i
= 1; i
<= 63; i
++) {
352 _cleanup_close_
int fd
= -1;
354 r
= verify_vc_allocation(i
);
361 sprintf(path
, "/dev/tty%u", i
);
362 fd
= open_terminal(path
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
368 r
= verify_vc_kbmode(fd
);
375 /* all checks passed, return this one as a source console */
377 *ret_path
= TAKE_PTR(path
);
381 return log_error_errno(err
, "No usable source console found: %m");
384 static int verify_source_vc(char **ret_path
, const char *src_vc
) {
385 _cleanup_close_
int fd
= -1;
389 fd
= open_terminal(src_vc
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
391 return log_error_errno(fd
, "Failed to open %s: %m", src_vc
);
393 r
= verify_vc_device(fd
);
395 return log_error_errno(r
, "Device %s is not a virtual console: %m", src_vc
);
397 r
= verify_vc_allocation_byfd(fd
);
399 return log_error_errno(r
, "Virtual console %s is not allocated: %m", src_vc
);
401 r
= verify_vc_kbmode(fd
);
403 return log_error_errno(r
, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc
);
405 path
= strdup(src_vc
);
413 int main(int argc
, char **argv
) {
416 *vc_keymap
= NULL
, *vc_keymap_toggle
= NULL
,
417 *vc_font
= NULL
, *vc_font_map
= NULL
, *vc_font_unimap
= NULL
;
418 _cleanup_close_
int fd
= -1;
419 bool utf8
, keyboard_ok
;
428 fd
= verify_source_vc(&vc
, argv
[1]);
430 fd
= find_source_vc(&vc
, &idx
);
434 utf8
= is_locale_utf8();
436 r
= parse_env_file(NULL
, "/etc/vconsole.conf",
437 "KEYMAP", &vc_keymap
,
438 "KEYMAP_TOGGLE", &vc_keymap_toggle
,
440 "FONT_MAP", &vc_font_map
,
441 "FONT_UNIMAP", &vc_font_unimap
);
442 if (r
< 0 && r
!= -ENOENT
)
443 log_warning_errno(r
, "Failed to read /etc/vconsole.conf: %m");
445 /* Let the kernel command line override /etc/vconsole.conf */
446 r
= proc_cmdline_get_key_many(
447 PROC_CMDLINE_STRIP_RD_PREFIX
,
448 "vconsole.keymap", &vc_keymap
,
449 "vconsole.keymap_toggle", &vc_keymap_toggle
,
450 "vconsole.font", &vc_font
,
451 "vconsole.font_map", &vc_font_map
,
452 "vconsole.font_unimap", &vc_font_unimap
,
453 /* compatibility with obsolete multiple-dot scheme */
454 "vconsole.keymap.toggle", &vc_keymap_toggle
,
455 "vconsole.font.map", &vc_font_map
,
456 "vconsole.font.unimap", &vc_font_unimap
);
457 if (r
< 0 && r
!= -ENOENT
)
458 log_warning_errno(r
, "Failed to read /proc/cmdline: %m");
460 (void) toggle_utf8_sysfs(utf8
);
461 (void) toggle_utf8_vc(vc
, fd
, utf8
);
463 r
= font_load_and_wait(vc
, vc_font
, vc_font_map
, vc_font_unimap
);
464 keyboard_ok
= keyboard_load_and_wait(vc
, vc_keymap
, vc_keymap_toggle
, utf8
) == 0;
468 setup_remaining_vcs(fd
, idx
, utf8
);
469 else if (r
== EX_OSERR
)
470 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails.
471 * This might mean various things, but in particular lack of a graphical
472 * console. Let's be generous and not treat this as an error. */
473 log_notice("Setting fonts failed with a \"system error\", ignoring.");
475 log_warning("Setting source virtual console failed, ignoring remaining ones");
478 return IN_SET(r
, 0, EX_OSERR
) && keyboard_ok
? EXIT_SUCCESS
: EXIT_FAILURE
;