eloop: move prefix to "ev_" instead of "kmscon_"
[platform/upstream/kmscon.git] / src / vt.c
1 /*
2  * kmscon - VT compatibility layer
3  *
4  * Copyright (c) 2011 David Herrmann <dh.herrmann@googlemail.com>
5  * Copyright (c) 2011 University of Tuebingen
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files
9  * (the "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included
16  * in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  */
26
27 /*
28  * This kmscon-VT API is based on the wayland-compositor demo:
29  *
30  * Copyright © 2010 Intel Corporation
31  *
32  * Permission to use, copy, modify, distribute, and sell this software and
33  * its documentation for any purpose is hereby granted without fee, provided
34  * that the above copyright notice appear in all copies and that both that
35  * copyright notice and this permission notice appear in supporting
36  * documentation, and that the name of the copyright holders not be used in
37  * advertising or publicity pertaining to distribution of the software
38  * without specific, written prior permission.  The copyright holders make
39  * no representations about the suitability of this software for any
40  * purpose.  It is provided "as is" without express or implied warranty.
41  *
42  * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
43  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
44  * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
45  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
46  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
47  * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
48  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
49  */
50
51 /*
52  * VT compatibility
53  * Although we do not use the VT for drawing or anything, we set it to graphical
54  * mode to avoid side effects.
55  * This behavior is copied from wayland. If someone has more insight in VT
56  * handling, they may adjust this code.
57  */
58
59 #include <errno.h>
60 #include <signal.h>
61 #include <stdbool.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65
66 #include <fcntl.h>
67 #include <linux/kd.h>
68 #include <linux/vt.h>
69 #include <sys/ioctl.h>
70 #include <termios.h>
71 #include <unistd.h>
72
73 #include "eloop.h"
74 #include "log.h"
75 #include "vt.h"
76
77 struct kmscon_vt {
78         unsigned long ref;
79
80         int fd;
81         int num;
82         int saved_num;
83         struct termios saved_attribs;
84         kmscon_vt_cb cb;
85         void *data;
86
87         struct ev_signal *sig1;
88         struct ev_signal *sig2;
89         struct ev_fd *efd;
90 };
91
92 int kmscon_vt_new(struct kmscon_vt **out, kmscon_vt_cb cb, void *data)
93 {
94         struct kmscon_vt *vt;
95
96         if (!out)
97                 return -EINVAL;
98
99         vt = malloc(sizeof(*vt));
100         if (!vt)
101                 return -ENOMEM;
102
103         memset(vt, 0, sizeof(*vt));
104         vt->ref = 1;
105         vt->fd = -1;
106         vt->num = -1;
107         vt->saved_num = -1;
108         vt->cb = cb;
109         vt->data = data;
110
111         log_debug("vt: create VT object\n");
112         *out = vt;
113         return 0;
114 }
115
116 void kmscon_vt_ref(struct kmscon_vt *vt)
117 {
118         if (!vt)
119                 return;
120
121         ++vt->ref;
122 }
123
124 void kmscon_vt_unref(struct kmscon_vt *vt)
125 {
126         if (!vt || !vt->ref)
127                 return;
128
129         if (--vt->ref)
130                 return;
131
132         log_debug("vt: destroy VT object\n");
133         kmscon_vt_close(vt);
134         free(vt);
135 }
136
137 static void vt_enter(struct ev_signal *sig, int signum, void *data)
138 {
139         struct kmscon_vt *vt = data;
140
141         if (!vt || vt->fd < 0)
142                 return;
143
144         log_debug("vt: entering VT\n");
145
146         ioctl(vt->fd, VT_RELDISP, VT_ACKACQ);
147
148         if (ioctl(vt->fd, KDSETMODE, KD_GRAPHICS))
149                 log_warn("vt: cannot set graphics mode on vt\n");
150
151         if (vt->cb)
152                 vt->cb(vt, KMSCON_VT_ENTER, vt->data);
153 }
154
155 static void vt_leave(struct ev_signal *sig, int signum, void *data)
156 {
157         struct kmscon_vt *vt = data;
158
159         if (!vt || vt->fd < 0)
160                 return;
161
162         if (vt->cb && !vt->cb(vt, KMSCON_VT_LEAVE, vt->data)) {
163                 log_debug("vt: leaving VT denied\n");
164                 ioctl(vt->fd, VT_RELDISP, 0);
165         } else {
166                 log_debug("vt: leaving VT\n");
167                 ioctl(vt->fd, VT_RELDISP, 1);
168                 if (ioctl(vt->fd, KDSETMODE, KD_TEXT))
169                         log_warn("vt: cannot set text mode on vt\n");
170         }
171 }
172
173 static void vt_input(struct ev_fd *fd, int mask, void *data)
174 {
175         struct kmscon_vt *vt = data;
176
177         if (!vt || vt->fd < 0)
178                 return;
179
180         /* we ignore input from the VT because we get it from evdev */
181         tcflush(vt->fd, TCIFLUSH);
182 }
183
184 static int connect_eloop(struct kmscon_vt *vt, struct ev_eloop *eloop)
185 {
186         int ret;
187
188         if (!vt || !eloop || vt->fd < 0)
189                 return -EINVAL;
190
191         ret = ev_eloop_new_signal(eloop, &vt->sig1, SIGUSR1, vt_leave, vt);
192         if (ret)
193                 return ret;
194
195         ret = ev_eloop_new_signal(eloop, &vt->sig2, SIGUSR2, vt_enter, vt);
196         if (ret)
197                 goto err_sig1;
198
199         ret = ev_eloop_new_fd(eloop, &vt->efd, vt->fd, EV_READABLE,
200                                                                 vt_input, vt);
201         if (ret)
202                 goto err_sig2;
203
204         return 0;
205
206 err_sig2:
207         ev_eloop_rm_signal(vt->sig2);
208         vt->sig2 = NULL;
209 err_sig1:
210         ev_eloop_rm_signal(vt->sig1);
211         vt->sig1 = NULL;
212         return ret;
213 }
214
215 static void disconnect_eloop(struct kmscon_vt *vt)
216 {
217         if (!vt)
218                 return;
219
220         ev_eloop_rm_signal(vt->sig1);
221         ev_eloop_rm_signal(vt->sig2);
222         ev_eloop_rm_fd(vt->efd);
223         vt->sig1 = NULL;
224         vt->sig2 = NULL;
225         vt->efd = NULL;
226 }
227
228 static int open_tty(int id, int *tty_fd, int *tty_num)
229 {
230         int fd;
231         char filename[16];
232
233         if (!tty_fd || !tty_num)
234                 return -EINVAL;
235
236         if (id == KMSCON_VT_NEW) {
237                 fd = open("/dev/tty0", O_NONBLOCK | O_NOCTTY | O_CLOEXEC);
238                 if (fd < 0) {
239                         fd = open("/dev/tty1", O_NONBLOCK | O_NOCTTY |
240                                                                 O_CLOEXEC);
241                         if (fd < 0) {
242                                 log_err("vt: cannot find unused tty\n");
243                                 return -errno;
244                         }
245                 }
246
247                 if (ioctl(fd, VT_OPENQRY, &id) || id <= 0) {
248                         close(fd);
249                         log_err("vt: cannot find unused tty\n");
250                         return -EINVAL;
251                 }
252                 close(fd);
253         }
254
255         snprintf(filename, sizeof(filename), "/dev/tty%d", id);
256         filename[sizeof(filename) - 1] = 0;
257         log_debug("vt: using tty %s\n", filename);
258
259         fd = open(filename, O_RDWR | O_NOCTTY | O_CLOEXEC);
260         if (fd < 0) {
261                 log_err("vt: cannot open vt\n");
262                 return -errno;
263         }
264
265         *tty_fd = fd;
266         *tty_num = id;
267         return 0;
268 }
269
270 int kmscon_vt_open(struct kmscon_vt *vt, int id, struct ev_eloop *eloop)
271 {
272         struct termios raw_attribs;
273         struct vt_mode mode;
274         struct vt_stat vts;
275         int ret;
276         sigset_t mask;
277
278         if (vt->fd >= 0)
279                 return -EALREADY;
280
281         ret = open_tty(id, &vt->fd, &vt->num);
282         if (ret)
283                 return ret;
284
285         ret = connect_eloop(vt, eloop);
286         if (ret)
287                 goto err_fd;
288
289         /*
290          * Get the number of the VT which is active now, so we have something
291          * to switch back to in kmscon_vt_switch_leave.
292          */
293         ret = ioctl(vt->fd, VT_GETSTATE, &vts);
294         if (ret) {
295                 log_warn("vt: cannot find the current VT\n");
296                 vt->saved_num = -1;
297         } else {
298                 vt->saved_num = vts.v_active;
299         }
300
301         if (tcgetattr(vt->fd, &vt->saved_attribs) < 0) {
302                 log_err("vt: cannot get terminal attributes\n");
303                 ret = -EFAULT;
304                 goto err_eloop;
305         }
306
307         /* Ignore control characters and disable echo */
308         raw_attribs = vt->saved_attribs;
309         cfmakeraw(&raw_attribs);
310
311         /* Fix up line endings to be normal (cfmakeraw hoses them) */
312         raw_attribs.c_oflag |= OPOST | OCRNL;
313
314         if (tcsetattr(vt->fd, TCSANOW, &raw_attribs) < 0)
315                 log_warn("vt: cannot put terminal into raw mode\n");
316
317         if (ioctl(vt->fd, KDSETMODE, KD_GRAPHICS)) {
318                 log_err("vt: cannot set graphics mode\n");
319                 ret = -errno;
320                 goto err_reset;
321         }
322
323         memset(&mode, 0, sizeof(mode));
324         mode.mode = VT_PROCESS;
325         mode.relsig = SIGUSR1;
326         mode.acqsig = SIGUSR2;
327
328         if (ioctl(vt->fd, VT_SETMODE, &mode)) {
329                 log_err("vt: cannot take control of vt handling\n");
330                 ret = -errno;
331                 goto err_text;
332         }
333
334         sigemptyset(&mask);
335         sigaddset(&mask, SIGUSR1);
336         sigaddset(&mask, SIGUSR2);
337         sigprocmask(SIG_BLOCK, &mask, NULL);
338
339         return 0;
340
341 err_text:
342         ioctl(vt->fd, KDSETMODE, KD_TEXT);
343 err_reset:
344         tcsetattr(vt->fd, TCSANOW, &vt->saved_attribs);
345 err_eloop:
346         disconnect_eloop(vt);
347 err_fd:
348         close(vt->fd);
349         vt->fd = -1;
350         return ret;
351 }
352
353 void kmscon_vt_close(struct kmscon_vt *vt)
354 {
355         if (!vt || vt->fd < 0)
356                 return;
357
358         ioctl(vt->fd, KDSETMODE, KD_TEXT);
359         tcsetattr(vt->fd, TCSANOW, &vt->saved_attribs);
360         disconnect_eloop(vt);
361         close(vt->fd);
362
363         vt->fd = -1;
364         vt->num = -1;
365         vt->saved_num = -1;
366 }
367
368 /* Switch to this VT and make it the active VT. */
369 int kmscon_vt_enter(struct kmscon_vt *vt)
370 {
371         int ret;
372
373         if (!vt || vt->fd < 0 || vt->num < 0)
374                 return -EINVAL;
375
376         ret = ioctl(vt->fd, VT_ACTIVATE, vt->num);
377         if (ret) {
378                 log_warn("vt: cannot enter VT\n");
379                 return -EFAULT;
380         }
381
382         log_debug("vt: enter VT on demand\n");
383         return 0;
384 }
385
386 /*
387  * Switch back to the VT from which we started.
388  * Note: The VT switch needs to be acknowledged by us so we need to react on
389  * SIGUSR. This function returns -EINPROGRESS if we started the VT switch but
390  * still needs to react on SIGUSR. Make sure you call the eloop dispatcher again
391  * if you get -EINPROGRESS here.
392  *
393  * Returns 0 if we don't know the previous VT or if the previous VT is already
394  * active. Returns -EINPROGRESS if we started the VT switch. Returns <0 on
395  * failure.
396  */
397 int kmscon_vt_leave(struct kmscon_vt *vt)
398 {
399         int ret;
400         struct vt_stat vts;
401
402         if (!vt || vt->fd < 0)
403                 return -EINVAL;
404
405         if (vt->saved_num < 0)
406                 return 0;
407
408         ret = ioctl(vt->fd, VT_GETSTATE, &vts);
409         if (ret) {
410                 log_warn("vt: cannot find current VT\n");
411                 return -EFAULT;
412         }
413
414         if (vts.v_active != vt->num)
415                 return 0;
416
417         ret = ioctl(vt->fd, VT_ACTIVATE, vt->saved_num);
418         if (ret) {
419                 log_warn("vt: cannot leave VT\n");
420                 return -EFAULT;
421         }
422
423         log_debug("vt: leave VT on demand\n");
424         return -EINPROGRESS;
425 }