]>
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. | |
78 | * Oterwise we would (likely) interfere with X11's processing of the | |
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 | ||
042d7f50 MS |
86 | static int toggle_utf8(int fd, bool utf8) { |
87 | int r; | |
88 | struct termios tc = {}; | |
97c4a07d | 89 | |
042d7f50 | 90 | r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE); |
97c4a07d | 91 | if (r < 0) |
042d7f50 | 92 | return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode: %m", utf8 ? "enable" : "disable"); |
97c4a07d | 93 | |
042d7f50 MS |
94 | r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false); |
95 | if (r < 0) | |
96 | return log_warning_errno(r, "Failed to %s UTF-8 term processing: %m", utf8 ? "enable" : "disable"); | |
97 | ||
98 | r = tcgetattr(fd, &tc); | |
99 | if (r >= 0) { | |
100 | if (utf8) | |
101 | tc.c_iflag |= IUTF8; | |
102 | else | |
103 | tc.c_iflag &= ~IUTF8; | |
104 | r = tcsetattr(fd, TCSANOW, &tc); | |
a25d4d0e | 105 | } |
042d7f50 MS |
106 | if (r < 0) |
107 | return log_warning_errno(errno, "Failed to %s iutf8 flag: %m", utf8 ? "enable" : "disable"); | |
d305a67b | 108 | |
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) |
042d7f50 | 117 | log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", utf8 ? "enable" : "disable"); |
d305a67b TG |
118 | return r; |
119 | } | |
120 | ||
aecb6fcb | 121 | static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) { |
5b396b06 | 122 | const char *args[8]; |
c9d2b3d0 | 123 | int i = 0; |
97c4a07d LP |
124 | pid_t pid; |
125 | ||
8931278c LDM |
126 | /* An empty map means kernel map */ |
127 | if (isempty(map)) | |
c9d2b3d0 | 128 | return 0; |
944d4c91 | 129 | |
9841e8e3 | 130 | args[i++] = KBD_LOADKEYS; |
97c4a07d LP |
131 | args[i++] = "-q"; |
132 | args[i++] = "-C"; | |
133 | args[i++] = vc; | |
134 | if (utf8) | |
135 | args[i++] = "-u"; | |
136 | args[i++] = map; | |
5b396b06 AB |
137 | if (map_toggle) |
138 | args[i++] = map_toggle; | |
97c4a07d LP |
139 | args[i++] = NULL; |
140 | ||
741f8cf6 | 141 | pid = fork(); |
aecb6fcb LP |
142 | if (pid < 0) |
143 | return log_error_errno(errno, "Failed to fork: %m"); | |
144 | else if (pid == 0) { | |
ce30c8dc LP |
145 | |
146 | (void) reset_all_signal_handlers(); | |
147 | (void) reset_signal_mask(); | |
148 | ||
97c4a07d LP |
149 | execv(args[0], (char **) args); |
150 | _exit(EXIT_FAILURE); | |
151 | } | |
152 | ||
c9d2b3d0 | 153 | return wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); |
97c4a07d LP |
154 | } |
155 | ||
aecb6fcb | 156 | static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) { |
97c4a07d | 157 | const char *args[9]; |
c9d2b3d0 | 158 | int i = 0; |
97c4a07d LP |
159 | pid_t pid; |
160 | ||
c9d2b3d0 MS |
161 | /* Any part can be set independently */ |
162 | if (isempty(font) && isempty(map) && isempty(unimap)) | |
163 | return 0; | |
944d4c91 | 164 | |
9841e8e3 | 165 | args[i++] = KBD_SETFONT; |
97c4a07d LP |
166 | args[i++] = "-C"; |
167 | args[i++] = vc; | |
c9d2b3d0 | 168 | if (!isempty(map)) { |
97c4a07d LP |
169 | args[i++] = "-m"; |
170 | args[i++] = map; | |
171 | } | |
c9d2b3d0 | 172 | if (!isempty(unimap)) { |
97c4a07d LP |
173 | args[i++] = "-u"; |
174 | args[i++] = unimap; | |
175 | } | |
c9d2b3d0 MS |
176 | if (!isempty(font)) |
177 | args[i++] = font; | |
97c4a07d LP |
178 | args[i++] = NULL; |
179 | ||
741f8cf6 | 180 | pid = fork(); |
aecb6fcb LP |
181 | if (pid < 0) |
182 | return log_error_errno(errno, "Failed to fork: %m"); | |
183 | else if (pid == 0) { | |
ce30c8dc LP |
184 | |
185 | (void) reset_all_signal_handlers(); | |
186 | (void) reset_signal_mask(); | |
187 | ||
97c4a07d LP |
188 | execv(args[0], (char **) args); |
189 | _exit(EXIT_FAILURE); | |
190 | } | |
191 | ||
c9d2b3d0 | 192 | return wait_for_terminate_and_warn(KBD_SETFONT, pid, true); |
97c4a07d LP |
193 | } |
194 | ||
d3b37e84 KS |
195 | /* |
196 | * A newly allocated VT uses the font from the active VT. Here | |
197 | * we update all possibly already allocated VTs with the configured | |
198 | * font. It also allows to restart systemd-vconsole-setup.service, | |
199 | * to apply a new font to all VTs. | |
eb22d84b MS |
200 | * |
201 | * We also setup per-console utf8 related stuff: kbdmode, term | |
202 | * processing, stty iutf8. | |
d3b37e84 | 203 | */ |
eb22d84b MS |
204 | static void setup_remaining_vcs(int fd, bool utf8) { |
205 | struct console_font_op cfo = { | |
206 | .op = KD_FONT_OP_GET, .flags = 0, | |
207 | .width = 32, .height = 32, | |
208 | .charcount = 512, | |
209 | }; | |
b92bea5d | 210 | struct vt_stat vcs = {}; |
eb22d84b | 211 | struct unimapinit adv = {}; |
ff452e76 | 212 | struct unimapdesc unimapd; |
6a08e1b0 | 213 | _cleanup_free_ struct unipair* unipairs = NULL; |
eb22d84b | 214 | _cleanup_free_ void *fontbuf = NULL; |
b92bea5d | 215 | int i, r; |
dd04aac9 | 216 | |
6a08e1b0 | 217 | unipairs = new(struct unipair, USHRT_MAX); |
e2c9192a LP |
218 | if (!unipairs) { |
219 | log_oom(); | |
6a08e1b0 KR |
220 | return; |
221 | } | |
222 | ||
eb22d84b MS |
223 | fontbuf = malloc(cfo.width * cfo.height * cfo.charcount / 8); |
224 | if (!fontbuf) { | |
225 | log_oom(); | |
226 | return; | |
227 | } | |
228 | ||
d3b37e84 | 229 | /* get active, and 16 bit mask of used VT numbers */ |
d3b37e84 | 230 | r = ioctl(fd, VT_GETSTATE, &vcs); |
ab51b943 | 231 | if (r < 0) { |
eb22d84b | 232 | log_warning_errno(errno, "VT_GETSTATE failed, ignoring remaining consoles: %m"); |
dd04aac9 | 233 | return; |
ab51b943 | 234 | } |
dd04aac9 | 235 | |
eb22d84b MS |
236 | /* get fonts from source console */ |
237 | cfo.data = fontbuf; | |
238 | r = ioctl(fd, KDFONTOP, &cfo); | |
239 | if (r < 0) | |
240 | log_warning_errno(errno, "KD_FONT_OP_GET failed, fonts will not be copied: %m"); | |
241 | else { | |
242 | unimapd.entries = unipairs; | |
243 | unimapd.entry_ct = USHRT_MAX; | |
244 | r = ioctl(fd, GIO_UNIMAP, &unimapd); | |
245 | if (r < 0) | |
246 | log_warning_errno(errno, "GIO_UNIMAP failed, fonts will not be copied: %m"); | |
247 | else | |
248 | cfo.op = KD_FONT_OP_SET; | |
249 | } | |
250 | ||
9fa71843 | 251 | for (i = 1; i <= 63; i++) { |
eb22d84b MS |
252 | char ttyname[strlen("/dev/tty") + DECIMAL_STR_MAX(int)]; |
253 | _cleanup_close_ int fd_d = -1; | |
dd04aac9 | 254 | |
eb22d84b | 255 | if (i == vcs.v_active || !is_allocated(i)) |
dd04aac9 KS |
256 | continue; |
257 | ||
eb22d84b MS |
258 | /* try to open terminal */ |
259 | xsprintf(ttyname, "/dev/tty%i", i); | |
260 | fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC); | |
261 | if (fd_d < 0) { | |
262 | log_warning_errno(fd_d, "Unable to open tty%i, fonts will not be copied: %m", i); | |
dd04aac9 | 263 | continue; |
eb22d84b | 264 | } |
dd04aac9 | 265 | |
eb22d84b | 266 | if (!is_settable(fd_d)) |
dd04aac9 KS |
267 | continue; |
268 | ||
eb22d84b MS |
269 | toggle_utf8(fd_d, utf8); |
270 | ||
271 | if (cfo.op != KD_FONT_OP_SET) | |
272 | continue; | |
273 | ||
274 | r = ioctl(fd_d, KDFONTOP, &cfo); | |
275 | if (r < 0) { | |
276 | log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%i: %m", i); | |
277 | continue; | |
278 | } | |
ff452e76 | 279 | |
ff452e76 CS |
280 | /* copy unicode translation table */ |
281 | /* unimapd is a ushort count and a pointer to an | |
282 | array of struct unipair { ushort, ushort } */ | |
eb22d84b MS |
283 | r = ioctl(fd_d, PIO_UNIMAPCLR, &adv); |
284 | if (r < 0) | |
285 | log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%i: %m", i); | |
286 | else { | |
287 | r = ioctl(fd_d, PIO_UNIMAP, &unimapd); | |
288 | if (r < 0) | |
289 | log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%i: %m", i); | |
ff452e76 | 290 | } |
dd04aac9 KS |
291 | } |
292 | } | |
293 | ||
97c4a07d LP |
294 | int main(int argc, char **argv) { |
295 | const char *vc; | |
abee28c5 ZJS |
296 | _cleanup_free_ char |
297 | *vc_keymap = NULL, *vc_keymap_toggle = NULL, | |
298 | *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL; | |
299 | _cleanup_close_ int fd = -1; | |
ab51b943 | 300 | bool utf8, font_copy = false, font_ok, keyboard_ok; |
dd04aac9 | 301 | int r = EXIT_FAILURE; |
97c4a07d | 302 | |
944d4c91 | 303 | log_set_target(LOG_TARGET_AUTO); |
97c4a07d LP |
304 | log_parse_environment(); |
305 | log_open(); | |
306 | ||
4c12626c LP |
307 | umask(0022); |
308 | ||
97c4a07d LP |
309 | if (argv[1]) |
310 | vc = argv[1]; | |
dd04aac9 | 311 | else { |
d3b37e84 KS |
312 | vc = "/dev/tty0"; |
313 | font_copy = true; | |
dd04aac9 | 314 | } |
97c4a07d | 315 | |
741f8cf6 LP |
316 | fd = open_terminal(vc, O_RDWR|O_CLOEXEC); |
317 | if (fd < 0) { | |
709f6e46 | 318 | log_error_errno(fd, "Failed to open %s: %m", vc); |
abee28c5 | 319 | return EXIT_FAILURE; |
97c4a07d LP |
320 | } |
321 | ||
653ab83b | 322 | if (!is_vconsole(fd)) { |
97c4a07d | 323 | log_error("Device %s is not a virtual console.", vc); |
abee28c5 | 324 | return EXIT_FAILURE; |
97c4a07d LP |
325 | } |
326 | ||
03044059 MS |
327 | if (!is_allocated_byfd(fd)) { |
328 | log_error("Virtual console %s is not allocated.", vc); | |
329 | return EXIT_FAILURE; | |
330 | } | |
331 | ||
332 | if (!is_settable(fd)) { | |
333 | log_error("Virtual console %s is not in K_XLATE or K_UNICODE.", vc); | |
334 | return EXIT_FAILURE; | |
335 | } | |
336 | ||
653ab83b | 337 | utf8 = is_locale_utf8(); |
97c4a07d | 338 | |
2034ec42 MS |
339 | r = parse_env_file("/etc/vconsole.conf", NEWLINE, |
340 | "KEYMAP", &vc_keymap, | |
341 | "KEYMAP_TOGGLE", &vc_keymap_toggle, | |
342 | "FONT", &vc_font, | |
343 | "FONT_MAP", &vc_font_map, | |
344 | "FONT_UNIMAP", &vc_font_unimap, | |
345 | NULL); | |
346 | ||
347 | if (r < 0 && r != -ENOENT) | |
da927ba9 | 348 | log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m"); |
2034ec42 MS |
349 | |
350 | /* Let the kernel command line override /etc/vconsole.conf */ | |
75f86906 | 351 | if (detect_container() <= 0) { |
741f8cf6 LP |
352 | r = parse_env_file("/proc/cmdline", WHITESPACE, |
353 | "vconsole.keymap", &vc_keymap, | |
9e303250 | 354 | "vconsole.keymap_toggle", &vc_keymap_toggle, |
741f8cf6 | 355 | "vconsole.font", &vc_font, |
9e303250 MS |
356 | "vconsole.font_map", &vc_font_map, |
357 | "vconsole.font_unimap", &vc_font_unimap, | |
358 | /* compatibility with obsolete multiple-dot scheme */ | |
359 | "vconsole.keymap.toggle", &vc_keymap_toggle, | |
741f8cf6 LP |
360 | "vconsole.font.map", &vc_font_map, |
361 | "vconsole.font.unimap", &vc_font_unimap, | |
362 | NULL); | |
363 | ||
364 | if (r < 0 && r != -ENOENT) | |
da927ba9 | 365 | log_warning_errno(r, "Failed to read /proc/cmdline: %m"); |
741f8cf6 | 366 | } |
1ebdf5b6 | 367 | |
042d7f50 MS |
368 | toggle_utf8_sysfs(utf8); |
369 | toggle_utf8(fd, utf8); | |
c9d2b3d0 MS |
370 | font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) == 0; |
371 | keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0; | |
97c4a07d | 372 | |
eb22d84b MS |
373 | if (font_copy) { |
374 | if (font_ok) | |
375 | setup_remaining_vcs(fd, utf8); | |
376 | else | |
377 | log_warning("Setting source virtual console failed, ignoring remaining ones."); | |
378 | } | |
97c4a07d | 379 | |
8931278c | 380 | return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; |
97c4a07d | 381 | } |