core: fix duplicate values in NM-specific PropertiesChanged signals
[NetworkManager.git] / src / nm-exported-object.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Copyright 2014-2015 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include <stdarg.h>
24 #include <string.h>
25
26 #include "nm-exported-object.h"
27 #include "nm-bus-manager.h"
28
29 static GHashTable *prefix_counters;
30 static gboolean quitting = FALSE;
31
32
33 #if NM_MORE_ASSERTS >= 2
34 #define _ASSERT_NO_EARLY_EXPORT
35 #endif
36
37 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMExportedObject, nm_exported_object, G_TYPE_DBUS_OBJECT_SKELETON,
38                                   prefix_counters = g_hash_table_new (g_str_hash, g_str_equal);
39                                   )
40
41 typedef struct {
42         GSList *interfaces;
43
44         NMBusManager *bus_mgr;
45         char *path;
46
47         GHashTable *pending_notifies;
48         guint notify_idle_id;
49
50 #ifdef _ASSERT_NO_EARLY_EXPORT
51         gboolean _constructed;
52 #endif
53 } NMExportedObjectPrivate;
54
55 #define NM_EXPORTED_OBJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_EXPORTED_OBJECT, NMExportedObjectPrivate))
56
57 typedef struct {
58         GHashTable *properties;
59         GSList *skeleton_types;
60         GArray *methods;
61 } NMExportedObjectClassInfo;
62
63 GQuark nm_exported_object_class_info_quark (void);
64 G_DEFINE_QUARK (NMExportedObjectClassInfo, nm_exported_object_class_info)
65
66 /*****************************************************************************/
67
68 #define _NMLOG_PREFIX_NAME                "exported-object"
69 #define _NMLOG_DOMAIN                     LOGD_CORE
70
71 #define _NMLOG(level, ...) \
72     nm_log (level, _NMLOG_DOMAIN, \
73             "%s[%p]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
74             _NMLOG_PREFIX_NAME, (self) \
75             _NM_UTILS_MACRO_REST (__VA_ARGS__))
76
77 /*****************************************************************************/
78
79 /* "AddConnectionUnsaved" -> "handle-add-connection-unsaved" */
80 char *
81 nm_exported_object_skeletonify_method_name (const char *dbus_method_name)
82 {
83         GString *out;
84         const char *p;
85
86         out = g_string_new ("handle");
87         for (p = dbus_method_name; *p; p++) {
88                 if (g_ascii_isupper (*p) || p == dbus_method_name) {
89                         g_string_append_c (out, '-');
90                         g_string_append_c (out, g_ascii_tolower (*p));
91                 } else
92                         g_string_append_c (out, *p);
93         }
94
95         return g_string_free (out, FALSE);
96 }
97
98 /* "can-modify" -> "CanModify" */
99 static char *
100 dbusify_name (const char *gobject_name)
101 {
102         GString *out;
103         const char *p;
104         gboolean capitalize = TRUE;
105
106         out = g_string_new ("");
107         for (p = gobject_name; *p; p++) {
108                 if (capitalize) {
109                         g_string_append_c (out, g_ascii_toupper (*p));
110                         capitalize = FALSE;
111                 } else if (*p == '-')
112                         capitalize = TRUE;
113                 else
114                         g_string_append_c (out, *p);
115         }
116
117         return g_string_free (out, FALSE);
118 }
119
120 /* "can_modify" -> "can-modify". Returns %NULL if @gobject_name contains no underscores */
121 static char *
122 hyphenify_name (const char *gobject_name)
123 {
124         char *hyphen_name, *p;
125
126         if (!strchr (gobject_name, '_'))
127                 return NULL;
128
129         hyphen_name = g_strdup (gobject_name);
130         for (p = hyphen_name; *p; p++) {
131                 if (*p == '_')
132                         *p = '-';
133         }
134         return hyphen_name;
135 }
136
137 /* Called when an #NMExportedObject emits a signal that corresponds to a D-Bus
138  * signal, and re-emits that signal on the correct skeleton object as well.
139  */
140 static gboolean
141 nm_exported_object_signal_hook (GSignalInvocationHint *ihint,
142                                 guint                  n_param_values,
143                                 const GValue          *param_values,
144                                 gpointer               data)
145 {
146         NMExportedObject *self = g_value_get_object (&param_values[0]);
147         NMExportedObjectPrivate *priv;
148         GSignalQuery *signal_info = data;
149         GDBusInterfaceSkeleton *interface = NULL;
150         GSList *iter;
151         GValue *dbus_param_values;
152         int i;
153
154         priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
155         if (!priv->path)
156                 return TRUE;
157
158         for (iter = priv->interfaces; iter; iter = iter->next) {
159                 if (g_type_is_a (G_OBJECT_TYPE (iter->data), signal_info->itype)) {
160                         interface = G_DBUS_INTERFACE_SKELETON (iter->data);
161                         break;
162                 }
163         }
164         g_return_val_if_fail (interface != NULL, TRUE);
165
166         dbus_param_values = g_new0 (GValue, n_param_values);
167         g_value_init (&dbus_param_values[0], G_OBJECT_TYPE (interface));
168         g_value_set_object (&dbus_param_values[0], interface);
169         for (i = 1; i < n_param_values; i++) {
170                 if (g_type_is_a (param_values[i].g_type, NM_TYPE_EXPORTED_OBJECT)) {
171                         NMExportedObject *arg = g_value_get_object (&param_values[i]);
172
173                         g_value_init (&dbus_param_values[i], G_TYPE_STRING);
174                         if (arg && nm_exported_object_is_exported (arg))
175                                 g_value_set_string (&dbus_param_values[i], nm_exported_object_get_path (arg));
176                         else
177                                 g_value_set_string (&dbus_param_values[i], "/");
178                 } else {
179                         g_value_init (&dbus_param_values[i], param_values[i].g_type);
180                         g_value_copy (&param_values[i], &dbus_param_values[i]);
181                 }
182         }
183
184         g_signal_emitv (dbus_param_values, signal_info->signal_id, 0, NULL);
185
186         for (i = 0; i < n_param_values; i++)
187                 g_value_unset (&dbus_param_values[i]);
188         g_free (dbus_param_values);
189
190         return TRUE;
191 }
192
193 /**
194  * nm_exported_object_class_add_interface:
195  * @object_class: an #NMExportedObjectClass
196  * @dbus_skeleton_type: the type of the #GDBusInterfaceSkeleton to add
197  * @...: method name / handler pairs, %NULL-terminated
198  *
199  * Adds @dbus_skeleton_type to the list of D-Bus interfaces implemented by
200  * @object_class. Instances of @object_class will automatically have a skeleton
201  * of that type created, which will be exported when you call
202  * nm_exported_object_export().
203  *
204  * The skeleton's properties will be initialized from the #NMExportedObject's,
205  * and bidirectional bindings will be set up between them. When exported
206  * properties change, both the org.freedesktop.DBus.Properties.PropertiesChanged
207  * signal and the traditional NetworkManager PropertiesChanged signal will be
208  * emitted.
209  *
210  * When a signal is emitted on an #NMExportedObject that has the same name as a
211  * signal on @dbus_skeleton_type, it will automatically be emitted on the
212  * skeleton as well; #NMExportedObject arguments in the signal will be converted
213  * to D-Bus object paths in the skeleton signal.
214  *
215  * The arguments after @dbus_skeleton_type are pairs of D-Bus method names (in
216  * CamelCase), and the corresponding handlers for them (which must have the same
217  * prototype as the corresponding "handle-..." signal on @dbus_skeleton_type,
218  * except with no return value, and with the first argument being an object of
219  * @object_class's type, not of @dbus_skeleton_type).
220  *
221  * It is a programmer error if:
222  *   - @object_class does not define a property of the same name and type as
223  *     each of @dbus_skeleton_type's properties.
224  *   - @object_class does not define a signal with the same name and arguments
225  *     as each of @dbus_skeleton_type's signals.
226  *   - the list of method names includes any names that do not correspond to
227  *     "handle-" signals on @dbus_skeleton_type.
228  *   - the list of method names does not include every method defined by
229  *     @dbus_skeleton_type.
230  */
231 void
232 nm_exported_object_class_add_interface (NMExportedObjectClass *object_class,
233                                         GType                  dbus_skeleton_type,
234                                         ...)
235 {
236         NMExportedObjectClassInfo *classinfo;
237         NMExportedObjectDBusMethodImpl method;
238         va_list ap;
239         const char *method_name;
240         GCallback impl;
241         gs_free GType *interfaces = NULL;
242         guint n_interfaces;
243         guint n_signals, n_method_signals;
244         guint object_signal_id;
245         GSignalQuery query;
246         int i, s;
247         GObjectClass *dbus_object_class;
248         gs_free GParamSpec **dbus_properties = NULL;
249         GParamSpec *object_property;
250         guint n_dbus_properties;
251
252         g_return_if_fail (NM_IS_EXPORTED_OBJECT_CLASS (object_class));
253         g_return_if_fail (g_type_is_a (dbus_skeleton_type, G_TYPE_DBUS_INTERFACE_SKELETON));
254
255         classinfo = g_slice_new (NMExportedObjectClassInfo);
256         classinfo->skeleton_types = NULL;
257         classinfo->methods = g_array_new (FALSE, FALSE, sizeof (NMExportedObjectDBusMethodImpl));
258         classinfo->properties = g_hash_table_new (g_str_hash, g_str_equal);
259         g_type_set_qdata (G_TYPE_FROM_CLASS (object_class),
260                           nm_exported_object_class_info_quark (), classinfo);
261
262         classinfo->skeleton_types = g_slist_prepend (classinfo->skeleton_types,
263                                                      GSIZE_TO_POINTER (dbus_skeleton_type));
264
265         /* Ensure @dbus_skeleton_type's class_init has run, so its signals/properties
266          * will be defined.
267          */
268         dbus_object_class = g_type_class_ref (dbus_skeleton_type);
269
270         /* Add method implementations from the varargs */
271         va_start (ap, dbus_skeleton_type);
272         while ((method_name = va_arg (ap, const char *)) && (impl = va_arg (ap, GCallback))) {
273                 method.dbus_skeleton_type = dbus_skeleton_type;
274                 method.method_name = nm_exported_object_skeletonify_method_name (method_name);
275                 g_assert (g_signal_lookup (method.method_name, dbus_skeleton_type) != 0);
276                 method.impl = impl;
277
278                 g_array_append_val (classinfo->methods, method);
279         }
280         va_end (ap);
281
282         /* Properties */
283         dbus_properties = g_object_class_list_properties (dbus_object_class, &n_dbus_properties);
284         for (i = 0; i < n_dbus_properties; i++) {
285                 char *hyphen_name;
286
287                 if (g_str_has_prefix (dbus_properties[i]->name, "g-"))
288                         continue;
289
290                 object_property = g_object_class_find_property (G_OBJECT_CLASS (object_class),
291                                                                 dbus_properties[i]->name);
292                 g_assert (object_property != NULL);
293                 g_assert (object_property->value_type == dbus_properties[i]->value_type);
294
295                 g_assert (!g_hash_table_contains (classinfo->properties, dbus_properties[i]->name));
296                 g_hash_table_insert (classinfo->properties,
297                                      g_strdup (dbus_properties[i]->name),
298                                      dbusify_name (dbus_properties[i]->name));
299                 hyphen_name = hyphenify_name (dbus_properties[i]->name);
300                 if (hyphen_name) {
301                         g_assert (!g_hash_table_contains (classinfo->properties, hyphen_name));
302                         g_hash_table_insert (classinfo->properties,
303                                              hyphen_name,
304                                              dbusify_name (dbus_properties[i]->name));
305                 }
306         }
307
308         /* Signals. Unlike g_object_class_list_properties(), g_signal_list_ids() is
309          * "shallow", so we need to query each implemented gdbus-generated interface
310          * separately.
311          */
312         interfaces = g_type_interfaces (dbus_skeleton_type, &n_interfaces);
313         n_method_signals = 0;
314         for (i = 0; i < n_interfaces; i++) {
315                 gs_free guint *dbus_signals = NULL;
316
317                 dbus_signals = g_signal_list_ids (interfaces[i], &n_signals);
318                 for (s = 0; s < n_signals; s++) {
319                         g_signal_query (dbus_signals[s], &query);
320
321                         /* PropertiesChanged is handled specially */
322                         if (!strcmp (query.signal_name, "properties-changed"))
323                                 continue;
324
325                         if (g_str_has_prefix (query.signal_name, "handle-")) {
326                                 n_method_signals++;
327                                 continue;
328                         }
329
330                         object_signal_id = g_signal_lookup (query.signal_name, G_TYPE_FROM_CLASS (object_class));
331                         g_assert (object_signal_id != 0);
332
333                         g_signal_add_emission_hook (object_signal_id, 0,
334                                                     nm_exported_object_signal_hook,
335                                                     g_memdup (&query, sizeof (query)),
336                                                     g_free);
337                 }
338         }
339
340         g_assert_cmpint (n_method_signals, ==, classinfo->methods->len);
341
342         g_type_class_unref (dbus_object_class);
343 }
344
345 /* "meta-marshaller" that receives the skeleton "handle-foo" signal, replaces
346  * the skeleton object with an #NMExportedObject in the parameters, drops the
347  * user_data parameter, and adds a "TRUE" return value (indicating to gdbus that
348  * the signal was handled).
349  */
350 static void
351 nm_exported_object_meta_marshal (GClosure *closure, GValue *return_value,
352                                  guint n_param_values, const GValue *param_values,
353                                  gpointer invocation_hint, gpointer marshal_data)
354 {
355         GValue *local_param_values;
356
357         local_param_values = g_new0 (GValue, n_param_values);
358         g_value_init (&local_param_values[0], G_TYPE_POINTER);
359         g_value_set_pointer (&local_param_values[0], closure->data);
360         memcpy (local_param_values + 1, param_values + 1, (n_param_values - 1) * sizeof (GValue));
361
362         g_cclosure_marshal_generic (closure, NULL,
363                                     n_param_values, local_param_values,
364                                     invocation_hint,
365                                     ((GCClosure *)closure)->callback);
366         g_value_set_boolean (return_value, TRUE);
367
368         g_value_unset (&local_param_values[0]);
369         g_free (local_param_values);
370 }
371
372 GQuark _skeleton_data_quark (void);
373 G_DEFINE_QUARK (skeleton-data, _skeleton_data);
374
375 typedef struct {
376         GBinding **prop_bindings;
377         gulong *method_signals;
378 } SkeletonData;
379
380 GDBusInterfaceSkeleton *
381 nm_exported_object_skeleton_create (GType dbus_skeleton_type,
382                                     GObjectClass *object_class,
383                                     const NMExportedObjectDBusMethodImpl *methods,
384                                     guint methods_len,
385                                     GObject *target)
386 {
387         GDBusInterfaceSkeleton *interface;
388         gs_free GParamSpec **properties = NULL;
389         SkeletonData *skeleton_data;
390         guint n_properties;
391         guint i, j;
392
393         interface = G_DBUS_INTERFACE_SKELETON (g_object_new (dbus_skeleton_type, NULL));
394
395         skeleton_data = g_slice_new (SkeletonData);
396
397         /* Bind properties */
398         properties = g_object_class_list_properties (G_OBJECT_GET_CLASS (interface), &n_properties);
399         skeleton_data->prop_bindings = g_new (GBinding *, n_properties + 1);
400         for (i = 0, j = 0; i < n_properties; i++) {
401                 GParamSpec *nm_property;
402                 GBindingFlags flags;
403                 GBinding *prop_binding;
404
405                 nm_property = g_object_class_find_property (object_class, properties[i]->name);
406                 if (!nm_property)
407                         continue;
408
409                 flags = G_BINDING_SYNC_CREATE;
410                 if (   (nm_property->flags & G_PARAM_WRITABLE)
411                         && !(nm_property->flags & G_PARAM_CONSTRUCT_ONLY))
412                         flags |= G_BINDING_BIDIRECTIONAL;
413                 prop_binding = g_object_bind_property (target, properties[i]->name,
414                                                        interface, properties[i]->name,
415                                                        flags);
416                 if (prop_binding)
417                         skeleton_data->prop_bindings[j++] = prop_binding;
418         }
419         skeleton_data->prop_bindings[j++] = NULL;
420
421         /* Bind methods */
422         skeleton_data->method_signals = g_new (gulong, methods_len + 1);
423         for (i = 0, j = 0; i < methods_len; i++) {
424                 const NMExportedObjectDBusMethodImpl *method = &methods[i];
425                 GClosure *closure;
426                 gulong method_signal;
427
428                 /* ignore methods that are for a different skeleton-type. */
429                 if (   method->dbus_skeleton_type
430                     && method->dbus_skeleton_type != dbus_skeleton_type)
431                         continue;
432
433                 closure = g_cclosure_new_swap (method->impl, target, NULL);
434                 g_closure_set_meta_marshal (closure, NULL, nm_exported_object_meta_marshal);
435                 method_signal = g_signal_connect_closure (interface, method->method_name, closure, FALSE);
436
437                 if (method_signal != 0)
438                         skeleton_data->method_signals[j++] = method_signal;
439         }
440         skeleton_data->method_signals[j++] = 0;
441
442         g_object_set_qdata ((GObject *) interface, _skeleton_data_quark (), skeleton_data);
443
444         return interface;
445 }
446
447 static void
448 nm_exported_object_create_skeletons (NMExportedObject *self,
449                                      GType object_type)
450 {
451         NMExportedObjectPrivate *priv;
452         GObjectClass *object_class;
453         NMExportedObjectClassInfo *classinfo;
454         GSList *iter;
455         GDBusInterfaceSkeleton *interface;
456         const NMExportedObjectDBusMethodImpl *methods;
457         guint methods_len;
458
459         classinfo = g_type_get_qdata (object_type, nm_exported_object_class_info_quark ());
460         if (!classinfo)
461                 return;
462
463         object_class = g_type_class_peek (object_type);
464         priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
465
466         methods = classinfo->methods->len ? &g_array_index (classinfo->methods, NMExportedObjectDBusMethodImpl, 0) : NULL;
467         methods_len = classinfo->methods->len;
468
469         for (iter = classinfo->skeleton_types; iter; iter = iter->next) {
470                 interface = nm_exported_object_skeleton_create (GPOINTER_TO_SIZE (iter->data),
471                                                                 object_class,
472                                                                 methods,
473                                                                 methods_len,
474                                                                 (GObject *) self);
475
476                 g_dbus_object_skeleton_add_interface ((GDBusObjectSkeleton *) self, interface);
477
478                 priv->interfaces = g_slist_prepend (priv->interfaces, interface);
479         }
480 }
481
482 void
483 nm_exported_object_skeleton_release (GDBusInterfaceSkeleton *interface)
484 {
485         SkeletonData *skeleton_data;
486         guint j;
487
488         g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface));
489
490         skeleton_data = g_object_steal_qdata ((GObject *) interface, _skeleton_data_quark ());
491
492         for (j = 0; skeleton_data->prop_bindings[j]; j++)
493                 g_object_unref (skeleton_data->prop_bindings[j]);
494         for (j = 0; skeleton_data->method_signals[j]; j++)
495                 g_signal_handler_disconnect (interface, skeleton_data->method_signals[j]);
496
497         g_free (skeleton_data->prop_bindings);
498         g_free (skeleton_data->method_signals);
499         g_slice_free (SkeletonData, skeleton_data);
500
501         g_object_unref (interface);
502 }
503
504 static void
505 nm_exported_object_destroy_skeletons (NMExportedObject *self)
506 {
507         NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
508
509         g_return_if_fail (priv->interfaces);
510
511         while (priv->interfaces) {
512                 GDBusInterfaceSkeleton *interface = priv->interfaces->data;
513
514                 priv->interfaces = g_slist_delete_link (priv->interfaces, priv->interfaces);
515                 g_dbus_object_skeleton_remove_interface ((GDBusObjectSkeleton *) self, interface);
516                 nm_exported_object_skeleton_release (interface);
517         }
518 }
519
520 /**
521  * nm_exported_object_export:
522  * @self: an #NMExportedObject
523  *
524  * Exports @self on all active and future D-Bus connections.
525  *
526  * The path to export @self on is taken from its #NMObjectClass's %export_path
527  * member. If the %export_path contains "%u", then it will be replaced with a
528  * monotonically increasing integer ID (with each distinct %export_path having
529  * its own counter). Otherwise, %export_path will be used literally (implying
530  * that @self must be a singleton).
531  *
532  * Returns: the path @self was exported under
533  */
534 const char *
535 nm_exported_object_export (NMExportedObject *self)
536 {
537         NMExportedObjectPrivate *priv;
538         const char *class_export_path, *p;
539         GType type;
540         char *path;
541
542         g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
543         priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
544
545         g_return_val_if_fail (!priv->path, priv->path);
546         g_return_val_if_fail (!priv->bus_mgr, priv->path);
547
548 #ifdef _ASSERT_NO_EARLY_EXPORT
549         nm_assert (priv->_constructed);
550 #endif
551
552         priv->bus_mgr = nm_bus_manager_get ();
553         if (!priv->bus_mgr)
554                 g_return_val_if_reached (NULL);
555         g_object_add_weak_pointer ((GObject *) priv->bus_mgr, (gpointer *) &priv->bus_mgr);
556
557         class_export_path = NM_EXPORTED_OBJECT_GET_CLASS (self)->export_path;
558         p = strchr (class_export_path, '%');
559         if (p) {
560                 guint *counter;
561
562                 g_return_val_if_fail (p[1] == 'u', NULL);
563                 g_return_val_if_fail (strchr (p + 1, '%') == NULL, NULL);
564
565                 counter = g_hash_table_lookup (prefix_counters, class_export_path);
566                 if (!counter) {
567                         counter = g_new0 (guint, 1);
568                         g_hash_table_insert (prefix_counters, g_strdup (class_export_path), counter);
569                 }
570
571                 path = g_strdup_printf (class_export_path, (*counter)++);
572         } else
573                 path = g_strdup (class_export_path);
574
575         type = G_OBJECT_TYPE (self);
576         while (type != NM_TYPE_EXPORTED_OBJECT) {
577                 nm_exported_object_create_skeletons (self, type);
578                 type = g_type_parent (type);
579         }
580
581         priv->path = path;
582         _LOGT ("export: \"%s\"", priv->path);
583         g_dbus_object_skeleton_set_object_path (G_DBUS_OBJECT_SKELETON (self), priv->path);
584
585         /* Important: priv->path and priv->interfaces must not change while
586          * the object is registered. */
587
588         nm_bus_manager_register_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
589
590         return priv->path;
591 }
592
593 /**
594  * nm_exported_object_get_path:
595  * @self: an #NMExportedObject
596  *
597  * Gets @self's D-Bus path.
598  *
599  * Returns: @self's D-Bus path, or %NULL if @self is not exported.
600  */
601 const char *
602 nm_exported_object_get_path (NMExportedObject *self)
603 {
604         g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
605
606         return NM_EXPORTED_OBJECT_GET_PRIVATE (self)->path;
607 }
608
609 /**
610  * nm_exported_object_is_exported:
611  * @self: an #NMExportedObject
612  *
613  * Checks if @self is exported
614  *
615  * Returns: %TRUE if @self is exported
616  */
617 gboolean
618 nm_exported_object_is_exported (NMExportedObject *self)
619 {
620         g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), FALSE);
621
622         return NM_EXPORTED_OBJECT_GET_PRIVATE (self)->path != NULL;
623 }
624
625 /**
626  * nm_exported_object_unexport:
627  * @self: an #NMExportedObject
628  *
629  * Unexports @self on all active D-Bus connections (and prevents it from being
630  * auto-exported on future connections).
631  */
632 void
633 nm_exported_object_unexport (NMExportedObject *self)
634 {
635         NMExportedObjectPrivate *priv;
636
637         g_return_if_fail (NM_IS_EXPORTED_OBJECT (self));
638         priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
639
640         g_return_if_fail (priv->path);
641
642         /* Important: priv->path and priv->interfaces must not change while
643          * the object is registered. */
644
645         _LOGT ("unexport: \"%s\"", priv->path);
646
647         if (priv->bus_mgr) {
648                 nm_bus_manager_unregister_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
649                 g_object_remove_weak_pointer ((GObject *) priv->bus_mgr, (gpointer *) &priv->bus_mgr);
650                 priv->bus_mgr = NULL;
651         }
652
653         nm_exported_object_destroy_skeletons (self);
654
655         g_dbus_object_skeleton_set_object_path ((GDBusObjectSkeleton *) self, NULL);
656
657         g_clear_pointer (&priv->path, g_free);
658
659         if (nm_clear_g_source (&priv->notify_idle_id)) {
660                 /* We had a notification queued. Since we removed all interfaces,
661                  * the notification is obsolete and must be cleaned up. */
662                 g_hash_table_remove_all (priv->pending_notifies);
663         }
664 }
665
666 GSList *
667 nm_exported_object_get_interfaces (NMExportedObject *self)
668 {
669         NMExportedObjectPrivate *priv;
670
671         g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
672
673         priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
674
675         g_return_val_if_fail (priv->path, NULL);
676         g_return_val_if_fail (priv->interfaces, NULL);
677
678         return priv->interfaces;
679 }
680
681 GDBusInterfaceSkeleton *
682 nm_exported_object_get_interface_by_type (NMExportedObject *self, GType interface_type)
683 {
684         GSList *interfaces;
685
686         interfaces = nm_exported_object_get_interfaces (self);
687         for (; interfaces; interfaces = interfaces->next) {
688                 if (G_TYPE_CHECK_INSTANCE_TYPE (interfaces->data, interface_type))
689                         return interfaces->data;
690         }
691         return NULL;
692 }
693
694 void
695 _nm_exported_object_clear_and_unexport (NMExportedObject **location)
696 {
697         NMExportedObject *self;
698         NMExportedObjectPrivate *priv;
699
700         if (!location || !*location)
701                 return;
702
703         self = *location;
704         *location = NULL;
705
706         g_return_if_fail (NM_IS_EXPORTED_OBJECT (self));
707
708         priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
709
710         if (priv->path)
711                 nm_exported_object_unexport (self);
712
713         g_object_unref (self);
714 }
715
716 static void
717 nm_exported_object_init (NMExportedObject *self)
718 {
719         NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
720
721         priv->pending_notifies = g_hash_table_new_full (g_direct_hash,
722                                                         g_direct_equal,
723                                                         NULL,
724                                                         (GDestroyNotify) g_variant_unref);
725 }
726
727 static gboolean
728 idle_emit_properties_changed (gpointer self)
729 {
730         NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
731         GVariant *variant;
732         GSList *iter;
733         GDBusInterfaceSkeleton *interface = NULL;
734         guint signal_id = 0;
735         GHashTableIter hash_iter;
736         const char *dbus_property_name;
737         GVariantBuilder notifies;
738
739         priv->notify_idle_id = 0;
740
741         g_variant_builder_init (&notifies, G_VARIANT_TYPE_VARDICT);
742         g_hash_table_iter_init (&hash_iter, priv->pending_notifies);
743         while (g_hash_table_iter_next (&hash_iter, (gpointer) &dbus_property_name, (gpointer) &variant))
744                 g_variant_builder_add (&notifies, "{sv}", dbus_property_name, variant);
745         g_hash_table_remove_all (priv->pending_notifies);
746         variant = g_variant_builder_end (&notifies);
747         g_variant_ref_sink (variant);
748
749         for (iter = priv->interfaces; iter; iter = iter->next) {
750                 signal_id = g_signal_lookup ("properties-changed", G_OBJECT_TYPE (iter->data));
751                 if (signal_id != 0) {
752                         interface = G_DBUS_INTERFACE_SKELETON (iter->data);
753                         break;
754                 }
755         }
756         g_return_val_if_fail (signal_id != 0, FALSE);
757
758         if (nm_logging_enabled (LOGL_DEBUG, LOGD_DBUS_PROPS)) {
759                 gs_free char *notification = g_variant_print (variant, TRUE);
760
761                 nm_log_dbg (LOGD_DBUS_PROPS, "PropertiesChanged %s %p: %s",
762                             G_OBJECT_TYPE_NAME (self), self, notification);
763         }
764
765         g_signal_emit (interface, signal_id, 0, variant);
766         g_variant_unref (variant);
767
768         return FALSE;
769 }
770
771 static const GVariantType *
772 find_dbus_property_type (GDBusInterfaceSkeleton *skel,
773                          const char *dbus_property_name)
774 {
775         GDBusInterfaceInfo *iinfo;
776         int i;
777
778         iinfo = g_dbus_interface_skeleton_get_info (skel);
779         for (i = 0; iinfo->properties[i]; i++) {
780                 if (!strcmp (iinfo->properties[i]->name, dbus_property_name))
781                         return G_VARIANT_TYPE (iinfo->properties[i]->signature);
782         }
783
784         return NULL;
785 }
786
787 static void
788 nm_exported_object_notify (GObject *object, GParamSpec *pspec)
789 {
790         NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (object);
791         NMExportedObjectClassInfo *classinfo;
792         GType type;
793         const char *dbus_property_name = NULL;
794         GValue value = G_VALUE_INIT;
795         const GVariantType *vtype;
796         GVariant *variant;
797         GSList *iter;
798
799         if (!priv->interfaces)
800                 return;
801
802         for (type = G_OBJECT_TYPE (object); type; type = g_type_parent (type)) {
803                 classinfo = g_type_get_qdata (type, nm_exported_object_class_info_quark ());
804                 if (!classinfo)
805                         continue;
806
807                 dbus_property_name = g_hash_table_lookup (classinfo->properties, pspec->name);
808                 if (dbus_property_name)
809                         break;
810         }
811         if (!dbus_property_name) {
812                 nm_log_trace (LOGD_DBUS_PROPS, "ignoring notification for prop %s on type %s",
813                               pspec->name, G_OBJECT_TYPE_NAME (object));
814                 return;
815         }
816
817         g_value_init (&value, pspec->value_type);
818         g_object_get_property (G_OBJECT (object), pspec->name, &value);
819
820         vtype = NULL;
821         for (iter = priv->interfaces; iter && !vtype; iter = iter->next)
822                 vtype = find_dbus_property_type (iter->data, dbus_property_name);
823         g_return_if_fail (vtype != NULL);
824
825         variant = g_dbus_gvalue_to_gvariant (&value, vtype);
826         /* @dbus_property_name is inside classinfo and never freed, thus we don't clone it.
827          * Also, we do a pointer, not string comparison. */
828         g_hash_table_insert (priv->pending_notifies, (gpointer) dbus_property_name, variant);
829         g_value_unset (&value);
830
831         if (!priv->notify_idle_id)
832                 priv->notify_idle_id = g_idle_add (idle_emit_properties_changed, object);
833 }
834
835 static void
836 constructed (GObject *object)
837 {
838         NMExportedObjectClass *klass;
839
840         G_OBJECT_CLASS (nm_exported_object_parent_class)->constructed (object);
841
842 #ifdef _ASSERT_NO_EARLY_EXPORT
843         NM_EXPORTED_OBJECT_GET_PRIVATE (object)->_constructed = TRUE;
844 #endif
845
846         klass = NM_EXPORTED_OBJECT_GET_CLASS (object);
847
848         if (klass->export_on_construction)
849                 nm_exported_object_export ((NMExportedObject *) object);
850 }
851
852 static void
853 nm_exported_object_dispose (GObject *object)
854 {
855         NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (object);
856
857         /* Objects should have already been unexported by their owner, unless
858          * we are quitting, where many objects stick around until exit.
859          */
860         if (!quitting) {
861                 if (priv->path) {
862                         g_warn_if_reached ();
863                         nm_exported_object_unexport (NM_EXPORTED_OBJECT (object));
864                 }
865         } else
866                 g_clear_pointer (&priv->path, g_free);
867
868         g_clear_pointer (&priv->pending_notifies, g_hash_table_destroy);
869         nm_clear_g_source (&priv->notify_idle_id);
870
871         G_OBJECT_CLASS (nm_exported_object_parent_class)->dispose (object);
872 }
873
874 static void
875 nm_exported_object_class_init (NMExportedObjectClass *klass)
876 {
877         GObjectClass *object_class = G_OBJECT_CLASS (klass);
878
879         g_type_class_add_private (object_class, sizeof (NMExportedObjectPrivate));
880
881         object_class->constructed = constructed;
882         object_class->notify = nm_exported_object_notify;
883         object_class->dispose = nm_exported_object_dispose;
884 }
885
886 void
887 nm_exported_object_class_set_quitting (void)
888 {
889         quitting = TRUE;
890 }
891