chiark / gitweb /
bus-policy: append items rather than prepending them
[elogind.git] / src / libsystemd-terminal / evcat.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  * Event Catenation
24  * The evcat tool catenates input events of all requested devices and prints
25  * them to standard-output. It's only meant for debugging of input-related
26  * problems.
27  */
28
29 #include <assert.h>
30 #include <errno.h>
31 #include <getopt.h>
32 #include <libevdev/libevdev.h>
33 #include <linux/kd.h>
34 #include <linux/vt.h>
35 #include <stdarg.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/ioctl.h>
41 #include <sys/stat.h>
42 #include <systemd/sd-bus.h>
43 #include <systemd/sd-event.h>
44 #include <systemd/sd-login.h>
45 #include <termios.h>
46 #include <unistd.h>
47 #include <xkbcommon/xkbcommon.h>
48 #include "build.h"
49 #include "bus-util.h"
50 #include "event-util.h"
51 #include "idev.h"
52 #include "macro.h"
53 #include "sysview.h"
54 #include "term-internal.h"
55 #include "util.h"
56
57 typedef struct Evcat Evcat;
58
59 struct Evcat {
60         char *session;
61         char *seat;
62         sd_event *event;
63         sd_bus *bus;
64         sysview_context *sysview;
65         idev_context *idev;
66         idev_session *idev_session;
67
68         bool managed : 1;
69 };
70
71 static Evcat *evcat_free(Evcat *e) {
72         if (!e)
73                 return NULL;
74
75         e->idev_session = idev_session_free(e->idev_session);
76         e->idev = idev_context_unref(e->idev);
77         e->sysview = sysview_context_free(e->sysview);
78         e->bus = sd_bus_unref(e->bus);
79         e->event = sd_event_unref(e->event);
80         free(e->seat);
81         free(e->session);
82         free(e);
83
84         tcflush(0, TCIOFLUSH);
85
86         return NULL;
87 }
88
89 DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free);
90
91 static bool is_managed(const char *session) {
92         unsigned int vtnr;
93         struct stat st;
94         long mode;
95         int r;
96
97         /* Using logind's Controller API is highly fragile if there is already
98          * a session controller running. If it is registered as controller
99          * itself, TakeControl will simply fail. But if its a legacy controller
100          * that does not use logind's controller API, we must never register
101          * our own controller. Otherwise, we really mess up the VT. Therefore,
102          * only run in managed mode if there's no-one else. */
103
104         if (geteuid() == 0)
105                 return false;
106
107         if (!isatty(1))
108                 return false;
109
110         if (!session)
111                 return false;
112
113         r = sd_session_get_vt(session, &vtnr);
114         if (r < 0 || vtnr < 1 || vtnr > 63)
115                 return false;
116
117         mode = 0;
118         r = ioctl(1, KDGETMODE, &mode);
119         if (r < 0 || mode != KD_TEXT)
120                 return false;
121
122         r = fstat(1, &st);
123         if (r < 0 || minor(st.st_rdev) != vtnr)
124                 return false;
125
126         return true;
127 }
128
129 static int evcat_new(Evcat **out) {
130         _cleanup_(evcat_freep) Evcat *e = NULL;
131         int r;
132
133         assert(out);
134
135         e = new0(Evcat, 1);
136         if (!e)
137                 return log_oom();
138
139         r = sd_pid_get_session(getpid(), &e->session);
140         if (r < 0) {
141                 log_error("Cannot retrieve logind session: %s", strerror(-r));
142                 return r;
143         }
144
145         r = sd_session_get_seat(e->session, &e->seat);
146         if (r < 0) {
147                 log_error("Cannot retrieve seat of logind session: %s", strerror(-r));
148                 return r;
149         }
150
151         e->managed = is_managed(e->session);
152
153         r = sd_event_default(&e->event);
154         if (r < 0)
155                 return r;
156
157         r = sd_bus_open_system(&e->bus);
158         if (r < 0)
159                 return r;
160
161         r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL);
162         if (r < 0)
163                 return r;
164
165         r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
166         if (r < 0)
167                 return r;
168
169         r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL);
170         if (r < 0)
171                 return r;
172
173         r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL);
174         if (r < 0)
175                 return r;
176
177         r = sysview_context_new(&e->sysview,
178                                 SYSVIEW_CONTEXT_SCAN_LOGIND |
179                                 SYSVIEW_CONTEXT_SCAN_EVDEV,
180                                 e->event,
181                                 e->bus,
182                                 NULL);
183         if (r < 0)
184                 return r;
185
186         r = idev_context_new(&e->idev, e->event, e->bus);
187         if (r < 0)
188                 return r;
189
190         *out = e;
191         e = NULL;
192         return 0;
193 }
194
195 static void kdata_print(idev_data *data) {
196         idev_data_keyboard *k = &data->keyboard;
197         char buf[128];
198         uint32_t i, c;
199         int cwidth;
200
201         /* Key-press state: UP/DOWN/REPEAT */
202         printf(" %-6s", k->value == 0 ? "UP" :
203                         k->value == 1 ? "DOWN" :
204                         "REPEAT");
205
206         /* Keycode that triggered the event */
207         printf(" | %5u", (unsigned)k->keycode);
208
209         /* Well-known name of the keycode */
210         printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>");
211
212         /* Well-known modifiers */
213         printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : "");
214         printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : "");
215         printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : "");
216         printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : "");
217         printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : "");
218
219         /* Resolved symbols */
220         printf(" |");
221         for (i = 0; i < k->n_syms; ++i) {
222                 buf[0] = 0;
223                 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
224
225                 if (is_locale_utf8()) {
226                         c = k->codepoints[i];
227                         if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) {
228                                 /* "%4lc" doesn't work well, so hard-code it */
229                                 cwidth = mk_wcwidth(c);
230                                 while (cwidth++ < 2)
231                                         printf(" ");
232
233                                 printf(" '%lc':", (wchar_t)c);
234                         } else {
235                                 printf("      ");
236                         }
237                 }
238
239                 printf(" XKB_KEY_%-30s", buf);
240         }
241
242         printf("\n");
243 }
244
245 static bool kdata_is_exit(idev_data *data) {
246         idev_data_keyboard *k = &data->keyboard;
247
248         if (k->value != 1)
249                 return false;
250         if (k->n_syms != 1)
251                 return false;
252
253         return k->codepoints[0] == 'q';
254 }
255
256 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
257         Evcat *e = userdata;
258
259         switch (ev->type) {
260         case IDEV_EVENT_DEVICE_ADD:
261                 idev_device_enable(ev->device_add.device);
262                 break;
263         case IDEV_EVENT_DEVICE_REMOVE:
264                 idev_device_disable(ev->device_remove.device);
265                 break;
266         case IDEV_EVENT_DEVICE_DATA:
267                 switch (ev->device_data.data.type) {
268                 case IDEV_DATA_KEYBOARD:
269                         if (kdata_is_exit(&ev->device_data.data))
270                                 sd_event_exit(e->event, 0);
271                         else
272                                 kdata_print(&ev->device_data.data);
273
274                         break;
275                 }
276
277                 break;
278         }
279
280         return 0;
281 }
282
283 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
284         unsigned int flags, type;
285         Evcat *e = userdata;
286         sysview_device *d;
287         const char *name;
288         int r;
289
290         switch (ev->type) {
291         case SYSVIEW_EVENT_SESSION_FILTER:
292                 if (streq_ptr(e->session, ev->session_filter.id))
293                         return 1;
294
295                 break;
296         case SYSVIEW_EVENT_SESSION_ADD:
297                 assert(!e->idev_session);
298
299                 name = sysview_session_get_name(ev->session_add.session);
300                 flags = 0;
301
302                 if (e->managed)
303                         flags |= IDEV_SESSION_MANAGED;
304
305                 r = idev_session_new(&e->idev_session,
306                                      e->idev,
307                                      flags,
308                                      name,
309                                      evcat_idev_fn,
310                                      e);
311                 if (r < 0) {
312                         log_error("Cannot create idev session: %s", strerror(-r));
313                         return r;
314                 }
315
316                 if (e->managed) {
317                         r = sysview_session_take_control(ev->session_add.session);
318                         if (r < 0) {
319                                 log_error("Cannot request session control: %s", strerror(-r));
320                                 return r;
321                         }
322                 }
323
324                 idev_session_enable(e->idev_session);
325
326                 break;
327         case SYSVIEW_EVENT_SESSION_REMOVE:
328                 idev_session_disable(e->idev_session);
329                 e->idev_session = idev_session_free(e->idev_session);
330                 sd_event_exit(e->event, 0);
331                 break;
332         case SYSVIEW_EVENT_SESSION_ATTACH:
333                 d = ev->session_attach.device;
334                 type = sysview_device_get_type(d);
335                 if (type == SYSVIEW_DEVICE_EVDEV) {
336                         r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d));
337                         if (r < 0) {
338                                 log_error("Cannot add evdev device to idev: %s", strerror(-r));
339                                 return r;
340                         }
341                 }
342
343                 break;
344         case SYSVIEW_EVENT_SESSION_DETACH:
345                 d = ev->session_detach.device;
346                 type = sysview_device_get_type(d);
347                 if (type == SYSVIEW_DEVICE_EVDEV) {
348                         r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
349                         if (r < 0) {
350                                 log_error("Cannot remove evdev device from idev: %s", strerror(-r));
351                                 return r;
352                         }
353                 }
354
355                 break;
356         case SYSVIEW_EVENT_SESSION_CONTROL:
357                 r = ev->session_control.error;
358                 if (r < 0) {
359                         log_error("Cannot acquire session control: %s", strerror(-r));
360                         return r;
361                 }
362
363                 r = ioctl(1, KDSKBMODE, K_UNICODE);
364                 if (r < 0) {
365                         log_error("Cannot set K_UNICODE on stdout: %m");
366                         return -errno;
367                 }
368
369                 r = ioctl(1, KDSETMODE, KD_TEXT);
370                 if (r < 0) {
371                         log_error("Cannot set KD_TEXT on stdout: %m");
372                         return -errno;
373                 }
374
375                 printf("\n");
376
377                 break;
378         }
379
380         return 0;
381 }
382
383 static int evcat_run(Evcat *e) {
384         struct termios in_attr, saved_attr;
385         int r;
386
387         assert(e);
388
389         if (!e->managed && geteuid() > 0)
390                 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
391
392         printf("evcat - Read and catenate events from selected input devices\n"
393                "        Running on seat '%s' in user-session '%s'\n"
394                "        Exit by pressing ^C or 'q'\n\n",
395                e->seat ? : "seat0", e->session ? : "<none>");
396
397         r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
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(e->event);
417         tcsetattr(0, TCSANOW, &saved_attr);
418         printf("exiting..\n");
419
420 out:
421         sysview_context_stop(e->sysview);
422         return r;
423 }
424
425 static int help(void) {
426         printf("%s [OPTIONS...]\n\n"
427                "Read and catenate events from selected input 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_(evcat_freep) Evcat *e = NULL;
477         int r;
478
479         log_set_target(LOG_TARGET_AUTO);
480         log_parse_environment();
481         log_open();
482
483         setlocale(LC_ALL, "");
484         if (!is_locale_utf8())
485                 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
486
487         r = parse_argv(argc, argv);
488         if (r <= 0)
489                 goto finish;
490
491         r = evcat_new(&e);
492         if (r < 0)
493                 goto finish;
494
495         r = evcat_run(e);
496
497 finish:
498         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
499 }