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