]>
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> | |
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 | 50 | static 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 |
57 | static 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 | ||
64 | static 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 | ||
74 | static 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 | 88 | static 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 |
114 | static 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 | 125 | static 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 | 164 | static 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 |
216 | static 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 |
331 | int 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 | } |