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