]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-terminal/evcat.c
treewide: use log_*_errno whenever %m is in the format string
[thirdparty/systemd.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 return log_error_errno(r, "Cannot retrieve logind session: %m");
142
143 r = sd_session_get_seat(e->session, &e->seat);
144 if (r < 0)
145 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
146
147 e->managed = is_managed(e->session);
148
149 r = sd_event_default(&e->event);
150 if (r < 0)
151 return r;
152
153 r = sd_bus_open_system(&e->bus);
154 if (r < 0)
155 return r;
156
157 r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL);
158 if (r < 0)
159 return r;
160
161 r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
162 if (r < 0)
163 return r;
164
165 r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL);
166 if (r < 0)
167 return r;
168
169 r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL);
170 if (r < 0)
171 return r;
172
173 r = sysview_context_new(&e->sysview,
174 SYSVIEW_CONTEXT_SCAN_LOGIND |
175 SYSVIEW_CONTEXT_SCAN_EVDEV,
176 e->event,
177 e->bus,
178 NULL);
179 if (r < 0)
180 return r;
181
182 r = idev_context_new(&e->idev, e->event, e->bus);
183 if (r < 0)
184 return r;
185
186 *out = e;
187 e = NULL;
188 return 0;
189 }
190
191 static void kdata_print(idev_data *data) {
192 idev_data_keyboard *k = &data->keyboard;
193 char buf[128];
194 uint32_t i, c;
195 int cwidth;
196
197 /* Key-press state: UP/DOWN/REPEAT */
198 printf(" %-6s", k->value == 0 ? "UP" :
199 k->value == 1 ? "DOWN" :
200 "REPEAT");
201
202 /* Resync state */
203 printf(" | %-6s", data->resync ? "RESYNC" : "");
204
205 /* Keycode that triggered the event */
206 printf(" | %5u", (unsigned)k->keycode);
207
208 /* Well-known name of the keycode */
209 printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>");
210
211 /* Well-known modifiers */
212 printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : "");
213 printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : "");
214 printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : "");
215 printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : "");
216 printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : "");
217
218 /* Consumed modifiers */
219 printf(" | %-5s", (k->consumed_mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : "");
220 printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CTRL) ? "CTRL" : "");
221 printf(" %-3s", (k->consumed_mods & IDEV_KBDMOD_ALT) ? "ALT" : "");
222 printf(" %-5s", (k->consumed_mods & IDEV_KBDMOD_LINUX) ? "LINUX" : "");
223 printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CAPS) ? "CAPS" : "");
224
225 /* Resolved symbols */
226 printf(" |");
227 for (i = 0; i < k->n_syms; ++i) {
228 buf[0] = 0;
229 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
230
231 if (is_locale_utf8()) {
232 c = k->codepoints[i];
233 if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) {
234 /* "%4lc" doesn't work well, so hard-code it */
235 cwidth = mk_wcwidth(c);
236 while (cwidth++ < 2)
237 printf(" ");
238
239 printf(" '%lc':", (wchar_t)c);
240 } else {
241 printf(" ");
242 }
243 }
244
245 printf(" XKB_KEY_%-30s", buf);
246 }
247
248 printf("\n");
249 }
250
251 static bool kdata_is_exit(idev_data *data) {
252 idev_data_keyboard *k = &data->keyboard;
253
254 if (k->value != 1)
255 return false;
256 if (k->n_syms != 1)
257 return false;
258
259 return k->codepoints[0] == 'q';
260 }
261
262 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
263 Evcat *e = userdata;
264
265 switch (ev->type) {
266 case IDEV_EVENT_DEVICE_ADD:
267 idev_device_enable(ev->device_add.device);
268 break;
269 case IDEV_EVENT_DEVICE_REMOVE:
270 idev_device_disable(ev->device_remove.device);
271 break;
272 case IDEV_EVENT_DEVICE_DATA:
273 switch (ev->device_data.data.type) {
274 case IDEV_DATA_KEYBOARD:
275 if (kdata_is_exit(&ev->device_data.data))
276 sd_event_exit(e->event, 0);
277 else
278 kdata_print(&ev->device_data.data);
279
280 break;
281 }
282
283 break;
284 }
285
286 return 0;
287 }
288
289 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
290 unsigned int flags, type;
291 Evcat *e = userdata;
292 sysview_device *d;
293 const char *name;
294 int r;
295
296 switch (ev->type) {
297 case SYSVIEW_EVENT_SESSION_FILTER:
298 if (streq_ptr(e->session, ev->session_filter.id))
299 return 1;
300
301 break;
302 case SYSVIEW_EVENT_SESSION_ADD:
303 assert(!e->idev_session);
304
305 name = sysview_session_get_name(ev->session_add.session);
306 flags = 0;
307
308 if (e->managed)
309 flags |= IDEV_SESSION_MANAGED;
310
311 r = idev_session_new(&e->idev_session,
312 e->idev,
313 flags,
314 name,
315 evcat_idev_fn,
316 e);
317 if (r < 0)
318 return log_error_errno(r, "Cannot create idev session: %m");
319
320 if (e->managed) {
321 r = sysview_session_take_control(ev->session_add.session);
322 if (r < 0)
323 return log_error_errno(r, "Cannot request session control: %m");
324 }
325
326 idev_session_enable(e->idev_session);
327
328 break;
329 case SYSVIEW_EVENT_SESSION_REMOVE:
330 idev_session_disable(e->idev_session);
331 e->idev_session = idev_session_free(e->idev_session);
332 if (sd_event_get_exit_code(e->event, &r) == -ENODATA)
333 sd_event_exit(e->event, 0);
334 break;
335 case SYSVIEW_EVENT_SESSION_ATTACH:
336 d = ev->session_attach.device;
337 type = sysview_device_get_type(d);
338 if (type == SYSVIEW_DEVICE_EVDEV) {
339 r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d));
340 if (r < 0)
341 return log_error_errno(r, "Cannot add evdev device to idev: %m");
342 }
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_EVDEV) {
349 r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
350 if (r < 0)
351 return log_error_errno(r, "Cannot remove evdev device from idev: %m");
352 }
353
354 break;
355 case SYSVIEW_EVENT_SESSION_CONTROL:
356 r = ev->session_control.error;
357 if (r < 0)
358 return log_error_errno(r, "Cannot acquire session control: %m");
359
360 r = ioctl(1, KDSKBMODE, K_UNICODE);
361 if (r < 0) {
362 log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
363 return -errno;
364 }
365
366 r = ioctl(1, KDSETMODE, KD_TEXT);
367 if (r < 0) {
368 log_error_errno(errno, "Cannot set KD_TEXT on stdout: %m");
369 return -errno;
370 }
371
372 printf("\n");
373
374 break;
375 }
376
377 return 0;
378 }
379
380 static int evcat_run(Evcat *e) {
381 struct termios in_attr, saved_attr;
382 int r;
383
384 assert(e);
385
386 if (!e->managed && geteuid() > 0)
387 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
388
389 printf("evcat - Read and catenate events from selected input devices\n"
390 " Running on seat '%s' in user-session '%s'\n"
391 " Exit by pressing ^C or 'q'\n\n",
392 e->seat ? : "seat0", e->session ? : "<none>");
393
394 r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
395 if (r < 0)
396 goto out;
397
398 r = tcgetattr(0, &in_attr);
399 if (r < 0) {
400 r = -errno;
401 goto out;
402 }
403
404 saved_attr = in_attr;
405 in_attr.c_lflag &= ~ECHO;
406
407 r = tcsetattr(0, TCSANOW, &in_attr);
408 if (r < 0) {
409 r = -errno;
410 goto out;
411 }
412
413 r = sd_event_loop(e->event);
414 tcsetattr(0, TCSANOW, &saved_attr);
415 printf("exiting..\n");
416
417 out:
418 sysview_context_stop(e->sysview);
419 return r;
420 }
421
422 static int help(void) {
423 printf("%s [OPTIONS...]\n\n"
424 "Read and catenate events from selected input devices.\n\n"
425 " -h --help Show this help\n"
426 " --version Show package version\n"
427 , program_invocation_short_name);
428
429 return 0;
430 }
431
432 static int parse_argv(int argc, char *argv[]) {
433 enum {
434 ARG_VERSION = 0x100,
435 };
436 static const struct option options[] = {
437 { "help", no_argument, NULL, 'h' },
438 { "version", no_argument, NULL, ARG_VERSION },
439 {},
440 };
441 int c;
442
443 assert(argc >= 0);
444 assert(argv);
445
446 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
447 switch (c) {
448 case 'h':
449 help();
450 return 0;
451
452 case ARG_VERSION:
453 puts(PACKAGE_STRING);
454 puts(SYSTEMD_FEATURES);
455 return 0;
456
457 case '?':
458 return -EINVAL;
459
460 default:
461 assert_not_reached("Unhandled option");
462 }
463
464 if (argc > optind) {
465 log_error("Too many arguments");
466 return -EINVAL;
467 }
468
469 return 1;
470 }
471
472 int main(int argc, char *argv[]) {
473 _cleanup_(evcat_freep) Evcat *e = NULL;
474 int r;
475
476 log_set_target(LOG_TARGET_AUTO);
477 log_parse_environment();
478 log_open();
479
480 setlocale(LC_ALL, "");
481 if (!is_locale_utf8())
482 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
483
484 r = parse_argv(argc, argv);
485 if (r <= 0)
486 goto finish;
487
488 r = evcat_new(&e);
489 if (r < 0)
490 goto finish;
491
492 r = evcat_run(e);
493
494 finish:
495 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
496 }