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>
20 #include "alloc-util.h"
25 #include "locale-util.h"
27 #include "proc-cmdline.h"
28 #include "process-util.h"
29 #include "signal-util.h"
30 #include "stdio-util.h"
31 #include "string-util.h"
33 #include "terminal-util.h"
37 static int verify_vc_device(int fd
) {
38 unsigned char data
[] = {
44 r
= ioctl(fd
, TIOCLINUX
, data
);
51 static int verify_vc_allocation(unsigned idx
) {
52 char vcname
[sizeof("/dev/vcs") + DECIMAL_STR_MAX(unsigned) - 2];
54 xsprintf(vcname
, "/dev/vcs%u", idx
);
56 if (access(vcname
, F_OK
) < 0)
62 static int verify_vc_allocation_byfd(int fd
) {
63 struct vt_stat vcs
= {};
65 if (ioctl(fd
, VT_GETSTATE
, &vcs
) < 0)
68 return verify_vc_allocation(vcs
.v_active
);
71 static int verify_vc_kbmode(int fd
) {
75 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
76 * Otherwise we would (likely) interfere with X11's processing of the
79 * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
82 if (ioctl(fd
, KDGKBMODE
, &curr_mode
) < 0)
85 return IN_SET(curr_mode
, K_XLATE
, K_UNICODE
) ? 0 : -EBUSY
;
88 static int toggle_utf8(const char *name
, int fd
, bool utf8
) {
90 struct termios tc
= {};
94 r
= ioctl(fd
, KDSKBMODE
, utf8
? K_UNICODE
: K_XLATE
);
96 return log_warning_errno(errno
, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8
), name
);
98 r
= loop_write(fd
, utf8
? "\033%G" : "\033%@", 3, false);
100 return log_warning_errno(r
, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8
), name
);
102 r
= tcgetattr(fd
, &tc
);
104 SET_FLAG(tc
.c_iflag
, IUTF8
, utf8
);
105 r
= tcsetattr(fd
, TCSANOW
, &tc
);
108 return log_warning_errno(errno
, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8
), name
);
110 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8
), name
);
114 static int toggle_utf8_sysfs(bool utf8
) {
117 r
= write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8
), WRITE_STRING_FILE_DISABLE_BUFFER
);
119 return log_warning_errno(r
, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8
));
121 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8
));
125 static int keyboard_load_and_wait(const char *vc
, const char *map
, const char *map_toggle
, bool utf8
) {
131 /* An empty map means kernel map */
135 args
[i
++] = KBD_LOADKEYS
;
143 args
[i
++] = map_toggle
;
147 _cleanup_free_
char *cmd
;
149 cmd
= strv_join((char**) args
, " ");
150 log_debug("Executing \"%s\"...", strnull(cmd
));
153 r
= safe_fork("(loadkeys)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
157 execv(args
[0], (char **) args
);
161 return wait_for_terminate_and_check(KBD_LOADKEYS
, pid
, WAIT_LOG
);
164 static int font_load_and_wait(const char *vc
, const char *font
, const char *map
, const char *unimap
) {
170 /* Any part can be set independently */
171 if (isempty(font
) && isempty(map
) && isempty(unimap
))
174 args
[i
++] = KBD_SETFONT
;
181 if (!isempty(unimap
)) {
190 _cleanup_free_
char *cmd
;
192 cmd
= strv_join((char**) args
, " ");
193 log_debug("Executing \"%s\"...", strnull(cmd
));
196 r
= safe_fork("(setfont)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
200 execv(args
[0], (char **) args
);
204 return wait_for_terminate_and_check(KBD_SETFONT
, pid
, WAIT_LOG
);
208 * A newly allocated VT uses the font from the source VT. Here
209 * we update all possibly already allocated VTs with the configured
210 * font. It also allows to restart systemd-vconsole-setup.service,
211 * to apply a new font to all VTs.
213 * We also setup per-console utf8 related stuff: kbdmode, term
214 * processing, stty iutf8.
216 static void setup_remaining_vcs(int src_fd
, unsigned src_idx
, bool utf8
) {
217 struct console_font_op cfo
= {
218 .op
= KD_FONT_OP_GET
,
219 .width
= UINT_MAX
, .height
= UINT_MAX
,
220 .charcount
= UINT_MAX
,
222 struct unimapinit adv
= {};
223 struct unimapdesc unimapd
;
224 _cleanup_free_
struct unipair
* unipairs
= NULL
;
225 _cleanup_free_
void *fontbuf
= NULL
;
229 unipairs
= new(struct unipair
, USHRT_MAX
);
235 /* get metadata of the current font (width, height, count) */
236 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
238 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
240 /* verify parameter sanity first */
241 if (cfo
.width
> 32 || cfo
.height
> 32 || cfo
.charcount
> 512)
242 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
243 cfo
.width
, cfo
.height
, cfo
.charcount
);
246 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
247 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
248 * requries 32 per glyph, regardless of the actual height - see the comment above #define
249 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
251 fontbuf
= malloc_multiply((cfo
.width
+ 7) / 8 * 32, cfo
.charcount
);
256 /* get fonts from the source console */
258 r
= ioctl(src_fd
, KDFONTOP
, &cfo
);
260 log_warning_errno(errno
, "KD_FONT_OP_GET failed while trying to read the font data: %m");
262 unimapd
.entries
= unipairs
;
263 unimapd
.entry_ct
= USHRT_MAX
;
264 r
= ioctl(src_fd
, GIO_UNIMAP
, &unimapd
);
266 log_warning_errno(errno
, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
268 cfo
.op
= KD_FONT_OP_SET
;
273 if (cfo
.op
!= KD_FONT_OP_SET
)
274 log_warning("Fonts will not be copied to remaining consoles");
276 for (i
= 1; i
<= 63; i
++) {
277 char ttyname
[sizeof("/dev/tty63")];
278 _cleanup_close_
int fd_d
= -1;
280 if (i
== src_idx
|| verify_vc_allocation(i
) < 0)
283 /* try to open terminal */
284 xsprintf(ttyname
, "/dev/tty%u", i
);
285 fd_d
= open_terminal(ttyname
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
287 log_warning_errno(fd_d
, "Unable to open tty%u, fonts will not be copied: %m", i
);
291 if (verify_vc_kbmode(fd_d
) < 0)
294 toggle_utf8(ttyname
, fd_d
, utf8
);
296 if (cfo
.op
!= KD_FONT_OP_SET
)
299 r
= ioctl(fd_d
, KDFONTOP
, &cfo
);
301 int last_errno
, mode
;
303 /* The fonts couldn't have been copied. It might be due to the
304 * terminal being in graphical mode. In this case the kernel
305 * returns -EINVAL which is too generic for distinguishing this
306 * specific case. So we need to retrieve the terminal mode and if
307 * the graphical mode is in used, let's assume that something else
308 * is using the terminal and the failure was expected as we
309 * shouldn't have tried to copy the fonts. */
312 if (ioctl(fd_d
, KDGETMODE
, &mode
) >= 0 && mode
!= KD_TEXT
)
313 log_debug("KD_FONT_OP_SET skipped: tty%u is not in text mode", i
);
315 log_warning_errno(last_errno
, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i
);
321 * copy unicode translation table unimapd is a ushort count and a pointer
322 * to an array of struct unipair { ushort, ushort }
324 r
= ioctl(fd_d
, PIO_UNIMAPCLR
, &adv
);
326 log_warning_errno(errno
, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%u: %m", i
);
330 r
= ioctl(fd_d
, PIO_UNIMAP
, &unimapd
);
332 log_warning_errno(errno
, "PIO_UNIMAP failed, unimaps might be incorrect for tty%u: %m", i
);
336 log_debug("Font and unimap successfully copied to %s", ttyname
);
340 static int find_source_vc(char **ret_path
, unsigned *ret_idx
) {
341 _cleanup_free_
char *path
= NULL
;
345 path
= new(char, sizeof("/dev/tty63"));
349 for (i
= 1; i
<= 63; i
++) {
350 _cleanup_close_
int fd
= -1;
352 r
= verify_vc_allocation(i
);
359 sprintf(path
, "/dev/tty%u", i
);
360 fd
= open_terminal(path
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
366 r
= verify_vc_kbmode(fd
);
373 /* all checks passed, return this one as a source console */
375 *ret_path
= TAKE_PTR(path
);
379 return log_error_errno(err
, "No usable source console found: %m");
382 static int verify_source_vc(char **ret_path
, const char *src_vc
) {
383 _cleanup_close_
int fd
= -1;
387 fd
= open_terminal(src_vc
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
);
389 return log_error_errno(fd
, "Failed to open %s: %m", src_vc
);
391 r
= verify_vc_device(fd
);
393 return log_error_errno(r
, "Device %s is not a virtual console: %m", src_vc
);
395 r
= verify_vc_allocation_byfd(fd
);
397 return log_error_errno(r
, "Virtual console %s is not allocated: %m", src_vc
);
399 r
= verify_vc_kbmode(fd
);
401 return log_error_errno(r
, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc
);
403 path
= strdup(src_vc
);
411 int main(int argc
, char **argv
) {
414 *vc_keymap
= NULL
, *vc_keymap_toggle
= NULL
,
415 *vc_font
= NULL
, *vc_font_map
= NULL
, *vc_font_unimap
= NULL
;
416 _cleanup_close_
int fd
= -1;
417 bool utf8
, keyboard_ok
;
426 fd
= verify_source_vc(&vc
, argv
[1]);
428 fd
= find_source_vc(&vc
, &idx
);
432 utf8
= is_locale_utf8();
434 r
= parse_env_file(NULL
, "/etc/vconsole.conf",
435 "KEYMAP", &vc_keymap
,
436 "KEYMAP_TOGGLE", &vc_keymap_toggle
,
438 "FONT_MAP", &vc_font_map
,
439 "FONT_UNIMAP", &vc_font_unimap
);
440 if (r
< 0 && r
!= -ENOENT
)
441 log_warning_errno(r
, "Failed to read /etc/vconsole.conf: %m");
443 /* Let the kernel command line override /etc/vconsole.conf */
444 r
= proc_cmdline_get_key_many(
445 PROC_CMDLINE_STRIP_RD_PREFIX
,
446 "vconsole.keymap", &vc_keymap
,
447 "vconsole.keymap_toggle", &vc_keymap_toggle
,
448 "vconsole.font", &vc_font
,
449 "vconsole.font_map", &vc_font_map
,
450 "vconsole.font_unimap", &vc_font_unimap
,
451 /* compatibility with obsolete multiple-dot scheme */
452 "vconsole.keymap.toggle", &vc_keymap_toggle
,
453 "vconsole.font.map", &vc_font_map
,
454 "vconsole.font.unimap", &vc_font_unimap
);
455 if (r
< 0 && r
!= -ENOENT
)
456 log_warning_errno(r
, "Failed to read /proc/cmdline: %m");
458 (void) toggle_utf8_sysfs(utf8
);
459 (void) toggle_utf8(vc
, fd
, utf8
);
461 r
= font_load_and_wait(vc
, vc_font
, vc_font_map
, vc_font_unimap
);
462 keyboard_ok
= keyboard_load_and_wait(vc
, vc_keymap
, vc_keymap_toggle
, utf8
) == 0;
466 setup_remaining_vcs(fd
, idx
, utf8
);
467 else if (r
== EX_OSERR
)
468 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails.
469 * This might mean various things, but in particular lack of a graphical
470 * console. Let's be generous and not treat this as an error. */
471 log_notice("Setting fonts failed with a \"system error\", ignoring.");
473 log_warning("Setting source virtual console failed, ignoring remaining ones");
476 return IN_SET(r
, 0, EX_OSERR
) && keyboard_ok
? EXIT_SUCCESS
: EXIT_FAILURE
;