]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/vconsole/vconsole-setup.c
sd-boot: stub: Obtain PE section offsets from RAM, not disk (#6250)
[thirdparty/systemd.git] / src / vconsole / vconsole-setup.c
CommitLineData
97c4a07d
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2010 Kay Sievers
af7a5213 5 Copyright 2016 Michal Soltys <soltys@ziu.info>
97c4a07d
LP
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
97c4a07d
LP
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
97c4a07d 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
97c4a07d
LP
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
97c4a07d 21#include <errno.h>
97c4a07d 22#include <fcntl.h>
97c4a07d 23#include <limits.h>
97c4a07d 24#include <linux/kd.h>
07630cea 25#include <linux/tiocl.h>
dd04aac9 26#include <linux/vt.h>
07630cea
LP
27#include <stdbool.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <sys/ioctl.h>
93c9a9d2 31#include <sysexits.h>
042d7f50 32#include <termios.h>
07630cea 33#include <unistd.h>
97c4a07d 34
b5efdb8a 35#include "alloc-util.h"
3ffd4af2 36#include "fd-util.h"
a5c32cff 37#include "fileio.h"
c004493c 38#include "io-util.h"
8752c575 39#include "locale-util.h"
07630cea 40#include "log.h"
0b452006 41#include "process-util.h"
ce30c8dc 42#include "signal-util.h"
d054f0a4 43#include "stdio-util.h"
07630cea 44#include "string-util.h"
3d623780 45#include "strv.h"
07630cea
LP
46#include "terminal-util.h"
47#include "util.h"
48#include "virt.h"
97c4a07d 49
653ab83b 50static bool is_vconsole(int fd) {
97c4a07d
LP
51 unsigned char data[1];
52
53 data[0] = TIOCL_GETFGCONSOLE;
54 return ioctl(fd, TIOCLINUX, data) >= 0;
97c4a07d
LP
55}
56
03044059
MS
57static bool is_allocated(unsigned int idx) {
58 char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)];
59
60 xsprintf(vcname, "/dev/vcs%i", idx);
61 return access(vcname, F_OK) == 0;
62}
63
64static bool is_allocated_byfd(int fd) {
65 struct vt_stat vcs = {};
66
67 if (ioctl(fd, VT_GETSTATE, &vcs) < 0) {
68 log_warning_errno(errno, "VT_GETSTATE failed: %m");
69 return false;
70 }
71 return is_allocated(vcs.v_active);
72}
73
74static bool is_settable(int fd) {
75 int r, curr_mode;
76
77 r = ioctl(fd, KDGKBMODE, &curr_mode);
78 /*
79 * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
d23a0044 80 * Otherwise we would (likely) interfere with X11's processing of the
03044059
MS
81 * key events.
82 *
83 * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
84 */
85 return r == 0 && IN_SET(curr_mode, K_XLATE, K_UNICODE);
86}
87
aaa709bb 88static int toggle_utf8(const char *name, int fd, bool utf8) {
042d7f50
MS
89 int r;
90 struct termios tc = {};
97c4a07d 91
aaa709bb
ZJS
92 assert(name);
93
042d7f50 94 r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE);
97c4a07d 95 if (r < 0)
aaa709bb 96 return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name);
97c4a07d 97
042d7f50
MS
98 r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false);
99 if (r < 0)
aaa709bb 100 return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name);
042d7f50
MS
101
102 r = tcgetattr(fd, &tc);
103 if (r >= 0) {
ab8ee0f2 104 SET_FLAG(tc.c_iflag, IUTF8, utf8);
042d7f50 105 r = tcsetattr(fd, TCSANOW, &tc);
a25d4d0e 106 }
042d7f50 107 if (r < 0)
aaa709bb 108 return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name);
d305a67b 109
aaa709bb 110 log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name);
042d7f50
MS
111 return 0;
112}
d305a67b 113
042d7f50
MS
114static int toggle_utf8_sysfs(bool utf8) {
115 int r;
d305a67b 116
042d7f50 117 r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), 0);
d305a67b 118 if (r < 0)
aaa709bb
ZJS
119 return log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8));
120
121 log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8));
122 return 0;
d305a67b
TG
123}
124
aecb6fcb 125static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
3d623780 126 _cleanup_free_ char *cmd = NULL;
5b396b06 127 const char *args[8];
c9d2b3d0 128 int i = 0;
97c4a07d
LP
129 pid_t pid;
130
8931278c
LDM
131 /* An empty map means kernel map */
132 if (isempty(map))
c9d2b3d0 133 return 0;
944d4c91 134
9841e8e3 135 args[i++] = KBD_LOADKEYS;
97c4a07d
LP
136 args[i++] = "-q";
137 args[i++] = "-C";
138 args[i++] = vc;
139 if (utf8)
140 args[i++] = "-u";
141 args[i++] = map;
5b396b06
AB
142 if (map_toggle)
143 args[i++] = map_toggle;
97c4a07d
LP
144 args[i++] = NULL;
145
3d623780
ZJS
146 log_debug("Executing \"%s\"...",
147 strnull((cmd = strv_join((char**) args, " "))));
148
741f8cf6 149 pid = fork();
aecb6fcb
LP
150 if (pid < 0)
151 return log_error_errno(errno, "Failed to fork: %m");
152 else if (pid == 0) {
ce30c8dc
LP
153
154 (void) reset_all_signal_handlers();
155 (void) reset_signal_mask();
156
97c4a07d
LP
157 execv(args[0], (char **) args);
158 _exit(EXIT_FAILURE);
159 }
160
c9d2b3d0 161 return wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true);
97c4a07d
LP
162}
163
aecb6fcb 164static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
3d623780 165 _cleanup_free_ char *cmd = NULL;
97c4a07d 166 const char *args[9];
c9d2b3d0 167 int i = 0;
97c4a07d
LP
168 pid_t pid;
169
c9d2b3d0
MS
170 /* Any part can be set independently */
171 if (isempty(font) && isempty(map) && isempty(unimap))
172 return 0;
944d4c91 173
9841e8e3 174 args[i++] = KBD_SETFONT;
97c4a07d
LP
175 args[i++] = "-C";
176 args[i++] = vc;
c9d2b3d0 177 if (!isempty(map)) {
97c4a07d
LP
178 args[i++] = "-m";
179 args[i++] = map;
180 }
c9d2b3d0 181 if (!isempty(unimap)) {
97c4a07d
LP
182 args[i++] = "-u";
183 args[i++] = unimap;
184 }
c9d2b3d0
MS
185 if (!isempty(font))
186 args[i++] = font;
97c4a07d
LP
187 args[i++] = NULL;
188
3d623780
ZJS
189 log_debug("Executing \"%s\"...",
190 strnull((cmd = strv_join((char**) args, " "))));
191
741f8cf6 192 pid = fork();
aecb6fcb
LP
193 if (pid < 0)
194 return log_error_errno(errno, "Failed to fork: %m");
195 else if (pid == 0) {
ce30c8dc
LP
196
197 (void) reset_all_signal_handlers();
198 (void) reset_signal_mask();
199
97c4a07d
LP
200 execv(args[0], (char **) args);
201 _exit(EXIT_FAILURE);
202 }
203
c9d2b3d0 204 return wait_for_terminate_and_warn(KBD_SETFONT, pid, true);
97c4a07d
LP
205}
206
d3b37e84
KS
207/*
208 * A newly allocated VT uses the font from the active 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.
eb22d84b
MS
212 *
213 * We also setup per-console utf8 related stuff: kbdmode, term
214 * processing, stty iutf8.
d3b37e84 215 */
eb22d84b
MS
216static void setup_remaining_vcs(int fd, bool utf8) {
217 struct console_font_op cfo = {
5297577f
MS
218 .op = KD_FONT_OP_GET,
219 .width = UINT_MAX, .height = UINT_MAX,
220 .charcount = UINT_MAX,
eb22d84b 221 };
b92bea5d 222 struct vt_stat vcs = {};
eb22d84b 223 struct unimapinit adv = {};
ff452e76 224 struct unimapdesc unimapd;
6a08e1b0 225 _cleanup_free_ struct unipair* unipairs = NULL;
eb22d84b 226 _cleanup_free_ void *fontbuf = NULL;
b92bea5d 227 int i, r;
dd04aac9 228
6a08e1b0 229 unipairs = new(struct unipair, USHRT_MAX);
e2c9192a
LP
230 if (!unipairs) {
231 log_oom();
6a08e1b0
KR
232 return;
233 }
234
d3b37e84 235 /* get active, and 16 bit mask of used VT numbers */
d3b37e84 236 r = ioctl(fd, VT_GETSTATE, &vcs);
ab51b943 237 if (r < 0) {
eb22d84b 238 log_warning_errno(errno, "VT_GETSTATE failed, ignoring remaining consoles: %m");
dd04aac9 239 return;
ab51b943 240 }
dd04aac9 241
5297577f 242 /* get metadata of the current font (width, height, count) */
eb22d84b
MS
243 r = ioctl(fd, KDFONTOP, &cfo);
244 if (r < 0)
5297577f 245 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
eb22d84b 246 else {
a5eebcff 247 /* verify parameter sanity first */
5297577f
MS
248 if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512)
249 log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
a5eebcff 250 cfo.width, cfo.height, cfo.charcount);
5297577f
MS
251 else {
252 /*
253 * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512
254 * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always
255 * requries 32 per glyph, regardless of the actual height - see the comment above #define
256 * max_font_size 65536 in drivers/tty/vt/vt.c for more details.
257 */
258 fontbuf = malloc((cfo.width + 7) / 8 * 32 * cfo.charcount);
259 if (!fontbuf) {
260 log_oom();
261 return;
262 }
263 /* get fonts from source console */
264 cfo.data = fontbuf;
265 r = ioctl(fd, KDFONTOP, &cfo);
266 if (r < 0)
267 log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to read the font data: %m");
268 else {
269 unimapd.entries = unipairs;
270 unimapd.entry_ct = USHRT_MAX;
271 r = ioctl(fd, GIO_UNIMAP, &unimapd);
272 if (r < 0)
273 log_warning_errno(errno, "GIO_UNIMAP failed while trying to read unicode mappings: %m");
274 else
275 cfo.op = KD_FONT_OP_SET;
276 }
277 }
eb22d84b
MS
278 }
279
a5eebcff 280 if (cfo.op != KD_FONT_OP_SET)
5297577f 281 log_warning("Fonts will not be copied to remaining consoles");
eb22d84b 282
9fa71843 283 for (i = 1; i <= 63; i++) {
eb22d84b
MS
284 char ttyname[strlen("/dev/tty") + DECIMAL_STR_MAX(int)];
285 _cleanup_close_ int fd_d = -1;
dd04aac9 286
eb22d84b 287 if (i == vcs.v_active || !is_allocated(i))
dd04aac9
KS
288 continue;
289
eb22d84b
MS
290 /* try to open terminal */
291 xsprintf(ttyname, "/dev/tty%i", i);
292 fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC);
293 if (fd_d < 0) {
294 log_warning_errno(fd_d, "Unable to open tty%i, fonts will not be copied: %m", i);
dd04aac9 295 continue;
eb22d84b 296 }
dd04aac9 297
eb22d84b 298 if (!is_settable(fd_d))
dd04aac9
KS
299 continue;
300
aaa709bb 301 toggle_utf8(ttyname, fd_d, utf8);
eb22d84b
MS
302
303 if (cfo.op != KD_FONT_OP_SET)
304 continue;
305
306 r = ioctl(fd_d, KDFONTOP, &cfo);
307 if (r < 0) {
308 log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%i: %m", i);
309 continue;
310 }
ff452e76 311
ff452e76
CS
312 /* copy unicode translation table */
313 /* unimapd is a ushort count and a pointer to an
314 array of struct unipair { ushort, ushort } */
eb22d84b 315 r = ioctl(fd_d, PIO_UNIMAPCLR, &adv);
aaa709bb 316 if (r < 0) {
eb22d84b 317 log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%i: %m", i);
aaa709bb 318 continue;
ff452e76 319 }
aaa709bb
ZJS
320
321 r = ioctl(fd_d, PIO_UNIMAP, &unimapd);
322 if (r < 0) {
323 log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%i: %m", i);
324 continue;
325 }
326
327 log_debug("Font and unimap successfully copied to %s", ttyname);
dd04aac9
KS
328 }
329}
330
97c4a07d
LP
331int main(int argc, char **argv) {
332 const char *vc;
abee28c5
ZJS
333 _cleanup_free_ char
334 *vc_keymap = NULL, *vc_keymap_toggle = NULL,
335 *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
336 _cleanup_close_ int fd = -1;
93c9a9d2
ZJS
337 bool utf8, font_copy = false, keyboard_ok;
338 int r;
97c4a07d 339
944d4c91 340 log_set_target(LOG_TARGET_AUTO);
97c4a07d
LP
341 log_parse_environment();
342 log_open();
343
4c12626c
LP
344 umask(0022);
345
97c4a07d
LP
346 if (argv[1])
347 vc = argv[1];
dd04aac9 348 else {
d3b37e84
KS
349 vc = "/dev/tty0";
350 font_copy = true;
dd04aac9 351 }
97c4a07d 352
741f8cf6
LP
353 fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
354 if (fd < 0) {
709f6e46 355 log_error_errno(fd, "Failed to open %s: %m", vc);
abee28c5 356 return EXIT_FAILURE;
97c4a07d
LP
357 }
358
653ab83b 359 if (!is_vconsole(fd)) {
97c4a07d 360 log_error("Device %s is not a virtual console.", vc);
abee28c5 361 return EXIT_FAILURE;
97c4a07d
LP
362 }
363
03044059
MS
364 if (!is_allocated_byfd(fd)) {
365 log_error("Virtual console %s is not allocated.", vc);
366 return EXIT_FAILURE;
367 }
368
369 if (!is_settable(fd)) {
370 log_error("Virtual console %s is not in K_XLATE or K_UNICODE.", vc);
371 return EXIT_FAILURE;
372 }
373
653ab83b 374 utf8 = is_locale_utf8();
97c4a07d 375
2034ec42
MS
376 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
377 "KEYMAP", &vc_keymap,
378 "KEYMAP_TOGGLE", &vc_keymap_toggle,
379 "FONT", &vc_font,
380 "FONT_MAP", &vc_font_map,
381 "FONT_UNIMAP", &vc_font_unimap,
382 NULL);
2034ec42 383 if (r < 0 && r != -ENOENT)
da927ba9 384 log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m");
2034ec42
MS
385
386 /* Let the kernel command line override /etc/vconsole.conf */
75f86906 387 if (detect_container() <= 0) {
741f8cf6
LP
388 r = parse_env_file("/proc/cmdline", WHITESPACE,
389 "vconsole.keymap", &vc_keymap,
9e303250 390 "vconsole.keymap_toggle", &vc_keymap_toggle,
741f8cf6 391 "vconsole.font", &vc_font,
9e303250
MS
392 "vconsole.font_map", &vc_font_map,
393 "vconsole.font_unimap", &vc_font_unimap,
394 /* compatibility with obsolete multiple-dot scheme */
395 "vconsole.keymap.toggle", &vc_keymap_toggle,
741f8cf6
LP
396 "vconsole.font.map", &vc_font_map,
397 "vconsole.font.unimap", &vc_font_unimap,
398 NULL);
741f8cf6 399 if (r < 0 && r != -ENOENT)
da927ba9 400 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
741f8cf6 401 }
1ebdf5b6 402
042d7f50 403 toggle_utf8_sysfs(utf8);
aaa709bb 404 toggle_utf8(vc, fd, utf8);
93c9a9d2
ZJS
405
406 r = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap);
c9d2b3d0 407 keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
97c4a07d 408
eb22d84b 409 if (font_copy) {
93c9a9d2 410 if (r == 0)
eb22d84b 411 setup_remaining_vcs(fd, utf8);
93c9a9d2
ZJS
412 else if (r == EX_OSERR)
413 /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails.
414 * This might mean various things, but in particular lack of a graphical
415 * console. Let's be generous and not treat this as an error. */
416 log_notice("Setting fonts failed with a \"system error\", ignoring.");
eb22d84b 417 else
aaa709bb 418 log_warning("Setting source virtual console failed, ignoring remaining ones");
eb22d84b 419 }
97c4a07d 420
93c9a9d2 421 return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
97c4a07d 422}