chiark / gitweb /
d2c26746ed5ca87a496e28346e0d4dc016b4cad5
[elogind.git] / src / libelogind / sd-bus / bus-track.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2013 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "sd-bus.h"
21
22 #include "alloc-util.h"
23 #include "bus-internal.h"
24 #include "bus-track.h"
25 #include "bus-util.h"
26
27 struct track_item {
28         unsigned n_ref;
29         char *name;
30         sd_bus_slot *slot;
31 };
32
33 struct sd_bus_track {
34         unsigned n_ref;
35         sd_bus *bus;
36         sd_bus_track_handler_t handler;
37         void *userdata;
38         Hashmap *names;
39         LIST_FIELDS(sd_bus_track, queue);
40         Iterator iterator;
41         bool in_queue:1;
42         bool modified:1;
43         bool recursive:1;
44 };
45
46 #define MATCH_PREFIX                                        \
47         "type='signal',"                                    \
48         "sender='org.freedesktop.DBus',"                    \
49         "path='/org/freedesktop/DBus',"                     \
50         "interface='org.freedesktop.DBus',"                 \
51         "member='NameOwnerChanged',"                        \
52         "arg0='"
53
54 #define MATCH_SUFFIX \
55         "'"
56
57 #define MATCH_FOR_NAME(name)                                            \
58         ({                                                              \
59                 char *_x;                                               \
60                 size_t _l = strlen(name);                               \
61                 _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \
62                 strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \
63                 _x;                                                     \
64         })
65
66 static struct track_item* track_item_free(struct track_item *i) {
67
68         if (!i)
69                 return NULL;
70
71         sd_bus_slot_unref(i->slot);
72         free(i->name);
73         free(i);
74
75         return NULL;
76 }
77
78 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
79
80 static void bus_track_add_to_queue(sd_bus_track *track) {
81         assert(track);
82
83         if (track->in_queue)
84                 return;
85
86         if (!track->handler)
87                 return;
88
89         LIST_PREPEND(queue, track->bus->track_queue, track);
90         track->in_queue = true;
91 }
92
93 static void bus_track_remove_from_queue(sd_bus_track *track) {
94         assert(track);
95
96         if (!track->in_queue)
97                 return;
98
99         LIST_REMOVE(queue, track->bus->track_queue, track);
100         track->in_queue = false;
101 }
102
103 static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
104         struct track_item *i;
105
106         assert(track);
107         assert(name);
108
109         i = hashmap_remove(track->names, name);
110         if (!i)
111                 return 0;
112
113         track_item_free(i);
114
115         if (hashmap_isempty(track->names))
116                 bus_track_add_to_queue(track);
117
118         track->modified = true;
119         return 1;
120 }
121
122 _public_ int sd_bus_track_new(
123                 sd_bus *bus,
124                 sd_bus_track **track,
125                 sd_bus_track_handler_t handler,
126                 void *userdata) {
127
128         sd_bus_track *t;
129
130         assert_return(bus, -EINVAL);
131         assert_return(track, -EINVAL);
132
133         if (!bus->bus_client)
134                 return -EINVAL;
135
136         t = new0(sd_bus_track, 1);
137         if (!t)
138                 return -ENOMEM;
139
140         t->n_ref = 1;
141         t->handler = handler;
142         t->userdata = userdata;
143         t->bus = sd_bus_ref(bus);
144
145         bus_track_add_to_queue(t);
146
147         *track = t;
148         return 0;
149 }
150
151 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
152
153         if (!track)
154                 return NULL;
155
156         assert(track->n_ref > 0);
157
158         track->n_ref++;
159
160         return track;
161 }
162
163 _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
164         struct track_item *i;
165
166         if (!track)
167                 return NULL;
168
169         assert(track->n_ref > 0);
170
171         if (track->n_ref > 1) {
172                 track->n_ref--;
173                 return NULL;
174         }
175
176         while ((i = hashmap_steal_first(track->names)))
177                 track_item_free(i);
178
179         bus_track_remove_from_queue(track);
180         hashmap_free(track->names);
181         sd_bus_unref(track->bus);
182         free(track);
183
184         return NULL;
185 }
186
187 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
188         sd_bus_track *track = userdata;
189         const char *name, *old, *new;
190         int r;
191
192         assert(message);
193         assert(track);
194
195         r = sd_bus_message_read(message, "sss", &name, &old, &new);
196         if (r < 0)
197                 return 0;
198
199         bus_track_remove_name_fully(track, name);
200         return 0;
201 }
202
203 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
204         _cleanup_(track_item_freep) struct track_item *n = NULL;
205         struct track_item *i;
206         const char *match;
207         int r;
208
209         assert_return(track, -EINVAL);
210         assert_return(service_name_is_valid(name), -EINVAL);
211
212         i = hashmap_get(track->names, name);
213         if (i) {
214                 if (track->recursive) {
215                         unsigned k = track->n_ref + 1;
216
217                         if (k < track->n_ref) /* Check for overflow */
218                                 return -EOVERFLOW;
219
220                         track->n_ref = k;
221                 }
222
223                 bus_track_remove_from_queue(track);
224                 return 0;
225         }
226
227         r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
228         if (r < 0)
229                 return r;
230
231         n = new0(struct track_item, 1);
232         if (!n)
233                 return -ENOMEM;
234         n->name = strdup(name);
235         if (!n->name)
236                 return -ENOMEM;
237
238         /* First, subscribe to this name */
239         match = MATCH_FOR_NAME(name);
240         r = sd_bus_add_match(track->bus, &n->slot, match, on_name_owner_changed, track);
241         if (r < 0)
242                 return r;
243
244         r = hashmap_put(track->names, n->name, n);
245         if (r < 0)
246                 return r;
247
248         /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
249         r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
250         if (r < 0) {
251                 hashmap_remove(track->names, name);
252                 return r;
253         }
254
255         n->n_ref = 1;
256         n = NULL;
257
258         bus_track_remove_from_queue(track);
259         track->modified = true;
260
261         return 1;
262 }
263
264 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
265         struct track_item *i;
266
267         assert_return(name, -EINVAL);
268
269         if (!track) /* Treat a NULL track object as an empty track object */
270                 return 0;
271
272         if (!track->recursive)
273                 return bus_track_remove_name_fully(track, name);
274
275         i = hashmap_get(track->names, name);
276         if (!i)
277                 return -EUNATCH;
278         if (i->n_ref <= 0)
279                 return -EUNATCH;
280
281         i->n_ref--;
282
283         if (i->n_ref <= 0)
284                 return bus_track_remove_name_fully(track, name);
285
286         return 1;
287 }
288
289 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
290
291         if (!track) /* Let's consider a NULL object equivalent to an empty object */
292                 return 0;
293
294         /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
295          * that this returns the number of names being watched, and multiple references to the same name are not
296          * counted. */
297
298         return hashmap_size(track->names);
299 }
300
301 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
302         assert_return(name, NULL);
303
304         if (!track) /* Let's consider a NULL object equivalent to an empty object */
305                 return NULL;
306
307         return hashmap_get(track->names, (void*) name) ? name : NULL;
308 }
309
310 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
311         const char *n = NULL;
312
313         if (!track)
314                 return NULL;
315
316         track->modified = false;
317         track->iterator = ITERATOR_FIRST;
318
319         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
320         return n;
321 }
322
323 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
324         const char *n = NULL;
325
326         if (!track)
327                 return NULL;
328
329         if (track->modified)
330                 return NULL;
331
332         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
333         return n;
334 }
335
336 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
337         const char *sender;
338
339         assert_return(track, -EINVAL);
340         assert_return(m, -EINVAL);
341
342         if (sd_bus_message_get_bus(m) != track->bus)
343                 return -EINVAL;
344
345         sender = sd_bus_message_get_sender(m);
346         if (!sender)
347                 return -EINVAL;
348
349         return sd_bus_track_add_name(track, sender);
350 }
351
352 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
353         const char *sender;
354
355         assert_return(m, -EINVAL);
356
357         if (!track) /* Treat a NULL track object as an empty track object */
358                 return 0;
359
360         if (sd_bus_message_get_bus(m) != track->bus)
361                 return -EINVAL;
362
363         sender = sd_bus_message_get_sender(m);
364         if (!sender)
365                 return -EINVAL;
366
367         return sd_bus_track_remove_name(track, sender);
368 }
369
370 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
371         assert_return(track, NULL);
372
373         return track->bus;
374 }
375
376 void bus_track_dispatch(sd_bus_track *track) {
377         int r;
378
379         assert(track);
380         assert(track->in_queue);
381         assert(track->handler);
382
383         bus_track_remove_from_queue(track);
384
385         sd_bus_track_ref(track);
386
387         r = track->handler(track, track->userdata);
388         if (r < 0)
389                 log_debug_errno(r, "Failed to process track handler: %m");
390         else if (r == 0)
391                 bus_track_add_to_queue(track);
392
393         sd_bus_track_unref(track);
394 }
395
396 #if 0 /// UNNEEDED by elogind
397 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
398         assert_return(track, NULL);
399
400         return track->userdata;
401 }
402
403 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
404         void *ret;
405
406         assert_return(track, NULL);
407
408         ret = track->userdata;
409         track->userdata = userdata;
410
411         return ret;
412 }
413 #endif // 0
414
415 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
416         assert_return(track, -EINVAL);
417
418         if (track->recursive == !!b)
419                 return 0;
420
421         if (!hashmap_isempty(track->names))
422                 return -EBUSY;
423
424         track->recursive = b;
425         return 0;
426 }
427
428 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
429         assert_return(track, -EINVAL);
430
431         return track->recursive;
432 }
433
434 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
435         const char *sender;
436
437         assert_return(m, -EINVAL);
438
439         if (!track) /* Let's consider a NULL object equivalent to an empty object */
440                 return 0;
441
442         if (sd_bus_message_get_bus(m) != track->bus)
443                 return -EINVAL;
444
445         sender = sd_bus_message_get_sender(m);
446         if (!sender)
447                 return -EINVAL;
448
449         return sd_bus_track_count_name(track, sender);
450 }
451
452 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
453         struct track_item *i;
454
455         assert_return(service_name_is_valid(name), -EINVAL);
456
457         if (!track) /* Let's consider a NULL object equivalent to an empty object */
458                 return 0;
459
460         i = hashmap_get(track->names, name);
461         if (!i)
462                 return 0;
463
464         return i->n_ref;
465 }