chiark / gitweb /
2316a6652952c8571559ef34df2bf34ec2bdcd4f
[elogind.git] / src / libsystemd-terminal / idev.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 #include <inttypes.h>
23 #include <libudev.h>
24 #include <linux/input.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <systemd/sd-bus.h>
28 #include <systemd/sd-event.h>
29 #include <systemd/sd-login.h>
30 #include "hashmap.h"
31 #include "idev.h"
32 #include "idev-internal.h"
33 #include "login-shared.h"
34 #include "macro.h"
35 #include "set.h"
36 #include "udev-util.h"
37 #include "util.h"
38
39 static void element_open(idev_element *e);
40 static void element_close(idev_element *e);
41
42 /*
43  * Devices
44  */
45
46 idev_device *idev_find_device(idev_session *s, const char *name) {
47         assert_return(s, NULL);
48         assert_return(name, NULL);
49
50         return hashmap_get(s->device_map, name);
51 }
52
53 int idev_device_add(idev_device *d, const char *name) {
54         int r;
55
56         assert_return(d, -EINVAL);
57         assert_return(d->vtable, -EINVAL);
58         assert_return(d->session, -EINVAL);
59         assert_return(name, -EINVAL);
60
61         d->name = strdup(name);
62         if (!d->name)
63                 return -ENOMEM;
64
65         r = hashmap_put(d->session->device_map, d->name, d);
66         if (r < 0)
67                 return r;
68
69         return 0;
70 }
71
72 idev_device *idev_device_free(idev_device *d) {
73         idev_device tmp;
74
75         if (!d)
76                 return NULL;
77
78         assert(!d->enabled);
79         assert(!d->public);
80         assert(!d->links);
81         assert(d->vtable);
82         assert(d->vtable->free);
83
84         if (d->name)
85                 hashmap_remove_value(d->session->device_map, d->name, d);
86
87         tmp = *d;
88         d->vtable->free(d);
89
90         free(tmp.name);
91
92         return NULL;
93 }
94
95 int idev_device_feed(idev_device *d, idev_data *data) {
96         assert(d);
97         assert(data);
98         assert(data->type < IDEV_DATA_CNT);
99
100         if (d->vtable->feed)
101                 return d->vtable->feed(d, data);
102         else
103                 return 0;
104 }
105
106 void idev_device_feedback(idev_device *d, idev_data *data) {
107         idev_link *l;
108
109         assert(d);
110         assert(data);
111         assert(data->type < IDEV_DATA_CNT);
112
113         LIST_FOREACH(links_by_device, l, d->links)
114                 idev_element_feedback(l->element, data);
115 }
116
117 static void device_attach(idev_device *d, idev_link *l) {
118         assert(d);
119         assert(l);
120
121         if (d->vtable->attach)
122                 d->vtable->attach(d, l);
123
124         if (d->enabled)
125                 element_open(l->element);
126 }
127
128 static void device_detach(idev_device *d, idev_link *l) {
129         assert(d);
130         assert(l);
131
132         if (d->enabled)
133                 element_close(l->element);
134
135         if (d->vtable->detach)
136                 d->vtable->detach(d, l);
137 }
138
139 void idev_device_enable(idev_device *d) {
140         idev_link *l;
141
142         assert(d);
143
144         if (!d->enabled) {
145                 d->enabled = true;
146                 LIST_FOREACH(links_by_device, l, d->links)
147                         element_open(l->element);
148         }
149 }
150
151 void idev_device_disable(idev_device *d) {
152         idev_link *l;
153
154         assert(d);
155
156         if (d->enabled) {
157                 d->enabled = false;
158                 LIST_FOREACH(links_by_device, l, d->links)
159                         element_close(l->element);
160         }
161 }
162
163 /*
164  * Elements
165  */
166
167 idev_element *idev_find_element(idev_session *s, const char *name) {
168         assert_return(s, NULL);
169         assert_return(name, NULL);
170
171         return hashmap_get(s->element_map, name);
172 }
173
174 int idev_element_add(idev_element *e, const char *name) {
175         int r;
176
177         assert_return(e, -EINVAL);
178         assert_return(e->vtable, -EINVAL);
179         assert_return(e->session, -EINVAL);
180         assert_return(name, -EINVAL);
181
182         e->name = strdup(name);
183         if (!e->name)
184                 return -ENOMEM;
185
186         r = hashmap_put(e->session->element_map, e->name, e);
187         if (r < 0)
188                 return r;
189
190         return 0;
191 }
192
193 idev_element *idev_element_free(idev_element *e) {
194         idev_element tmp;
195
196         if (!e)
197                 return NULL;
198
199         assert(!e->enabled);
200         assert(!e->links);
201         assert(e->n_open == 0);
202         assert(e->vtable);
203         assert(e->vtable->free);
204
205         if (e->name)
206                 hashmap_remove_value(e->session->element_map, e->name, e);
207
208         tmp = *e;
209         e->vtable->free(e);
210
211         free(tmp.name);
212
213         return NULL;
214 }
215
216 int idev_element_feed(idev_element *e, idev_data *data) {
217         int r, error = 0;
218         idev_link *l;
219
220         assert(e);
221         assert(data);
222         assert(data->type < IDEV_DATA_CNT);
223
224         LIST_FOREACH(links_by_element, l, e->links) {
225                 r = idev_device_feed(l->device, data);
226                 if (r != 0)
227                         error = r;
228         }
229
230         return error;
231 }
232
233 void idev_element_feedback(idev_element *e, idev_data *data) {
234         assert(e);
235         assert(data);
236         assert(data->type < IDEV_DATA_CNT);
237
238         if (e->vtable->feedback)
239                e->vtable->feedback(e, data);
240 }
241
242 static void element_open(idev_element *e) {
243         assert(e);
244
245         if (e->n_open++ == 0 && e->vtable->open)
246                 e->vtable->open(e);
247 }
248
249 static void element_close(idev_element *e) {
250         assert(e);
251         assert(e->n_open > 0);
252
253         if (--e->n_open == 0 && e->vtable->close)
254                 e->vtable->close(e);
255 }
256
257 static void element_enable(idev_element *e) {
258         assert(e);
259
260         if (!e->enabled) {
261                 e->enabled = true;
262                 if (e->vtable->enable)
263                         e->vtable->enable(e);
264         }
265 }
266
267 static void element_disable(idev_element *e) {
268         assert(e);
269
270         if (e->enabled) {
271                 e->enabled = false;
272                 if (e->vtable->disable)
273                         e->vtable->disable(e);
274         }
275 }
276
277 /*
278  * Sessions
279  */
280
281 static int session_raise(idev_session *s, idev_event *ev) {
282         return s->event_fn(s, s->userdata, ev);
283 }
284
285 static int session_raise_device_add(idev_session *s, idev_device *d) {
286         idev_event event = {
287                 .type = IDEV_EVENT_DEVICE_ADD,
288                 .device_add = {
289                         .device = d,
290                 },
291         };
292
293         return session_raise(s, &event);
294 }
295
296 static int session_raise_device_remove(idev_session *s, idev_device *d) {
297         idev_event event = {
298                 .type = IDEV_EVENT_DEVICE_REMOVE,
299                 .device_remove = {
300                         .device = d,
301                 },
302         };
303
304         return session_raise(s, &event);
305 }
306
307 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
308         idev_event event = {
309                 .type = IDEV_EVENT_DEVICE_DATA,
310                 .device_data = {
311                         .device = d,
312                         .data = *data,
313                 },
314         };
315
316         return session_raise(s, &event);
317 }
318
319 static int session_add_device(idev_session *s, idev_device *d) {
320         int r;
321
322         assert(s);
323         assert(d);
324
325         log_debug("idev: %s: add device '%s'", s->name, d->name);
326
327         d->public = true;
328         r = session_raise_device_add(s, d);
329         if (r != 0) {
330                 d->public = false;
331                 goto error;
332         }
333
334         return 0;
335
336 error:
337         if (r < 0)
338                 log_debug("idev: %s: error while adding device '%s': %s",
339                           s->name, d->name, strerror(-r));
340         return r;
341 }
342
343 static int session_remove_device(idev_session *s, idev_device *d) {
344         int r, error = 0;
345
346         assert(s);
347         assert(d);
348
349         log_debug("idev: %s: remove device '%s'", s->name, d->name);
350
351         d->public = false;
352         r = session_raise_device_remove(s, d);
353         if (r != 0)
354                 error = r;
355
356         idev_device_disable(d);
357
358         if (error < 0)
359                 log_debug("idev: %s: error while removing device '%s': %s",
360                           s->name, d->name, strerror(-error));
361         idev_device_free(d);
362         return error;
363 }
364
365 static int session_add_element(idev_session *s, idev_element *e) {
366         assert(s);
367         assert(e);
368
369         log_debug("idev: %s: add element '%s'", s->name, e->name);
370
371         if (s->enabled)
372                 element_enable(e);
373
374         return 0;
375 }
376
377 static int session_remove_element(idev_session *s, idev_element *e) {
378         int r, error = 0;
379         idev_device *d;
380         idev_link *l;
381
382         assert(s);
383         assert(e);
384
385         log_debug("idev: %s: remove element '%s'", s->name, e->name);
386
387         while ((l = e->links)) {
388                 d = l->device;
389                 LIST_REMOVE(links_by_device, d->links, l);
390                 LIST_REMOVE(links_by_element, e->links, l);
391                 device_detach(d, l);
392
393                 if (!d->links) {
394                         r = session_remove_device(s, d);
395                         if (r != 0)
396                                 error = r;
397                 }
398
399                 l->device = NULL;
400                 l->element = NULL;
401                 free(l);
402         }
403
404         element_disable(e);
405
406         if (error < 0)
407                 log_debug("idev: %s: error while removing element '%s': %s",
408                           s->name, e->name, strerror(-r));
409         idev_element_free(e);
410         return error;
411 }
412
413 idev_session *idev_find_session(idev_context *c, const char *name) {
414         assert_return(c, NULL);
415         assert_return(name, NULL);
416
417         return hashmap_get(c->session_map, name);
418 }
419
420 int idev_session_new(idev_session **out,
421                      idev_context *c,
422                      unsigned int flags,
423                      const char *name,
424                      idev_event_fn event_fn,
425                      void *userdata) {
426         _cleanup_(idev_session_freep) idev_session *s = NULL;
427         int r;
428
429         assert_return(out, -EINVAL);
430         assert_return(c, -EINVAL);
431         assert_return(name, -EINVAL);
432         assert_return(event_fn, -EINVAL);
433         assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL);
434         assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL);
435         assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL);
436
437         s = new0(idev_session, 1);
438         if (!s)
439                 return -ENOMEM;
440
441         s->context = idev_context_ref(c);
442         s->custom = flags & IDEV_SESSION_CUSTOM;
443         s->managed = flags & IDEV_SESSION_MANAGED;
444         s->event_fn = event_fn;
445         s->userdata = userdata;
446
447         s->name = strdup(name);
448         if (!s->name)
449                 return -ENOMEM;
450
451         if (s->managed) {
452                 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
453                 if (r < 0)
454                         return r;
455         }
456
457         s->element_map = hashmap_new(string_hash_func, string_compare_func);
458         if (!s->element_map)
459                 return -ENOMEM;
460
461         s->device_map = hashmap_new(string_hash_func, string_compare_func);
462         if (!s->device_map)
463                 return -ENOMEM;
464
465         r = hashmap_put(c->session_map, s->name, s);
466         if (r < 0)
467                 return r;
468
469         *out = s;
470         s = NULL;
471         return 0;
472 }
473
474 idev_session *idev_session_free(idev_session *s) {
475         idev_element *e;
476
477         if (!s)
478                 return NULL;
479
480         while ((e = hashmap_first(s->element_map)))
481                 session_remove_element(s, e);
482
483         assert(hashmap_size(s->device_map) == 0);
484
485         if (s->name)
486                 hashmap_remove_value(s->context->session_map, s->name, s);
487
488         s->context = idev_context_unref(s->context);
489         hashmap_free(s->device_map);
490         hashmap_free(s->element_map);
491         free(s->path);
492         free(s->name);
493         free(s);
494
495         return NULL;
496 }
497
498 bool idev_session_is_enabled(idev_session *s) {
499         return s && s->enabled;
500 }
501
502 void idev_session_enable(idev_session *s) {
503         idev_element *e;
504         Iterator i;
505
506         assert(s);
507
508         if (!s->enabled) {
509                 s->enabled = true;
510                 HASHMAP_FOREACH(e, s->element_map, i)
511                         element_enable(e);
512         }
513 }
514
515 void idev_session_disable(idev_session *s) {
516         idev_element *e;
517         Iterator i;
518
519         assert(s);
520
521         if (s->enabled) {
522                 s->enabled = false;
523                 HASHMAP_FOREACH(e, s->element_map, i)
524                         element_disable(e);
525         }
526 }
527
528 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
529         idev_element *e;
530         dev_t devnum;
531         int r;
532
533         assert_return(s, -EINVAL);
534         assert_return(ud, -EINVAL);
535
536         devnum = udev_device_get_devnum(ud);
537         if (devnum == 0)
538                 return 0;
539
540         e = idev_find_evdev(s, devnum);
541         if (e)
542                 return 0;
543
544         r = idev_evdev_new(&e, s, ud);
545         if (r < 0)
546                 return r;
547
548         r = session_add_element(s, e);
549         if (r != 0)
550                 return r;
551
552         return 0;
553 }
554
555 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
556         idev_element *e;
557         dev_t devnum;
558
559         assert(s);
560         assert(ud);
561
562         devnum = udev_device_get_devnum(ud);
563         if (devnum == 0)
564                 return 0;
565
566         e = idev_find_evdev(s, devnum);
567         if (!e)
568                 return 0;
569
570         return session_remove_element(s, e);
571 }
572
573 /*
574  * Contexts
575  */
576
577 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
578         _cleanup_(idev_context_unrefp) idev_context *c = NULL;
579
580         assert_return(out, -EINVAL);
581         assert_return(event, -EINVAL);
582
583         c = new0(idev_context, 1);
584         if (!c)
585                 return -ENOMEM;
586
587         c->ref = 1;
588         c->event = sd_event_ref(event);
589
590         if (sysbus)
591                 c->sysbus = sd_bus_ref(sysbus);
592
593         c->session_map = hashmap_new(string_hash_func, string_compare_func);
594         if (!c->session_map)
595                 return -ENOMEM;
596
597         c->data_map = hashmap_new(string_hash_func, string_compare_func);
598         if (!c->data_map)
599                 return -ENOMEM;
600
601         *out = c;
602         c = NULL;
603         return 0;
604 }
605
606 static void context_cleanup(idev_context *c) {
607         assert(hashmap_size(c->data_map) == 0);
608         assert(hashmap_size(c->session_map) == 0);
609
610         hashmap_free(c->data_map);
611         hashmap_free(c->session_map);
612         c->sysbus = sd_bus_unref(c->sysbus);
613         c->event = sd_event_unref(c->event);
614         free(c);
615 }
616
617 idev_context *idev_context_ref(idev_context *c) {
618         assert_return(c, NULL);
619         assert_return(c->ref > 0, NULL);
620
621         ++c->ref;
622         return c;
623 }
624
625 idev_context *idev_context_unref(idev_context *c) {
626         if (!c)
627                 return NULL;
628
629         assert_return(c->ref > 0, NULL);
630
631         if (--c->ref == 0)
632                 context_cleanup(c);
633
634         return NULL;
635 }