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