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