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