chiark / gitweb /
679dc1aafb77cba7e532a22ee476c3ec5affd261
[elogind.git] / src / vconsole / vconsole-setup.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Kay Sievers
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <fcntl.h>
28 #include <ctype.h>
29 #include <stdbool.h>
30 #include <stdarg.h>
31 #include <limits.h>
32 #include <sys/ioctl.h>
33 #include <sys/wait.h>
34 #include <linux/tiocl.h>
35 #include <linux/kd.h>
36 #include <linux/vt.h>
37
38 #include "util.h"
39 #include "log.h"
40 #include "macro.h"
41 #include "virt.h"
42
43 static bool is_vconsole(int fd) {
44         unsigned char data[1];
45
46         data[0] = TIOCL_GETFGCONSOLE;
47         return ioctl(fd, TIOCLINUX, data) >= 0;
48 }
49
50 static int disable_utf8(int fd) {
51         int r = 0, k;
52
53         if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
54                 r = -errno;
55
56         if (loop_write(fd, "\033%@", 3, false) < 0)
57                 r = -errno;
58
59         k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "0");
60         if (k < 0)
61                 r = k;
62
63         if (r < 0)
64                 log_warning("Failed to disable UTF-8: %s", strerror(-r));
65
66         return r;
67 }
68
69 static int enable_utf8(int fd) {
70         int r = 0, k;
71
72         if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0)
73                 r = -errno;
74
75         if (loop_write(fd, "\033%G", 3, false) < 0)
76                 r = -errno;
77
78         k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "1");
79         if (k < 0)
80                 r = k;
81
82         if (r < 0)
83                 log_warning("Failed to enable UTF-8: %s", strerror(-r));
84
85         return r;
86 }
87
88 static int keymap_load(const char *vc, const char *map, const char *map_toggle, bool utf8, pid_t *_pid) {
89         const char *args[8];
90         int i = 0;
91         pid_t pid;
92
93         if (isempty(map)) {
94                 /* An empty map means kernel map */
95                 *_pid = 0;
96                 return 0;
97         }
98
99         args[i++] = KBD_LOADKEYS;
100         args[i++] = "-q";
101         args[i++] = "-C";
102         args[i++] = vc;
103         if (utf8)
104                 args[i++] = "-u";
105         args[i++] = map;
106         if (map_toggle)
107                 args[i++] = map_toggle;
108         args[i++] = NULL;
109
110         pid = fork();
111         if (pid < 0) {
112                 log_error("Failed to fork: %m");
113                 return -errno;
114         } else if (pid == 0) {
115                 execv(args[0], (char **) args);
116                 _exit(EXIT_FAILURE);
117         }
118
119         *_pid = pid;
120         return 0;
121 }
122
123 static int font_load(const char *vc, const char *font, const char *map, const char *unimap, pid_t *_pid) {
124         const char *args[9];
125         int i = 0;
126         pid_t pid;
127
128         if (isempty(font)) {
129                 /* An empty font means kernel font */
130                 *_pid = 0;
131                 return 0;
132         }
133
134         args[i++] = KBD_SETFONT;
135         args[i++] = "-C";
136         args[i++] = vc;
137         args[i++] = font;
138         if (map) {
139                 args[i++] = "-m";
140                 args[i++] = map;
141         }
142         if (unimap) {
143                 args[i++] = "-u";
144                 args[i++] = unimap;
145         }
146         args[i++] = NULL;
147
148         pid = fork();
149         if (pid < 0) {
150                 log_error("Failed to fork: %m");
151                 return -errno;
152         } else if (pid == 0) {
153                 execv(args[0], (char **) args);
154                 _exit(EXIT_FAILURE);
155         }
156
157         *_pid = pid;
158         return 0;
159 }
160
161 /*
162  * A newly allocated VT uses the font from the active VT. Here
163  * we update all possibly already allocated VTs with the configured
164  * font. It also allows to restart systemd-vconsole-setup.service,
165  * to apply a new font to all VTs.
166  */
167 static void font_copy_to_all_vcs(int fd) {
168         struct vt_stat vcs;
169         int i;
170         int r;
171
172         /* get active, and 16 bit mask of used VT numbers */
173         zero(vcs);
174         r = ioctl(fd, VT_GETSTATE, &vcs);
175         if (r < 0)
176                 return;
177
178         for (i = 1; i <= 15; i++) {
179                 char vcname[16];
180                 int vcfd;
181                 struct console_font_op cfo;
182
183                 if (i == vcs.v_active)
184                         continue;
185
186                 /* skip unused VTs above tty6 to avoid allocating them */
187                 if (i > 6 && ((vcs.v_state >> i) & 1) == 0)
188                         continue;
189
190                 snprintf(vcname , sizeof(vcname), "/dev/tty%i", i);
191                 vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
192                 if (vcfd < 0)
193                         continue;
194
195                 /* copy font from active VT, where the font was uploaded to */
196                 zero(cfo);
197                 cfo.op = KD_FONT_OP_COPY;
198                 cfo.height = vcs.v_active-1; /* tty1 == index 0 */
199                 ioctl(vcfd, KDFONTOP, &cfo);
200
201                 close_nointr_nofail(vcfd);
202         }
203 }
204
205 int main(int argc, char **argv) {
206         const char *vc;
207         char *vc_keymap = NULL;
208         char *vc_keymap_toggle = NULL;
209         char *vc_font = NULL;
210         char *vc_font_map = NULL;
211         char *vc_font_unimap = NULL;
212         int fd = -1;
213         bool utf8;
214         pid_t font_pid = 0, keymap_pid = 0;
215         bool font_copy = false;
216         int r = EXIT_FAILURE;
217
218         log_set_target(LOG_TARGET_AUTO);
219         log_parse_environment();
220         log_open();
221
222         umask(0022);
223
224         if (argv[1])
225                 vc = argv[1];
226         else {
227                 vc = "/dev/tty0";
228                 font_copy = true;
229         }
230
231         fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
232         if (fd < 0) {
233                 log_error("Failed to open %s: %m", vc);
234                 goto finish;
235         }
236
237         if (!is_vconsole(fd)) {
238                 log_error("Device %s is not a virtual console.", vc);
239                 goto finish;
240         }
241
242         utf8 = is_locale_utf8();
243
244         r = 0;
245
246         if (detect_container(NULL) <= 0) {
247                 r = parse_env_file("/proc/cmdline", WHITESPACE,
248                                    "vconsole.keymap", &vc_keymap,
249                                    "vconsole.keymap.toggle", &vc_keymap_toggle,
250                                    "vconsole.font", &vc_font,
251                                    "vconsole.font.map", &vc_font_map,
252                                    "vconsole.font.unimap", &vc_font_unimap,
253                                    NULL);
254
255                 if (r < 0 && r != -ENOENT)
256                         log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
257         }
258
259         /* Hmm, nothing set on the kernel cmd line? Then let's
260          * try /etc/vconsole.conf */
261         if (r <= 0) {
262                 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
263                                    "KEYMAP", &vc_keymap,
264                                    "KEYMAP_TOGGLE", &vc_keymap_toggle,
265                                    "FONT", &vc_font,
266                                    "FONT_MAP", &vc_font_map,
267                                    "FONT_UNIMAP", &vc_font_unimap,
268                                    NULL);
269
270                 if (r < 0 && r != -ENOENT)
271                         log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
272         }
273
274         if (utf8)
275                 enable_utf8(fd);
276         else
277                 disable_utf8(fd);
278
279         r = EXIT_FAILURE;
280         if (keymap_load(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
281             font_load(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
282                 r = EXIT_SUCCESS;
283
284 finish:
285         if (keymap_pid > 0)
286                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
287
288         if (font_pid > 0) {
289                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
290                 if (font_copy)
291                         font_copy_to_all_vcs(fd);
292         }
293
294         free(vc_keymap);
295         free(vc_font);
296         free(vc_font_map);
297         free(vc_font_unimap);
298
299         if (fd >= 0)
300                 close_nointr_nofail(fd);
301
302         return r;
303 }