chiark / gitweb /
udev: udevadm hwdb - add --root=
[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 non-allocated ttys */
187                 snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i);
188                 if (access(vcname, F_OK) < 0)
189                         continue;
190
191                 snprintf(vcname, sizeof(vcname), "/dev/tty%i", i);
192                 vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
193                 if (vcfd < 0)
194                         continue;
195
196                 /* copy font from active VT, where the font was uploaded to */
197                 zero(cfo);
198                 cfo.op = KD_FONT_OP_COPY;
199                 cfo.height = vcs.v_active-1; /* tty1 == index 0 */
200                 ioctl(vcfd, KDFONTOP, &cfo);
201
202                 close_nointr_nofail(vcfd);
203         }
204 }
205
206 int main(int argc, char **argv) {
207         const char *vc;
208         char *vc_keymap = NULL;
209         char *vc_keymap_toggle = NULL;
210         char *vc_font = NULL;
211         char *vc_font_map = NULL;
212         char *vc_font_unimap = NULL;
213         int fd = -1;
214         bool utf8;
215         pid_t font_pid = 0, keymap_pid = 0;
216         bool font_copy = false;
217         int r = EXIT_FAILURE;
218
219         log_set_target(LOG_TARGET_AUTO);
220         log_parse_environment();
221         log_open();
222
223         umask(0022);
224
225         if (argv[1])
226                 vc = argv[1];
227         else {
228                 vc = "/dev/tty0";
229                 font_copy = true;
230         }
231
232         fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
233         if (fd < 0) {
234                 log_error("Failed to open %s: %m", vc);
235                 goto finish;
236         }
237
238         if (!is_vconsole(fd)) {
239                 log_error("Device %s is not a virtual console.", vc);
240                 goto finish;
241         }
242
243         utf8 = is_locale_utf8();
244
245         r = 0;
246
247         if (detect_container(NULL) <= 0) {
248                 r = parse_env_file("/proc/cmdline", WHITESPACE,
249                                    "vconsole.keymap", &vc_keymap,
250                                    "vconsole.keymap.toggle", &vc_keymap_toggle,
251                                    "vconsole.font", &vc_font,
252                                    "vconsole.font.map", &vc_font_map,
253                                    "vconsole.font.unimap", &vc_font_unimap,
254                                    NULL);
255
256                 if (r < 0 && r != -ENOENT)
257                         log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
258         }
259
260         /* Hmm, nothing set on the kernel cmd line? Then let's
261          * try /etc/vconsole.conf */
262         if (r <= 0) {
263                 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
264                                    "KEYMAP", &vc_keymap,
265                                    "KEYMAP_TOGGLE", &vc_keymap_toggle,
266                                    "FONT", &vc_font,
267                                    "FONT_MAP", &vc_font_map,
268                                    "FONT_UNIMAP", &vc_font_unimap,
269                                    NULL);
270
271                 if (r < 0 && r != -ENOENT)
272                         log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
273         }
274
275         if (utf8)
276                 enable_utf8(fd);
277         else
278                 disable_utf8(fd);
279
280         r = EXIT_FAILURE;
281         if (keymap_load(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
282             font_load(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
283                 r = EXIT_SUCCESS;
284
285 finish:
286         if (keymap_pid > 0)
287                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
288
289         if (font_pid > 0) {
290                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
291                 if (font_copy)
292                         font_copy_to_all_vcs(fd);
293         }
294
295         free(vc_keymap);
296         free(vc_font);
297         free(vc_font_map);
298         free(vc_font_unimap);
299
300         if (fd >= 0)
301                 close_nointr_nofail(fd);
302
303         return r;
304 }