chiark / gitweb /
d8cdb7c609bf810fd2974511b0d15525b7073893
[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 static void font_copy_to_all_vts(int fd, int from_vt) {
162         struct vt_stat vts;
163         unsigned short bits;
164         int i;
165         int r;
166
167         /* get 16 bit mask of used VT numbers */
168         zero(vts);
169         r = ioctl(fd, VT_GETSTATE, &vts);
170         if (r < 0)
171                 return;
172
173         bits = vts.v_state;
174         for (i = 1; i <= 16; i++) {
175                 char vtname[16];
176                 int vtfd;
177                 struct console_font_op cfo;
178                 bool used;
179
180                 /* skip unused VTs */
181                 used = bits & 1;
182                 bits >>= 1;
183                 if (!used)
184                         continue;
185
186                 if (i == from_vt)
187                         continue;
188
189                 snprintf(vtname , sizeof(vtname), "/dev/tty%i", i);
190                 vtfd = open_terminal(vtname, O_RDWR|O_CLOEXEC);
191                 if (vtfd < 0)
192                         continue;
193
194                 /* copy font from from_vt to this VT */
195                 zero(cfo);
196                 cfo.op = KD_FONT_OP_COPY;
197                 /* the index numbers seem to start at 0 for tty1 */
198                 cfo.height = from_vt - 1;
199                 ioctl(vtfd, KDFONTOP, &cfo);
200
201                 close_nointr_nofail(vtfd);
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         int font_copy_from_vt = 0;
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/tty1";
228                 font_copy_from_vt = 1;
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_from_vt > 0)
291                         font_copy_to_all_vts(fd, font_copy_from_vt);
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 }