chiark / gitweb /
terminal: verify kernel-returned DRM events are not truncated
[elogind.git] / src / libsystemd-terminal / modeset.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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 /*
23  * Modeset Testing
24  * The modeset tool attaches to the session of the caller and shows a
25  * test-pattern on all displays of this session. It is meant as debugging tool
26  * for the grdev infrastructure.
27  */
28
29 #include <drm_fourcc.h>
30 #include <errno.h>
31 #include <getopt.h>
32 #include <linux/kd.h>
33 #include <linux/vt.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/ioctl.h>
39 #include <sys/stat.h>
40 #include <systemd/sd-bus.h>
41 #include <systemd/sd-event.h>
42 #include <systemd/sd-login.h>
43 #include <termios.h>
44 #include <unistd.h>
45 #include "build.h"
46 #include "bus-util.h"
47 #include "event-util.h"
48 #include "grdev.h"
49 #include "grdev-internal.h"
50 #include "macro.h"
51 #include "sysview.h"
52 #include "util.h"
53
54 typedef struct Modeset Modeset;
55
56 struct Modeset {
57         char *session;
58         char *seat;
59         sd_event *event;
60         sd_bus *bus;
61         sd_event_source *exit_src;
62         sysview_context *sysview;
63         grdev_context *grdev;
64         grdev_session *grdev_session;
65
66         uint8_t r, g, b;
67         bool r_up, g_up, b_up;
68
69         bool my_tty : 1;
70         bool managed : 1;
71 };
72
73 static int modeset_exit_fn(sd_event_source *source, void *userdata) {
74         Modeset *m = userdata;
75
76         if (m->grdev_session)
77                 grdev_session_restore(m->grdev_session);
78
79         return 0;
80 }
81
82 static Modeset *modeset_free(Modeset *m) {
83         if (!m)
84                 return NULL;
85
86         m->grdev_session = grdev_session_free(m->grdev_session);
87         m->grdev = grdev_context_unref(m->grdev);
88         m->sysview = sysview_context_free(m->sysview);
89         m->exit_src = sd_event_source_unref(m->exit_src);
90         m->bus = sd_bus_unref(m->bus);
91         m->event = sd_event_unref(m->event);
92         free(m->seat);
93         free(m->session);
94         free(m);
95
96         return NULL;
97 }
98
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free);
100
101 static bool is_my_tty(const char *session) {
102         unsigned int vtnr;
103         struct stat st;
104         long mode;
105         int r;
106
107         /* Using logind's Controller API is highly fragile if there is already
108          * a session controller running. If it is registered as controller
109          * itself, TakeControl will simply fail. But if its a legacy controller
110          * that does not use logind's controller API, we must never register
111          * our own controller. Otherwise, we really mess up the VT. Therefore,
112          * only run in managed mode if there's no-one else.  Furthermore, never
113          * try to access graphics devices if there's someone else. Unlike input
114          * devices, graphics devies cannot be shared easily. */
115
116         if (!isatty(1))
117                 return false;
118
119         if (!session)
120                 return false;
121
122         r = sd_session_get_vt(session, &vtnr);
123         if (r < 0 || vtnr < 1 || vtnr > 63)
124                 return false;
125
126         mode = 0;
127         r = ioctl(1, KDGETMODE, &mode);
128         if (r < 0 || mode != KD_TEXT)
129                 return false;
130
131         r = fstat(1, &st);
132         if (r < 0 || minor(st.st_rdev) != vtnr)
133                 return false;
134
135         return true;
136 }
137
138 static int modeset_new(Modeset **out) {
139         _cleanup_(modeset_freep) Modeset *m = NULL;
140         int r;
141
142         assert(out);
143
144         m = new0(Modeset, 1);
145         if (!m)
146                 return log_oom();
147
148         r = sd_pid_get_session(getpid(), &m->session);
149         if (r < 0) {
150                 log_error("Cannot retrieve logind session: %s", strerror(-r));
151                 return r;
152         }
153
154         r = sd_session_get_seat(m->session, &m->seat);
155         if (r < 0) {
156                 log_error("Cannot retrieve seat of logind session: %s", strerror(-r));
157                 return r;
158         }
159
160         m->my_tty = is_my_tty(m->session);
161         m->managed = m->my_tty && geteuid() > 0;
162
163         m->r = rand() % 0xff;
164         m->g = rand() % 0xff;
165         m->b = rand() % 0xff;
166         m->r_up = m->g_up = m->b_up = true;
167
168         r = sd_event_default(&m->event);
169         if (r < 0)
170                 return r;
171
172         r = sd_bus_open_system(&m->bus);
173         if (r < 0)
174                 return r;
175
176         r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
177         if (r < 0)
178                 return r;
179
180         r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
181         if (r < 0)
182                 return r;
183
184         r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
185         if (r < 0)
186                 return r;
187
188         r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
189         if (r < 0)
190                 return r;
191
192         r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
193         if (r < 0)
194                 return r;
195
196         /* schedule before sd-bus close */
197         r = sd_event_source_set_priority(m->exit_src, -10);
198         if (r < 0)
199                 return r;
200
201         r = sysview_context_new(&m->sysview,
202                                 SYSVIEW_CONTEXT_SCAN_LOGIND |
203                                 SYSVIEW_CONTEXT_SCAN_DRM,
204                                 m->event,
205                                 m->bus,
206                                 NULL);
207         if (r < 0)
208                 return r;
209
210         r = grdev_context_new(&m->grdev, m->event, m->bus);
211         if (r < 0)
212                 return r;
213
214         *out = m;
215         m = NULL;
216         return 0;
217 }
218
219 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
220         uint8_t next;
221
222         /* generate smoothly morphing colors */
223
224         next = cur + (*up ? 1 : -1) * (rand() % mod);
225         if ((*up && next < cur) || (!*up && next > cur)) {
226                 *up = !*up;
227                 next = cur;
228         }
229
230         return next;
231 }
232
233 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
234         uint32_t j, k, *b;
235         uint8_t *l;
236
237         assert(t->fb->format == DRM_FORMAT_XRGB8888 || t->fb->format == DRM_FORMAT_ARGB8888);
238         assert(!t->rotate);
239         assert(!t->flip);
240
241         l = t->fb->maps[0];
242         for (j = 0; j < t->height; ++j) {
243                 for (k = 0; k < t->width; ++k) {
244                         b = (uint32_t*)l;
245                         b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
246                 }
247
248                 l += t->fb->strides[0];
249         }
250 }
251
252 static void modeset_render(Modeset *m, grdev_display *d) {
253         const grdev_display_target *t;
254
255         m->r = next_color(&m->r_up, m->r, 4);
256         m->g = next_color(&m->g_up, m->g, 3);
257         m->b = next_color(&m->b_up, m->b, 2);
258
259         GRDEV_DISPLAY_FOREACH_TARGET(d, t, 0) {
260                 modeset_draw(m, t);
261                 grdev_display_flip_target(d, t, 1);
262         }
263
264         grdev_session_commit(m->grdev_session);
265 }
266
267 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
268         Modeset *m = userdata;
269
270         switch (ev->type) {
271         case GRDEV_EVENT_DISPLAY_ADD:
272                 grdev_display_enable(ev->display_add.display);
273                 break;
274         case GRDEV_EVENT_DISPLAY_REMOVE:
275                 break;
276         case GRDEV_EVENT_DISPLAY_CHANGE:
277                 break;
278         case GRDEV_EVENT_DISPLAY_FRAME:
279                 modeset_render(m, ev->display_frame.display);
280                 break;
281         }
282 }
283
284 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
285         unsigned int flags, type;
286         Modeset *m = userdata;
287         sysview_device *d;
288         const char *name;
289         int r;
290
291         switch (ev->type) {
292         case SYSVIEW_EVENT_SESSION_FILTER:
293                 if (streq_ptr(m->session, ev->session_filter.id))
294                         return 1;
295
296                 break;
297         case SYSVIEW_EVENT_SESSION_ADD:
298                 assert(!m->grdev_session);
299
300                 name = sysview_session_get_name(ev->session_add.session);
301                 flags = 0;
302
303                 if (m->managed)
304                         flags |= GRDEV_SESSION_MANAGED;
305
306                 r = grdev_session_new(&m->grdev_session,
307                                       m->grdev,
308                                       flags,
309                                       name,
310                                       modeset_grdev_fn,
311                                       m);
312                 if (r < 0) {
313                         log_error("Cannot create grdev session: %s", strerror(-r));
314                         return r;
315                 }
316
317                 if (m->managed) {
318                         r = sysview_session_take_control(ev->session_add.session);
319                         if (r < 0) {
320                                 log_error("Cannot request session control: %s", strerror(-r));
321                                 return r;
322                         }
323                 }
324
325                 grdev_session_enable(m->grdev_session);
326
327                 break;
328         case SYSVIEW_EVENT_SESSION_REMOVE:
329                 if (!m->grdev_session)
330                         return 0;
331
332                 grdev_session_restore(m->grdev_session);
333                 grdev_session_disable(m->grdev_session);
334                 m->grdev_session = grdev_session_free(m->grdev_session);
335                 if (sd_event_get_exit_code(m->event, &r) == -ENODATA)
336                         sd_event_exit(m->event, 0);
337                 break;
338         case SYSVIEW_EVENT_SESSION_ATTACH:
339                 d = ev->session_attach.device;
340                 type = sysview_device_get_type(d);
341                 if (type == SYSVIEW_DEVICE_DRM)
342                         grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d));
343
344                 break;
345         case SYSVIEW_EVENT_SESSION_DETACH:
346                 d = ev->session_detach.device;
347                 type = sysview_device_get_type(d);
348                 if (type == SYSVIEW_DEVICE_DRM)
349                         grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d));
350
351                 break;
352         case SYSVIEW_EVENT_SESSION_REFRESH:
353                 d = ev->session_refresh.device;
354                 type = sysview_device_get_type(d);
355                 if (type == SYSVIEW_DEVICE_DRM)
356                         grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud);
357
358                 break;
359         case SYSVIEW_EVENT_SESSION_CONTROL:
360                 r = ev->session_control.error;
361                 if (r < 0) {
362                         log_error("Cannot acquire session control: %s", strerror(-r));
363                         return r;
364                 }
365
366                 r = ioctl(1, KDSKBMODE, K_UNICODE);
367                 if (r < 0) {
368                         log_error("Cannot set K_UNICODE on stdout: %m");
369                         return -errno;
370                 }
371
372                 break;
373         }
374
375         return 0;
376 }
377
378 static int modeset_run(Modeset *m) {
379         struct termios in_attr, saved_attr;
380         int r;
381
382         assert(m);
383
384         if (!m->my_tty) {
385                 log_warning("You need to run this program on a free VT");
386                 return -EACCES;
387         }
388
389         if (!m->managed && geteuid() > 0)
390                 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
391
392         printf("modeset - Show test pattern on selected graphics devices\n"
393                "          Running on seat '%s' in user-session '%s'\n"
394                "          Exit by pressing ^C\n\n",
395                m->seat ? : "seat0", m->session ? : "<none>");
396
397         r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
398         if (r < 0)
399                 goto out;
400
401         r = tcgetattr(0, &in_attr);
402         if (r < 0) {
403                 r = -errno;
404                 goto out;
405         }
406
407         saved_attr = in_attr;
408         in_attr.c_lflag &= ~ECHO;
409
410         r = tcsetattr(0, TCSANOW, &in_attr);
411         if (r < 0) {
412                 r = -errno;
413                 goto out;
414         }
415
416         r = sd_event_loop(m->event);
417         tcsetattr(0, TCSANOW, &saved_attr);
418         printf("exiting..\n");
419
420 out:
421         sysview_context_stop(m->sysview);
422         return r;
423 }
424
425 static int help(void) {
426         printf("%s [OPTIONS...]\n\n"
427                "Show test pattern on all selected graphics devices.\n\n"
428                "  -h --help               Show this help\n"
429                "     --version            Show package version\n"
430                , program_invocation_short_name);
431
432         return 0;
433 }
434
435 static int parse_argv(int argc, char *argv[]) {
436         enum {
437                 ARG_VERSION = 0x100,
438         };
439         static const struct option options[] = {
440                 { "help",       no_argument,    NULL, 'h'               },
441                 { "version",    no_argument,    NULL, ARG_VERSION       },
442                 {},
443         };
444         int c;
445
446         assert(argc >= 0);
447         assert(argv);
448
449         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
450                 switch (c) {
451                 case 'h':
452                         help();
453                         return 0;
454
455                 case ARG_VERSION:
456                         puts(PACKAGE_STRING);
457                         puts(SYSTEMD_FEATURES);
458                         return 0;
459
460                 case '?':
461                         return -EINVAL;
462
463                 default:
464                         assert_not_reached("Unhandled option");
465                 }
466
467         if (argc > optind) {
468                 log_error("Too many arguments");
469                 return -EINVAL;
470         }
471
472         return 1;
473 }
474
475 int main(int argc, char *argv[]) {
476         _cleanup_(modeset_freep) Modeset *m = NULL;
477         int r;
478
479         log_set_target(LOG_TARGET_AUTO);
480         log_parse_environment();
481         log_open();
482
483         srand(time(NULL));
484
485         r = parse_argv(argc, argv);
486         if (r <= 0)
487                 goto finish;
488
489         r = modeset_new(&m);
490         if (r < 0)
491                 goto finish;
492
493         r = modeset_run(m);
494
495 finish:
496         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
497 }