]>
Commit | Line | Data |
---|---|---|
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 | 48 | static 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 |
55 | static 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 | ||
62 | static 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 | ||
72 | static 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 | 86 | static 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 |
112 | static 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 | 123 | static 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 | 158 | static 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 |
206 | static 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 |
321 | int 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 | } |