]> git.proxmox.com Git - systemd.git/blame - src/libsystemd/sd-bus/bus-track.c
New upstream version 236
[systemd.git] / src / libsystemd / sd-bus / bus-track.c
CommitLineData
52ad194e 1/* SPDX-License-Identifier: LGPL-2.1+ */
60f067b4
JS
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"
db2df898
MP
22
23#include "alloc-util.h"
60f067b4
JS
24#include "bus-internal.h"
25#include "bus-track.h"
db2df898 26#include "bus-util.h"
60f067b4 27
8a584da2
MP
28struct track_item {
29 unsigned n_ref;
30 char *name;
31 sd_bus_slot *slot;
32};
33
60f067b4
JS
34struct sd_bus_track {
35 unsigned n_ref;
8a584da2 36 unsigned n_adding; /* are we in the process of adding a new name? */
60f067b4
JS
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;
8a584da2
MP
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);
60f067b4
JS
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); \
52ad194e 66 _x = alloca(STRLEN(MATCH_PREFIX)+_l+STRLEN(MATCH_SUFFIX)+1); \
60f067b4
JS
67 strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \
68 _x; \
69 })
70
8a584da2
MP
71static 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
81DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
82
60f067b4
JS
83static void bus_track_add_to_queue(sd_bus_track *track) {
84 assert(track);
85
8a584da2
MP
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? */
60f067b4
JS
90 if (track->in_queue)
91 return;
92
8a584da2
MP
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? */
60f067b4
JS
103 if (!track->handler)
104 return;
105
8a584da2
MP
106 /* Already closed? */
107 if (!track->in_list)
108 return;
109
60f067b4
JS
110 LIST_PREPEND(queue, track->bus->track_queue, track);
111 track->in_queue = true;
112}
113
114static 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
8a584da2
MP
124static 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
60f067b4
JS
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
e3bff60a
MP
153 if (!bus->bus_client)
154 return -EINVAL;
155
60f067b4
JS
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
8a584da2
MP
165 LIST_PREPEND(tracks, bus->tracks, t);
166 t->in_list = true;
167
60f067b4
JS
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) {
4c89c718
MP
175
176 if (!track)
177 return NULL;
60f067b4
JS
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) {
60f067b4
JS
187 if (!track)
188 return NULL;
189
190 assert(track->n_ref > 0);
191
192 if (track->n_ref > 1) {
aa27b158 193 track->n_ref--;
60f067b4
JS
194 return NULL;
195 }
196
8a584da2
MP
197 if (track->in_list)
198 LIST_REMOVE(tracks, track->bus->tracks, track);
60f067b4
JS
199
200 bus_track_remove_from_queue(track);
52ad194e 201 hashmap_free_with_destructor(track->names, track_item_free);
60f067b4 202 sd_bus_unref(track->bus);
8a584da2 203 return mfree(track);
60f067b4
JS
204}
205
e3bff60a 206static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
60f067b4
JS
207 sd_bus_track *track = userdata;
208 const char *name, *old, *new;
209 int r;
210
60f067b4
JS
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
8a584da2 218 bus_track_remove_name_fully(track, name);
60f067b4
JS
219 return 0;
220}
221
222_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
8a584da2
MP
223 _cleanup_(track_item_freep) struct track_item *n = NULL;
224 struct track_item *i;
60f067b4
JS
225 const char *match;
226 int r;
227
228 assert_return(track, -EINVAL);
229 assert_return(service_name_is_valid(name), -EINVAL);
230
8a584da2
MP
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
5eef597e 246 r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
60f067b4
JS
247 if (r < 0)
248 return r;
249
8a584da2 250 n = new0(struct track_item, 1);
60f067b4
JS
251 if (!n)
252 return -ENOMEM;
8a584da2
MP
253 n->name = strdup(name);
254 if (!n->name)
255 return -ENOMEM;
60f067b4
JS
256
257 /* First, subscribe to this name */
8a584da2
MP
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);
60f067b4 267 return r;
8a584da2 268 }
60f067b4 269
8a584da2
MP
270 r = hashmap_put(track->names, n->name, n);
271 if (r < 0) {
272 bus_track_add_to_queue(track);
60f067b4 273 return r;
8a584da2 274 }
60f067b4 275
8a584da2
MP
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--;
60f067b4 280 if (r < 0) {
8a584da2
MP
281 hashmap_remove(track->names, name);
282 bus_track_add_to_queue(track);
60f067b4
JS
283 return r;
284 }
285
8a584da2 286 n->n_ref = 1;
60f067b4 287 n = NULL;
60f067b4
JS
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) {
8a584da2 296 struct track_item *i;
60f067b4
JS
297
298 assert_return(name, -EINVAL);
299
8a584da2 300 if (!track) /* Treat a NULL track object as an empty track object */
60f067b4
JS
301 return 0;
302
8a584da2
MP
303 if (!track->recursive)
304 return bus_track_remove_name_fully(track, name);
60f067b4 305
8a584da2
MP
306 i = hashmap_get(track->names, name);
307 if (!i)
308 return -EUNATCH;
309 if (i->n_ref <= 0)
310 return -EUNATCH;
60f067b4 311
8a584da2
MP
312 i->n_ref--;
313
314 if (i->n_ref <= 0)
315 return bus_track_remove_name_fully(track, name);
60f067b4
JS
316
317 return 1;
318}
319
320_public_ unsigned sd_bus_track_count(sd_bus_track *track) {
8a584da2
MP
321
322 if (!track) /* Let's consider a NULL object equivalent to an empty object */
60f067b4
JS
323 return 0;
324
8a584da2
MP
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
60f067b4
JS
329 return hashmap_size(track->names);
330}
331
332_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
60f067b4
JS
333 assert_return(name, NULL);
334
8a584da2
MP
335 if (!track) /* Let's consider a NULL object equivalent to an empty object */
336 return NULL;
337
60f067b4
JS
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;
5eef597e 348 track->iterator = ITERATOR_FIRST;
60f067b4 349
86f210e9 350 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
60f067b4
JS
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
86f210e9 363 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
60f067b4
JS
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
8a584da2
MP
373 if (sd_bus_message_get_bus(m) != track->bus)
374 return -EINVAL;
375
60f067b4
JS
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
60f067b4
JS
386 assert_return(m, -EINVAL);
387
8a584da2
MP
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
60f067b4
JS
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
407void bus_track_dispatch(sd_bus_track *track) {
408 int r;
409
410 assert(track);
60f067b4
JS
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)
f47781d8 419 log_debug_errno(r, "Failed to process track handler: %m");
60f067b4
JS
420 else if (r == 0)
421 bus_track_add_to_queue(track);
422
423 sd_bus_track_unref(track);
424}
425
8a584da2 426void bus_track_close(sd_bus_track *track) {
8a584da2
MP
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 */
52ad194e 444 hashmap_clear_with_destructor(track->names, track_item_free);
8a584da2
MP
445
446 /* Invoke handler */
447 if (track->handler)
448 bus_track_dispatch(track);
449}
450
60f067b4
JS
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}
8a584da2
MP
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}