// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018 Red Hat, Inc. */ #include "nm-default.h" #include "nm-dbus-object.h" #include "nm-dbus-manager.h" /*****************************************************************************/ enum { EXPORTED_CHANGED, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_ABSTRACT_TYPE (NMDBusObject, nm_dbus_object, G_TYPE_OBJECT); /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_CORE #define _NMLOG(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG_DOMAIN, "dbus-object", __VA_ARGS__) #define _NMLOG2_DOMAIN LOGD_DBUS_PROPS #define _NMLOG2(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG2_DOMAIN, "properties-changed", __VA_ARGS__) /*****************************************************************************/ static void _emit_exported_changed (NMDBusObject *self) { g_signal_emit (self, signals[EXPORTED_CHANGED], 0); } static char * _create_export_path (NMDBusObjectClass *klass) { nm_assert (NM_IS_DBUS_OBJECT_CLASS (klass)); nm_assert (klass->export_path.path); #if NM_MORE_ASSERTS { const char *p; p = strchr (klass->export_path.path, '%'); if (klass->export_path.int_counter) { nm_assert (p); nm_assert (p[1] == 'l'); nm_assert (p[2] == 'l'); nm_assert (p[3] == 'u'); nm_assert (p[4] == '\0'); } else nm_assert (!p); } #endif if (klass->export_path.int_counter) { NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral") return g_strdup_printf (klass->export_path.path, ++(*klass->export_path.int_counter)); NM_PRAGMA_WARNING_REENABLE } return g_strdup (klass->export_path.path); } /** * nm_dbus_object_export: * @self: an #NMDBusObject * * Exports @self on all active and future D-Bus connections. * * The path to export @self on is taken from its #NMObjectClass's %export_path * member. If the %export_path contains "%u", then it will be replaced with a * monotonically increasing integer ID (with each distinct %export_path having * its own counter). Otherwise, %export_path will be used literally (implying * that @self must be a singleton). * * Returns: the path @self was exported under */ const char * nm_dbus_object_export (NMDBusObject *self) { static guint64 id_counter = 0; g_return_val_if_fail (NM_IS_DBUS_OBJECT (self), NULL); g_return_val_if_fail (!self->internal.path, self->internal.path); nm_assert (!self->internal.is_unexporting); self->internal.path = _create_export_path (NM_DBUS_OBJECT_GET_CLASS (self)); self->internal.export_version_id = ++id_counter; _LOGT ("export: \"%s\"", self->internal.path); _nm_dbus_manager_obj_export (self); _emit_exported_changed (self); return self->internal.path; } /** * nm_dbus_object_unexport: * @self: an #NMDBusObject * * Unexports @self on all active D-Bus connections (and prevents it from being * auto-exported on future connections). */ void nm_dbus_object_unexport (NMDBusObject *self) { g_return_if_fail (NM_IS_DBUS_OBJECT (self)); g_return_if_fail (self->internal.path); _LOGT ("unexport: \"%s\"", self->internal.path); /* note that we emit the signal *before* actually unexporting the object. * The reason is, that listeners want to use this signal to know that * the object goes away, and clear their D-Bus path to this object. * * But this must happen before we actually unregister the object, so * that we first emit a D-Bus signal that other objects no longer * reference this object, before finally unregistering the object itself. * * The inconvenient part is, that at this point nm_dbus_object_get_path() * still returns the path. So, the callee needs to handle that. Possibly * by using "nm_dbus_object_get_path_still_exported()". */ self->internal.is_unexporting = TRUE; _emit_exported_changed (self); _nm_dbus_manager_obj_unexport (self); nm_clear_g_free (&self->internal.path); self->internal.export_version_id = 0; self->internal.is_unexporting = FALSE; } /*****************************************************************************/ void _nm_dbus_object_clear_and_unexport (NMDBusObject **location) { NMDBusObject *self; g_return_if_fail (location); if (!*location) return; self = g_steal_pointer (location); g_return_if_fail (NM_IS_DBUS_OBJECT (self)); if (self->internal.path) nm_dbus_object_unexport (self); g_object_unref (self); } /*****************************************************************************/ void nm_dbus_object_emit_signal_variant (NMDBusObject *self, const NMDBusInterfaceInfoExtended *interface_info, const GDBusSignalInfo *signal_info, GVariant *args) { if (!self->internal.path) { nm_g_variant_unref_floating (args); return; } _nm_dbus_manager_obj_emit_signal (self, interface_info, signal_info, args); } void nm_dbus_object_emit_signal (NMDBusObject *self, const NMDBusInterfaceInfoExtended *interface_info, const GDBusSignalInfo *signal_info, const char *format, ...) { va_list ap; nm_assert (NM_IS_DBUS_OBJECT (self)); nm_assert (format); if (!self->internal.path) return; va_start (ap, format); _nm_dbus_manager_obj_emit_signal (self, interface_info, signal_info, g_variant_new_va (format, NULL, &ap)); va_end (ap); } /*****************************************************************************/ static void dispatch_properties_changed (GObject *object, guint n_pspecs, GParamSpec **pspecs) { NMDBusObject *self = NM_DBUS_OBJECT (object); if (self->internal.path) _nm_dbus_manager_obj_notify (self, n_pspecs, (const GParamSpec *const*) pspecs); G_OBJECT_CLASS (nm_dbus_object_parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs); } /*****************************************************************************/ static void nm_dbus_object_init (NMDBusObject *self) { c_list_init (&self->internal.objects_lst); c_list_init (&self->internal.registration_lst_head); self->internal.bus_manager = nm_g_object_ref (nm_dbus_manager_get ()); } static void constructed (GObject *object) { NMDBusObjectClass *klass; G_OBJECT_CLASS (nm_dbus_object_parent_class)->constructed (object); klass = NM_DBUS_OBJECT_GET_CLASS (object); if (klass->export_on_construction) nm_dbus_object_export ((NMDBusObject *) object); /* NMDBusObject types should be very careful when overwriting notify(). * It is possible to do, but this is a reminder that it's probably not * a good idea. * * It's not a good idea, because NMDBusObject uses dispatch_properties_changed() * to emit signals about a bunch of property changes. So, we want to make * use of g_object_freeze_notify() / g_object_thaw_notify() to combine multiple * property changes in one signal on D-Bus. Note that notify() is not invoked * while the signal is frozen, that means, whatever you do inside notify() * will not make it into the same batch of PropertiesChanged signal. That is * confusing, and probably not what you want. * * Simple solution: don't overwrite notify(). */ nm_assert (!G_OBJECT_CLASS (klass)->notify); } static void dispose (GObject *object) { NMDBusObject *self = NM_DBUS_OBJECT (object); /* Objects should have already been unexported by their owner, unless * we are quitting, where many objects stick around until exit. */ if (self->internal.path) { if (!nm_dbus_manager_is_stopping (nm_dbus_object_get_manager (self))) g_warn_if_reached (); nm_dbus_object_unexport (self); } G_OBJECT_CLASS (nm_dbus_object_parent_class)->dispose (object); g_clear_object (&self->internal.bus_manager); } static void nm_dbus_object_class_init (NMDBusObjectClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = constructed; object_class->dispose = dispose; object_class->dispatch_properties_changed = dispatch_properties_changed; signals[EXPORTED_CHANGED] = g_signal_new (NM_DBUS_OBJECT_EXPORTED_CHANGED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); }