]>
Commit | Line | Data |
---|---|---|
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 |
27 | struct track_item { |
28 | unsigned n_ref; | |
29 | char *name; | |
30 | sd_bus_slot *slot; | |
31 | }; | |
32 | ||
8f8f05a9 LP |
33 | struct 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 |
70 | static 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 | ||
80 | DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free); | |
81 | ||
8f8f05a9 LP |
82 | static 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 | ||
113 | static 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 |
123 | static 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 | 210 | static 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 | ||
411 | void 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 |
430 | void 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 | } |