/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2007 - 2008 Novell, Inc. * Copyright (C) 2007 - 2018 Red Hat, Inc. */ #include "libnm-client-impl/nm-default-libnm.h" #include "nm-client.h" #include #include "libnm-std-aux/c-list-util.h" #include "libnm-glib-aux/nm-c-list.h" #include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "nm-access-point.h" #include "nm-active-connection.h" #include "nm-checkpoint.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-helpers.h" #include "nm-device-6lowpan.h" #include "nm-device-adsl.h" #include "nm-device-bond.h" #include "nm-device-bridge.h" #include "nm-device-bt.h" #include "nm-device-dummy.h" #include "nm-device-ethernet.h" #include "nm-device-generic.h" #include "nm-device-infiniband.h" #include "nm-device-ip-tunnel.h" #include "nm-device-macsec.h" #include "nm-device-macvlan.h" #include "nm-device-modem.h" #include "nm-device-olpc-mesh.h" #include "nm-device-ovs-bridge.h" #include "nm-device-ovs-interface.h" #include "nm-device-ovs-port.h" #include "nm-device-ppp.h" #include "nm-device-team.h" #include "nm-device-tun.h" #include "nm-device-vlan.h" #include "nm-device-vxlan.h" #include "nm-device-wifi-p2p.h" #include "nm-device-wifi.h" #include "nm-device-wireguard.h" #include "nm-device-wpan.h" #include "nm-dhcp-config.h" #include "nm-dhcp4-config.h" #include "nm-dhcp6-config.h" #include "nm-dns-manager.h" #include "nm-ip4-config.h" #include "nm-ip6-config.h" #include "nm-object-private.h" #include "nm-remote-connection.h" #include "nm-utils.h" #include "nm-vpn-connection.h" #include "nm-wifi-p2p-peer.h" /*****************************************************************************/ NM_CACHED_QUARK_FCN("nm-context-busy-watcher", nm_context_busy_watcher_quark); static void _context_busy_watcher_attach_integration_source_cb(gpointer data, GObject *where_the_object_was) { nm_g_source_destroy_and_unref(data); } void nm_context_busy_watcher_integrate_source(GMainContext *outer_context, GMainContext *inner_context, GObject * context_busy_watcher) { GSource *source; nm_assert(outer_context); nm_assert(inner_context); nm_assert(outer_context != inner_context); nm_assert(G_IS_OBJECT(context_busy_watcher)); source = nm_utils_g_main_context_create_integrate_source(inner_context); g_source_attach(source, outer_context); /* The problem is... * * NMClient is associated with a GMainContext, just like its underlying GDBusConnection * also queues signals and callbacks on that main context. During operation, NMClient * will schedule async operations which will return asynchronously on the GMainContext. * * Note that depending on whether NMClient got initialized synchronously or asynchronously, * it has an internal priv->dbus_context that is different from the outer priv->main_context. * However, the problem is in both cases. * * So, as long as there are pending D-Bus calls, the GMainContext is referenced and kept alive. * When NMClient gets destroyed, the pending calls get cancelled, but the async callback are still * scheduled to return. * That means, the main context stays alive until it gets iterated long enough so that all pending * operations are completed. * * Note that pending operations don't keep NMClient alive, so NMClient can already be gone by * then, but the user still should iterate the main context long enough to process the (cancelled) * callbacks... at least, if the user cares about whether the remaining memory and file descriptors * of the GMainContext can be reclaimed. * * In hindsight, maybe pending references should kept NMClient alive. But then NMClient would * need a special "shutdown()" API that the user must invoke, because unrefing would no longer * be enough to ensure a shutdown (imagine a situation where NMClient receives a constant flow * of "CheckPermissions" signals, which keeps retriggering an async request). Anyway, we cannot * add such a shutdown API now, as it would break client's expectations that they can just unref * the NMClient to destroy it. * * So, we allow NMClient to unref, but the user is advised to keep iterating the main context. * But for how long? Here comes nm_client_get_context_busy_watcher() into play. The user may * subscribe a weak pointer to that instance and should keep iterating as long as the object * exists. * * Now, back to synchronous initialization. Here we have the internal priv->dbus_context context. * We also cannot remove that context right away, instead we need to keep it integrated in the * caller's priv->main_context as long as we have pending calls: that is, as long as the * context-busy-watcher is alive. */ g_object_weak_ref(context_busy_watcher, _context_busy_watcher_attach_integration_source_cb, source); } /*****************************************************************************/ typedef struct { /* It is quite wasteful to require 2 pointers per property (of an instance) only to track whether * the property got changed. But it's convenient! */ CList changed_prop_lst; GVariant *prop_data_value; } NMLDBusObjPropData; typedef struct { CList iface_lst; union { const NMLDBusMetaIface *meta; NMRefString * name; } dbus_iface; CList changed_prop_lst_head; /* We also keep track of non-well known interfaces. The presence of a D-Bus interface * is what makes a D-Bus alive or not. As we should track all D-Bus objects, we also * need to track whether there are any interfaces on it -- even if we otherwise don't * care about the interface. */ bool dbus_iface_is_wellknown : 1; /* if TRUE, the interface is about to be removed. */ bool iface_removed : 1; bool nmobj_checked : 1; bool nmobj_compatible : 1; NMLDBusObjPropData prop_datas[]; } NMLDBusObjIfaceData; /* The dbus_path must be the first element, so when we hash the object by the dbus_path, * we also can lookup the object by only having a NMRefString at hand * using nm_pdirect_hash()/nm_pdirect_equal(). */ G_STATIC_ASSERT(G_STRUCT_OFFSET(NMLDBusObject, dbus_path) == 0); typedef void (*NMLDBusObjWatchNotifyFcn)(NMClient *client, gpointer obj_watcher); struct _NMLDBusObjWatcher { NMLDBusObject *dbobj; struct { CList watcher_lst; NMLDBusObjWatchNotifyFcn notify_fcn; } _priv; }; typedef struct { NMLDBusObjWatcher parent; gpointer user_data; } NMLDBusObjWatcherWithPtr; /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE(NMClient, PROP_DBUS_CONNECTION, PROP_DBUS_NAME_OWNER, PROP_VERSION, PROP_INSTANCE_FLAGS, PROP_STATE, PROP_STARTUP, PROP_NM_RUNNING, PROP_NETWORKING_ENABLED, PROP_WIRELESS_ENABLED, PROP_WIRELESS_HARDWARE_ENABLED, PROP_WWAN_ENABLED, PROP_WWAN_HARDWARE_ENABLED, PROP_WIMAX_ENABLED, PROP_WIMAX_HARDWARE_ENABLED, PROP_ACTIVE_CONNECTIONS, PROP_CONNECTIVITY, PROP_CONNECTIVITY_CHECK_URI, PROP_CONNECTIVITY_CHECK_AVAILABLE, PROP_CONNECTIVITY_CHECK_ENABLED, PROP_PRIMARY_CONNECTION, PROP_ACTIVATING_CONNECTION, PROP_DEVICES, PROP_ALL_DEVICES, PROP_CONNECTIONS, PROP_HOSTNAME, PROP_CAN_MODIFY, PROP_METERED, PROP_DNS_MODE, PROP_DNS_RC_MANAGER, PROP_DNS_CONFIGURATION, PROP_CHECKPOINTS, PROP_CAPABILITIES, PROP_PERMISSIONS_STATE, ); enum { DEVICE_ADDED, DEVICE_REMOVED, ANY_DEVICE_ADDED, ANY_DEVICE_REMOVED, PERMISSION_CHANGED, CONNECTION_ADDED, CONNECTION_REMOVED, ACTIVE_CONNECTION_ADDED, ACTIVE_CONNECTION_REMOVED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; enum { PROPERTY_O_IDX_NM_ACTIVATING_CONNECTION = 0, PROPERTY_O_IDX_NM_PRIMAY_CONNECTION, _PROPERTY_O_IDX_NM_NUM, }; enum { PROPERTY_AO_IDX_DEVICES = 0, PROPERTY_AO_IDX_ALL_DEVICES, PROPERTY_AO_IDX_ACTIVE_CONNECTIONS, PROPERTY_AO_IDX_CHECKPOINTS, _PROPERTY_AO_IDX_NM_NUM, }; typedef struct { struct udev * udev; GMainContext * main_context; GMainContext * dbus_context; GObject * context_busy_watcher; GDBusConnection *dbus_connection; NMLInitData * init_data; GHashTable * dbus_objects; CList obj_changed_lst_head; GCancellable * name_owner_get_cancellable; GCancellable * get_managed_objects_cancellable; CList queue_notify_lst_head; CList notify_event_lst_head; CList dbus_objects_lst_head_watched_only; CList dbus_objects_lst_head_on_dbus; CList dbus_objects_lst_head_with_nmobj_not_ready; CList dbus_objects_lst_head_with_nmobj_ready; NMLDBusObject *dbobj_nm; NMLDBusObject *dbobj_settings; NMLDBusObject *dbobj_dns_manager; gsize log_call_counter; guint8 * permissions; GCancellable *permissions_cancellable; char *name_owner; guint name_owner_changed_id; guint dbsid_nm_object_manager; guint dbsid_dbus_properties_properties_changed; guint dbsid_nm_settings_connection_updated; guint dbsid_nm_connection_active_state_changed; guint dbsid_nm_vpn_connection_state_changed; guint dbsid_nm_check_permissions; NMClientInstanceFlags instance_flags : 3; NMTernary permissions_state : 3; bool instance_flags_constructed : 1; bool udev_inited : 1; bool notify_event_lst_changed : 1; bool check_dbobj_visible_all : 1; bool nm_running : 1; struct { NMLDBusPropertyO property_o[_PROPERTY_O_IDX_NM_NUM]; NMLDBusPropertyAO property_ao[_PROPERTY_AO_IDX_NM_NUM]; char * connectivity_check_uri; char * version; guint32 * capabilities_arr; gsize capabilities_len; guint32 connectivity; guint32 state; guint32 metered; bool connectivity_check_available; bool connectivity_check_enabled; bool networking_enabled; bool startup; bool wireless_enabled; bool wireless_hardware_enabled; bool wwan_enabled; bool wwan_hardware_enabled; } nm; struct { NMLDBusPropertyAO connections; char * hostname; bool can_modify; } settings; struct { GPtrArray *configuration; char * mode; char * rc_manager; } dns_manager; } NMClientPrivate; struct _NMClient { union { GObject parent; NMObjectBase obj_base; }; NMClientPrivate _priv; }; struct _NMClientClass { union { GObjectClass parent; NMObjectBaseClass obj_base; }; }; static void nm_client_initable_iface_init(GInitableIface *iface); static void nm_client_async_initable_iface_init(GAsyncInitableIface *iface); G_DEFINE_TYPE_WITH_CODE(NMClient, nm_client, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, nm_client_initable_iface_init); G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, nm_client_async_initable_iface_init);) #define NM_CLIENT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMClient, NM_IS_CLIENT) /*****************************************************************************/ static void _init_start_check_complete(NMClient *self); static void name_owner_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data); static void name_owner_get_call(NMClient *self); static void _set_nm_running(NMClient *self); /*****************************************************************************/ static NMRefString *_dbus_path_nm = NULL; static NMRefString *_dbus_path_settings = NULL; static NMRefString *_dbus_path_dns_manager = NULL; /*****************************************************************************/ static NM_UTILS_LOOKUP_STR_DEFINE( nml_dbus_obj_state_to_string, NMLDBusObjState, NM_UTILS_LOOKUP_DEFAULT_WARN("???"), NM_UTILS_LOOKUP_ITEM(NML_DBUS_OBJ_STATE_UNLINKED, "unlinked"), NM_UTILS_LOOKUP_ITEM(NML_DBUS_OBJ_STATE_WATCHED_ONLY, "watched-only"), NM_UTILS_LOOKUP_ITEM(NML_DBUS_OBJ_STATE_ON_DBUS, "on-dbus"), NM_UTILS_LOOKUP_ITEM(NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY, "not-ready"), NM_UTILS_LOOKUP_ITEM(NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY, "ready"), ); /*****************************************************************************/ /** * nm_client_error_quark: * * Registers an error quark for #NMClient if necessary. * * Returns: the error quark used for #NMClient errors. **/ NM_CACHED_QUARK_FCN("nm-client-error-quark", nm_client_error_quark); /*****************************************************************************/ NMLInitData * nml_init_data_new_sync(GCancellable *cancellable, GMainLoop *main_loop, GError **error_location) { NMLInitData *init_data; init_data = g_slice_new(NMLInitData); *init_data = (NMLInitData){ .cancellable = nm_g_object_ref(cancellable), .is_sync = TRUE, .data.sync = { .main_loop = main_loop, .error_location = error_location, }, }; return init_data; } NMLInitData * nml_init_data_new_async(GCancellable *cancellable, GTask *task_take) { NMLInitData *init_data; init_data = g_slice_new(NMLInitData); *init_data = (NMLInitData){ .cancellable = nm_g_object_ref(cancellable), .is_sync = FALSE, .data.async = { .task = g_steal_pointer(&task_take), }, }; return init_data; } void nml_init_data_return(NMLInitData *init_data, GError *error_take) { nm_assert(init_data); nm_clear_pointer(&init_data->cancel_on_idle_source, nm_g_source_destroy_and_unref); nm_clear_g_signal_handler(init_data->cancellable, &init_data->cancelled_id); if (init_data->is_sync) { if (error_take) g_propagate_error(init_data->data.sync.error_location, error_take); g_main_loop_quit(init_data->data.sync.main_loop); } else { if (error_take) g_task_return_error(init_data->data.async.task, error_take); else g_task_return_boolean(init_data->data.async.task, TRUE); g_object_unref(init_data->data.async.task); } nm_g_object_unref(init_data->cancellable); nm_g_slice_free(init_data); } /*****************************************************************************/ GError * _nm_client_new_error_nm_not_running(void) { return g_error_new_literal(NM_CLIENT_ERROR, NM_CLIENT_ERROR_MANAGER_NOT_RUNNING, "NetworkManager is not running"); } GError * _nm_client_new_error_nm_not_cached(void) { return g_error_new_literal(NM_CLIENT_ERROR, NM_CLIENT_ERROR_FAILED, "Object is no longer in the client cache"); } static void _nm_client_dbus_call_simple_cb(GObject *source, GAsyncResult *result, gpointer user_data) { GAsyncReadyCallback callback; gpointer callback_user_data; gs_unref_object GObject *context_busy_watcher = NULL; gpointer obfuscated_self_ptr; gpointer log_call_counter_ptr; nm_utils_user_data_unpack(user_data, &callback, &callback_user_data, &context_busy_watcher, &obfuscated_self_ptr, &log_call_counter_ptr); NML_DBUS_LOG(_NML_NMCLIENT_LOG_LEVEL_COERCE(NML_DBUS_LOG_LEVEL_TRACE), "nmclient[" NM_HASH_OBFUSCATE_PTR_FMT "]: call[%" G_GSIZE_FORMAT "] completed", (guint64) GPOINTER_TO_SIZE(obfuscated_self_ptr), GPOINTER_TO_SIZE(log_call_counter_ptr)); callback(source, result, callback_user_data); } void _nm_client_dbus_call_simple(NMClient * self, GCancellable * cancellable, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, const GVariantType *reply_type, GDBusCallFlags flags, int timeout_msec, GAsyncReadyCallback callback, gpointer user_data) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; gs_free char * log_str = NULL; gsize log_call_counter; nm_assert(priv->name_owner); nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable)); nm_assert(callback); nm_assert(object_path); nm_assert(interface_name); nm_assert(method_name); nm_assert(parameters); nm_assert(reply_type); dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); log_call_counter = ++priv->log_call_counter; NML_NMCLIENT_LOG_T(self, "call[%" G_GSIZE_FORMAT "] D-Bus method on %s: %s, %s.%s -> %s (%s)", log_call_counter, priv->name_owner, object_path, interface_name, method_name, (const char *) reply_type ?: "???", parameters ? (log_str = g_variant_print(parameters, TRUE)) : "NULL"); g_dbus_connection_call(priv->dbus_connection, priv->name_owner, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, cancellable, _nm_client_dbus_call_simple_cb, nm_utils_user_data_pack(callback, user_data, g_object_ref(priv->context_busy_watcher), GSIZE_TO_POINTER(NM_HASH_OBFUSCATE_PTR(self)), GSIZE_TO_POINTER(log_call_counter))); } void _nm_client_dbus_call(NMClient * self, gpointer source_obj, gpointer source_tag, GCancellable * cancellable, GAsyncReadyCallback user_callback, gpointer user_callback_data, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, const GVariantType *reply_type, GDBusCallFlags flags, int timeout_msec, GAsyncReadyCallback internal_callback) { NMClientPrivate *priv; gs_unref_object GTask *task = NULL; nm_assert(!source_obj || G_IS_OBJECT(source_obj)); nm_assert(source_tag); nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable)); nm_assert(internal_callback); nm_assert(object_path); nm_assert(interface_name); nm_assert(method_name); nm_assert(parameters); nm_assert(reply_type); task = nm_g_task_new(source_obj, cancellable, source_tag, user_callback, user_callback_data); if (!self) { nm_g_variant_unref_floating(parameters); g_task_return_error(task, _nm_client_new_error_nm_not_cached()); return; } priv = NM_CLIENT_GET_PRIVATE(self); if (!priv->name_owner) { nm_g_variant_unref_floating(parameters); g_task_return_error(task, _nm_client_new_error_nm_not_running()); return; } _nm_client_dbus_call_simple(self, cancellable, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, internal_callback, g_steal_pointer(&task)); } GVariant * _nm_client_dbus_call_sync(NMClient * self, GCancellable * cancellable, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, const GVariantType *reply_type, GDBusCallFlags flags, int timeout_msec, gboolean strip_dbus_error, GError ** error) { NMClientPrivate *priv; gs_unref_variant GVariant *ret = NULL; nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable)); nm_assert(!error || !*error); nm_assert(object_path); nm_assert(interface_name); nm_assert(method_name); nm_assert(parameters); nm_assert(reply_type); if (!self) { nm_g_variant_unref_floating(parameters); nm_g_set_error_take_lazy(error, _nm_client_new_error_nm_not_cached()); return NULL; } priv = NM_CLIENT_GET_PRIVATE(self); if (!priv->name_owner) { nm_g_variant_unref_floating(parameters); nm_g_set_error_take_lazy(error, _nm_client_new_error_nm_not_running()); return NULL; } ret = g_dbus_connection_call_sync(priv->dbus_connection, priv->name_owner, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, cancellable, error); if (!ret) { if (error && strip_dbus_error) g_dbus_error_strip_remote_error(*error); return NULL; } return g_steal_pointer(&ret); } gboolean _nm_client_dbus_call_sync_void(NMClient * self, GCancellable * cancellable, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, GDBusCallFlags flags, int timeout_msec, gboolean strip_dbus_error, GError ** error) { gs_unref_variant GVariant *ret = NULL; ret = _nm_client_dbus_call_sync(self, cancellable, object_path, interface_name, method_name, parameters, G_VARIANT_TYPE("()"), flags, timeout_msec, strip_dbus_error, error); return !!ret; } void _nm_client_set_property_sync_legacy(NMClient * self, const char *object_path, const char *interface_name, const char *property_name, const char *format_string, ...) { NMClientPrivate *priv; GVariant * val; gs_unref_variant GVariant *ret = NULL; va_list ap; nm_assert(!self || NM_IS_CLIENT(self)); nm_assert(interface_name); nm_assert(property_name); nm_assert(format_string); if (!self) return; priv = NM_CLIENT_GET_PRIVATE(self); if (!priv->name_owner) return; va_start(ap, format_string); val = g_variant_new_va(format_string, NULL, &ap); va_end(ap); nm_assert(val); /* A synchronous D-Bus call that is not cancellable an ignores the return value. * This function only exists for backward compatibility. */ ret = g_dbus_connection_call_sync(priv->dbus_connection, priv->name_owner, object_path, DBUS_INTERFACE_PROPERTIES, "Set", g_variant_new("(ssv)", interface_name, property_name, val), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, NULL); } /*****************************************************************************/ #define _assert_main_context_is_current_source(self, x_context) \ G_STMT_START \ { \ if (NM_MORE_ASSERTS > 0) { \ GSource *_source = g_main_current_source(); \ \ if (_source) { \ NMClientPrivate *_priv = NM_CLIENT_GET_PRIVATE(self); \ \ nm_assert(g_source_get_context(_source) == _priv->x_context); \ nm_assert(g_main_context_is_owner(_priv->x_context)); \ } \ } \ } \ G_STMT_END #define _assert_main_context_is_current_thread_default(self, x_context) \ G_STMT_START \ { \ if (NM_MORE_ASSERTS > 0) { \ NMClientPrivate *_priv = NM_CLIENT_GET_PRIVATE(self); \ \ nm_assert((g_main_context_get_thread_default() ?: g_main_context_default()) \ == _priv->x_context); \ nm_assert(g_main_context_is_owner(_priv->x_context)); \ } \ } \ G_STMT_END /*****************************************************************************/ void _nm_client_queue_notify_object(NMClient *self, gpointer nmobj, const GParamSpec *pspec) { NMObjectBase *base; nm_assert(NM_IS_CLIENT(self)); nm_assert(NM_IS_OBJECT(nmobj) || NM_IS_CLIENT(nmobj)); base = (NMObjectBase *) nmobj; if (base->is_disposing) { /* Don't emit property changed signals once the NMClient * instance is about to shut down. */ nm_assert(nmobj == self); return; } if (c_list_is_empty(&base->queue_notify_lst)) { c_list_link_tail(&NM_CLIENT_GET_PRIVATE(self)->queue_notify_lst_head, &base->queue_notify_lst); g_object_ref(nmobj); g_object_freeze_notify(nmobj); } if (pspec) g_object_notify_by_pspec(nmobj, (GParamSpec *) pspec); } /*****************************************************************************/ gpointer _nm_client_notify_event_queue(NMClient * self, int priority, NMClientNotifyEventCb callback, gsize event_size) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); NMClientNotifyEvent *notify_event; nm_assert(callback); nm_assert(event_size > sizeof(NMClientNotifyEvent)); notify_event = g_malloc(event_size); notify_event->priority = priority; notify_event->callback = callback; c_list_link_tail(&priv->notify_event_lst_head, ¬ify_event->lst); priv->notify_event_lst_changed = TRUE; return notify_event; } NMClientNotifyEventWithPtr * _nm_client_notify_event_queue_with_ptr(NMClient * self, int priority, NMClientNotifyEventWithPtrCb callback, gpointer user_data) { NMClientNotifyEventWithPtr *notify_event; notify_event = _nm_client_notify_event_queue(self, priority, (NMClientNotifyEventCb) callback, sizeof(NMClientNotifyEventWithPtr)); notify_event->user_data = user_data; return notify_event; } /*****************************************************************************/ typedef struct { NMClientNotifyEvent parent; GObject * source; NMObject * obj; guint signal_id; } NMClientNotifyEventObjAddedRemove; static void _nm_client_notify_event_queue_emit_obj_signal_cb(NMClient *self, gpointer notify_event_base) { NMClientNotifyEventObjAddedRemove *notify_event = notify_event_base; NML_NMCLIENT_LOG_T( self, "[%s] emit \"%s\" signal for %s", NM_IS_CLIENT(notify_event->source) ? "nmclient" : _nm_object_get_path(notify_event->source), g_signal_name(notify_event->signal_id), _nm_object_get_path(notify_event->obj)); nm_assert(NM_IS_OBJECT(notify_event->source) || NM_IS_CLIENT(notify_event->source)); g_signal_emit(notify_event->source, notify_event->signal_id, 0, notify_event->obj); g_object_unref(notify_event->obj); g_object_unref(notify_event->source); } void _nm_client_notify_event_queue_emit_obj_signal(NMClient *self, GObject * source, NMObject *nmobj, gboolean is_added /* or else removed */, int prio_offset, guint signal_id) { NMClientNotifyEventObjAddedRemove *notify_event; nm_assert(prio_offset >= 0); nm_assert(prio_offset < 20); nm_assert(NM_IS_OBJECT(source) || NM_IS_CLIENT(source)); nm_assert(NM_IS_OBJECT(nmobj)); if (((NMObjectBase *) source)->is_disposing) { nm_assert(NM_IS_CLIENT(source)); return; } notify_event = _nm_client_notify_event_queue( self, is_added ? NM_CLIENT_NOTIFY_EVENT_PRIO_AFTER - 20 + prio_offset : NM_CLIENT_NOTIFY_EVENT_PRIO_BEFORE + 20 - prio_offset, _nm_client_notify_event_queue_emit_obj_signal_cb, sizeof(NMClientNotifyEventObjAddedRemove)); notify_event->source = g_object_ref(source); notify_event->obj = g_object_ref(nmobj); notify_event->signal_id = signal_id; } /*****************************************************************************/ static int _nm_client_notify_event_cmp(const CList *a, const CList *b, const void *user_data) { NM_CMP_DIRECT(c_list_entry(a, NMClientNotifyEvent, lst)->priority, c_list_entry(b, NMClientNotifyEvent, lst)->priority); return 0; } static void _nm_client_notify_event_emit_parts(NMClient *self, int max_priority /* included! */) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); NMClientNotifyEvent *notify_event; while (TRUE) { if (priv->notify_event_lst_changed) { priv->notify_event_lst_changed = FALSE; c_list_sort(&priv->notify_event_lst_head, _nm_client_notify_event_cmp, NULL); } notify_event = c_list_first_entry(&priv->notify_event_lst_head, NMClientNotifyEvent, lst); if (!notify_event) return; if (notify_event->priority > max_priority) return; c_list_unlink_stale(¬ify_event->lst); notify_event->callback(self, notify_event); g_free(notify_event); } } static void _nm_client_notify_event_emit(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NMObjectBase * base; _nm_client_notify_event_emit_parts(self, NM_CLIENT_NOTIFY_EVENT_PRIO_GPROP); while ( (base = c_list_first_entry(&priv->queue_notify_lst_head, NMObjectBase, queue_notify_lst))) { c_list_unlink(&base->queue_notify_lst); g_object_thaw_notify(G_OBJECT(base)); g_object_unref(base); } _nm_client_notify_event_emit_parts(self, G_MAXINT); } /*****************************************************************************/ GDBusConnection * _nm_client_get_dbus_connection(NMClient *self) { return NM_CLIENT_GET_PRIVATE(self)->dbus_connection; } const char * _nm_client_get_dbus_name_owner(NMClient *self) { return NM_CLIENT_GET_PRIVATE(self)->name_owner; } GMainContext * _nm_client_get_context_main(NMClient *self) { return NM_CLIENT_GET_PRIVATE(self)->main_context; } GMainContext * _nm_client_get_context_dbus(NMClient *self) { return NM_CLIENT_GET_PRIVATE(self)->dbus_context; } /** * nm_client_get_main_context: * @self: the #NMClient instance * * The #NMClient instance is permanently associated with the current * thread default #GMainContext, referenced the time when the instance * was created. To receive events, the user must iterate this context * and can use it to synchronize access to the client. * * Note that even after #NMClient instance got destroyed, there might * still be pending sources registered in the context. That means, to fully * clean up, the user must continue iterating the context as long as * the nm_client_get_context_busy_watcher() object is alive. * * Returns: (transfer none): the #GMainContext of the client. * * Since: 1.22 */ GMainContext * nm_client_get_main_context(NMClient *self) { g_return_val_if_fail(NM_IS_CLIENT(self), NULL); return _nm_client_get_context_main(self); } /** * nm_client_get_context_busy_watcher: * @self: the NMClient instance. * * Returns: (transfer none): a GObject that stays alive as long as there are pending * D-Bus operations. * * NMClient will schedule asynchronous D-Bus requests which will complete on * the GMainContext associated with the instance. When destroying the NMClient * instance, those requests are cancelled right away, however their pending requests are * still outstanding and queued in the GMainContext. These outstanding callbacks * keep the GMainContext alive. In order to fully release all resources, * the user must keep iterating the main context until all these callbacks * are handled. Of course, at this point no more actual callbacks will be invoked * for the user, those are all internally cancelled. * * This just leaves one problem: how long does the user need to keep the * GMainContext running to ensure everything is cleaned up? The answer is * this GObject. Subscribe a weak reference to the returned object and keep * iterating the main context until the object got unreferenced. * * Note that after the NMClient instance gets destroyed, the remaining callbacks * will be invoked right away. That means, the user won't have to iterate the * main context much longer. * * Since: 1.22 */ GObject * nm_client_get_context_busy_watcher(NMClient *self) { GObject *w; g_return_val_if_fail(NM_IS_CLIENT(self), NULL); w = NM_CLIENT_GET_PRIVATE(self)->context_busy_watcher; return g_object_get_qdata(w, nm_context_busy_watcher_quark()) ?: w; } struct udev * _nm_client_get_udev(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); if (G_UNLIKELY(!priv->udev_inited)) { priv->udev_inited = TRUE; /* for testing, we don't want to use udev in libnm. */ if (!nm_streq0(g_getenv("LIBNM_USE_NO_UDEV"), "1")) priv->udev = udev_new(); } return priv->udev; } /*****************************************************************************/ static void _ASSERT_dbobj(NMLDBusObject *dbobj, NMClient *self) { #if NM_MORE_ASSERTS > 5 nm_assert(NM_IS_CLIENT(self)); nm_assert(NML_IS_DBUS_OBJECT(dbobj)); nm_assert(dbobj == g_hash_table_lookup(NM_CLIENT_GET_PRIVATE(self)->dbus_objects, dbobj)); #endif } static NMLDBusObject * nml_dbus_object_new(NMRefString *dbus_path_take) { NMLDBusObject *dbobj; nm_assert(NM_IS_REF_STRING(dbus_path_take)); dbobj = g_slice_new(NMLDBusObject); *dbobj = (NMLDBusObject){ .dbus_path = g_steal_pointer(&dbus_path_take), .ref_count = 1, .dbus_objects_lst = C_LIST_INIT(dbobj->dbus_objects_lst), .iface_lst_head = C_LIST_INIT(dbobj->iface_lst_head), .watcher_lst_head = C_LIST_INIT(dbobj->watcher_lst_head), .obj_changed_lst = C_LIST_INIT(dbobj->obj_changed_lst), .obj_state = NML_DBUS_OBJ_STATE_UNLINKED, }; return dbobj; } NMLDBusObject * nml_dbus_object_ref(NMLDBusObject *dbobj) { nm_assert(dbobj); nm_assert(dbobj->ref_count > 0); dbobj->ref_count++; return dbobj; } void nml_dbus_object_unref(NMLDBusObject *dbobj) { nm_assert(dbobj); nm_assert(dbobj->ref_count > 0); if (--dbobj->ref_count > 0) return; nm_assert(c_list_is_empty(&dbobj->obj_changed_lst)); nm_assert(c_list_is_empty(&dbobj->iface_lst_head)); nm_assert(c_list_is_empty(&dbobj->watcher_lst_head)); nm_assert(!dbobj->nmobj); nm_ref_string_unref(dbobj->dbus_path); nm_g_slice_free(dbobj); } static NMLDBusObjIfaceData * nml_dbus_object_iface_data_get(NMLDBusObject *dbobj, const char * dbus_iface_name, gboolean allow_create) { const NMLDBusMetaIface *meta_iface; NMLDBusObjIfaceData * db_iface_data; NMLDBusObjPropData * db_prop_data; guint count = 0; guint i; nm_assert(NML_IS_DBUS_OBJECT(dbobj)); nm_assert(dbus_iface_name); #if NM_MORE_ASSERTS > 10 { gboolean expect_well_known = TRUE; /* all well-known interfaces must come first in the list. */ c_list_for_each_entry (db_iface_data, &dbobj->iface_lst_head, iface_lst) { if (db_iface_data->dbus_iface_is_wellknown == expect_well_known) continue; nm_assert(expect_well_known); expect_well_known = FALSE; } } #endif meta_iface = nml_dbus_meta_iface_get(dbus_iface_name); if (meta_iface) { c_list_for_each_entry (db_iface_data, &dbobj->iface_lst_head, iface_lst) { if (!db_iface_data->dbus_iface_is_wellknown) break; if (db_iface_data->iface_removed) continue; if (db_iface_data->dbus_iface.meta == meta_iface) return db_iface_data; count++; } } else { c_list_for_each_entry_prev (db_iface_data, &dbobj->iface_lst_head, iface_lst) { if (db_iface_data->dbus_iface_is_wellknown) break; if (db_iface_data->iface_removed) continue; if (nm_streq(db_iface_data->dbus_iface.name->str, dbus_iface_name)) return db_iface_data; count++; } } if (!allow_create) return NULL; if (count > 20) { /* We track the list of interfaces that an object has in a linked list. * That is efficient and convenient, if we assume that each object only has a small * number of interfaces (which very much should be the case). Here, something is very * odd, maybe there is a bug or the server side is misbehaving. Anyway, error out. */ return NULL; } db_iface_data = g_malloc( G_STRUCT_OFFSET(NMLDBusObjIfaceData, prop_datas) + (meta_iface ? (sizeof(NMLDBusObjPropData) * meta_iface->n_dbus_properties) : 0u)); if (meta_iface) { *db_iface_data = (NMLDBusObjIfaceData){ .dbus_iface.meta = meta_iface, .dbus_iface_is_wellknown = TRUE, .changed_prop_lst_head = C_LIST_INIT(db_iface_data->changed_prop_lst_head), .iface_removed = FALSE, }; db_prop_data = &db_iface_data->prop_datas[0]; for (i = 0; i < meta_iface->n_dbus_properties; i++, db_prop_data++) { *db_prop_data = (NMLDBusObjPropData){ .prop_data_value = NULL, .changed_prop_lst = C_LIST_INIT(db_prop_data->changed_prop_lst), }; } c_list_link_front(&dbobj->iface_lst_head, &db_iface_data->iface_lst); } else { /* Intentionally don't initialize the other fields. We are not supposed * to touch them, and a valgrind warning would be preferable. */ db_iface_data->dbus_iface.name = nm_ref_string_new(dbus_iface_name); db_iface_data->dbus_iface_is_wellknown = FALSE; db_iface_data->iface_removed = FALSE; c_list_link_tail(&dbobj->iface_lst_head, &db_iface_data->iface_lst); } return db_iface_data; } static void nml_dbus_obj_iface_data_destroy(NMLDBusObjIfaceData *db_iface_data) { guint i; nm_assert(db_iface_data); nm_assert(c_list_is_empty(&db_iface_data->iface_lst)); if (db_iface_data->dbus_iface_is_wellknown) { for (i = 0; i < db_iface_data->dbus_iface.meta->n_dbus_properties; i++) nm_g_variant_unref(db_iface_data->prop_datas[i].prop_data_value); } else nm_ref_string_unref(db_iface_data->dbus_iface.name); g_free(db_iface_data); } gpointer nml_dbus_object_get_property_location(NMLDBusObject * dbobj, const NMLDBusMetaIface * meta_iface, const NMLDBusMetaProperty *meta_property) { char *target_c; target_c = (char *) dbobj->nmobj; if (meta_iface->base_struct_offset > 0) target_c = *((gpointer *) (&target_c[meta_iface->base_struct_offset])); return &target_c[meta_property->prop_struct_offset]; } static void nml_dbus_object_set_obj_state(NMLDBusObject *dbobj, NMLDBusObjState obj_state, NMClient *self) { NMClientPrivate *priv; nm_assert(NM_IS_CLIENT(self)); nm_assert(NML_IS_DBUS_OBJECT(dbobj)); #if NM_MORE_ASSERTS > 10 priv = NM_CLIENT_GET_PRIVATE(self); switch (dbobj->obj_state) { case NML_DBUS_OBJ_STATE_UNLINKED: nm_assert(c_list_is_empty(&dbobj->dbus_objects_lst)); break; case NML_DBUS_OBJ_STATE_WATCHED_ONLY: nm_assert( c_list_contains(&priv->dbus_objects_lst_head_watched_only, &dbobj->dbus_objects_lst)); break; case NML_DBUS_OBJ_STATE_ON_DBUS: nm_assert(c_list_contains(&priv->dbus_objects_lst_head_on_dbus, &dbobj->dbus_objects_lst)); break; case NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY: nm_assert(c_list_contains(&priv->dbus_objects_lst_head_with_nmobj_not_ready, &dbobj->dbus_objects_lst)); break; case NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY: nm_assert(c_list_contains(&priv->dbus_objects_lst_head_with_nmobj_ready, &dbobj->dbus_objects_lst)); break; } #endif if (dbobj->obj_state == obj_state) return; NML_NMCLIENT_LOG_T(self, "[%s]: set D-Bus object state %s", dbobj->dbus_path->str, nml_dbus_obj_state_to_string(obj_state)); priv = NM_CLIENT_GET_PRIVATE(self); dbobj->obj_state = obj_state; switch (obj_state) { case NML_DBUS_OBJ_STATE_UNLINKED: c_list_unlink(&dbobj->dbus_objects_lst); c_list_unlink(&dbobj->obj_changed_lst); dbobj->obj_changed_type = NML_DBUS_OBJ_CHANGED_TYPE_NONE; break; case NML_DBUS_OBJ_STATE_WATCHED_ONLY: nm_c_list_move_tail(&priv->dbus_objects_lst_head_watched_only, &dbobj->dbus_objects_lst); break; case NML_DBUS_OBJ_STATE_ON_DBUS: nm_c_list_move_tail(&priv->dbus_objects_lst_head_on_dbus, &dbobj->dbus_objects_lst); break; case NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY: nm_c_list_move_tail(&priv->dbus_objects_lst_head_with_nmobj_not_ready, &dbobj->dbus_objects_lst); break; case NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY: nm_c_list_move_tail(&priv->dbus_objects_lst_head_with_nmobj_ready, &dbobj->dbus_objects_lst); break; default: nm_assert_not_reached(); } } /*****************************************************************************/ static void nml_dbus_object_obj_changed_link(NMClient * self, NMLDBusObject * dbobj, NMLDBusObjChangedType changed_type) { nm_assert(NM_IS_CLIENT(self)); nm_assert(NML_IS_DBUS_OBJECT(dbobj)); nm_assert(changed_type != NML_DBUS_OBJ_CHANGED_TYPE_NONE); if (!NM_FLAGS_ALL((NMLDBusObjChangedType) dbobj->obj_changed_type, changed_type)) NML_NMCLIENT_LOG_T(self, "[%s]: changed-type 0x%02x linked", dbobj->dbus_path->str, (guint) changed_type); if (dbobj->obj_changed_type == NML_DBUS_OBJ_CHANGED_TYPE_NONE) { NMClientPrivate *priv; /* We set the changed-type flag. Need to queue the object in the * changed list. */ nm_assert(c_list_is_empty(&dbobj->obj_changed_lst)); priv = NM_CLIENT_GET_PRIVATE(self); c_list_link_tail(&priv->obj_changed_lst_head, &dbobj->obj_changed_lst); } else { /* The object has changes flags and must be linked already. Note that * this may be priv->obj_changed_lst_head, or a temporary list on the * stack. * * This dance with the temporary list is done to ensure we can enqueue * objects while we process the changes. */ nm_assert(!c_list_is_empty(&dbobj->obj_changed_lst)); } dbobj->obj_changed_type |= changed_type; nm_assert(NM_FLAGS_ALL(dbobj->obj_changed_type, changed_type)); } static NMLDBusObjChangedType nml_dbus_object_obj_changed_consume(NMClient * self, NMLDBusObject * dbobj, NMLDBusObjChangedType changed_type) { NMClientPrivate * priv; NMLDBusObjChangedType changed_type_res; nm_assert(NM_IS_CLIENT(self)); nm_assert(NML_IS_DBUS_OBJECT(dbobj)); nm_assert(changed_type != NML_DBUS_OBJ_CHANGED_TYPE_NONE); nm_assert(dbobj->obj_changed_type != NML_DBUS_OBJ_CHANGED_TYPE_NONE); nm_assert(!c_list_is_empty(&dbobj->obj_changed_lst)); changed_type_res = dbobj->obj_changed_type & changed_type; dbobj->obj_changed_type &= ~changed_type; if (dbobj->obj_changed_type == NML_DBUS_OBJ_CHANGED_TYPE_NONE) { c_list_unlink(&dbobj->obj_changed_lst); nm_assert(changed_type_res != NML_DBUS_OBJ_CHANGED_TYPE_NONE); NML_NMCLIENT_LOG_T(self, "[%s]: changed-type 0x%02x consumed", dbobj->dbus_path->str, (guint) changed_type_res); return changed_type_res; } priv = NM_CLIENT_GET_PRIVATE(self); nm_assert(!c_list_contains(&priv->obj_changed_lst_head, &dbobj->obj_changed_lst)); nm_c_list_move_tail(&priv->obj_changed_lst_head, &dbobj->obj_changed_lst); NML_NMCLIENT_LOG_T(self, "[%s]: changed-type 0x%02x consumed (still has 0x%02x)", dbobj->dbus_path->str, (guint) changed_type_res, (guint) dbobj->obj_changed_type); return changed_type_res; } static gboolean nml_dbus_object_obj_changed_any_linked(NMClient *self, NMLDBusObjChangedType changed_type) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NMLDBusObject * dbobj; nm_assert(changed_type != NML_DBUS_OBJ_CHANGED_TYPE_NONE); c_list_for_each_entry (dbobj, &priv->obj_changed_lst_head, obj_changed_lst) { nm_assert(dbobj->obj_changed_type != NML_DBUS_OBJ_CHANGED_TYPE_NONE); if (NM_FLAGS_ANY(dbobj->obj_changed_type, changed_type)) return TRUE; } return FALSE; } /*****************************************************************************/ static void _dbobjs_notify_watchers_for_dbobj(NMClient *self, NMLDBusObject *dbobj) { NMLDBusObjWatcher *obj_watcher; NMLDBusObjWatcher *obj_watcher_safe; c_list_for_each_entry_safe (obj_watcher, obj_watcher_safe, &dbobj->watcher_lst_head, _priv.watcher_lst) obj_watcher->_priv.notify_fcn(self, obj_watcher); } static gboolean _dbobjs_check_dbobj_ready(NMClient *self, NMLDBusObject *dbobj) { nm_assert(NM_IS_CLIENT(self)); nm_assert(NML_IS_DBUS_OBJECT(dbobj)); nm_assert(G_IS_OBJECT(dbobj->nmobj)); nm_assert(NM_IS_OBJECT(dbobj->nmobj) || NM_IS_CLIENT(dbobj->nmobj)); nm_assert(NM_IN_SET((NMLDBusObjState) dbobj->obj_state, NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY, NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY)); if (G_LIKELY(dbobj->obj_state == NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY)) return TRUE; if (!NM_OBJECT_GET_CLASS(dbobj->nmobj)->is_ready(NM_OBJECT(dbobj->nmobj))) return FALSE; nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY, self); nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); _dbobjs_notify_watchers_for_dbobj(self, dbobj); return TRUE; } void _nm_client_notify_object_changed(NMClient *self, NMLDBusObject *dbobj) { nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); _dbobjs_notify_watchers_for_dbobj(self, dbobj); } /*****************************************************************************/ static NMLDBusObject * _dbobjs_dbobj_get_r(NMClient *self, NMRefString *dbus_path_r) { nm_assert(NM_IS_REF_STRING(dbus_path_r)); return g_hash_table_lookup(NM_CLIENT_GET_PRIVATE(self)->dbus_objects, &dbus_path_r); } static NMLDBusObject * _dbobjs_dbobj_get_s(NMClient *self, const char *dbus_path) { nm_auto_ref_string NMRefString *dbus_path_r = NULL; nm_assert(dbus_path); dbus_path_r = nm_ref_string_new(dbus_path); return _dbobjs_dbobj_get_r(self, dbus_path_r); } static NMLDBusObject * _dbobjs_dbobj_create(NMClient *self, NMRefString *dbus_path_take) { nm_auto_ref_string NMRefString *dbus_path = g_steal_pointer(&dbus_path_take); NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); NMLDBusObject * dbobj; nm_assert(!_dbobjs_dbobj_get_r(self, dbus_path)); dbobj = nml_dbus_object_new(g_steal_pointer(&dbus_path)); if (!g_hash_table_add(priv->dbus_objects, dbobj)) nm_assert_not_reached(); return dbobj; } static NMLDBusObject * _dbobjs_dbobj_get_or_create(NMClient *self, NMRefString *dbus_path_take) { nm_auto_ref_string NMRefString *dbus_path = g_steal_pointer(&dbus_path_take); NMLDBusObject * dbobj; dbobj = _dbobjs_dbobj_get_r(self, dbus_path); if (dbobj) return dbobj; return _dbobjs_dbobj_create(self, g_steal_pointer(&dbus_path)); } static NMLDBusObject * _dbobjs_get_nmobj(NMClient *self, const char *dbus_path, GType gtype) { NMLDBusObject *dbobj; nm_assert(gtype == G_TYPE_NONE || g_type_is_a(gtype, NM_TYPE_OBJECT)); dbobj = _dbobjs_dbobj_get_s(self, dbus_path); if (!dbobj) return NULL; if (!dbobj->nmobj) return NULL; if (gtype != G_TYPE_NONE && !g_type_is_a(G_OBJECT_TYPE(dbobj->nmobj), gtype)) return NULL; return dbobj; } static gpointer _dbobjs_get_nmobj_unpack_visible(NMClient *self, const char *dbus_path, GType gtype) { NMLDBusObject *dbobj; dbobj = _dbobjs_get_nmobj(self, dbus_path, gtype); if (!dbobj) return NULL; if (dbobj->obj_state != NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY) return NULL; return dbobj->nmobj; } /*****************************************************************************/ static gpointer _dbobjs_obj_watcher_register_o(NMClient * self, NMLDBusObject * dbobj, NMLDBusObjWatchNotifyFcn notify_fcn, gsize struct_size) { NMLDBusObjWatcher *obj_watcher; nm_assert(NM_IS_CLIENT(self)); _ASSERT_dbobj(dbobj, self); nm_assert(notify_fcn); nm_assert(struct_size > sizeof(NMLDBusObjWatcher)); obj_watcher = g_malloc(struct_size); obj_watcher->dbobj = dbobj; obj_watcher->_priv.notify_fcn = notify_fcn; /* we must enqueue the item in the front of the list. That is, because while * invoking notify_fcn(), we iterate the watchers front-to-end. As we want to * allow the callee to register new watches and unregister itself, this is * the right way to do it. */ c_list_link_front(&dbobj->watcher_lst_head, &obj_watcher->_priv.watcher_lst); return obj_watcher; } static gpointer _dbobjs_obj_watcher_register_r(NMClient * self, NMRefString * dbus_path_take, NMLDBusObjWatchNotifyFcn notify_fcn, gsize struct_size) { nm_auto_ref_string NMRefString *dbus_path = g_steal_pointer(&dbus_path_take); NMLDBusObject * dbobj; nm_assert(NM_IS_CLIENT(self)); nm_assert(notify_fcn); dbobj = _dbobjs_dbobj_get_or_create(self, g_steal_pointer(&dbus_path)); if (dbobj->obj_state == NML_DBUS_OBJ_STATE_UNLINKED) nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_WATCHED_ONLY, self); return _dbobjs_obj_watcher_register_o(self, dbobj, notify_fcn, struct_size); } static void _dbobjs_obj_watcher_unregister(NMClient *self, gpointer obj_watcher_base) { NMLDBusObjWatcher *obj_watcher = obj_watcher_base; NMLDBusObject * dbobj; nm_assert(NM_IS_CLIENT(self)); nm_assert(obj_watcher); nm_assert(NML_IS_DBUS_OBJECT(obj_watcher->dbobj)); nm_assert(g_hash_table_lookup(NM_CLIENT_GET_PRIVATE(self)->dbus_objects, obj_watcher->dbobj) == obj_watcher->dbobj); nm_assert( c_list_contains(&obj_watcher->dbobj->watcher_lst_head, &obj_watcher->_priv.watcher_lst)); c_list_unlink(&obj_watcher->_priv.watcher_lst); dbobj = obj_watcher->dbobj; g_free(obj_watcher); if (c_list_is_empty(&dbobj->iface_lst_head) && c_list_is_empty(&dbobj->watcher_lst_head)) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NML_NMCLIENT_LOG_T(self, "[%s]: drop D-Bus watcher", dbobj->dbus_path->str); nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_UNLINKED, self); if (!g_hash_table_steal(priv->dbus_objects, dbobj)) nm_assert_not_reached(); nml_dbus_object_unref(dbobj); } } /*****************************************************************************/ typedef struct { NMLDBusObjWatcher parent; NMLDBusPropertyO *pr_o; } PropertyOData; gpointer nml_dbus_property_o_get_obj(NMLDBusPropertyO *pr_o) { nm_assert(!pr_o->nmobj || nml_dbus_property_o_is_ready(pr_o)); return pr_o->nmobj; } gboolean nml_dbus_property_o_is_ready(const NMLDBusPropertyO *pr_o) { return pr_o->is_ready || !pr_o->owner_dbobj; } gboolean nml_dbus_property_o_is_ready_fully(const NMLDBusPropertyO *pr_o) { return !pr_o->owner_dbobj || !pr_o->obj_watcher || pr_o->nmobj; } static void nml_dbus_property_o_notify_changed(NMLDBusPropertyO *pr_o, NMClient *self) { const NMLDBusPropertVTableO *vtable; GObject * nmobj = NULL; gboolean is_ready = TRUE; gboolean changed_ready; GType gtype; nm_assert(pr_o); nm_assert(NM_IS_CLIENT(self)); if (!pr_o->owner_dbobj) return; if (!pr_o->is_changed) { if (pr_o->is_ready) return; goto done; } pr_o->is_changed = FALSE; if (!pr_o->obj_watcher) goto done; if (!pr_o->obj_watcher->dbobj->nmobj) { if (pr_o->obj_watcher->dbobj->obj_state >= NML_DBUS_OBJ_STATE_ON_DBUS) { NML_NMCLIENT_LOG_W( self, "[%s]: property %s references %s but object is not created", pr_o->owner_dbobj->dbus_path->str, pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].dbus_property_name, pr_o->obj_watcher->dbobj->dbus_path->str); } else { NML_NMCLIENT_LOG_E( self, "[%s]: property %s references %s but object is not present on D-Bus", pr_o->owner_dbobj->dbus_path->str, pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].dbus_property_name, pr_o->obj_watcher->dbobj->dbus_path->str); } goto done; } vtable = pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].extra.property_vtable_o; gtype = vtable->get_o_type_fcn(); if (!g_type_is_a(G_OBJECT_TYPE(pr_o->obj_watcher->dbobj->nmobj), gtype)) { NML_NMCLIENT_LOG_E( self, "[%s]: property %s references %s with unexpected GObject type %s instead of %s", pr_o->owner_dbobj->dbus_path->str, pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].dbus_property_name, pr_o->obj_watcher->dbobj->dbus_path->str, G_OBJECT_TYPE_NAME(pr_o->obj_watcher->dbobj->nmobj), g_type_name(gtype)); goto done; } if (pr_o->obj_watcher->dbobj == pr_o->owner_dbobj) { NML_NMCLIENT_LOG_W( self, "[%s]: property %s references itself", pr_o->owner_dbobj->dbus_path->str, pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].dbus_property_name); nmobj = pr_o->owner_dbobj->nmobj; goto done; } pr_o->block_is_changed = TRUE; is_ready = _dbobjs_check_dbobj_ready(self, pr_o->obj_watcher->dbobj); pr_o->block_is_changed = FALSE; if (!is_ready) { is_ready = vtable->is_always_ready; goto done; } nmobj = pr_o->obj_watcher->dbobj->nmobj; done: changed_ready = FALSE; if (!pr_o->is_ready && is_ready) { pr_o->is_ready = TRUE; changed_ready = TRUE; } if (pr_o->nmobj != nmobj) { pr_o->nmobj = nmobj; _nm_client_queue_notify_object( self, pr_o->owner_dbobj->nmobj, pr_o->meta_iface->obj_properties [pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].obj_properties_idx]); } if (changed_ready && pr_o->owner_dbobj->obj_state == NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY) nml_dbus_object_obj_changed_link(self, pr_o->owner_dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } void nml_dbus_property_o_notify_changed_many(NMLDBusPropertyO *ptr, guint len, NMClient *self) { while (len-- > 0) nml_dbus_property_o_notify_changed(ptr++, self); } static void nml_dbus_property_o_notify_watch_cb(NMClient *self, gpointer obj_watcher) { PropertyOData * pr_o_data = obj_watcher; NMLDBusPropertyO *pr_o = pr_o_data->pr_o; nm_assert(pr_o->obj_watcher == obj_watcher); if (!pr_o->block_is_changed && !pr_o->is_changed) { pr_o->is_changed = TRUE; nml_dbus_object_obj_changed_link(self, pr_o->owner_dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } } static NMLDBusNotifyUpdatePropFlags nml_dbus_property_o_notify(NMClient * self, NMLDBusPropertyO * pr_o, NMLDBusObject * dbobj, const NMLDBusMetaIface *meta_iface, guint dbus_property_idx, GVariant * value) { const char *dbus_path = NULL; gboolean changed = FALSE; if (!pr_o->owner_dbobj) { nm_assert(!pr_o->meta_iface); nm_assert(pr_o->dbus_property_idx == 0); nm_assert(!pr_o->is_ready); pr_o->owner_dbobj = dbobj; pr_o->meta_iface = meta_iface; pr_o->dbus_property_idx = dbus_property_idx; } else { nm_assert(pr_o->owner_dbobj == dbobj); nm_assert(pr_o->meta_iface == meta_iface); nm_assert(pr_o->dbus_property_idx == dbus_property_idx); } if (value) dbus_path = nm_dbus_path_not_empty(g_variant_get_string(value, NULL)); if (pr_o->obj_watcher && (!dbus_path || !nm_streq(dbus_path, pr_o->obj_watcher->dbobj->dbus_path->str))) { _dbobjs_obj_watcher_unregister(self, g_steal_pointer(&pr_o->obj_watcher)); changed = TRUE; } if (!pr_o->obj_watcher && dbus_path) { pr_o->obj_watcher = _dbobjs_obj_watcher_register_r(self, nm_ref_string_new(dbus_path), nml_dbus_property_o_notify_watch_cb, sizeof(PropertyOData)); ((PropertyOData *) pr_o->obj_watcher)->pr_o = pr_o; changed = TRUE; } if (changed && !pr_o->is_changed) { pr_o->is_changed = TRUE; nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NONE; } void nml_dbus_property_o_clear(NMLDBusPropertyO *pr_o, NMClient *self) { if (pr_o->obj_watcher) { nm_assert(NM_IS_CLIENT(self)); _dbobjs_obj_watcher_unregister(self, g_steal_pointer(&pr_o->obj_watcher)); } if (pr_o->nmobj && pr_o->owner_dbobj && pr_o->owner_dbobj->nmobj) { _nm_client_queue_notify_object( self, pr_o->owner_dbobj->nmobj, pr_o->meta_iface->obj_properties [pr_o->meta_iface->dbus_properties[pr_o->dbus_property_idx].obj_properties_idx]); } pr_o->owner_dbobj = NULL; pr_o->meta_iface = NULL; pr_o->dbus_property_idx = 0; pr_o->is_ready = FALSE; } void nml_dbus_property_o_clear_many(NMLDBusPropertyO *pr_o, guint len, NMClient *self) { while (len-- > 0) nml_dbus_property_o_clear(pr_o++, self); } /*****************************************************************************/ typedef struct _NMLDBusPropertyAOData { NMLDBusObjWatcher obj_watcher; NMLDBusPropertyAO * parent; CList data_lst; GObject * nmobj; struct _NMLDBusPropertyAOData *changed_next; bool is_ready : 1; bool is_notified : 1; bool is_changed : 1; bool block_is_changed : 1; } PropertyAOData; static void _ASSERT_pr_ao(NMLDBusPropertyAO *pr_ao) { nm_assert(pr_ao); #if NM_MORE_ASSERTS > 10 if (pr_ao->owner_dbobj) { guint n_not_ready = 0; guint n_is_changed = 0; guint n_is_changed_2; PropertyAOData *pr_ao_data; c_list_for_each_entry (pr_ao_data, &pr_ao->data_lst_head, data_lst) { if (pr_ao_data->is_changed) n_is_changed++; if (!pr_ao_data->is_ready) n_not_ready++; } nm_assert(n_not_ready == pr_ao->n_not_ready); n_is_changed_2 = 0; pr_ao_data = pr_ao->changed_head; while (pr_ao_data) { nm_assert(pr_ao_data->is_changed); n_is_changed_2++; pr_ao_data = pr_ao_data->changed_next; } nm_assert(n_is_changed == n_is_changed_2); } #endif } static gboolean nml_dbus_property_ao_notify_changed_ao(PropertyAOData *pr_ao_data, NMClient * self, gboolean is_added /* or else removed */) { NMLDBusPropertyAO * pr_ao; const NMLDBusPropertVTableAO *vtable; if (!pr_ao_data->nmobj) return FALSE; nm_assert(pr_ao_data->is_ready); if (is_added) { if (pr_ao_data->is_notified) return FALSE; pr_ao_data->is_notified = TRUE; } else { if (!pr_ao_data->is_notified) return FALSE; pr_ao_data->is_notified = FALSE; } pr_ao = pr_ao_data->parent; vtable = pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].extra.property_vtable_ao; if (vtable->notify_changed_ao) vtable->notify_changed_ao(pr_ao, self, NM_OBJECT(pr_ao_data->nmobj), is_added); return TRUE; } const GPtrArray * nml_dbus_property_ao_get_objs_as_ptrarray(NMLDBusPropertyAO *pr_ao) { if (!pr_ao->arr) { PropertyAOData *pr_ao_data; gsize n; n = 0; if (pr_ao->owner_dbobj) { c_list_for_each_entry (pr_ao_data, &pr_ao->data_lst_head, data_lst) { if (pr_ao_data->nmobj) n++; } } pr_ao->arr = g_ptr_array_new_full(n, g_object_unref); if (pr_ao->owner_dbobj) { c_list_for_each_entry (pr_ao_data, &pr_ao->data_lst_head, data_lst) { if (pr_ao_data->nmobj) g_ptr_array_add(pr_ao->arr, g_object_ref(pr_ao_data->nmobj)); } } } return pr_ao->arr; } gboolean nml_dbus_property_ao_is_ready(const NMLDBusPropertyAO *pr_ao) { return pr_ao->n_not_ready == 0; } static void nml_dbus_property_ao_notify_changed(NMLDBusPropertyAO *pr_ao, NMClient *self) { gboolean changed_prop = FALSE; gboolean changed_ready = FALSE; PropertyAOData *pr_ao_data; nm_assert(NM_IS_CLIENT(self)); _ASSERT_pr_ao(pr_ao); if (!pr_ao->owner_dbobj) return; if (!pr_ao->is_changed) { if (pr_ao->n_not_ready == 0) return; goto done; } pr_ao->is_changed = FALSE; while (pr_ao->changed_head) { const NMLDBusPropertVTableAO *vtable; GObject * nmobj = NULL; gboolean is_ready = TRUE; GType gtype; pr_ao_data = g_steal_pointer(&pr_ao->changed_head); nm_assert(pr_ao_data->is_changed); pr_ao->changed_head = pr_ao_data->changed_next; pr_ao_data->is_changed = FALSE; if (!pr_ao_data->obj_watcher.dbobj->nmobj) { if (pr_ao_data->obj_watcher.dbobj->obj_state >= NML_DBUS_OBJ_STATE_ON_DBUS) { NML_NMCLIENT_LOG_W( self, "[%s]: property %s references %s but object is not created", pr_ao->owner_dbobj->dbus_path->str, pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].dbus_property_name, pr_ao_data->obj_watcher.dbobj->dbus_path->str); } else { NML_NMCLIENT_LOG_E( self, "[%s]: property %s references %s but object is not present on D-Bus", pr_ao->owner_dbobj->dbus_path->str, pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].dbus_property_name, pr_ao_data->obj_watcher.dbobj->dbus_path->str); } goto done_pr_ao_data; } vtable = pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].extra.property_vtable_ao; gtype = vtable->get_o_type_fcn(); if (!g_type_is_a(G_OBJECT_TYPE(pr_ao_data->obj_watcher.dbobj->nmobj), gtype)) { NML_NMCLIENT_LOG_E( self, "[%s]: property %s references %s with unexpected GObject type %s instead of %s", pr_ao->owner_dbobj->dbus_path->str, pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].dbus_property_name, pr_ao_data->obj_watcher.dbobj->dbus_path->str, G_OBJECT_TYPE_NAME(pr_ao_data->obj_watcher.dbobj->nmobj), g_type_name(gtype)); goto done_pr_ao_data; } if (pr_ao_data->obj_watcher.dbobj == pr_ao->owner_dbobj) { NML_NMCLIENT_LOG_W( self, "[%s]: property %s references itself", pr_ao->owner_dbobj->dbus_path->str, pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].dbus_property_name); nmobj = pr_ao->owner_dbobj->nmobj; goto done_pr_ao_data; } pr_ao_data->block_is_changed = TRUE; is_ready = _dbobjs_check_dbobj_ready(self, pr_ao_data->obj_watcher.dbobj); pr_ao_data->block_is_changed = FALSE; if (!is_ready) { is_ready = vtable->is_always_ready; goto done_pr_ao_data; } if (vtable->check_nmobj_visible_fcn && !vtable->check_nmobj_visible_fcn(pr_ao_data->obj_watcher.dbobj->nmobj)) { is_ready = TRUE; goto done_pr_ao_data; } nmobj = pr_ao_data->obj_watcher.dbobj->nmobj; done_pr_ao_data: if (!pr_ao_data->is_ready && is_ready) { nm_assert(pr_ao->n_not_ready > 0); pr_ao->n_not_ready--; pr_ao_data->is_ready = TRUE; changed_ready = TRUE; } if (pr_ao_data->nmobj != nmobj) { if (nml_dbus_property_ao_notify_changed_ao(pr_ao_data, self, FALSE)) changed_prop = TRUE; pr_ao_data->nmobj = nmobj; } if (!pr_ao_data->is_notified) { if (nml_dbus_property_ao_notify_changed_ao(pr_ao_data, self, TRUE)) changed_prop = TRUE; } } _ASSERT_pr_ao(pr_ao); done: if (changed_prop) { nm_clear_pointer(&pr_ao->arr, g_ptr_array_unref); _nm_client_queue_notify_object( self, pr_ao->owner_dbobj->nmobj, pr_ao->meta_iface->obj_properties [pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].obj_properties_idx]); } if (changed_ready && pr_ao->n_not_ready == 0 && pr_ao->owner_dbobj->obj_state == NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY) nml_dbus_object_obj_changed_link(self, pr_ao->owner_dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } void nml_dbus_property_ao_notify_changed_many(NMLDBusPropertyAO *ptr, guint len, NMClient *self) { while (len-- > 0) nml_dbus_property_ao_notify_changed(ptr++, self); } static void nml_dbus_property_ao_notify_watch_cb(NMClient *self, gpointer obj_watcher) { PropertyAOData * pr_ao_data = obj_watcher; NMLDBusPropertyAO *pr_ao = pr_ao_data->parent; nm_assert(g_hash_table_lookup(pr_ao->hash, pr_ao_data) == pr_ao_data); if (!pr_ao_data->block_is_changed && !pr_ao_data->is_changed) { pr_ao_data->is_changed = TRUE; pr_ao_data->changed_next = pr_ao->changed_head; pr_ao->changed_head = pr_ao_data; if (!pr_ao->is_changed) { pr_ao->is_changed = TRUE; nml_dbus_object_obj_changed_link(self, pr_ao->owner_dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } } _ASSERT_pr_ao(pr_ao); } static NMLDBusNotifyUpdatePropFlags nml_dbus_property_ao_notify(NMClient * self, NMLDBusPropertyAO * pr_ao, NMLDBusObject * dbobj, const NMLDBusMetaIface *meta_iface, guint dbus_property_idx, GVariant * value) { CList stale_lst_head = C_LIST_INIT(stale_lst_head); PropertyAOData *pr_ao_data; gboolean changed_prop = FALSE; gboolean changed_obj = FALSE; if (!pr_ao->owner_dbobj) { nm_assert(!pr_ao->data_lst_head.next); nm_assert(!pr_ao->data_lst_head.prev); nm_assert(!pr_ao->hash); nm_assert(!pr_ao->meta_iface); nm_assert(pr_ao->dbus_property_idx == 0); nm_assert(pr_ao->n_not_ready == 0); nm_assert(!pr_ao->changed_head); nm_assert(!pr_ao->is_changed); c_list_init(&pr_ao->data_lst_head); pr_ao->hash = g_hash_table_new(nm_ppdirect_hash, nm_ppdirect_equal); pr_ao->owner_dbobj = dbobj; pr_ao->meta_iface = meta_iface; pr_ao->dbus_property_idx = dbus_property_idx; } else { nm_assert(pr_ao->data_lst_head.next); nm_assert(pr_ao->data_lst_head.prev); nm_assert(pr_ao->hash); nm_assert(pr_ao->meta_iface == meta_iface); nm_assert(pr_ao->dbus_property_idx == dbus_property_idx); } c_list_splice(&stale_lst_head, &pr_ao->data_lst_head); if (value) { GVariantIter iter; const char * path; g_variant_iter_init(&iter, value); while (g_variant_iter_next(&iter, "&o", &path)) { nm_auto_ref_string NMRefString *dbus_path_r = NULL; gpointer p_dbus_path_1; G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(PropertyAOData, obj_watcher) == 0); G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMLDBusObjWatcher, dbobj) == 0); G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMLDBusObject, dbus_path) == 0); if (!nm_dbus_path_not_empty(path)) { /* should not happen. Anyway, silently skip empty paths. */ continue; } dbus_path_r = nm_ref_string_new(path); p_dbus_path_1 = &dbus_path_r; pr_ao_data = g_hash_table_lookup(pr_ao->hash, &p_dbus_path_1); if (pr_ao_data) { /* With this implementation we cannot track the same path multiple times. * Of course, for none of the properties where we use this, the server * should expose the same path more than once, so this limitation is fine * (maybe even preferable to drop duplicates form NMClient's API). */ nm_assert(pr_ao_data->obj_watcher.dbobj->dbus_path == dbus_path_r); if (!changed_prop && pr_ao_data->is_notified) { /* The order of a notified entry changed. That means, we need to signal * a change of the property. This detection of a change is not always * correct, in particular we might detect some changes when there were * none. That's not a serious problem, and fixing it would be expensive * to implement. */ changed_prop = (c_list_first(&stale_lst_head) != &pr_ao_data->data_lst); } nm_c_list_move_tail(&pr_ao->data_lst_head, &pr_ao_data->data_lst); } else { pr_ao_data = _dbobjs_obj_watcher_register_r(self, g_steal_pointer(&dbus_path_r), nml_dbus_property_ao_notify_watch_cb, sizeof(PropertyAOData)), pr_ao_data->parent = pr_ao; pr_ao_data->nmobj = NULL; pr_ao_data->changed_next = NULL; pr_ao_data->is_changed = TRUE; pr_ao_data->block_is_changed = FALSE; pr_ao_data->is_ready = FALSE; pr_ao_data->is_notified = FALSE; c_list_link_tail(&pr_ao->data_lst_head, &pr_ao_data->data_lst); if (!g_hash_table_add(pr_ao->hash, pr_ao_data)) nm_assert_not_reached(); nm_assert(pr_ao->n_not_ready < G_MAXUINT); pr_ao->n_not_ready++; } #if NM_MORE_ASSERTS > 10 { nm_auto_ref_string NMRefString *p = nm_ref_string_new(path); gpointer pp = &p; nm_assert(g_hash_table_lookup(pr_ao->hash, &pp) == pr_ao_data); } #endif } } pr_ao->changed_head = NULL; c_list_for_each_entry (pr_ao_data, &pr_ao->data_lst_head, data_lst) { if (pr_ao_data->is_changed) { pr_ao_data->changed_next = pr_ao->changed_head; pr_ao->changed_head = pr_ao_data; changed_obj = TRUE; } } while ((pr_ao_data = c_list_first_entry(&stale_lst_head, PropertyAOData, data_lst))) { changed_obj = TRUE; c_list_unlink(&pr_ao_data->data_lst); if (!g_hash_table_remove(pr_ao->hash, pr_ao_data)) nm_assert_not_reached(); if (!pr_ao_data->is_ready) { nm_assert(pr_ao->n_not_ready > 0); pr_ao->n_not_ready--; } else { if (nml_dbus_property_ao_notify_changed_ao(pr_ao_data, self, FALSE)) changed_prop = TRUE; } _dbobjs_obj_watcher_unregister(self, pr_ao_data); } _ASSERT_pr_ao(pr_ao); if (changed_obj) { pr_ao->is_changed = TRUE; nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } if (changed_prop) { nm_clear_pointer(&pr_ao->arr, g_ptr_array_unref); _nm_client_queue_notify_object( self, pr_ao->owner_dbobj->nmobj, pr_ao->meta_iface->obj_properties [pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx].obj_properties_idx]); } return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NONE; } void nml_dbus_property_ao_clear(NMLDBusPropertyAO *pr_ao, NMClient *self) { _ASSERT_pr_ao(pr_ao); if (!pr_ao->owner_dbobj) { nm_assert(pr_ao->n_not_ready == 0); nm_assert((!pr_ao->data_lst_head.next && !pr_ao->data_lst_head.prev) || (pr_ao->data_lst_head.next == pr_ao->data_lst_head.prev)); nm_assert(!pr_ao->hash); nm_assert(!pr_ao->meta_iface); nm_assert(pr_ao->dbus_property_idx == 0); nm_assert(!pr_ao->is_changed); } else { PropertyAOData *pr_ao_data; gboolean changed_prop = FALSE; nm_assert(NM_IS_CLIENT(self)); nm_assert(pr_ao->data_lst_head.next); nm_assert(pr_ao->data_lst_head.prev); nm_assert(pr_ao->hash); nm_assert(pr_ao->meta_iface); while ((pr_ao_data = c_list_first_entry(&pr_ao->data_lst_head, PropertyAOData, data_lst))) { if (!pr_ao_data->is_ready) { nm_assert(pr_ao->n_not_ready > 0); pr_ao->n_not_ready--; } else { if (nml_dbus_property_ao_notify_changed_ao(pr_ao_data, self, FALSE)) changed_prop = TRUE; } c_list_unlink(&pr_ao_data->data_lst); if (!g_hash_table_remove(pr_ao->hash, pr_ao_data)) nm_assert_not_reached(); _dbobjs_obj_watcher_unregister(self, pr_ao_data); } nm_assert(c_list_is_empty(&pr_ao->data_lst_head)); nm_assert(pr_ao->n_not_ready == 0); nm_assert(g_hash_table_size(pr_ao->hash) == 0); if (changed_prop && pr_ao->owner_dbobj->nmobj) { _nm_client_queue_notify_object( self, pr_ao->owner_dbobj->nmobj, pr_ao->meta_iface ->obj_properties[pr_ao->meta_iface->dbus_properties[pr_ao->dbus_property_idx] .obj_properties_idx]); } nm_assert(c_list_is_empty(&pr_ao->data_lst_head)); nm_assert(pr_ao->n_not_ready == 0); nm_assert(g_hash_table_size(pr_ao->hash) == 0); nm_clear_pointer(&pr_ao->hash, g_hash_table_unref); pr_ao->owner_dbobj = NULL; pr_ao->meta_iface = NULL; pr_ao->dbus_property_idx = 0; pr_ao->data_lst_head.next = NULL; pr_ao->data_lst_head.prev = NULL; pr_ao->is_changed = FALSE; } nm_clear_pointer(&pr_ao->arr, g_ptr_array_unref); } void nml_dbus_property_ao_clear_many(NMLDBusPropertyAO *pr_ao, guint len, NMClient *self) { while (len-- > 0) nml_dbus_property_ao_clear(pr_ao++, self); } /*****************************************************************************/ NMLDBusNotifyUpdatePropFlags _nml_dbus_notify_update_prop_ignore(NMClient * self, NMLDBusObject * dbobj, const NMLDBusMetaIface *meta_iface, guint dbus_property_idx, GVariant * value) { return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NONE; } NMLDBusNotifyUpdatePropFlags _nml_dbus_notify_update_prop_o(NMClient * self, NMLDBusObject * dbobj, const NMLDBusMetaIface *meta_iface, guint dbus_property_idx, GVariant * value) { const char * path = NULL; NMRefString **p_property; if (value) path = g_variant_get_string(value, NULL); p_property = nml_dbus_object_get_property_location(dbobj, meta_iface, &meta_iface->dbus_properties[dbus_property_idx]); if (!nm_streq0(nm_ref_string_get_str(*p_property), path)) { nm_ref_string_unref(*p_property); *p_property = nm_ref_string_new(path); } return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY; } /*****************************************************************************/ static void _obj_handle_dbus_prop_changes(NMClient * self, NMLDBusObject * dbobj, NMLDBusObjIfaceData *db_iface_data, guint dbus_property_idx, GVariant * value) { const NMLDBusMetaIface * meta_iface = db_iface_data->dbus_iface.meta; const NMLDBusMetaProperty * meta_property = &meta_iface->dbus_properties[dbus_property_idx]; gpointer p_property; const char * dbus_type_s; const GParamSpec * param_spec; NMLDBusNotifyUpdatePropFlags notify_update_prop_flags; nm_assert(G_IS_OBJECT(dbobj->nmobj)); if (value && !g_variant_is_of_type(value, meta_property->dbus_type)) { NML_NMCLIENT_LOG_E(self, "[%s] property %s.%s expected of type \"%s\" but is \"%s\". Ignore", dbobj->dbus_path->str, meta_iface->dbus_iface_name, meta_property->dbus_property_name, (const char *) meta_property->dbus_type, (const char *) g_variant_get_type(value)); value = NULL; } if (meta_property->use_notify_update_prop) { notify_update_prop_flags = meta_property->notify_update_prop(self, dbobj, meta_iface, dbus_property_idx, value); if (notify_update_prop_flags == NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NONE) return; nm_assert(notify_update_prop_flags == NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY); nm_assert(G_IS_OBJECT(dbobj->nmobj)); nm_assert(meta_iface->obj_properties); nm_assert(meta_property->obj_properties_idx > 0); param_spec = meta_iface->obj_properties[meta_property->obj_properties_idx]; goto notify; } p_property = nml_dbus_object_get_property_location(dbobj, meta_iface, meta_property); dbus_type_s = (const char *) meta_property->dbus_type; nm_assert(G_IS_OBJECT(dbobj->nmobj)); nm_assert(meta_iface->obj_properties); nm_assert(meta_property->obj_properties_idx > 0); param_spec = meta_iface->obj_properties[meta_property->obj_properties_idx]; notify_update_prop_flags = NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY; switch (dbus_type_s[0]) { case 'b': nm_assert(dbus_type_s[1] == '\0'); if (value) *((bool *) p_property) = g_variant_get_boolean(value); else if (param_spec->value_type == G_TYPE_BOOLEAN) *((bool *) p_property) = ((GParamSpecBoolean *) param_spec)->default_value; else { nm_assert_not_reached(); *((bool *) p_property) = FALSE; } break; case 'y': nm_assert(dbus_type_s[1] == '\0'); if (value) *((guint8 *) p_property) = g_variant_get_byte(value); else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((guint8 *) p_property) = 0; } break; case 'q': nm_assert(dbus_type_s[1] == '\0'); if (value) *((guint16 *) p_property) = g_variant_get_uint16(value); else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((guint16 *) p_property) = 0; } break; case 'i': nm_assert(dbus_type_s[1] == '\0'); if (value) *((gint32 *) p_property) = g_variant_get_int32(value); else if (param_spec->value_type == G_TYPE_INT) *((gint32 *) p_property) = ((GParamSpecInt *) param_spec)->default_value; else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((gint32 *) p_property) = 0; } break; case 'u': nm_assert(dbus_type_s[1] == '\0'); if (value) *((guint32 *) p_property) = g_variant_get_uint32(value); else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((guint32 *) p_property) = 0; } break; case 'x': nm_assert(dbus_type_s[1] == '\0'); if (value) *((gint64 *) p_property) = g_variant_get_int64(value); else if (param_spec->value_type == G_TYPE_INT64) *((gint64 *) p_property) = ((GParamSpecInt64 *) param_spec)->default_value; else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((gint64 *) p_property) = 0; } break; case 't': nm_assert(dbus_type_s[1] == '\0'); if (value) *((guint64 *) p_property) = g_variant_get_uint64(value); else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((guint64 *) p_property) = 0; } break; case 's': nm_assert(dbus_type_s[1] == '\0'); nm_clear_g_free((char **) p_property); if (value) *((char **) p_property) = g_variant_dup_string(value, NULL); else { nm_assert(nm_utils_g_param_spec_is_default(param_spec)); *((char **) p_property) = NULL; } break; case 'o': nm_assert(dbus_type_s[1] == '\0'); notify_update_prop_flags = nml_dbus_property_o_notify(self, p_property, dbobj, meta_iface, dbus_property_idx, value); break; case 'a': switch (dbus_type_s[1]) { case 'y': nm_assert(dbus_type_s[2] == '\0'); { gconstpointer v; gsize l; GBytes * b = NULL; if (value) { v = g_variant_get_fixed_array(value, &l, 1); if (l > 0) { /* empty arrays are coerced to NULL. */ b = g_bytes_new(v, l); } } nm_clear_pointer((GBytes **) p_property, g_bytes_unref); *((GBytes **) p_property) = b; } break; case 's': nm_assert(dbus_type_s[2] == '\0'); nm_assert(param_spec->value_type == G_TYPE_STRV); g_strfreev(*((char ***) p_property)); if (value) *((char ***) p_property) = g_variant_dup_strv(value, NULL); else *((char ***) p_property) = NULL; break; case 'o': nm_assert(dbus_type_s[2] == '\0'); notify_update_prop_flags = nml_dbus_property_ao_notify(self, p_property, dbobj, meta_iface, dbus_property_idx, value); break; default: nm_assert_not_reached(); } break; default: nm_assert_not_reached(); } notify: if (NM_FLAGS_HAS(notify_update_prop_flags, NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY)) g_object_notify_by_pspec(dbobj->nmobj, (GParamSpec *) param_spec); } static void _obj_handle_dbus_iface_changes(NMClient * self, NMLDBusObject * dbobj, NMLDBusObjIfaceData *db_iface_data) { NMLDBusObjPropData *db_prop_data; gboolean is_self = (G_OBJECT(self) == dbobj->nmobj); gboolean is_removed; gboolean type_compatible; guint8 i_prop; nm_assert(NM_IS_CLIENT(self)); nm_assert(is_self || NM_IS_OBJECT(dbobj->nmobj)); if (G_UNLIKELY(!db_iface_data->nmobj_checked)) { db_iface_data->nmobj_checked = TRUE; type_compatible = db_iface_data->dbus_iface.meta->get_type_fcn && g_type_is_a(G_OBJECT_TYPE(dbobj->nmobj), db_iface_data->dbus_iface.meta->get_type_fcn()); db_iface_data->nmobj_compatible = type_compatible; } else type_compatible = db_iface_data->nmobj_compatible; if (!type_compatible) { /* on D-Bus, we have this interface associate with the object, but apparently * it is not compatible. This is either a bug, or NetworkManager exposed an * unexpected interface on D-Bus object for which we create a certain NMObject * type. */ return; } is_removed = c_list_is_empty(&db_iface_data->iface_lst); nm_assert(is_removed || !c_list_is_empty(&db_iface_data->changed_prop_lst_head)); _nm_client_queue_notify_object(self, dbobj->nmobj, NULL); if (is_removed) { for (i_prop = 0; i_prop < db_iface_data->dbus_iface.meta->n_dbus_properties; i_prop++) { _obj_handle_dbus_prop_changes(self, dbobj, db_iface_data, i_prop, NULL); } } else { while ((db_prop_data = c_list_first_entry(&db_iface_data->changed_prop_lst_head, NMLDBusObjPropData, changed_prop_lst))) { gs_unref_variant GVariant *prop_data_value = NULL; c_list_unlink(&db_prop_data->changed_prop_lst); nm_assert(db_prop_data >= db_iface_data->prop_datas); nm_assert( db_prop_data < &db_iface_data->prop_datas[db_iface_data->dbus_iface.meta->n_dbus_properties]); nm_assert(db_prop_data->prop_data_value); /* Currently, NMLDBusObject forgets about the variant. Theoretically, it could cache * it, but there is no need because we update the property in nmobj (which extracts and * keeps the property value itself). * * Note that we only consume the variant here when we process it. * That implies that we already created a NMObject for the dbobj * instance. Unless that happens, we cache the last seen property values. */ prop_data_value = g_steal_pointer(&db_prop_data->prop_data_value); i_prop = (db_prop_data - &db_iface_data->prop_datas[0]); _obj_handle_dbus_prop_changes(self, dbobj, db_iface_data, i_prop, prop_data_value); } } } static void _obj_handle_dbus_changes(NMClient *self, NMLDBusObject *dbobj) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); NMLDBusObjIfaceData *db_iface_data; NMLDBusObjIfaceData *db_iface_data_safe; gs_unref_object GObject *nmobj_unregistering = NULL; _ASSERT_dbobj(dbobj, self); /* In a first step we only remember all the changes that a D-Bus message brings * and queue the object to process them. * * Here (in step 2) we look at what changed on D-Bus and propagate those changes * to the NMObject instance. * * Note that here we still must not emit any GObject signals. That follows later, * and again if the object changes, we will just queue that we handle the changes * later. */ c_list_for_each_entry_safe (db_iface_data, db_iface_data_safe, &dbobj->iface_lst_head, iface_lst) { if (!db_iface_data->iface_removed) continue; c_list_unlink(&db_iface_data->iface_lst); if (db_iface_data->dbus_iface_is_wellknown && dbobj->nmobj) _obj_handle_dbus_iface_changes(self, dbobj, db_iface_data); nml_dbus_obj_iface_data_destroy(db_iface_data); } if (G_UNLIKELY(!dbobj->nmobj) && !c_list_is_empty(&dbobj->iface_lst_head)) { /* Try to create a NMObject for this D-Bus object. Note that we detect the type * based on the interfaces that it has, and if we make a choice once, we don't * change. That means, one D-Bus object can only be of one type. */ if (NM_IN_SET(dbobj->dbus_path, _dbus_path_nm, _dbus_path_settings, _dbus_path_dns_manager)) { /* For the main types, we don't detect them based on the interfaces present, * but on the path names. Of course, both should correspond anyway. */ NML_NMCLIENT_LOG_T(self, "[%s]: register NMClient for D-Bus object", dbobj->dbus_path->str); dbobj->nmobj = G_OBJECT(self); if (dbobj->dbus_path == _dbus_path_nm) { nm_assert(!priv->dbobj_nm); priv->dbobj_nm = dbobj; } else if (dbobj->dbus_path == _dbus_path_settings) { nm_assert(!priv->dbobj_settings); priv->dbobj_settings = dbobj; } else { nm_assert(dbobj->dbus_path == _dbus_path_dns_manager); nm_assert(!priv->dbobj_dns_manager); priv->dbobj_dns_manager = dbobj; } nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY, self); } else { GType gtype = G_TYPE_NONE; NMLDBusMetaInteracePrio curr_prio = NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_10 - 1; c_list_for_each_entry (db_iface_data, &dbobj->iface_lst_head, iface_lst) { nm_assert(!db_iface_data->iface_removed); if (!db_iface_data->dbus_iface_is_wellknown) break; if (db_iface_data->dbus_iface.meta->interface_prio <= curr_prio) continue; curr_prio = db_iface_data->dbus_iface.meta->interface_prio; gtype = db_iface_data->dbus_iface.meta->get_type_fcn(); } if (gtype != G_TYPE_NONE) { dbobj->nmobj = g_object_new(gtype, NULL); NML_NMCLIENT_LOG_T(self, "[%s]: register new NMObject " NM_HASH_OBFUSCATE_PTR_FMT " of type %s", dbobj->dbus_path->str, NM_HASH_OBFUSCATE_PTR(dbobj->nmobj), g_type_name(gtype)); nm_assert(NM_IS_OBJECT(dbobj->nmobj)); NM_OBJECT_GET_CLASS(dbobj->nmobj) ->register_client(NM_OBJECT(dbobj->nmobj), self, dbobj); nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY, self); } } } c_list_for_each_entry (db_iface_data, &dbobj->iface_lst_head, iface_lst) { nm_assert(!db_iface_data->iface_removed); if (!db_iface_data->dbus_iface_is_wellknown) break; if (c_list_is_empty(&db_iface_data->changed_prop_lst_head)) continue; if (dbobj->nmobj) _obj_handle_dbus_iface_changes(self, dbobj, db_iface_data); } if (c_list_is_empty(&dbobj->iface_lst_head) && dbobj->nmobj) { if (dbobj->nmobj == G_OBJECT(self)) { dbobj->nmobj = NULL; NML_NMCLIENT_LOG_T(self, "[%s]: unregister NMClient from D-Bus object", dbobj->dbus_path->str); if (dbobj->dbus_path == _dbus_path_nm) { nm_assert(priv->dbobj_nm == dbobj); priv->dbobj_nm = NULL; nml_dbus_property_o_clear_many(priv->nm.property_o, G_N_ELEMENTS(priv->nm.property_o), self); nml_dbus_property_ao_clear_many(priv->nm.property_ao, G_N_ELEMENTS(priv->nm.property_ao), self); } else if (dbobj->dbus_path == _dbus_path_settings) { nm_assert(priv->dbobj_settings == dbobj); priv->dbobj_settings = NULL; nml_dbus_property_ao_clear(&priv->settings.connections, self); } else { nm_assert(dbobj->dbus_path == _dbus_path_dns_manager); nm_assert(priv->dbobj_dns_manager == dbobj); priv->dbobj_dns_manager = NULL; } } else { nmobj_unregistering = g_steal_pointer(&dbobj->nmobj); nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_WATCHED_ONLY, self); NML_NMCLIENT_LOG_T(self, "[%s]: unregister NMObject " NM_HASH_OBFUSCATE_PTR_FMT " of type %s", dbobj->dbus_path->str, NM_HASH_OBFUSCATE_PTR(nmobj_unregistering), g_type_name(G_OBJECT_TYPE(nmobj_unregistering))); NM_OBJECT_GET_CLASS(nmobj_unregistering) ->unregister_client(NM_OBJECT(nmobj_unregistering), self, dbobj); } } nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ); } /*****************************************************************************/ static void _dbus_handle_obj_changed_nmobj(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NMLDBusObject * dbobj; CList obj_changed_tmp_lst_head = C_LIST_INIT(obj_changed_tmp_lst_head); nm_assert(!nml_dbus_object_obj_changed_any_linked(self, ~NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ)); /* First we notify all watchers that these objects changed. Note that we only do that * here for the list before processing the changes below in a loop. That is, because * processing changes can again enqueue changed objects, and we only want to want to * notify watchers for the events that happened earlier (not repeatedly notify them). */ c_list_splice(&obj_changed_tmp_lst_head, &priv->obj_changed_lst_head); while ( (dbobj = c_list_first_entry(&obj_changed_tmp_lst_head, NMLDBusObject, obj_changed_lst))) { nm_c_list_move_tail(&priv->obj_changed_lst_head, &dbobj->obj_changed_lst); _dbobjs_notify_watchers_for_dbobj(self, dbobj); } again: nm_assert(!nml_dbus_object_obj_changed_any_linked(self, ~NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ)); c_list_splice(&obj_changed_tmp_lst_head, &priv->obj_changed_lst_head); while ( (dbobj = c_list_first_entry(&obj_changed_tmp_lst_head, NMLDBusObject, obj_changed_lst))) { if (!nml_dbus_object_obj_changed_consume(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ)) { nm_assert_not_reached(); continue; } if (!dbobj->nmobj) continue; if (dbobj->nmobj == G_OBJECT(self)) { if (dbobj == priv->dbobj_nm) { nml_dbus_property_o_notify_changed_many(priv->nm.property_o, G_N_ELEMENTS(priv->nm.property_o), self); nml_dbus_property_ao_notify_changed_many(priv->nm.property_ao, G_N_ELEMENTS(priv->nm.property_ao), self); } else if (dbobj == priv->dbobj_settings) nml_dbus_property_ao_notify_changed(&priv->settings.connections, self); else nm_assert(dbobj == priv->dbobj_dns_manager); } else NM_OBJECT_GET_CLASS(dbobj->nmobj)->obj_changed_notify(NM_OBJECT(dbobj->nmobj)); _dbobjs_check_dbobj_ready(self, dbobj); } if (!c_list_is_empty(&priv->obj_changed_lst_head)) { nm_assert(nml_dbus_object_obj_changed_any_linked(self, NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ)); /* we got new changes enqueued. Need to check again. */ goto again; } } static void _dbus_handle_obj_changed_dbus(NMClient *self, const char *log_context) { NMClientPrivate *priv; NMLDBusObject * dbobj; CList obj_changed_tmp_lst_head = C_LIST_INIT(obj_changed_tmp_lst_head); priv = NM_CLIENT_GET_PRIVATE(self); /* We move the changed list onto a temporary list and consume that. * Note that nml_dbus_object_obj_changed_consume() will move the object * back to the original list if there are changes of another type. * * This is done so that we can enqueue more changes while processing the * change list. */ c_list_splice(&obj_changed_tmp_lst_head, &priv->obj_changed_lst_head); while ( (dbobj = c_list_first_entry(&obj_changed_tmp_lst_head, NMLDBusObject, obj_changed_lst))) { nm_auto_unref_nml_dbusobj NMLDBusObject *dbobj_unref = NULL; if (!nml_dbus_object_obj_changed_consume(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_DBUS)) continue; nm_assert(dbobj->obj_state >= NML_DBUS_OBJ_STATE_ON_DBUS); dbobj_unref = nml_dbus_object_ref(dbobj); _obj_handle_dbus_changes(self, dbobj); if (dbobj->obj_state == NML_DBUS_OBJ_STATE_UNLINKED) continue; if (c_list_is_empty(&dbobj->iface_lst_head) && c_list_is_empty(&dbobj->watcher_lst_head)) { NML_NMCLIENT_LOG_T(self, "[%s]: drop D-Bus instance", dbobj->dbus_path->str); nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_UNLINKED, self); if (!g_hash_table_steal(priv->dbus_objects, dbobj)) nm_assert_not_reached(); nml_dbus_object_unref(dbobj); } } /* D-Bus changes can only be enqueued in an earlier stage. We don't expect * anymore changes of type D-Bus at this point. */ nm_assert(!nml_dbus_object_obj_changed_any_linked(self, NML_DBUS_OBJ_CHANGED_TYPE_DBUS)); } static void _dbus_handle_changes_commit(NMClient *self, gboolean allow_init_start_check_complete) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; _dbus_handle_obj_changed_nmobj(self); dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->main_context); _nm_client_notify_event_emit(self); _set_nm_running(self); if (allow_init_start_check_complete) _init_start_check_complete(self); } static void _dbus_handle_changes(NMClient * self, const char *log_context, gboolean allow_init_start_check_complete) { _dbus_handle_obj_changed_dbus(self, log_context); _dbus_handle_changes_commit(self, allow_init_start_check_complete); } static gboolean _dbus_handle_properties_changed(NMClient * self, const char * log_context, const char * object_path, const char * interface_name, gboolean allow_add_iface, GVariant * changed_properties, NMLDBusObject **inout_dbobj) { NMLDBusObject * dbobj = NULL; NMLDBusObjIfaceData *db_iface_data = NULL; nm_auto_ref_string NMRefString *dbus_path = NULL; nm_assert(!changed_properties || g_variant_is_of_type(changed_properties, G_VARIANT_TYPE("a{sv}"))); { gs_free char *ss = NULL; NML_NMCLIENT_LOG_T(self, "[%s]: %s: properties changed for interface %s %s%s%s", object_path, log_context, interface_name, NM_PRINT_FMT_QUOTED(changed_properties, "{ ", (ss = g_variant_print(changed_properties, TRUE)), " }", "(no changed properties)")); } if (inout_dbobj) { dbobj = *inout_dbobj; nm_assert(!dbobj || nm_streq(object_path, dbobj->dbus_path->str)); } if (!dbobj) { dbus_path = nm_ref_string_new(object_path); dbobj = _dbobjs_dbobj_get_r(self, dbus_path); } if (dbobj) { nm_assert(dbobj->obj_state >= NML_DBUS_OBJ_STATE_WATCHED_ONLY); db_iface_data = nml_dbus_object_iface_data_get(dbobj, interface_name, allow_add_iface); if (db_iface_data && dbobj->obj_state == NML_DBUS_OBJ_STATE_WATCHED_ONLY) nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_ON_DBUS, self); } else if (allow_add_iface) { dbobj = _dbobjs_dbobj_create(self, g_steal_pointer(&dbus_path)); nml_dbus_object_set_obj_state(dbobj, NML_DBUS_OBJ_STATE_ON_DBUS, self); db_iface_data = nml_dbus_object_iface_data_get(dbobj, interface_name, TRUE); nm_assert(db_iface_data); } NM_SET_OUT(inout_dbobj, dbobj); if (!db_iface_data) { if (allow_add_iface) NML_NMCLIENT_LOG_E(self, "%s: [%s] too many interfaces on object. Something is very wrong", log_context, object_path); else NML_NMCLIENT_LOG_E(self, "%s: [%s] property changed signal for non existing interface %s", log_context, object_path, interface_name); nm_assert(!dbobj || dbobj->obj_state != NML_DBUS_OBJ_STATE_UNLINKED); return FALSE; } if (!db_iface_data->dbus_iface_is_wellknown) NML_NMCLIENT_LOG_W(self, "%s: [%s] ignore unknown interface %s", log_context, object_path, interface_name); else if (changed_properties) { GVariantIter iter_prop; const char * property_name; GVariant * property_value_tmp; g_variant_iter_init(&iter_prop, changed_properties); while (g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value_tmp)) { _nm_unused gs_unref_variant GVariant *property_value = property_value_tmp; const NMLDBusMetaProperty * meta_property; NMLDBusObjPropData * db_propdata; guint property_idx; meta_property = nml_dbus_meta_property_get(db_iface_data->dbus_iface.meta, property_name, &property_idx); if (!meta_property) { NML_NMCLIENT_LOG_W(self, "%s: [%s]: ignore unknown property %s.%s", log_context, object_path, interface_name, property_name); continue; } db_propdata = &db_iface_data->prop_datas[property_idx]; NML_NMCLIENT_LOG_T(self, "[%s]: %s: %s property %s.%s", object_path, log_context, db_propdata->prop_data_value ? "update" : "set", interface_name, property_name); nm_g_variant_unref(db_propdata->prop_data_value); db_propdata->prop_data_value = g_steal_pointer(&property_value); nm_c_list_move_tail(&db_iface_data->changed_prop_lst_head, &db_propdata->changed_prop_lst); } } nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_DBUS); return TRUE; } static gboolean _dbus_handle_interface_added(NMClient * self, const char *log_context, const char *object_path, GVariant * ifaces) { gboolean changed = FALSE; const char * interface_name; GVariant * changed_properties; GVariantIter iter_ifaces; NMLDBusObject *dbobj = NULL; nm_assert(g_variant_is_of_type(ifaces, G_VARIANT_TYPE("a{sa{sv}}"))); g_variant_iter_init(&iter_ifaces, ifaces); while (g_variant_iter_next(&iter_ifaces, "{&s@a{sv}}", &interface_name, &changed_properties)) { _nm_unused gs_unref_variant GVariant *changed_properties_free = changed_properties; if (_dbus_handle_properties_changed(self, log_context, object_path, interface_name, TRUE, changed_properties, &dbobj)) changed = TRUE; } return changed; } static gboolean _dbus_handle_interface_removed(NMClient * self, const char * log_context, const char * object_path, NMLDBusObject ** inout_dbobj, const char *const *removed_interfaces) { gboolean changed = FALSE; NMLDBusObject *dbobj; gsize i; if (inout_dbobj && *inout_dbobj) { dbobj = *inout_dbobj; nm_assert(dbobj == _dbobjs_dbobj_get_s(self, object_path)); } else { dbobj = _dbobjs_dbobj_get_s(self, object_path); if (!dbobj) { NML_NMCLIENT_LOG_E(self, "%s: [%s]: receive interface removed event for non existing object", log_context, object_path); return FALSE; } NM_SET_OUT(inout_dbobj, dbobj); } for (i = 0; removed_interfaces[i]; i++) { NMLDBusObjIfaceData *db_iface_data; const char * interface_name = removed_interfaces[i]; db_iface_data = nml_dbus_object_iface_data_get(dbobj, interface_name, FALSE); if (!db_iface_data) { NML_NMCLIENT_LOG_E( self, "%s: [%s] receive interface remove event for unexpected interface %s", log_context, object_path, interface_name); continue; } NML_NMCLIENT_LOG_T(self, "%s: [%s] receive interface remove event for interface %s", log_context, object_path, interface_name); db_iface_data->iface_removed = TRUE; changed = TRUE; } if (changed) nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_DBUS); return changed; } static void _dbus_managed_objects_changed_cb(GDBusConnection *connection, const char * sender_name, const char * arg_object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); const char * log_context; gboolean changed; nm_assert(nm_streq0(interface_name, DBUS_INTERFACE_OBJECT_MANAGER)); if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (nm_streq(signal_name, "InterfacesAdded")) { gs_unref_variant GVariant *interfaces_and_properties = NULL; const char * object_path; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oa{sa{sv}})"))) return; g_variant_get(parameters, "(&o@a{sa{sv}})", &object_path, &interfaces_and_properties); log_context = "interfaces-added"; changed = _dbus_handle_interface_added(self, log_context, object_path, interfaces_and_properties); goto out; } if (nm_streq(signal_name, "InterfacesRemoved")) { gs_free const char **interfaces = NULL; const char * object_path; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oas)"))) return; g_variant_get(parameters, "(&o^a&s)", &object_path, &interfaces); log_context = "interfaces-removed"; changed = _dbus_handle_interface_removed(self, log_context, object_path, NULL, interfaces); goto out; } return; out: if (changed) _dbus_handle_changes(self, log_context, TRUE); } static void _dbus_properties_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * signal_interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); const char * interface_name; gs_unref_variant GVariant *changed_properties = NULL; gs_free const char ** invalidated_properties = NULL; const char * log_context = "properties-changed"; if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) return; g_variant_get(parameters, "(&s@a{sv}^a&s)", &interface_name, &changed_properties, &invalidated_properties); if (invalidated_properties && invalidated_properties[0]) { NML_NMCLIENT_LOG_W(self, "%s: [%s] ignore invalidated properties on interface %s", log_context, object_path, interface_name); } if (_dbus_handle_properties_changed(self, log_context, object_path, interface_name, FALSE, changed_properties, NULL)) _dbus_handle_changes(self, log_context, TRUE); } static void _dbus_get_managed_objects_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMClient * self; NMClientPrivate *priv; gs_unref_variant GVariant *ret = NULL; gs_unref_variant GVariant *managed_objects = NULL; gs_free_error GError *error = NULL; gs_unref_object GObject *context_busy_watcher = NULL; nm_utils_user_data_unpack(user_data, &self, &context_busy_watcher); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); nm_assert((!!ret) != (!!error)); if (!ret && nm_utils_error_is_cancelled(error)) return; priv = NM_CLIENT_GET_PRIVATE(self); if (ret) { nm_assert(g_variant_is_of_type(ret, G_VARIANT_TYPE("(a{oa{sa{sv}}})"))); managed_objects = g_variant_get_child_value(ret, 0); } g_clear_object(&priv->get_managed_objects_cancellable); if (!managed_objects) { NML_NMCLIENT_LOG_D(self, "GetManagedObjects() call failed: %s", error->message); /* hm, now that's odd. Maybe NetworkManager just quit and we are about to get * a name-owner changed signal soon. Treat this as if we got no managed objects at all. */ } else NML_NMCLIENT_LOG_D(self, "GetManagedObjects() completed"); if (managed_objects) { GVariantIter iter; const char * object_path; GVariant * ifaces_tmp; g_variant_iter_init(&iter, managed_objects); while (g_variant_iter_next(&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces_tmp)) { gs_unref_variant GVariant *ifaces = ifaces_tmp; _dbus_handle_interface_added(self, "get-managed-objects", object_path, ifaces); } } /* always call _dbus_handle_changes(), even if nothing changed. We need this to complete * initialization. */ _dbus_handle_changes(self, "get-managed-objects", TRUE); } /*****************************************************************************/ static void _nm_client_get_settings_call_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMRemoteConnection *remote_connection; NMClient * self; gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; gs_unref_variant GVariant *settings = NULL; NMLDBusObject * dbobj; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; remote_connection = user_data; self = _nm_object_get_client(remote_connection); dbobj = _nm_object_get_dbobj(remote_connection); _ASSERT_dbobj(dbobj, self); if (!ret) { NML_NMCLIENT_LOG_T(self, "[%s] GetSettings() completed with error: %s", dbobj->dbus_path->str, error->message); } else { NML_NMCLIENT_LOG_T(self, "[%s] GetSettings() completed with success", dbobj->dbus_path->str); g_variant_get(ret, "(@a{sa{sv}})", &settings); } _nm_remote_settings_get_settings_commit(remote_connection, settings); _dbus_handle_changes_commit(self, TRUE); } void _nm_client_get_settings_call(NMClient *self, NMLDBusObject *dbobj) { GCancellable *cancellable; cancellable = _nm_remote_settings_get_settings_prepare(NM_REMOTE_CONNECTION(dbobj->nmobj)); _nm_client_dbus_call_simple(self, cancellable, dbobj->dbus_path->str, NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "GetSettings", g_variant_new("()"), G_VARIANT_TYPE("(a{sa{sv}})"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _nm_client_get_settings_call_cb, dbobj->nmobj); } static void _dbus_settings_updated_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * signal_interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); const char * log_context = "settings-updated"; NMLDBusObject * dbobj; if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("()"))) return; dbobj = _dbobjs_dbobj_get_s(self, object_path); if (!dbobj || !NM_IS_REMOTE_CONNECTION(dbobj->nmobj)) { NML_NMCLIENT_LOG_W(self, "%s: [%s] ignore Updated signal for non-existing setting", log_context, object_path); return; } NML_NMCLIENT_LOG_T(self, "%s: [%s] Updated signal received", log_context, object_path); _nm_client_get_settings_call(self, dbobj); } /*****************************************************************************/ static void _dbus_nm_connection_active_state_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * signal_interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); const char * log_context = "active-connection-state-changed"; NMLDBusObject * dbobj; guint32 state; guint32 reason; if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(uu)"))) { NML_NMCLIENT_LOG_E(self, "%s: [%s] ignore StateChanged signal with unexpected signature", log_context, object_path); return; } dbobj = _dbobjs_dbobj_get_s(self, object_path); if (!dbobj || !NM_IS_ACTIVE_CONNECTION(dbobj->nmobj)) { NML_NMCLIENT_LOG_E(self, "%s: [%s] ignore StateChanged signal for non-existing active connection", log_context, object_path); return; } g_variant_get(parameters, "(uu)", &state, &reason); NML_NMCLIENT_LOG_T(self, "%s: [%s] StateChanged signal received", log_context, object_path); _nm_active_connection_state_changed_commit(NM_ACTIVE_CONNECTION(dbobj->nmobj), state, reason); _dbus_handle_changes_commit(self, TRUE); } /*****************************************************************************/ static void _dbus_nm_vpn_connection_state_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * signal_interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); const char * log_context = "vpn-connection-state-changed"; NMLDBusObject * dbobj; guint32 state; guint32 reason; if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(uu)"))) { NML_NMCLIENT_LOG_E(self, "%s: [%s] ignore VpnStateChanged signal with unexpected signature", log_context, object_path); return; } dbobj = _dbobjs_dbobj_get_s(self, object_path); if (!dbobj || !NM_IS_VPN_CONNECTION(dbobj->nmobj)) { NML_NMCLIENT_LOG_E(self, "%s: [%s] ignore VpnStateChanged signal for non-existing vpn connection", log_context, object_path); return; } g_variant_get(parameters, "(uu)", &state, &reason); NML_NMCLIENT_LOG_T(self, "%s: [%s] VpnStateChanged signal received", log_context, object_path); _nm_vpn_connection_state_changed_commit(NM_VPN_CONNECTION(dbobj->nmobj), state, reason); _dbus_handle_changes_commit(self, TRUE); } /*****************************************************************************/ static void _emit_permissions_changed(NMClient *self, const guint8 *old_permissions, const guint8 *permissions) { int i; if (self->obj_base.is_disposing) return; if (old_permissions == permissions) return; for (i = 0; i < (int) G_N_ELEMENTS(nm_auth_permission_sorted); i++) { NMClientPermission perm = nm_auth_permission_sorted[i]; NMClientPermissionResult perm_result = NM_CLIENT_PERMISSION_RESULT_UNKNOWN; NMClientPermissionResult perm_result_old = NM_CLIENT_PERMISSION_RESULT_UNKNOWN; if (permissions) perm_result = permissions[perm - 1]; if (old_permissions) perm_result_old = old_permissions[perm - 1]; if (perm_result == perm_result_old) continue; g_signal_emit(self, signals[PERMISSION_CHANGED], 0, (guint) perm, (guint) perm_result); } } static void _dbus_check_permissions_start_cb(GObject *source, GAsyncResult *result, gpointer user_data) { nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; NMClient * self; NMClientPrivate * priv; gs_unref_variant GVariant *ret = NULL; nm_auto_free_variant_iter GVariantIter *v_permissions = NULL; gs_free guint8 *old_permissions = NULL; gs_free_error GError *error = NULL; const char * pkey; const char * pvalue; int i; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; self = user_data; priv = NM_CLIENT_GET_PRIVATE(self); g_clear_object(&priv->permissions_cancellable); old_permissions = g_steal_pointer(&priv->permissions); if (!ret) { /* when the call completes, we always pretend success. Even a failure means * that we fetched the permissions, however they are all unknown. */ NML_NMCLIENT_LOG_T(self, "GetPermissions call failed: %s", error->message); goto out; } NML_NMCLIENT_LOG_T(self, "GetPermissions call finished with success"); g_variant_get(ret, "(a{ss})", &v_permissions); while (g_variant_iter_next(v_permissions, "{&s&s}", &pkey, &pvalue)) { NMClientPermission perm; NMClientPermissionResult perm_result; perm = nm_auth_permission_from_string(pkey); if (perm == NM_CLIENT_PERMISSION_NONE) continue; perm_result = nm_client_permission_result_from_string(pvalue); if (!priv->permissions) { if (perm_result == NM_CLIENT_PERMISSION_RESULT_UNKNOWN) continue; priv->permissions = g_new(guint8, G_N_ELEMENTS(nm_auth_permission_sorted)); for (i = 0; i < (int) G_N_ELEMENTS(nm_auth_permission_sorted); i++) priv->permissions[i] = NM_CLIENT_PERMISSION_RESULT_UNKNOWN; } priv->permissions[perm - 1] = perm_result; } out: priv->permissions_state = NM_TERNARY_TRUE; dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->main_context); _emit_permissions_changed(self, old_permissions, priv->permissions); _notify(self, PROP_PERMISSIONS_STATE); } static void _dbus_check_permissions_start(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); gboolean fetch; fetch = !NM_FLAGS_HAS((NMClientInstanceFlags) priv->instance_flags, NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS); nm_clear_g_cancellable(&priv->permissions_cancellable); if (fetch) { NML_NMCLIENT_LOG_T(self, "GetPermissions() call started..."); priv->permissions_cancellable = g_cancellable_new(); _nm_client_dbus_call_simple(self, priv->permissions_cancellable, NM_DBUS_PATH, NM_DBUS_INTERFACE, "GetPermissions", g_variant_new("()"), G_VARIANT_TYPE("(a{ss})"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _dbus_check_permissions_start_cb, self); } } static void _dbus_nm_check_permissions_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * signal_interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("()"))) { NML_NMCLIENT_LOG_E(self, "ignore CheckPermissions signal with unexpected signature %s", g_variant_get_type_string(parameters)); return; } _dbus_check_permissions_start(self); if (priv->permissions_state == NM_TERNARY_TRUE) priv->permissions_state = NM_TERNARY_FALSE; _notify(self, PROP_PERMISSIONS_STATE); } /*****************************************************************************/ static void _property_ao_notify_changed_connections_cb(NMLDBusPropertyAO *pr_ao, NMClient * self, NMObject * nmobj, gboolean is_added /* or else removed */) { _nm_client_notify_event_queue_emit_obj_signal(self, G_OBJECT(self), nmobj, is_added, 5, is_added ? signals[CONNECTION_ADDED] : signals[CONNECTION_REMOVED]); } static void _property_ao_notify_changed_all_devices_cb(NMLDBusPropertyAO *pr_ao, NMClient * self, NMObject * nmobj, gboolean is_added /* or else removed */) { _nm_client_notify_event_queue_emit_obj_signal(self, G_OBJECT(self), nmobj, is_added, 6, is_added ? signals[ANY_DEVICE_ADDED] : signals[ANY_DEVICE_REMOVED]); } static void _property_ao_notify_changed_devices_cb(NMLDBusPropertyAO *pr_ao, NMClient * self, NMObject * nmobj, gboolean is_added /* or else removed */) { _nm_client_notify_event_queue_emit_obj_signal(self, G_OBJECT(self), nmobj, is_added, 7, is_added ? signals[DEVICE_ADDED] : signals[DEVICE_REMOVED]); } static void _property_ao_notify_changed_active_connections_cb(NMLDBusPropertyAO *pr_ao, NMClient * self, NMObject * nmobj, gboolean is_added /* or else removed */) { _nm_client_notify_event_queue_emit_obj_signal(self, G_OBJECT(self), nmobj, is_added, 8, is_added ? signals[ACTIVE_CONNECTION_ADDED] : signals[ACTIVE_CONNECTION_REMOVED]); } /*****************************************************************************/ typedef struct { NMLDBusObjWatcherWithPtr *obj_watcher; const char * op_name; NMLDBusObject * dbobj; GTask * task; GVariant * extra_results; gpointer result; GType gtype; gulong cancellable_id; } RequestWaitData; static void _request_wait_data_free(RequestWaitData *request_data) { nm_assert(!request_data->obj_watcher); nm_assert(request_data->cancellable_id == 0); nm_assert(!request_data->task || G_IS_TASK(request_data->task)); nm_g_object_unref(request_data->task); nm_g_object_unref(request_data->result); nm_g_variant_unref(request_data->extra_results); if (request_data->dbobj) nml_dbus_object_unref(request_data->dbobj); nm_g_slice_free(request_data); } static void _request_wait_task_return(RequestWaitData *request_data) { gs_unref_object GTask *task = NULL; nm_assert(request_data); nm_assert(G_IS_TASK(request_data->task)); nm_assert(request_data->dbobj); nm_assert(NM_IS_OBJECT(request_data->dbobj->nmobj)); nm_assert(!request_data->result); task = g_steal_pointer(&request_data->task); request_data->result = g_object_ref(request_data->dbobj->nmobj); nm_clear_g_signal_handler(g_task_get_cancellable(task), &request_data->cancellable_id); nm_clear_pointer(&request_data->dbobj, nml_dbus_object_unref); g_task_return_pointer(task, request_data, (GDestroyNotify) _request_wait_data_free); } static gboolean _request_wait_complete(NMClient *self, RequestWaitData *request_data, gboolean force_complete) { NMLDBusObject *dbobj; nm_assert(request_data); nm_assert(!request_data->result); nm_assert(!request_data->obj_watcher); nm_assert(request_data->dbobj); dbobj = request_data->dbobj; if (dbobj->obj_state == NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY) { NML_NMCLIENT_LOG_D(self, "%s() succeeded with %s", request_data->op_name, dbobj->dbus_path->str); nm_assert(G_TYPE_CHECK_INSTANCE_TYPE(dbobj->nmobj, request_data->gtype)); _request_wait_task_return(request_data); return TRUE; } if (force_complete || dbobj->obj_state != NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY) { if (force_complete) NML_NMCLIENT_LOG_D(self, "%s() succeeded with %s but object is in an unsuitable state", request_data->op_name, dbobj->dbus_path->str); else NML_NMCLIENT_LOG_W(self, "%s() succeeded with %s but object is in an unsuitable state", request_data->op_name, dbobj->dbus_path->str); g_task_return_error( request_data->task, g_error_new(NM_CLIENT_ERROR, NM_CLIENT_ERROR_OBJECT_CREATION_FAILED, _("request succeeded with %s but object is in an unsuitable state"), dbobj->dbus_path->str)); _request_wait_data_free(request_data); return TRUE; } return FALSE; } static void _request_wait_complete_cb(NMClient *self, NMClientNotifyEventWithPtr *notify_event) { _request_wait_complete(self, notify_event->user_data, TRUE); } static void _request_wait_obj_watcher_cb(NMClient *self, gpointer obj_watcher_base) { NMLDBusObjWatcherWithPtr *obj_watcher = obj_watcher_base; RequestWaitData * request_data = obj_watcher->user_data; NMLDBusObject * dbobj; dbobj = request_data->dbobj; if (dbobj->obj_state == NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY) return; nm_assert(NM_IN_SET((NMLDBusObjState) dbobj->obj_state, NML_DBUS_OBJ_STATE_WATCHED_ONLY, NML_DBUS_OBJ_STATE_ON_DBUS, NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY)); _dbobjs_obj_watcher_unregister(self, g_steal_pointer(&request_data->obj_watcher)); nm_clear_g_signal_handler(g_task_get_cancellable(request_data->task), &request_data->cancellable_id); _nm_client_notify_event_queue_with_ptr(self, NM_CLIENT_NOTIFY_EVENT_PRIO_AFTER + 30, _request_wait_complete_cb, request_data); } static void _request_wait_cancelled_cb(GCancellable *cancellable, gpointer user_data) { RequestWaitData *request_data = user_data; NMClient * self; GError * error = NULL; nm_assert(cancellable == g_task_get_cancellable(request_data->task)); nm_utils_error_set_cancelled(&error, FALSE, NULL); self = g_task_get_source_object(request_data->task); nm_clear_g_signal_handler(cancellable, &request_data->cancellable_id); _dbobjs_obj_watcher_unregister(self, g_steal_pointer(&request_data->obj_watcher)); g_task_return_error(request_data->task, error); _request_wait_data_free(request_data); } static void _request_wait_start(GTask * task_take, const char *op_name, GType gtype, const char *dbus_path, GVariant * extra_results_take) { NMClient * self; gs_unref_object GTask *task = g_steal_pointer(&task_take); RequestWaitData * request_data; GCancellable * cancellable; NMLDBusObject * dbobj; nm_assert(G_IS_TASK(task)); self = g_task_get_source_object(task); dbobj = _dbobjs_get_nmobj(self, dbus_path, gtype); if (!dbobj) { NML_NMCLIENT_LOG_E(self, "%s() succeeded with %s but object does not exist", op_name, dbus_path); g_task_return_error(task, g_error_new(NM_CLIENT_ERROR, NM_CLIENT_ERROR_FAILED, _("operation succeeded but object %s does not exist"), dbus_path)); return; } request_data = g_slice_new(RequestWaitData); *request_data = (RequestWaitData){ .task = g_steal_pointer(&task), .op_name = op_name, .gtype = gtype, .dbobj = nml_dbus_object_ref(dbobj), .obj_watcher = NULL, .extra_results = g_steal_pointer(&extra_results_take), .result = NULL, .cancellable_id = 0, }; if (_request_wait_complete(self, request_data, FALSE)) return; NML_NMCLIENT_LOG_T(self, "%s() succeeded with %s. Wait for object to become ready", op_name, dbobj->dbus_path->str); request_data->obj_watcher = _dbobjs_obj_watcher_register_o(self, dbobj, _request_wait_obj_watcher_cb, sizeof(NMLDBusObjWatcherWithPtr)); request_data->obj_watcher->user_data = request_data; cancellable = g_task_get_cancellable(request_data->task); if (cancellable) { gulong id; id = g_cancellable_connect(cancellable, G_CALLBACK(_request_wait_cancelled_cb), request_data, NULL); if (id == 0) { /* the callback was invoked synchronously, which destroyed @request_data. * We must not touch @info anymore. */ } else request_data->cancellable_id = id; } } static gpointer _request_wait_finish(NMClient * client, GAsyncResult *result, gpointer source_tag, GVariant ** out_result, GError ** error) { RequestWaitData *request_data = NULL; gpointer r; g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(nm_g_task_is_valid(result, client, source_tag), NULL); request_data = g_task_propagate_pointer(G_TASK(result), error); if (!request_data) { NM_SET_OUT(out_result, NULL); return NULL; } nm_assert(NM_IS_OBJECT(request_data->result)); NM_SET_OUT(out_result, g_steal_pointer(&request_data->extra_results)); r = g_steal_pointer(&request_data->result); nm_assert(NM_IS_OBJECT(r)); _request_wait_data_free(request_data); return r; } /*****************************************************************************/ /** * nm_client_get_instance_flags: * @self: the #NMClient instance. * * Returns: the #NMClientInstanceFlags flags. * * Since: 1.24 */ NMClientInstanceFlags nm_client_get_instance_flags(NMClient *self) { g_return_val_if_fail(NM_IS_CLIENT(self), NM_CLIENT_INSTANCE_FLAGS_NONE); return NM_CLIENT_GET_PRIVATE(self)->instance_flags; } /** * nm_client_get_dbus_connection: * @client: a #NMClient * * Gets the %GDBusConnection of the instance. This can be either passed when * constructing the instance (as "dbus-connection" property), or it will be * automatically initialized during async/sync init. * * Returns: (transfer none): the D-Bus connection of the client, or %NULL if none is set. * * Since: 1.22 **/ GDBusConnection * nm_client_get_dbus_connection(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->dbus_connection; } /** * nm_client_get_dbus_name_owner: * @client: a #NMClient * * Returns: (transfer none): the current name owner of the D-Bus service of NetworkManager. * * Since: 1.22 **/ const char * nm_client_get_dbus_name_owner(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->name_owner; } /** * nm_client_get_version: * @client: a #NMClient * * Gets NetworkManager version. * * Returns: string with the version (or %NULL if NetworkManager is not running) **/ const char * nm_client_get_version(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->nm.version; } /** * nm_client_get_state: * @client: a #NMClient * * Gets the current daemon state. * * Returns: the current %NMState **/ NMState nm_client_get_state(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NM_STATE_UNKNOWN); return NM_CLIENT_GET_PRIVATE(client)->nm.state; } /** * nm_client_get_startup: * @client: a #NMClient * * Tests whether the daemon is still in the process of activating * connections at startup. * * Returns: whether the daemon is still starting up **/ gboolean nm_client_get_startup(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.startup; } static void _set_nm_running(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); gboolean nm_running; nm_running = priv->name_owner && !priv->get_managed_objects_cancellable; if (priv->nm_running != nm_running) { priv->nm_running = nm_running; _notify(self, PROP_NM_RUNNING); } } /** * nm_client_get_nm_running: * @client: a #NMClient * * Determines whether the daemon is running. * * Returns: %TRUE if the daemon is running **/ gboolean nm_client_get_nm_running(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm_running; } /** * nm_client_get_object_by_path: * @client: the #NMClient instance * @dbus_path: the D-Bus path of the object to look up * * Returns: (transfer none): the #NMObject instance that is * cached under @dbus_path, or %NULL if no such object exists. * * Since: 1.24 */ NMObject * nm_client_get_object_by_path(NMClient *client, const char *dbus_path) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(dbus_path, NULL); return _dbobjs_get_nmobj_unpack_visible(client, dbus_path, G_TYPE_NONE); } /** * nm_client_get_metered: * @client: a #NMClient * * Returns: whether the default route is metered. * * Since: 1.22 */ NMMetered nm_client_get_metered(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NM_METERED_UNKNOWN); return NM_CLIENT_GET_PRIVATE(client)->nm.metered; } /** * nm_client_networking_get_enabled: * @client: a #NMClient * * Whether networking is enabled or disabled. * * Returns: %TRUE if networking is enabled, %FALSE if networking is disabled **/ gboolean nm_client_networking_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.networking_enabled; } /** * nm_client_networking_set_enabled: * @client: a #NMClient * @enabled: %TRUE to set networking enabled, %FALSE to set networking disabled * @error: (allow-none): return location for a #GError, or %NULL * * Enables or disables networking. When networking is disabled, all controlled * interfaces are disconnected and deactivated. When networking is enabled, * all controlled interfaces are available for activation. * * Returns: %TRUE on success, %FALSE otherwise * * Deprecated: 1.22: Use the async command nm_client_dbus_call() on %NM_DBUS_PATH, * %NM_DBUS_INTERFACE to call "Enable" with "(b)" arguments and no return value. **/ gboolean nm_client_networking_set_enabled(NMClient *client, gboolean enable, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return _nm_client_dbus_call_sync_void(client, NULL, NM_DBUS_PATH, NM_DBUS_INTERFACE, "Enable", g_variant_new("(b)", enable), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); } /** * nm_client_wireless_get_enabled: * @client: a #NMClient * * Determines whether the wireless is enabled. * * Returns: %TRUE if wireless is enabled **/ gboolean nm_client_wireless_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.wireless_enabled; } /** * nm_client_wireless_set_enabled: * @client: a #NMClient * @enabled: %TRUE to enable wireless * * Enables or disables wireless devices. * * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on %NM_DBUS_PATH, * %NM_DBUS_INTERFACE to set "WirelessEnabled" property to a "(b)" value. */ void nm_client_wireless_set_enabled(NMClient *client, gboolean enabled) { g_return_if_fail(NM_IS_CLIENT(client)); _nm_client_set_property_sync_legacy(client, NM_DBUS_PATH, NM_DBUS_INTERFACE, "WirelessEnabled", "b", enabled); } /** * nm_client_wireless_hardware_get_enabled: * @client: a #NMClient * * Determines whether the wireless hardware is enabled. * * Returns: %TRUE if the wireless hardware is enabled **/ gboolean nm_client_wireless_hardware_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.wireless_hardware_enabled; } /** * nm_client_wwan_get_enabled: * @client: a #NMClient * * Determines whether WWAN is enabled. * * Returns: %TRUE if WWAN is enabled **/ gboolean nm_client_wwan_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.wwan_enabled; } /** * nm_client_wwan_set_enabled: * @client: a #NMClient * @enabled: %TRUE to enable WWAN * * Enables or disables WWAN devices. * * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on %NM_DBUS_PATH, * %NM_DBUS_INTERFACE to set "WwanEnabled" property to a "(b)" value. **/ void nm_client_wwan_set_enabled(NMClient *client, gboolean enabled) { g_return_if_fail(NM_IS_CLIENT(client)); _nm_client_set_property_sync_legacy(client, NM_DBUS_PATH, NM_DBUS_INTERFACE, "WwanEnabled", "b", enabled); } /** * nm_client_wwan_hardware_get_enabled: * @client: a #NMClient * * Determines whether the WWAN hardware is enabled. * * Returns: %TRUE if the WWAN hardware is enabled **/ gboolean nm_client_wwan_hardware_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.wwan_hardware_enabled; } /** * nm_client_wimax_get_enabled: * @client: a #NMClient * * Determines whether WiMAX is enabled. * * Returns: %TRUE if WiMAX is enabled * * Deprecated: 1.22: This function always returns FALSE because WiMax is no longer supported. **/ gboolean nm_client_wimax_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return FALSE; } /** * nm_client_wimax_set_enabled: * @client: a #NMClient * @enabled: %TRUE to enable WiMAX * * Enables or disables WiMAX devices. * * Deprecated: 1.22: This function does nothing because WiMax is no longer supported. **/ void nm_client_wimax_set_enabled(NMClient *client, gboolean enabled) { g_return_if_fail(NM_IS_CLIENT(client)); } /** * nm_client_wimax_hardware_get_enabled: * @client: a #NMClient * * Determines whether the WiMAX hardware is enabled. * * Returns: %TRUE if the WiMAX hardware is enabled * * Deprecated: 1.22: This function always returns FALSE because WiMax is no longer supported. **/ gboolean nm_client_wimax_hardware_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return FALSE; } /** * nm_client_connectivity_check_get_available: * @client: a #NMClient * * Determine whether connectivity checking is available. This * requires that the URI of a connectivity service has been set in the * configuration file. * * Returns: %TRUE if connectivity checking is available. * * Since: 1.10 */ gboolean nm_client_connectivity_check_get_available(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.connectivity_check_available; } /** * nm_client_connectivity_check_get_enabled: * @client: a #NMClient * * Determine whether connectivity checking is enabled. * * Returns: %TRUE if connectivity checking is enabled. * * Since: 1.10 */ gboolean nm_client_connectivity_check_get_enabled(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); return NM_CLIENT_GET_PRIVATE(client)->nm.connectivity_check_enabled; } /** * nm_client_connectivity_check_set_enabled: * @client: a #NMClient * @enabled: %TRUE to enable connectivity checking * * Enable or disable connectivity checking. Note that if a * connectivity checking URI has not been configured, this will not * have any effect. * * Since: 1.10 * * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on %NM_DBUS_PATH, * %NM_DBUS_INTERFACE to set "ConnectivityCheckEnabled" property to a "(b)" value. */ void nm_client_connectivity_check_set_enabled(NMClient *client, gboolean enabled) { g_return_if_fail(NM_IS_CLIENT(client)); _nm_client_set_property_sync_legacy(client, NM_DBUS_PATH, NM_DBUS_INTERFACE, "ConnectivityCheckEnabled", "b", enabled); } /** * nm_client_connectivity_check_get_uri: * @client: a #NMClient * * Get the URI that will be queried to determine if there is internet * connectivity. * * Returns: (transfer none): the connectivity URI in use * * Since: 1.20 */ const char * nm_client_connectivity_check_get_uri(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->nm.connectivity_check_uri; } /** * nm_client_get_logging: * @client: a #NMClient * @level: (allow-none): return location for logging level string * @domains: (allow-none): return location for log domains string. The string is * a list of domains separated by "," * @error: (allow-none): return location for a #GError, or %NULL * * Gets NetworkManager current logging level and domains. * * Returns: %TRUE on success, %FALSE otherwise * * Deprecated: 1.22: Use the async command nm_client_dbus_call() on %NM_DBUS_PATH, * %NM_DBUS_INTERFACE to call "GetLogging" with no arguments to get "(ss)" for level * and domains. **/ gboolean nm_client_get_logging(NMClient *client, char **level, char **domains, GError **error) { gs_unref_variant GVariant *ret = NULL; g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(level == NULL || *level == NULL, FALSE); g_return_val_if_fail(domains == NULL || *domains == NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); ret = _nm_client_dbus_call_sync(client, NULL, NM_DBUS_PATH, NM_DBUS_INTERFACE, "GetLogging", g_variant_new("()"), G_VARIANT_TYPE("(ss)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); if (!ret) return FALSE; g_variant_get(ret, "(ss)", level, domains); return TRUE; } /** * nm_client_set_logging: * @client: a #NMClient * @level: (allow-none): logging level to set (%NULL or an empty string for no change) * @domains: (allow-none): logging domains to set. The string should be a list of log * domains separated by ",". (%NULL or an empty string for no change) * @error: (allow-none): return location for a #GError, or %NULL * * Sets NetworkManager logging level and/or domains. * * Returns: %TRUE on success, %FALSE otherwise * * Deprecated: 1.22: Use the async command nm_client_dbus_call() on %NM_DBUS_PATH, * %NM_DBUS_INTERFACE to call "SetLogging" with "(ss)" arguments for level and domains. **/ gboolean nm_client_set_logging(NMClient *client, const char *level, const char *domains, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return _nm_client_dbus_call_sync_void(client, NULL, NM_DBUS_PATH, NM_DBUS_INTERFACE, "SetLogging", g_variant_new("(ss)", level ?: "", domains ?: ""), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); } /** * nm_client_get_permission_result: * @client: a #NMClient * @permission: the permission for which to return the result, one of #NMClientPermission * * Requests the result of a specific permission, which indicates whether the * client can or cannot perform the action the permission represents * * Returns: the permission's result, one of #NMClientPermissionResult **/ NMClientPermissionResult nm_client_get_permission_result(NMClient *client, NMClientPermission permission) { NMClientPrivate * priv; NMClientPermissionResult result = NM_CLIENT_PERMISSION_RESULT_UNKNOWN; g_return_val_if_fail(NM_IS_CLIENT(client), NM_CLIENT_PERMISSION_RESULT_UNKNOWN); if (permission > NM_CLIENT_PERMISSION_NONE && permission <= NM_CLIENT_PERMISSION_LAST) { priv = NM_CLIENT_GET_PRIVATE(client); if (priv->permissions) result = priv->permissions[permission - 1]; } return result; } /** * nm_client_get_permissions_state: * @self: the #NMClient instance * * Returns: the state of the cached permissions. %NM_TERNARY_DEFAULT * means that no permissions result was yet received. All permissions * are unknown. %NM_TERNARY_TRUE means that the permissions got received * and are cached. %%NM_TERNARY_FALSE means that permissions are cached, * but they are invalided as "CheckPermissions" signal was received * in the meantime. * * Since: 1.24 */ NMTernary nm_client_get_permissions_state(NMClient *self) { g_return_val_if_fail(NM_IS_CLIENT(self), NM_TERNARY_DEFAULT); return NM_CLIENT_GET_PRIVATE(self)->permissions_state; } /** * nm_client_get_connectivity: * @client: an #NMClient * * Gets the current network connectivity state. Contrast * nm_client_check_connectivity() and * nm_client_check_connectivity_async(), which re-check the * connectivity state first before returning any information. * * Returns: the current connectivity state */ NMConnectivityState nm_client_get_connectivity(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NM_CONNECTIVITY_UNKNOWN); return NM_CLIENT_GET_PRIVATE(client)->nm.connectivity; } /** * nm_client_check_connectivity: * @client: an #NMClient * @cancellable: a #GCancellable * @error: return location for a #GError * * Updates the network connectivity state and returns the (new) * current state. Contrast nm_client_get_connectivity(), which returns * the most recent known state without re-checking. * * This is a blocking call; use nm_client_check_connectivity_async() * if you do not want to block. * * Returns: the (new) current connectivity state * * Deprecated: 1.22: Use nm_client_check_connectivity_async() or GDBusConnection. */ NMConnectivityState nm_client_check_connectivity(NMClient *client, GCancellable *cancellable, GError **error) { NMClientPrivate *priv; gs_unref_variant GVariant *ret = NULL; guint32 connectivity; g_return_val_if_fail(NM_IS_CLIENT(client), NM_CONNECTIVITY_UNKNOWN); ret = _nm_client_dbus_call_sync(client, cancellable, NM_DBUS_PATH, NM_DBUS_INTERFACE, "CheckConnectivity", g_variant_new("()"), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); if (!ret) return NM_CONNECTIVITY_UNKNOWN; g_variant_get(ret, "(u)", &connectivity); /* upon receiving the synchronous response, we hack the NMClient state * and update the property outside the ordered D-Bus messages (like * "PropertiesChanged" signals). * * This is really ugly, we shouldn't do this. */ priv = NM_CLIENT_GET_PRIVATE(client); if (priv->nm.connectivity != connectivity) { priv->nm.connectivity = connectivity; _notify(client, PROP_CONNECTIVITY); } return connectivity; } /** * nm_client_check_connectivity_async: * @client: an #NMClient * @cancellable: a #GCancellable * @callback: callback to call with the result * @user_data: data for @callback. * * Asynchronously updates the network connectivity state and invokes * @callback when complete. Contrast nm_client_get_connectivity(), * which (immediately) returns the most recent known state without * re-checking, and nm_client_check_connectivity(), which blocks. */ void nm_client_check_connectivity_async(NMClient * client, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); _nm_client_dbus_call(client, client, nm_client_check_connectivity_async, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "CheckConnectivity", g_variant_new("()"), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_variant_strip_dbus_error_cb); } /** * nm_client_check_connectivity_finish: * @client: an #NMClient * @result: the #GAsyncResult * @error: return location for a #GError * * Retrieves the result of an nm_client_check_connectivity_async() * call. * * Returns: the (new) current connectivity state */ NMConnectivityState nm_client_check_connectivity_finish(NMClient *client, GAsyncResult *result, GError **error) { gs_unref_variant GVariant *ret = NULL; guint32 connectivity; g_return_val_if_fail(NM_IS_CLIENT(client), NM_CONNECTIVITY_UNKNOWN); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_check_connectivity_async), NM_CONNECTIVITY_UNKNOWN); ret = g_task_propagate_pointer(G_TASK(result), error); if (!ret) return NM_CONNECTIVITY_UNKNOWN; g_variant_get(ret, "(u)", &connectivity); return connectivity; } /** * nm_client_save_hostname: * @client: the %NMClient * @hostname: (allow-none): the new persistent hostname to set, or %NULL to * clear any existing persistent hostname * @cancellable: a #GCancellable, or %NULL * @error: return location for #GError * * Requests that the machine's persistent hostname be set to the specified value * or cleared. * * Returns: %TRUE if the request was successful, %FALSE if it failed * * Deprecated: 1.22: Use nm_client_save_hostname_async() or GDBusConnection. **/ gboolean nm_client_save_hostname(NMClient * client, const char * hostname, GCancellable *cancellable, GError ** error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), FALSE); return _nm_client_dbus_call_sync_void(client, cancellable, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "SaveHostname", g_variant_new("(s)", hostname ?: ""), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); } /** * nm_client_save_hostname_async: * @client: the %NMClient * @hostname: (allow-none): the new persistent hostname to set, or %NULL to * clear any existing persistent hostname * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the operation completes * @user_data: (closure): caller-specific data passed to @callback * * Requests that the machine's persistent hostname be set to the specified value * or cleared. **/ void nm_client_save_hostname_async(NMClient * client, const char * hostname, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); _nm_client_dbus_call(client, client, nm_client_save_hostname_async, cancellable, callback, user_data, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "SaveHostname", g_variant_new("(s)", hostname ?: ""), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_void_strip_dbus_error_cb); } /** * nm_client_save_hostname_finish: * @client: the %NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: return location for #GError * * Gets the result of an nm_client_save_hostname_async() call. * * Returns: %TRUE if the request was successful, %FALSE if it failed **/ gboolean nm_client_save_hostname_finish(NMClient *client, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_save_hostname_async), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /*****************************************************************************/ /* Devices */ /*****************************************************************************/ /** * nm_client_get_devices: * @client: a #NMClient * * Gets all the known network devices. Use nm_device_get_type() or the * NM_IS_DEVICE_XXXX functions to determine what kind of * device member of the returned array is, and then you may use device-specific * methods such as nm_device_ethernet_get_hw_address(). * * Returns: (transfer none) (element-type NMDevice): a #GPtrArray * containing all the #NMDevices. The returned array is owned by the * #NMClient object and should not be modified. **/ const GPtrArray * nm_client_get_devices(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_ao_get_objs_as_ptrarray( &NM_CLIENT_GET_PRIVATE(client)->nm.property_ao[PROPERTY_AO_IDX_DEVICES]); } /** * nm_client_get_all_devices: * @client: a #NMClient * * Gets both real devices and device placeholders (eg, software devices which * do not currently exist, but could be created automatically by NetworkManager * if one of their NMDevice::ActivatableConnections was activated). Use * nm_device_is_real() to determine whether each device is a real device or * a placeholder. * * Use nm_device_get_type() or the NM_IS_DEVICE_XXXX() functions to determine * what kind of device each member of the returned array is, and then you may * use device-specific methods such as nm_device_ethernet_get_hw_address(). * * Returns: (transfer none) (element-type NMDevice): a #GPtrArray * containing all the #NMDevices. The returned array is owned by the * #NMClient object and should not be modified. * * Since: 1.2 **/ const GPtrArray * nm_client_get_all_devices(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_ao_get_objs_as_ptrarray( &NM_CLIENT_GET_PRIVATE(client)->nm.property_ao[PROPERTY_AO_IDX_ALL_DEVICES]); } /** * nm_client_get_device_by_path: * @client: a #NMClient * @object_path: the object path to search for * * Gets a #NMDevice from a #NMClient. * * Returns: (transfer none): the #NMDevice for the given @object_path or %NULL if none is found. **/ NMDevice * nm_client_get_device_by_path(NMClient *client, const char *object_path) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(object_path, NULL); return _dbobjs_get_nmobj_unpack_visible(client, object_path, NM_TYPE_DEVICE); } /** * nm_client_get_device_by_iface: * @client: a #NMClient * @iface: the interface name to search for * * Gets a #NMDevice from a #NMClient. * * Returns: (transfer none): the #NMDevice for the given @iface or %NULL if none is found. **/ NMDevice * nm_client_get_device_by_iface(NMClient *client, const char *iface) { const GPtrArray *devices; guint i; g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(iface, NULL); devices = nm_client_get_devices(client); for (i = 0; i < devices->len; i++) { NMDevice *candidate = g_ptr_array_index(devices, i); if (nm_streq0(nm_device_get_iface(candidate), iface)) return candidate; } return NULL; } /*****************************************************************************/ /* Active Connections */ /*****************************************************************************/ /** * nm_client_get_active_connections: * @client: a #NMClient * * Gets the active connections. * * Returns: (transfer none) (element-type NMActiveConnection): a #GPtrArray * containing all the active #NMActiveConnections. * The returned array is owned by the client and should not be modified. **/ const GPtrArray * nm_client_get_active_connections(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_ao_get_objs_as_ptrarray( &NM_CLIENT_GET_PRIVATE(client)->nm.property_ao[PROPERTY_AO_IDX_ACTIVE_CONNECTIONS]); } /** * nm_client_get_primary_connection: * @client: an #NMClient * * Gets the #NMActiveConnection corresponding to the primary active * network device. * * In particular, when there is no VPN active, or the VPN does not * have the default route, this returns the active connection that has * the default route. If there is a VPN active with the default route, * then this function returns the active connection that contains the * route to the VPN endpoint. * * If there is no default route, or the default route is over a * non-NetworkManager-recognized device, this will return %NULL. * * Returns: (transfer none): the appropriate #NMActiveConnection, if * any */ NMActiveConnection * nm_client_get_primary_connection(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_o_get_obj( &NM_CLIENT_GET_PRIVATE(client)->nm.property_o[PROPERTY_O_IDX_NM_PRIMAY_CONNECTION]); } /** * nm_client_get_activating_connection: * @client: an #NMClient * * Gets the #NMActiveConnection corresponding to a * currently-activating connection that is expected to become the new * #NMClient:primary-connection upon successful activation. * * Returns: (transfer none): the appropriate #NMActiveConnection, if * any. */ NMActiveConnection * nm_client_get_activating_connection(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_o_get_obj( &NM_CLIENT_GET_PRIVATE(client)->nm.property_o[PROPERTY_O_IDX_NM_ACTIVATING_CONNECTION]); } /*****************************************************************************/ static void activate_connection_cb(GObject *object, GAsyncResult *result, gpointer user_data) { gs_unref_object GTask *task = user_data; gs_unref_variant GVariant *ret = NULL; const char * v_active_connection; GError * error = NULL; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), result, &error); if (!ret) { if (!nm_utils_error_is_cancelled(error)) g_dbus_error_strip_remote_error(error); g_task_return_error(task, error); return; } g_variant_get(ret, "(&o)", &v_active_connection); _request_wait_start(g_steal_pointer(&task), "ActivateConnection", NM_TYPE_ACTIVE_CONNECTION, v_active_connection, NULL); } /** * nm_client_activate_connection_async: * @client: a #NMClient * @connection: (allow-none): an #NMConnection * @device: (allow-none): the #NMDevice * @specific_object: (allow-none): the object path of a connection-type-specific * object this activation should use. This parameter is currently ignored for * wired and mobile broadband connections, and the value of %NULL should be used * (ie, no specific object). For Wi-Fi or WiMAX connections, pass the object * path of a #NMAccessPoint or #NMWimaxNsp owned by @device, which you can * get using nm_object_get_path(), and which will be used to complete the * details of the newly added connection. * @cancellable: a #GCancellable, or %NULL * @callback: callback to be called when the activation has started * @user_data: caller-specific data passed to @callback * * Asynchronously starts a connection to a particular network using the * configuration settings from @connection and the network device @device. * Certain connection types also take a "specific object" which is the object * path of a connection- specific object, like an #NMAccessPoint for Wi-Fi * connections, or an #NMWimaxNsp for WiMAX connections, to which you wish to * connect. If the specific object is not given, NetworkManager can, in some * cases, automatically determine which network to connect to given the settings * in @connection. * * If @connection is not given for a device-based activation, NetworkManager * picks the best available connection for the device and activates it. * * Note that the callback is invoked when NetworkManager has started activating * the new connection, not when it finishes. You can use the returned * #NMActiveConnection object (in particular, #NMActiveConnection:state) to * track the activation to its completion. **/ void nm_client_activate_connection_async(NMClient * client, NMConnection * connection, NMDevice * device, const char * specific_object, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { const char *arg_connection = NULL; const char *arg_device = NULL; g_return_if_fail(NM_IS_CLIENT(client)); if (connection) { g_return_if_fail(NM_IS_CONNECTION(connection)); arg_connection = nm_connection_get_path(connection); g_return_if_fail(arg_connection); } if (device) { g_return_if_fail(NM_IS_DEVICE(device)); arg_device = nm_object_get_path(NM_OBJECT(device)); g_return_if_fail(arg_device); } NML_NMCLIENT_LOG_T( client, "ActivateConnection() for connection \"%s\", device \"%s\", specific_object \"%s", arg_connection ?: "/", arg_device ?: "/", specific_object ?: "/"); _nm_client_dbus_call( client, client, nm_client_activate_connection_async, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "ActivateConnection", g_variant_new("(ooo)", arg_connection ?: "/", arg_device ?: "/", specific_object ?: "/"), G_VARIANT_TYPE("(o)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, activate_connection_cb); } /** * nm_client_activate_connection_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_activate_connection_async(). * * Returns: (transfer full): the new #NMActiveConnection on success, %NULL on * failure, in which case @error will be set. **/ NMActiveConnection * nm_client_activate_connection_finish(NMClient *client, GAsyncResult *result, GError **error) { return NM_ACTIVE_CONNECTION( _request_wait_finish(client, result, nm_client_activate_connection_async, NULL, error)); } /*****************************************************************************/ static void _add_and_activate_connection_done(GObject * object, GAsyncResult *result, gboolean use_add_and_activate_v2, GTask * task_take) { _nm_unused gs_unref_object GTask *task = task_take; gs_unref_variant GVariant *ret = NULL; GError * error = NULL; gs_unref_variant GVariant *v_result = NULL; const char * v_active_connection; const char * v_path; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), result, &error); if (!ret) { if (!nm_utils_error_is_cancelled(error)) g_dbus_error_strip_remote_error(error); g_task_return_error(task, error); return; } if (use_add_and_activate_v2) { g_variant_get(ret, "(&o&o@a{sv})", &v_path, &v_active_connection, &v_result); } else { g_variant_get(ret, "(&o&o)", &v_path, &v_active_connection); } _request_wait_start(g_steal_pointer(&task), "AddAndActivateConnection", NM_TYPE_ACTIVE_CONNECTION, v_active_connection, g_steal_pointer(&v_result)); } static void _add_and_activate_connection_v1_cb(GObject *object, GAsyncResult *result, gpointer user_data) { _add_and_activate_connection_done(object, result, FALSE, user_data); } static void _add_and_activate_connection_v2_cb(GObject *object, GAsyncResult *result, gpointer user_data) { _add_and_activate_connection_done(object, result, TRUE, user_data); } static void _add_and_activate_connection(NMClient * self, gboolean is_v2, NMConnection * partial, NMDevice * device, const char * specific_object, GVariant * options, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { GVariant * arg_connection = NULL; gboolean use_add_and_activate_v2 = FALSE; const char *arg_device = NULL; gpointer source_tag; g_return_if_fail(NM_IS_CLIENT(self)); g_return_if_fail(!partial || NM_IS_CONNECTION(partial)); if (device) { g_return_if_fail(NM_IS_DEVICE(device)); arg_device = nm_object_get_path(NM_OBJECT(device)); g_return_if_fail(arg_device); } if (partial) arg_connection = nm_connection_to_dbus(partial, NM_CONNECTION_SERIALIZE_ALL); if (!arg_connection) arg_connection = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0); if (is_v2) { if (!options) options = g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0); use_add_and_activate_v2 = TRUE; source_tag = nm_client_add_and_activate_connection2; } else { if (options) { if (g_variant_n_children(options) > 0) use_add_and_activate_v2 = TRUE; else nm_clear_pointer(&options, nm_g_variant_unref_floating); } source_tag = nm_client_add_and_activate_connection_async; } NML_NMCLIENT_LOG_D(self, "AddAndActivateConnection() started..."); if (use_add_and_activate_v2) { _nm_client_dbus_call(self, self, source_tag, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "AddAndActivateConnection2", g_variant_new("(@a{sa{sv}}oo@a{sv})", arg_connection, arg_device ?: "/", specific_object ?: "/", options), G_VARIANT_TYPE("(ooa{sv})"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _add_and_activate_connection_v2_cb); } else { _nm_client_dbus_call(self, self, source_tag, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "AddAndActivateConnection", g_variant_new("(@a{sa{sv}}oo)", arg_connection, arg_device ?: "/", specific_object ?: "/"), G_VARIANT_TYPE("(oo)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _add_and_activate_connection_v1_cb); } } /** * nm_client_add_and_activate_connection_async: * @client: a #NMClient * @partial: (allow-none): an #NMConnection to add; the connection may be * partially filled (or even %NULL) and will be completed by NetworkManager * using the given @device and @specific_object before being added * @device: the #NMDevice * @specific_object: (allow-none): the object path of a connection-type-specific * object this activation should use. This parameter is currently ignored for * wired and mobile broadband connections, and the value of %NULL should be used * (ie, no specific object). For Wi-Fi or WiMAX connections, pass the object * path of a #NMAccessPoint or #NMWimaxNsp owned by @device, which you can * get using nm_object_get_path(), and which will be used to complete the * details of the newly added connection. * If the variant is floating, it will be consumed. * @cancellable: a #GCancellable, or %NULL * @callback: callback to be called when the activation has started * @user_data: caller-specific data passed to @callback * * Adds a new connection using the given details (if any) as a template, * automatically filling in missing settings with the capabilities of the given * device and specific object. The new connection is then asynchronously * activated as with nm_client_activate_connection_async(). Cannot be used for * VPN connections at this time. * * Note that the callback is invoked when NetworkManager has started activating * the new connection, not when it finishes. You can used the returned * #NMActiveConnection object (in particular, #NMActiveConnection:state) to * track the activation to its completion. **/ void nm_client_add_and_activate_connection_async(NMClient * client, NMConnection * partial, NMDevice * device, const char * specific_object, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { _add_and_activate_connection(client, FALSE, partial, device, specific_object, NULL, cancellable, callback, user_data); } /** * nm_client_add_and_activate_connection_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_add_and_activate_connection_async(). * * You can call nm_active_connection_get_connection() on the returned * #NMActiveConnection to find the path of the created #NMConnection. * * Returns: (transfer full): the new #NMActiveConnection on success, %NULL on * failure, in which case @error will be set. **/ NMActiveConnection * nm_client_add_and_activate_connection_finish(NMClient *client, GAsyncResult *result, GError **error) { return NM_ACTIVE_CONNECTION(_request_wait_finish(client, result, nm_client_add_and_activate_connection_async, NULL, error)); } /** * nm_client_add_and_activate_connection2: * @client: a #NMClient * @partial: (allow-none): an #NMConnection to add; the connection may be * partially filled (or even %NULL) and will be completed by NetworkManager * using the given @device and @specific_object before being added * @device: the #NMDevice * @specific_object: (allow-none): the object path of a connection-type-specific * object this activation should use. This parameter is currently ignored for * wired and mobile broadband connections, and the value of %NULL should be used * (i.e., no specific object). For Wi-Fi or WiMAX connections, pass the object * path of a #NMAccessPoint or #NMWimaxNsp owned by @device, which you can * get using nm_object_get_path(), and which will be used to complete the * details of the newly added connection. * @options: a #GVariant containing a dictionary with options, or %NULL * @cancellable: a #GCancellable, or %NULL * @callback: callback to be called when the activation has started * @user_data: caller-specific data passed to @callback * * Adds a new connection using the given details (if any) as a template, * automatically filling in missing settings with the capabilities of the given * device and specific object. The new connection is then asynchronously * activated as with nm_client_activate_connection_async(). Cannot be used for * VPN connections at this time. * * Note that the callback is invoked when NetworkManager has started activating * the new connection, not when it finishes. You can used the returned * #NMActiveConnection object (in particular, #NMActiveConnection:state) to * track the activation to its completion. * * This is identical to nm_client_add_and_activate_connection_async() but takes * a further @options parameter. Currently, the following options are supported * by the daemon: * * "persist": A string describing how the connection should be stored. * The default is "disk", but it can be modified to "memory" (until * the daemon quits) or "volatile" (will be deleted on disconnect). * * "bind-activation": Bind the connection lifetime to something. The default is "none", * meaning an explicit disconnect is needed. The value "dbus-client" * means the connection will automatically be deactivated when the calling * D-Bus client disappears from the system bus. * * Since: 1.16 **/ void nm_client_add_and_activate_connection2(NMClient * client, NMConnection * partial, NMDevice * device, const char * specific_object, GVariant * options, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { _add_and_activate_connection(client, TRUE, partial, device, specific_object, options, cancellable, callback, user_data); } /** * nm_client_add_and_activate_connection2_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * @out_result: (allow-none) (transfer full): the output result * of type "a{sv}" returned by D-Bus' AddAndActivate2 call. Currently, no * output is implemented yet. * * Gets the result of a call to nm_client_add_and_activate_connection2(). * * You can call nm_active_connection_get_connection() on the returned * #NMActiveConnection to find the path of the created #NMConnection. * * Returns: (transfer full): the new #NMActiveConnection on success, %NULL on * failure, in which case @error will be set. **/ NMActiveConnection * nm_client_add_and_activate_connection2_finish(NMClient * client, GAsyncResult *result, GVariant ** out_result, GError ** error) { return NM_ACTIVE_CONNECTION(_request_wait_finish(client, result, nm_client_add_and_activate_connection2, out_result, error)); } /*****************************************************************************/ /** * nm_client_deactivate_connection: * @client: a #NMClient * @active: the #NMActiveConnection to deactivate * @cancellable: a #GCancellable, or %NULL * @error: location for a #GError, or %NULL * * Deactivates an active #NMActiveConnection. * * Returns: success or failure * * Deprecated: 1.22: Use nm_client_deactivate_connection_async() or GDBusConnection. **/ gboolean nm_client_deactivate_connection(NMClient * client, NMActiveConnection *active, GCancellable * cancellable, GError ** error) { const char *active_path; g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(active), FALSE); active_path = nm_object_get_path(NM_OBJECT(active)); g_return_val_if_fail(active_path, FALSE); return _nm_client_dbus_call_sync_void(client, cancellable, NM_DBUS_PATH, NM_DBUS_INTERFACE, "DeactivateConnection", g_variant_new("(o)", active_path), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); } /** * nm_client_deactivate_connection_async: * @client: a #NMClient * @active: the #NMActiveConnection to deactivate * @cancellable: a #GCancellable, or %NULL * @callback: callback to be called when the deactivation has completed * @user_data: caller-specific data passed to @callback * * Asynchronously deactivates an active #NMActiveConnection. **/ void nm_client_deactivate_connection_async(NMClient * client, NMActiveConnection *active, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { const char *active_path; g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(NM_IS_ACTIVE_CONNECTION(active)); active_path = nm_object_get_path(NM_OBJECT(active)); g_return_if_fail(active_path); _nm_client_dbus_call(client, client, nm_client_deactivate_connection_async, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "DeactivateConnection", g_variant_new("(o)", active_path), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_void_strip_dbus_error_cb); } /** * nm_client_deactivate_connection_finish: * @client: a #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_deactivate_connection_async(). * * Returns: success or failure **/ gboolean nm_client_deactivate_connection_finish(NMClient *client, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_deactivate_connection_async), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /*****************************************************************************/ /* Connections */ /*****************************************************************************/ /** * nm_client_get_connections: * @client: the %NMClient * * Returns: (transfer none) (element-type NMRemoteConnection): an array * containing all connections provided by the remote settings service. The * returned array is owned by the #NMClient object and should not be modified. * * The connections are as received from D-Bus and might not validate according * to nm_connection_verify(). **/ const GPtrArray * nm_client_get_connections(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_ao_get_objs_as_ptrarray( &NM_CLIENT_GET_PRIVATE(client)->settings.connections); } /** * nm_client_get_connection_by_id: * @client: the %NMClient * @id: the id of the remote connection * * Returns the first matching %NMRemoteConnection matching a given @id. * * Returns: (transfer none): the remote connection object on success, or %NULL if no * matching object was found. * * The connection is as received from D-Bus and might not validate according * to nm_connection_verify(). **/ NMRemoteConnection * nm_client_get_connection_by_id(NMClient *client, const char *id) { const GPtrArray *arr; guint i; g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(id, NULL); arr = nm_client_get_connections(client); for (i = 0; i < arr->len; i++) { NMRemoteConnection *c = NM_REMOTE_CONNECTION(arr->pdata[i]); if (nm_streq0(id, nm_connection_get_id(NM_CONNECTION(c)))) return c; } return NULL; } /** * nm_client_get_connection_by_path: * @client: the %NMClient * @path: the D-Bus object path of the remote connection * * Returns the %NMRemoteConnection representing the connection at @path. * * Returns: (transfer none): the remote connection object on success, or %NULL if the object was * not known * * The connection is as received from D-Bus and might not validate according * to nm_connection_verify(). **/ NMRemoteConnection * nm_client_get_connection_by_path(NMClient *client, const char *path) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(path != NULL, NULL); return _dbobjs_get_nmobj_unpack_visible(client, path, NM_TYPE_REMOTE_CONNECTION); } /** * nm_client_get_connection_by_uuid: * @client: the %NMClient * @uuid: the UUID of the remote connection * * Returns the %NMRemoteConnection identified by @uuid. * * Returns: (transfer none): the remote connection object on success, or %NULL if the object was * not known * * The connection is as received from D-Bus and might not validate according * to nm_connection_verify(). **/ NMRemoteConnection * nm_client_get_connection_by_uuid(NMClient *client, const char *uuid) { const GPtrArray *arr; guint i; g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(uuid, NULL); arr = nm_client_get_connections(client); for (i = 0; i < arr->len; i++) { NMRemoteConnection *c = NM_REMOTE_CONNECTION(arr->pdata[i]); if (nm_streq0(uuid, nm_connection_get_uuid(NM_CONNECTION(c)))) return c; } return NULL; } /*****************************************************************************/ static void _add_connection_cb(GObject * source, GAsyncResult *result, gboolean with_extra_arg, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_unref_object GTask *task = user_data; gs_unref_variant GVariant *v_result = NULL; const char * v_path; GError * error = NULL; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret) { if (!nm_utils_error_is_cancelled(error)) g_dbus_error_strip_remote_error(error); g_task_return_error(task, error); return; } if (with_extra_arg) { g_variant_get(ret, "(&o@a{sv})", &v_path, &v_result); } else { g_variant_get(ret, "(&o)", &v_path); } _request_wait_start(g_steal_pointer(&task), "AddConnection", NM_TYPE_REMOTE_CONNECTION, v_path, g_steal_pointer(&v_result)); } static void _add_connection_cb_without_extra_result(GObject *object, GAsyncResult *result, gpointer user_data) { _add_connection_cb(object, result, FALSE, user_data); } static void _add_connection_cb_with_extra_result(GObject *object, GAsyncResult *result, gpointer user_data) { _add_connection_cb(object, result, TRUE, user_data); } static void _add_connection_call(NMClient * self, gpointer source_tag, gboolean ignore_out_result, GVariant * settings, NMSettingsAddConnection2Flags flags, GVariant * args, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(self)); g_return_if_fail(!settings || g_variant_is_of_type(settings, G_VARIANT_TYPE("a{sa{sv}}"))); g_return_if_fail(!args || g_variant_is_of_type(args, G_VARIANT_TYPE("a{sv}"))); NML_NMCLIENT_LOG_D(self, "AddConnection() started..."); if (!settings) settings = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0); /* Although AddConnection2() being capable to handle also AddConnection() and * AddConnectionUnsaved() variants, we prefer to use the old D-Bus methods when * they are sufficient. The reason is that libnm should avoid hard dependencies * on 1.20 API whenever possible. */ if (ignore_out_result && flags == NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK) { _nm_client_dbus_call(self, self, source_tag, cancellable, callback, user_data, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection", g_variant_new("(@a{sa{sv}})", settings), G_VARIANT_TYPE("(o)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _add_connection_cb_without_extra_result); } else if (ignore_out_result && flags == NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY) { _nm_client_dbus_call(self, self, source_tag, cancellable, callback, user_data, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnectionUnsaved", g_variant_new("(@a{sa{sv}})", settings), G_VARIANT_TYPE("(o)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _add_connection_cb_without_extra_result); } else { _nm_client_dbus_call( self, self, source_tag, cancellable, callback, user_data, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection2", g_variant_new("(@a{sa{sv}}u@a{sv})", settings, (guint32) flags, args ?: g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)), G_VARIANT_TYPE("(oa{sv})"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, _add_connection_cb_with_extra_result); } } /** * nm_client_add_connection_async: * @client: the %NMClient * @connection: the connection to add. Note that this object's settings will be * added, not the object itself * @save_to_disk: whether to immediately save the connection to disk * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Requests that the remote settings service add the given settings to a new * connection. If @save_to_disk is %TRUE, the connection is immediately written * to disk; otherwise it is initially only stored in memory, but may be saved * later by calling the connection's nm_remote_connection_commit_changes() * method. * * @connection is untouched by this function and only serves as a template of * the settings to add. The #NMRemoteConnection object that represents what * NetworkManager actually added is returned to @callback when the addition * operation is complete. * * Note that the #NMRemoteConnection returned in @callback may not contain * identical settings to @connection as NetworkManager may perform automatic * completion and/or normalization of connection properties. **/ void nm_client_add_connection_async(NMClient * client, NMConnection * connection, gboolean save_to_disk, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CONNECTION(connection)); _add_connection_call(client, nm_client_add_connection_async, TRUE, nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL), save_to_disk ? NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK : NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY, NULL, cancellable, callback, user_data); } /** * nm_client_add_connection_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_add_connection_async(). * * Returns: (transfer full): the new #NMRemoteConnection on success, %NULL on * failure, in which case @error will be set. **/ NMRemoteConnection * nm_client_add_connection_finish(NMClient *client, GAsyncResult *result, GError **error) { return NM_REMOTE_CONNECTION( _request_wait_finish(client, result, nm_client_add_connection_async, NULL, error)); } /** * nm_client_add_connection2: * @client: the %NMClient * @settings: the "a{sa{sv}}" #GVariant with the content of the setting. * @flags: the %NMSettingsAddConnection2Flags argument. * @args: (allow-none): the "a{sv}" #GVariant with extra argument or %NULL * for no extra arguments. * @ignore_out_result: this function wraps AddConnection2(), which has an * additional result "a{sv}" output parameter. By setting this to %TRUE, * you signal that you are not interested in that output parameter. * This allows the function to fall back to AddConnection() and AddConnectionUnsaved(), * which is interesting if you run against an older server version that does * not yet provide AddConnection2(). By setting this to %FALSE, the function * under the hood always calls AddConnection2(). * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Call AddConnection2() D-Bus API asynchronously. * * Since: 1.20 **/ void nm_client_add_connection2(NMClient * client, GVariant * settings, NMSettingsAddConnection2Flags flags, GVariant * args, gboolean ignore_out_result, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { _add_connection_call(client, nm_client_add_connection2, ignore_out_result, settings, flags, args, cancellable, callback, user_data); } /** * nm_client_add_connection2_finish: * @client: the #NMClient * @result: the #GAsyncResult * @out_result: (allow-none) (transfer full) (out): the output #GVariant * from AddConnection2(). * If you care about the output result, then the "ignore_out_result" * parameter of nm_client_add_connection2() must not be set to %TRUE. * @error: (allow-none): the error argument. * * Returns: (transfer full): on success, a pointer to the added * #NMRemoteConnection. * * Since: 1.20 */ NMRemoteConnection * nm_client_add_connection2_finish(NMClient * client, GAsyncResult *result, GVariant ** out_result, GError ** error) { return NM_REMOTE_CONNECTION( _request_wait_finish(client, result, nm_client_add_connection2, out_result, error)); } /*****************************************************************************/ /** * nm_client_load_connections: * @client: the %NMClient * @filenames: (array zero-terminated=1): %NULL-terminated array of filenames to load * @failures: (out) (transfer full): on return, a %NULL-terminated array of * filenames that failed to load * @cancellable: a #GCancellable, or %NULL * @error: return location for #GError * * Requests that the remote settings service load or reload the given files, * adding or updating the connections described within. * * The changes to the indicated files will not yet be reflected in * @client's connections array when the function returns. * * If all of the indicated files were successfully loaded, the * function will return %TRUE, and @failures will be set to %NULL. If * NetworkManager tried to load the files, but some (or all) failed, * then @failures will be set to a %NULL-terminated array of the * filenames that failed to load. * * Returns: %TRUE on success. * * Warning: before libnm 1.22, the boolean return value was inconsistent. * That is made worse, because when running against certain server versions * before 1.20, the server would return wrong values for success/failure. * This means, if you use this function in libnm before 1.22, you are advised * to ignore the boolean return value and only look at @failures and @error. * With libnm >= 1.22, the boolean return value corresponds to whether @error was * set. Note that even in the success case, you might have individual @failures. * With 1.22, the return value is consistent with nm_client_load_connections_finish(). * * Deprecated: 1.22: Use nm_client_load_connections_async() or GDBusConnection. **/ gboolean nm_client_load_connections(NMClient * client, char ** filenames, char *** failures, GCancellable *cancellable, GError ** error) { gs_unref_variant GVariant *ret = NULL; g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), FALSE); ret = _nm_client_dbus_call_sync(client, cancellable, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "LoadConnections", g_variant_new("(^as)", filenames ?: NM_PTRARRAY_EMPTY(char *)), G_VARIANT_TYPE("(bas)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); if (!ret) { *failures = NULL; return FALSE; } g_variant_get(ret, "(b^as)", NULL, &failures); return TRUE; } /** * nm_client_load_connections_async: * @client: the %NMClient * @filenames: (array zero-terminated=1): %NULL-terminated array of filenames to load * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the operation completes * @user_data: (closure): caller-specific data passed to @callback * * Requests that the remote settings service asynchronously load or reload the * given files, adding or updating the connections described within. * * See nm_client_load_connections() for more details. **/ void nm_client_load_connections_async(NMClient * client, char ** filenames, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); _nm_client_dbus_call(client, client, nm_client_load_connections_async, cancellable, callback, user_data, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "LoadConnections", g_variant_new("(^as)", filenames ?: NM_PTRARRAY_EMPTY(char *)), G_VARIANT_TYPE("(bas)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_variant_strip_dbus_error_cb); } /** * nm_client_load_connections_finish: * @client: the %NMClient * @failures: (out) (transfer full) (array zero-terminated=1): on return, a * %NULL-terminated array of filenames that failed to load * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of an nm_client_load_connections_async() call. * See nm_client_load_connections() for more details. * * Returns: %TRUE on success. * Note that even in the success case, you might have individual @failures. **/ gboolean nm_client_load_connections_finish(NMClient * client, char *** failures, GAsyncResult *result, GError ** error) { gs_unref_variant GVariant *ret = NULL; g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_load_connections_async), FALSE); ret = g_task_propagate_pointer(G_TASK(result), error); if (!ret) { *failures = NULL; return FALSE; } g_variant_get(ret, "(b^as)", NULL, &failures); return TRUE; } /** * nm_client_reload_connections: * @client: the #NMClient * @cancellable: a #GCancellable, or %NULL * @error: return location for #GError * * Requests that the remote settings service reload all connection * files from disk, adding, updating, and removing connections until * the in-memory state matches the on-disk state. * * Return value: %TRUE on success, %FALSE on failure * * Deprecated: 1.22: Use nm_client_reload_connections_async() or GDBusConnection. **/ gboolean nm_client_reload_connections(NMClient *client, GCancellable *cancellable, GError **error) { gs_unref_variant GVariant *ret = NULL; g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), FALSE); ret = _nm_client_dbus_call_sync(client, cancellable, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "ReloadConnections", g_variant_new("()"), G_VARIANT_TYPE("(b)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, TRUE, error); if (!ret) return FALSE; return TRUE; } /** * nm_client_reload_connections_async: * @client: the #NMClient * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the reload operation completes * @user_data: (closure): caller-specific data passed to @callback * * Requests that the remote settings service begin reloading all connection * files from disk, adding, updating, and removing connections until the * in-memory state matches the on-disk state. **/ void nm_client_reload_connections_async(NMClient * client, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); _nm_client_dbus_call(client, client, nm_client_reload_connections_async, cancellable, callback, user_data, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "ReloadConnections", g_variant_new("()"), G_VARIANT_TYPE("(b)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_variant_strip_dbus_error_cb); } /** * nm_client_reload_connections_finish: * @client: the #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: return location for #GError * * Gets the result of an nm_client_reload_connections_async() call. * * Return value: %TRUE on success, %FALSE on failure **/ gboolean nm_client_reload_connections_finish(NMClient *client, GAsyncResult *result, GError **error) { gs_unref_variant GVariant *ret = NULL; g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_reload_connections_async), FALSE); ret = g_task_propagate_pointer(G_TASK(result), error); if (!ret) return FALSE; return TRUE; } /*****************************************************************************/ /** * nm_client_get_dns_mode: * @client: the #NMClient * * Gets the current DNS processing mode. * * Return value: the DNS processing mode, or %NULL in case the * value is not available. * * Since: 1.6 **/ const char * nm_client_get_dns_mode(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->dns_manager.mode; } /** * nm_client_get_dns_rc_manager: * @client: the #NMClient * * Gets the current DNS resolv.conf manager. * * Return value: the resolv.conf manager or %NULL in case the * value is not available. * * Since: 1.6 **/ const char * nm_client_get_dns_rc_manager(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->dns_manager.rc_manager; } /** * nm_client_get_dns_configuration: * @client: a #NMClient * * Gets the current DNS configuration * * Returns: (transfer none) (element-type NMDnsEntry): a #GPtrArray * containing #NMDnsEntry elements or %NULL in case the value is not * available. The returned array is owned by the #NMClient object * and should not be modified. * * Since: 1.6 **/ const GPtrArray * nm_client_get_dns_configuration(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return NM_CLIENT_GET_PRIVATE(client)->dns_manager.configuration; } static NMLDBusNotifyUpdatePropFlags _notify_update_prop_dns_manager_configuration(NMClient * self, NMLDBusObject * dbobj, const NMLDBusMetaIface *meta_iface, guint dbus_property_idx, GVariant * value) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); gs_unref_ptrarray GPtrArray *configuration_old = NULL; gs_unref_ptrarray GPtrArray *configuration_new = NULL; nm_assert(G_OBJECT(self) == dbobj->nmobj); if (value) { GVariant * entry_var_tmp; GVariantIter iter; GPtrArray * array; configuration_new = g_ptr_array_new_with_free_func((GDestroyNotify) nm_dns_entry_unref); g_variant_iter_init(&iter, value); while (g_variant_iter_next(&iter, "@a{sv}", &entry_var_tmp)) { gs_unref_variant GVariant *entry_var = entry_var_tmp; nm_auto_free_variant_iter GVariantIter *iterp_nameservers = NULL; nm_auto_free_variant_iter GVariantIter *iterp_domains = NULL; gs_free char ** nameservers = NULL; gs_free char ** domains = NULL; gboolean vpn = FALSE; NMDnsEntry * entry; char * interface = NULL; char * str; gint32 priority = 0; if (!g_variant_lookup(entry_var, "nameservers", "as", &iterp_nameservers) || !g_variant_lookup(entry_var, "priority", "i", &priority)) { g_warning("Ignoring invalid DNS configuration"); continue; } array = g_ptr_array_new(); while (g_variant_iter_next(iterp_nameservers, "&s", &str)) g_ptr_array_add(array, str); g_ptr_array_add(array, NULL); nameservers = (char **) g_ptr_array_free(array, FALSE); if (g_variant_lookup(entry_var, "domains", "as", &iterp_domains)) { array = g_ptr_array_new(); while (g_variant_iter_next(iterp_domains, "&s", &str)) g_ptr_array_add(array, str); g_ptr_array_add(array, NULL); domains = (char **) g_ptr_array_free(array, FALSE); } g_variant_lookup(entry_var, "interface", "&s", &interface); g_variant_lookup(entry_var, "vpn", "b", &vpn); entry = nm_dns_entry_new(interface, (const char *const *) nameservers, (const char *const *) domains, priority, vpn); if (!entry) { g_warning("Ignoring invalid DNS entry"); continue; } g_ptr_array_add(configuration_new, entry); } } configuration_old = priv->dns_manager.configuration; priv->dns_manager.configuration = g_steal_pointer(&configuration_new); return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY; } /** * nm_client_get_capabilities: * @client: the #NMClient instance * @length: (out) (allow-none): the number of returned capabilities. * * Returns: (transfer none) (array length=length): the * list of capabilities reported by the server or %NULL * if the capabilities are unknown. * The numeric values correspond to #NMCapability enum. * The array is terminated by a numeric zero sentinel * at position @length. * * Since: 1.24 */ const guint32 * nm_client_get_capabilities(NMClient *client, gsize *length) { NMClientPrivate *priv; g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(length, NULL); priv = NM_CLIENT_GET_PRIVATE(client); NM_SET_OUT(length, priv->nm.capabilities_len); return priv->nm.capabilities_arr; } static NMLDBusNotifyUpdatePropFlags _notify_update_prop_nm_capabilities(NMClient * self, NMLDBusObject * dbobj, const NMLDBusMetaIface *meta_iface, guint dbus_property_idx, GVariant * value) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); nm_assert(G_OBJECT(self) == dbobj->nmobj); nm_clear_g_free(&priv->nm.capabilities_arr); priv->nm.capabilities_len = 0; if (value) { const guint32 *arr; gsize len; arr = g_variant_get_fixed_array(value, &len, sizeof(guint32)); priv->nm.capabilities_len = len; priv->nm.capabilities_arr = g_new(guint32, len + 1); if (len > 0) memcpy(priv->nm.capabilities_arr, arr, len * sizeof(guint32)); priv->nm.capabilities_arr[len] = 0; } return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY; } /*****************************************************************************/ /** * nm_client_get_checkpoints: * @client: a #NMClient * * Gets all the active checkpoints. * * Returns: (transfer none) (element-type NMCheckpoint): a #GPtrArray * containing all the #NMCheckpoint. The returned array is owned by the * #NMClient object and should not be modified. * * Since: 1.12 **/ const GPtrArray * nm_client_get_checkpoints(NMClient *client) { g_return_val_if_fail(NM_IS_CLIENT(client), NULL); return nml_dbus_property_ao_get_objs_as_ptrarray( &NM_CLIENT_GET_PRIVATE(client)->nm.property_ao[PROPERTY_AO_IDX_CHECKPOINTS]); } static void checkpoint_create_cb(GObject *object, GAsyncResult *result, gpointer user_data) { gs_unref_object GTask *task = user_data; gs_unref_variant GVariant *ret = NULL; const char * v_checkpoint_path; GError * error = NULL; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), result, &error); if (!ret) { if (!nm_utils_error_is_cancelled(error)) g_dbus_error_strip_remote_error(error); g_task_return_error(task, error); return; } g_variant_get(ret, "(&o)", &v_checkpoint_path); _request_wait_start(g_steal_pointer(&task), "CheckpointCreate", NM_TYPE_CHECKPOINT, v_checkpoint_path, NULL); } /** * nm_client_checkpoint_create: * @client: the %NMClient * @devices: (element-type NMDevice): a list of devices for which a * checkpoint should be created. * @rollback_timeout: the rollback timeout in seconds * @flags: creation flags * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Creates a checkpoint of the current networking configuration * for given interfaces. An empty @devices argument means all * devices. If @rollback_timeout is not zero, a rollback is * automatically performed after the given timeout. * * Since: 1.12 **/ void nm_client_checkpoint_create(NMClient * client, const GPtrArray * devices, guint32 rollback_timeout, NMCheckpointCreateFlags flags, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { gs_free const char **paths = NULL; guint i; g_return_if_fail(NM_IS_CLIENT(client)); if (devices && devices->len > 0) { paths = g_new(const char *, devices->len + 1); for (i = 0; i < devices->len; i++) paths[i] = nm_object_get_path(NM_OBJECT(devices->pdata[i])); paths[i] = NULL; } _nm_client_dbus_call( client, client, nm_client_checkpoint_create, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "CheckpointCreate", g_variant_new("(^aouu)", paths ?: NM_PTRARRAY_EMPTY(const char *), rollback_timeout, flags), G_VARIANT_TYPE("(o)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, checkpoint_create_cb); } /** * nm_client_checkpoint_create_finish: * @client: the #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_checkpoint_create(). * * Returns: (transfer full): the new #NMCheckpoint on success, %NULL on * failure, in which case @error will be set. * * Since: 1.12 **/ NMCheckpoint * nm_client_checkpoint_create_finish(NMClient *client, GAsyncResult *result, GError **error) { return NM_CHECKPOINT( _request_wait_finish(client, result, nm_client_checkpoint_create, NULL, error)); } /** * nm_client_checkpoint_destroy: * @client: the %NMClient * @checkpoint_path: the D-Bus path for the checkpoint * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Destroys an existing checkpoint without performing a rollback. * * Since: 1.12 **/ void nm_client_checkpoint_destroy(NMClient * client, const char * checkpoint_path, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(checkpoint_path && checkpoint_path[0] == '/'); _nm_client_dbus_call(client, client, nm_client_checkpoint_destroy, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "CheckpointDestroy", g_variant_new("(o)", checkpoint_path), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_void_strip_dbus_error_cb); } /** * nm_client_checkpoint_destroy_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_checkpoint_destroy(). * * Returns: %TRUE on success or %FALSE on failure, in which case * @error will be set. * * Since: 1.12 **/ gboolean nm_client_checkpoint_destroy_finish(NMClient *client, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_checkpoint_destroy), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /** * nm_client_checkpoint_rollback: * @client: the %NMClient * @checkpoint_path: the D-Bus path to the checkpoint * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Performs the rollback of a checkpoint before the timeout is reached. * * Since: 1.12 **/ void nm_client_checkpoint_rollback(NMClient * client, const char * checkpoint_path, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(checkpoint_path && checkpoint_path[0] == '/'); _nm_client_dbus_call(client, client, nm_client_checkpoint_rollback, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "CheckpointRollback", g_variant_new("(o)", checkpoint_path), G_VARIANT_TYPE("(a{su})"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_variant_strip_dbus_error_cb); } /** * nm_client_checkpoint_rollback_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_checkpoint_rollback(). * * Returns: (transfer full) (element-type utf8 guint32): an hash table of * devices and results. Devices are represented by their original * D-Bus path; each result is a #NMRollbackResult. * * Since: 1.12 **/ GHashTable * nm_client_checkpoint_rollback_finish(NMClient *client, GAsyncResult *result, GError **error) { gs_unref_variant GVariant *ret = NULL; gs_unref_variant GVariant *v_result = NULL; GVariantIter iter; GHashTable * hash; const char * path; guint32 r; g_return_val_if_fail(NM_IS_CLIENT(client), NULL); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_checkpoint_rollback), NULL); ret = g_task_propagate_pointer(G_TASK(result), error); if (!ret) return NULL; g_variant_get(ret, "(@a{su})", &v_result); hash = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); g_variant_iter_init(&iter, v_result); while (g_variant_iter_next(&iter, "{&su}", &path, &r)) g_hash_table_insert(hash, g_strdup(path), GUINT_TO_POINTER(r)); return hash; } /** * nm_client_checkpoint_adjust_rollback_timeout: * @client: the %NMClient * @checkpoint_path: a D-Bus path to a checkpoint * @add_timeout: the timeout in seconds counting from now. * Set to zero, to disable the timeout. * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Resets the timeout for the checkpoint with path @checkpoint_path * to @timeout_add. * * Since: 1.12 **/ void nm_client_checkpoint_adjust_rollback_timeout(NMClient * client, const char * checkpoint_path, guint32 add_timeout, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(checkpoint_path && checkpoint_path[0] == '/'); _nm_client_dbus_call(client, client, nm_client_checkpoint_adjust_rollback_timeout, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "CheckpointAdjustRollbackTimeout", g_variant_new("(ou)", checkpoint_path, add_timeout), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_void_strip_dbus_error_cb); } /** * nm_client_checkpoint_adjust_rollback_timeout_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_checkpoint_adjust_rollback_timeout(). * * Returns: %TRUE on success or %FALSE on failure. * * Since: 1.12 **/ gboolean nm_client_checkpoint_adjust_rollback_timeout_finish(NMClient * client, GAsyncResult *result, GError ** error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail( nm_g_task_is_valid(result, client, nm_client_checkpoint_adjust_rollback_timeout), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /** * nm_client_reload: * @client: the %NMClient * @flags: flags indicating what to reload. * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback * * Reload NetworkManager's configuration and perform certain updates, like * flushing caches or rewriting external state to disk. This is similar to * sending SIGHUP to NetworkManager but it allows for more fine-grained control * over what to reload (see @flags). It also allows non-root access via * PolicyKit and contrary to signals it is synchronous. * * Since: 1.22 **/ void nm_client_reload(NMClient * client, NMManagerReloadFlags flags, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); _nm_client_dbus_call(client, client, nm_client_reload, cancellable, callback, user_data, NM_DBUS_PATH, NM_DBUS_INTERFACE, "Reload", g_variant_new("(u)", (guint32) flags), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, nm_dbus_connection_call_finish_void_strip_dbus_error_cb); } /** * nm_client_reload_finish: * @client: an #NMClient * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_reload(). * * Returns: %TRUE on success or %FALSE on failure. * * Since: 1.22 **/ gboolean nm_client_reload_finish(NMClient *client, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_reload), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /*****************************************************************************/ /** * nm_client_dbus_call: * @client: the #NMClient * @object_path: path of remote object * @interface_name: D-Bus interface to invoke method on * @method_name: the name of the method to invoke * @parameters: (nullable): a #GVariant tuple with parameters for the method * or %NULL if not passing parameters * @reply_type: (nullable): the expected type of the reply (which will be a * tuple), or %NULL * @timeout_msec: the timeout in milliseconds, -1 to use the default * timeout or %G_MAXINT for no timeout * @cancellable: (nullable): a #GCancellable or %NULL * @callback: (nullable): a #GAsyncReadyCallback to call when the request * is satisfied or %NULL if you don't care about the result of the * method invocation * @user_data: the data to pass to @callback * * Call g_dbus_connection_call() on the current name owner with the specified * arguments. Most importantly, this invokes g_dbus_connection_call() with the * client's #GMainContext, so that the response is always in order with other * events D-Bus events. Of course, the call uses #GTask and will invoke the * callback on the current g_main_context_get_thread_default(). * * This API is merely a convenient wrapper for g_dbus_connection_call(). You can * also use g_dbus_connection_call() directly, with the same effect. * * Since: 1.24 **/ void nm_client_dbus_call(NMClient * client, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, const GVariantType *reply_type, int timeout_msec, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); _nm_client_dbus_call(client, client, nm_client_dbus_call, cancellable, callback, user_data, object_path, interface_name, method_name, parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, timeout_msec == -1 ? NM_DBUS_DEFAULT_TIMEOUT_MSEC : timeout_msec, nm_dbus_connection_call_finish_variant_cb); } /** * nm_client_dbus_call_finish: * @client: the #NMClient instance * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_dbus_call(). * * Returns: (transfer full): the result #GVariant or %NULL on error. * * Since: 1.24 **/ GVariant * nm_client_dbus_call_finish(NMClient *client, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_dbus_call), FALSE); return g_task_propagate_pointer(G_TASK(result), error); } /*****************************************************************************/ /** * nm_client_dbus_set_property: * @client: the #NMClient * @object_path: path of remote object * @interface_name: D-Bus interface for the property to set. * @property_name: the name of the property to set * @value: a #GVariant with the value to set. * @timeout_msec: the timeout in milliseconds, -1 to use the default * timeout or %G_MAXINT for no timeout * @cancellable: (nullable): a #GCancellable or %NULL * @callback: (nullable): a #GAsyncReadyCallback to call when the request * is satisfied or %NULL if you don't care about the result of the * method invocation * @user_data: the data to pass to @callback * * Like nm_client_dbus_call() but calls "Set" on the standard "org.freedesktop.DBus.Properties" * D-Bus interface. * * Since: 1.24 **/ void nm_client_dbus_set_property(NMClient * client, const char * object_path, const char * interface_name, const char * property_name, GVariant * value, int timeout_msec, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(NM_IS_CLIENT(client)); g_return_if_fail(interface_name); g_return_if_fail(property_name); g_return_if_fail(value); _nm_client_dbus_call(client, client, nm_client_dbus_set_property, cancellable, callback, user_data, object_path, DBUS_INTERFACE_PROPERTIES, "Set", g_variant_new("(ssv)", interface_name, property_name, value), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, timeout_msec == -1 ? NM_DBUS_DEFAULT_TIMEOUT_MSEC : timeout_msec, nm_dbus_connection_call_finish_void_cb); } /** * nm_client_dbus_set_property_finish: * @client: the #NMClient instance * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * * Gets the result of a call to nm_client_dbus_set_property(). * * Returns: %TRUE on success or %FALSE on failure. * * Since: 1.24 **/ gboolean nm_client_dbus_set_property_finish(NMClient *client, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(client), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, client, nm_client_dbus_set_property), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /*****************************************************************************/ static void _init_fetch_all(NMClient *self) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); NML_NMCLIENT_LOG_D(self, "fetch all"); nm_assert(!priv->get_managed_objects_cancellable); priv->get_managed_objects_cancellable = g_cancellable_new(); priv->dbsid_nm_object_manager = nm_dbus_connection_signal_subscribe_object_manager(priv->dbus_connection, priv->name_owner, "/org/freedesktop", NULL, _dbus_managed_objects_changed_cb, self, NULL); priv->dbsid_dbus_properties_properties_changed = nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, priv->name_owner, NULL, NULL, _dbus_properties_changed_cb, self, NULL); priv->dbsid_nm_settings_connection_updated = g_dbus_connection_signal_subscribe(priv->dbus_connection, priv->name_owner, NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "Updated", NULL, NULL, G_DBUS_SIGNAL_FLAGS_NONE, _dbus_settings_updated_cb, self, NULL); priv->dbsid_nm_connection_active_state_changed = g_dbus_connection_signal_subscribe(priv->dbus_connection, priv->name_owner, NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "StateChanged", NULL, NULL, G_DBUS_SIGNAL_FLAGS_NONE, _dbus_nm_connection_active_state_changed_cb, self, NULL); priv->dbsid_nm_vpn_connection_state_changed = g_dbus_connection_signal_subscribe(priv->dbus_connection, priv->name_owner, NM_DBUS_INTERFACE_VPN_CONNECTION, "VpnStateChanged", NULL, NULL, G_DBUS_SIGNAL_FLAGS_NONE, _dbus_nm_vpn_connection_state_changed_cb, self, NULL); priv->dbsid_nm_check_permissions = g_dbus_connection_signal_subscribe(priv->dbus_connection, priv->name_owner, NM_DBUS_INTERFACE, "CheckPermissions", NULL, NULL, G_DBUS_SIGNAL_FLAGS_NONE, _dbus_nm_check_permissions_cb, self, NULL); g_dbus_connection_call(priv->dbus_connection, priv->name_owner, "/org/freedesktop", DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects", NULL, G_VARIANT_TYPE("(a{oa{sa{sv}}})"), G_DBUS_CALL_FLAGS_NO_AUTO_START, NM_DBUS_DEFAULT_TIMEOUT_MSEC, priv->get_managed_objects_cancellable, _dbus_get_managed_objects_cb, nm_utils_user_data_pack(self, g_object_ref(priv->context_busy_watcher))); _dbus_check_permissions_start(self); } static void _init_release_all(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); CList ** dbus_objects_lst_heads; NMLDBusObject * dbobj; int i; gboolean permissions_state_changed = FALSE; NML_NMCLIENT_LOG_D(self, "release all"); nm_clear_g_cancellable(&priv->permissions_cancellable); nm_clear_g_cancellable(&priv->get_managed_objects_cancellable); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->dbsid_nm_object_manager); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->dbsid_dbus_properties_properties_changed); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->dbsid_nm_settings_connection_updated); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->dbsid_nm_connection_active_state_changed); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->dbsid_nm_vpn_connection_state_changed); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->dbsid_nm_check_permissions); if (priv->permissions_state != NM_TERNARY_DEFAULT) { priv->permissions_state = NM_TERNARY_DEFAULT; permissions_state_changed = TRUE; } if (priv->permissions) { gs_free guint8 *old_permissions = g_steal_pointer(&priv->permissions); _emit_permissions_changed(self, old_permissions, NULL); } if (permissions_state_changed) _notify(self, PROP_PERMISSIONS_STATE); nm_assert(c_list_is_empty(&priv->obj_changed_lst_head)); dbus_objects_lst_heads = ((CList *[]){ &priv->dbus_objects_lst_head_on_dbus, &priv->dbus_objects_lst_head_with_nmobj_not_ready, &priv->dbus_objects_lst_head_with_nmobj_ready, NULL, }); for (i = 0; dbus_objects_lst_heads[i]; i++) { c_list_for_each_entry (dbobj, dbus_objects_lst_heads[i], dbus_objects_lst) { NMLDBusObjIfaceData *db_iface_data; nm_assert(c_list_is_empty(&dbobj->obj_changed_lst)); c_list_for_each_entry (db_iface_data, &dbobj->iface_lst_head, iface_lst) db_iface_data->iface_removed = TRUE; nml_dbus_object_obj_changed_link(self, dbobj, NML_DBUS_OBJ_CHANGED_TYPE_DBUS); } } _dbus_handle_changes(self, "release-all", FALSE); /* We require that when we remove all D-Bus interfaces, that all object will go * away. Note that a NMLDBusObject can be alive due to a NMLDBusObjWatcher, but * even those should be all cleaned up. */ nm_assert(c_list_is_empty(&priv->obj_changed_lst_head)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_watched_only)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_on_dbus)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_with_nmobj_not_ready)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_with_nmobj_ready)); nm_assert(nm_g_hash_table_size(priv->dbus_objects) == 0); } /*****************************************************************************/ static void name_owner_changed(NMClient *self, const char *name_owner) { NMClientPrivate * priv = NM_CLIENT_GET_PRIVATE(self); gboolean changed; gs_free char * old_name_owner_free = NULL; const char * old_name_owner; nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; name_owner = nm_str_not_empty(name_owner); changed = !nm_streq0(priv->name_owner, name_owner); if (!name_owner && priv->main_context != priv->dbus_context) { gs_unref_object GObject *old_context_busy_watcher = NULL; NML_NMCLIENT_LOG_D(self, "resync main context as we have no name owner"); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); /* Our instance was initialized synchronously. Usually we must henceforth * stick to a internal main context. But now we have no name-owner... * at this point, we anyway are going to do a full resync. Swap the main * contexts again. */ old_context_busy_watcher = g_steal_pointer(&priv->context_busy_watcher); priv->context_busy_watcher = g_object_ref( g_object_get_qdata(old_context_busy_watcher, nm_context_busy_watcher_quark())); g_main_context_ref(priv->main_context); g_main_context_unref(priv->dbus_context); priv->dbus_context = priv->main_context; dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); /* we need to sync again... */ _assert_main_context_is_current_thread_default(self, dbus_context); priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, NM_DBUS_SERVICE, name_owner_changed_cb, self, NULL); name_owner_get_call(self); } else dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); if (changed) { NML_NMCLIENT_LOG_D(self, "name owner changed: %s%s%s -> %s%s%s", NM_PRINT_FMT_QUOTE_STRING(priv->name_owner), NM_PRINT_FMT_QUOTE_STRING(name_owner)); old_name_owner_free = priv->name_owner; priv->name_owner = g_strdup(name_owner); old_name_owner = old_name_owner_free; } else old_name_owner = priv->name_owner; if (changed) _notify(self, PROP_DBUS_NAME_OWNER); if (changed && old_name_owner) _init_release_all(self); if (changed && priv->name_owner) _init_fetch_all(self); _set_nm_running(self); if (priv->init_data) { nm_auto_pop_gmaincontext GMainContext *main_context = NULL; if (priv->main_context != priv->dbus_context) main_context = nm_g_main_context_push_thread_default_if_necessary(priv->main_context); _init_start_check_complete(self); } } static void name_owner_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv; const char * new_owner; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) return; priv = NM_CLIENT_GET_PRIVATE(self); if (priv->name_owner_get_cancellable) return; g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); name_owner_changed(self, new_owner); } static void name_owner_get_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMClient * self; NMClientPrivate *priv; gs_unref_object GObject *context_busy_watcher = NULL; gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; const char * name_owner = NULL; nm_utils_user_data_unpack(user_data, &self, &context_busy_watcher); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; priv = NM_CLIENT_GET_PRIVATE(self); g_clear_object(&priv->name_owner_get_cancellable); if (ret) g_variant_get(ret, "(&s)", &name_owner); name_owner_changed(self, name_owner); } static void name_owner_get_call(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); nm_assert(!priv->name_owner_get_cancellable); priv->name_owner_get_cancellable = g_cancellable_new(); g_dbus_connection_call(priv->dbus_connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner", g_variant_new("(s)", NM_DBUS_SERVICE), G_VARIANT_TYPE("(s)"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, priv->name_owner_get_cancellable, name_owner_get_cb, nm_utils_user_data_pack(self, g_object_ref(priv->context_busy_watcher))); } /*****************************************************************************/ static inline gboolean _nml_cleanup_context_busy_watcher_on_idle_cb(gpointer user_data) { nm_auto_unref_gmaincontext GMainContext *context = NULL; gs_unref_object GObject *context_busy_watcher = NULL; nm_utils_user_data_unpack(user_data, &context, &context_busy_watcher); nm_assert(context); nm_assert(G_IS_OBJECT(context_busy_watcher)); return G_SOURCE_REMOVE; } void nml_cleanup_context_busy_watcher_on_idle(GObject *context_busy_watcher_take, GMainContext *context) { gs_unref_object GObject *context_busy_watcher = g_steal_pointer(&context_busy_watcher_take); GSource * cleanup_source; nm_assert(G_IS_OBJECT(context_busy_watcher)); nm_assert(context); /* Technically, we cancelled all pending actions (and these actions * (GTask) keep the context_busy_watcher object alive). Also, we passed * no destroy notify to g_dbus_connection_signal_subscribe(). * That means, there should be no other unaccounted GSource'es left. * * However, we really need to be sure that the context_busy_watcher's * lifetime matches the time that the context is busy. That is especially * important with synchronous initialization, where the context-busy-watcher * keeps the inner GMainContext integrated in the caller's. * We must not g_source_destroy() that integration too early. * * So to be really sure all this is given, always schedule one last * cleanup idle action with low priority. This should be the last * thing related to this instance that keeps the context busy. * * Note that we could also *not* take a reference on @context * and unref @context_busy_watcher via the GDestroyNotify. That would * allow for the context to be wrapped up early, and when the last user * gives up the reference to the context, the destroy notify could complete * without even invoke the idle handler. However, that destroy notify may * not be called in the right thread. So, we want to be sure that we unref * the context-busy-watcher in the right context. Hence, we always take an * additional reference and always cleanup in the idle handler. This means: * the user *MUST* always keep iterating the context after NMClient got destroyed. * But that is not a severe limitation, because the user anyway must be prepared * to do that. That is because in many cases it is necessary anyway (and the user * wouldn't know a priory when not). This way, it is just always necessary. */ cleanup_source = nm_g_idle_source_new(G_PRIORITY_LOW + 10, _nml_cleanup_context_busy_watcher_on_idle_cb, nm_utils_user_data_pack(g_main_context_ref(context), g_steal_pointer(&context_busy_watcher)), NULL); g_source_attach(cleanup_source, context); g_source_unref(cleanup_source); } /*****************************************************************************/ static void _init_start_complete(NMClient *self, GError *error_take) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NML_NMCLIENT_LOG_D( self, "%s init complete with %s%s%s", priv->init_data->is_sync ? "sync" : "async", NM_PRINT_FMT_QUOTED(error_take, "error: ", error_take->message, "", "success")); nml_init_data_return(g_steal_pointer(&priv->init_data), error_take); } static void _init_start_check_complete(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); _assert_main_context_is_current_thread_default(self, main_context); if (!priv->init_data) return; if (priv->get_managed_objects_cancellable) { /* still initializing. Wait. */ return; } #if NM_MORE_ASSERTS > 10 { NMLDBusObject *dbobj; c_list_for_each_entry (dbobj, &priv->dbus_objects_lst_head_with_nmobj_not_ready, dbus_objects_lst) { NML_NMCLIENT_LOG_T(self, "init-start waiting for %s", dbobj->dbus_path->str); break; } } #endif if (!c_list_is_empty(&priv->dbus_objects_lst_head_with_nmobj_not_ready)) return; _init_start_complete(self, NULL); } static void _init_start_cancelled_cb(GCancellable *cancellable, gpointer user_data) { NMClient *self = user_data; GError * error; nm_assert(NM_IS_CLIENT(self)); nm_assert(NM_CLIENT_GET_PRIVATE(self)->init_data); nm_assert(NM_CLIENT_GET_PRIVATE(self)->init_data->cancellable == cancellable); nm_utils_error_set_cancelled(&error, FALSE, NULL); _init_start_complete(self, error); } static gboolean _init_start_cancel_on_idle_cb(gpointer user_data) { NMClient *self = user_data; GError * error; nm_utils_error_set_cancelled(&error, FALSE, NULL); _init_start_complete(self, error); return G_SOURCE_CONTINUE; } static void _init_start_with_bus(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); if (priv->init_data->cancellable) { priv->init_data->cancelled_id = g_signal_connect(priv->init_data->cancellable, "cancelled", G_CALLBACK(_init_start_cancelled_cb), self); if (g_cancellable_is_cancelled(priv->init_data->cancellable)) { priv->init_data->cancel_on_idle_source = g_idle_source_new(); g_source_set_callback(priv->init_data->cancel_on_idle_source, _init_start_cancel_on_idle_cb, self, NULL); g_source_attach(priv->init_data->cancel_on_idle_source, priv->main_context); return; } } _assert_main_context_is_current_thread_default(self, dbus_context); priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, NM_DBUS_SERVICE, name_owner_changed_cb, self, NULL); name_owner_get_call(self); } static void _init_start_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMClient * self = user_data; NMClientPrivate *priv; GDBusConnection *dbus_connection; GError * error = NULL; nm_assert(NM_IS_CLIENT(self)); dbus_connection = g_bus_get_finish(result, &error); if (!dbus_connection) { _init_start_complete(self, error); return; } priv = NM_CLIENT_GET_PRIVATE(self); priv->dbus_connection = dbus_connection; _init_start_with_bus(self); _notify(self, PROP_DBUS_CONNECTION); } static void _init_start(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NML_NMCLIENT_LOG_D(self, "starting %s initialization...", priv->init_data->is_sync ? "sync" : "async"); if (!priv->dbus_connection) { g_bus_get(_nm_dbus_bus_type(), priv->init_data->cancellable, _init_start_bus_get_cb, self); return; } _init_start_with_bus(self); } /*****************************************************************************/ static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMClient * self = NM_CLIENT(object); NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(object); switch (prop_id) { case PROP_INSTANCE_FLAGS: g_value_set_uint(value, priv->instance_flags); break; case PROP_DBUS_CONNECTION: g_value_set_object(value, priv->dbus_connection); break; case PROP_DBUS_NAME_OWNER: g_value_set_string(value, nm_client_get_dbus_name_owner(self)); break; case PROP_NM_RUNNING: g_value_set_boolean(value, nm_client_get_nm_running(self)); break; /* Manager properties. */ case PROP_VERSION: g_value_set_string(value, nm_client_get_version(self)); break; case PROP_STATE: g_value_set_enum(value, nm_client_get_state(self)); break; case PROP_STARTUP: g_value_set_boolean(value, nm_client_get_startup(self)); break; case PROP_NETWORKING_ENABLED: g_value_set_boolean(value, nm_client_networking_get_enabled(self)); break; case PROP_WIRELESS_ENABLED: g_value_set_boolean(value, nm_client_wireless_get_enabled(self)); break; case PROP_WIRELESS_HARDWARE_ENABLED: g_value_set_boolean(value, nm_client_wireless_hardware_get_enabled(self)); break; case PROP_WWAN_ENABLED: g_value_set_boolean(value, nm_client_wwan_get_enabled(self)); break; case PROP_WWAN_HARDWARE_ENABLED: g_value_set_boolean(value, nm_client_wwan_hardware_get_enabled(self)); break; case PROP_WIMAX_ENABLED: g_value_set_boolean(value, FALSE); break; case PROP_WIMAX_HARDWARE_ENABLED: g_value_set_boolean(value, FALSE); break; case PROP_ACTIVE_CONNECTIONS: g_value_take_boxed(value, _nm_utils_copy_object_array(nm_client_get_active_connections(self))); break; case PROP_CONNECTIVITY: g_value_set_enum(value, nm_client_get_connectivity(self)); break; case PROP_CONNECTIVITY_CHECK_AVAILABLE: g_value_set_boolean(value, nm_client_connectivity_check_get_available(self)); break; case PROP_CONNECTIVITY_CHECK_ENABLED: g_value_set_boolean(value, nm_client_connectivity_check_get_enabled(self)); break; case PROP_CONNECTIVITY_CHECK_URI: g_value_set_string(value, nm_client_connectivity_check_get_uri(self)); break; case PROP_PRIMARY_CONNECTION: g_value_set_object(value, nm_client_get_primary_connection(self)); break; case PROP_ACTIVATING_CONNECTION: g_value_set_object(value, nm_client_get_activating_connection(self)); break; case PROP_DEVICES: g_value_take_boxed(value, _nm_utils_copy_object_array(nm_client_get_devices(self))); break; case PROP_METERED: g_value_set_uint(value, nm_client_get_metered(self)); break; case PROP_ALL_DEVICES: g_value_take_boxed(value, _nm_utils_copy_object_array(nm_client_get_all_devices(self))); break; case PROP_CHECKPOINTS: g_value_take_boxed(value, _nm_utils_copy_object_array(nm_client_get_checkpoints(self))); break; case PROP_CAPABILITIES: { const guint32 *arr; GArray * out; gsize len; arr = nm_client_get_capabilities(self, &len); if (arr) { out = g_array_new(TRUE, FALSE, sizeof(guint32)); g_array_append_vals(out, arr, len); } else out = NULL; g_value_take_boxed(value, out); } break; case PROP_PERMISSIONS_STATE: g_value_set_enum(value, priv->permissions_state); break; /* Settings properties. */ case PROP_CONNECTIONS: g_value_take_boxed(value, _nm_utils_copy_object_array(nm_client_get_connections(self))); break; case PROP_HOSTNAME: g_value_set_string(value, priv->settings.hostname); break; case PROP_CAN_MODIFY: g_value_set_boolean(value, priv->settings.can_modify); break; /* DNS properties */ case PROP_DNS_MODE: g_value_set_string(value, nm_client_get_dns_mode(self)); break; case PROP_DNS_RC_MANAGER: g_value_set_string(value, nm_client_get_dns_rc_manager(self)); break; case PROP_DNS_CONFIGURATION: g_value_take_boxed(value, _nm_utils_copy_array(nm_client_get_dns_configuration(self), (NMUtilsCopyFunc) nm_dns_entry_dup, (GDestroyNotify) nm_dns_entry_unref)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMClient * self = NM_CLIENT(object); NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); gboolean b; guint v_uint; switch (prop_id) { case PROP_INSTANCE_FLAGS: /* construct */ v_uint = g_value_get_uint(value); g_return_if_fail(!NM_FLAGS_ANY(v_uint, ~((guint) NM_CLIENT_INSTANCE_FLAGS_ALL))); v_uint &= ((guint) NM_CLIENT_INSTANCE_FLAGS_ALL); if (!priv->instance_flags_constructed) { priv->instance_flags_constructed = TRUE; priv->instance_flags = v_uint; nm_assert((guint) priv->instance_flags == v_uint); } else { NMClientInstanceFlags flags = v_uint; /* After object construction, we only allow to toggle certain flags and * ignore all other flags. */ if ((priv->instance_flags ^ flags) & NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS) { if (NM_FLAGS_HAS(flags, NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS)) priv->instance_flags |= NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS; else priv->instance_flags &= ~NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS; if (priv->dbsid_nm_check_permissions != 0) _dbus_check_permissions_start(self); } } break; case PROP_DBUS_CONNECTION: /* construct-only */ priv->dbus_connection = g_value_dup_object(value); break; case PROP_NETWORKING_ENABLED: b = g_value_get_boolean(value); if (priv->nm.networking_enabled != b) { nm_client_networking_set_enabled(self, b, NULL); /* Let the property value flip when we get the change signal from NM */ } break; case PROP_WIRELESS_ENABLED: b = g_value_get_boolean(value); if (priv->nm.wireless_enabled != b) { nm_client_wireless_set_enabled(self, b); /* Let the property value flip when we get the change signal from NM */ } break; case PROP_WWAN_ENABLED: b = g_value_get_boolean(value); if (priv->nm.wwan_enabled != b) { nm_client_wwan_set_enabled(self, b); /* Let the property value flip when we get the change signal from NM */ } break; case PROP_CONNECTIVITY_CHECK_ENABLED: b = g_value_get_boolean(value); if (priv->nm.connectivity_check_enabled != b) { nm_client_connectivity_check_set_enabled(self, b); /* Let the property value flip when we get the change signal from NM */ } break; case PROP_WIMAX_ENABLED: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ static gboolean init_sync(GInitable *initable, GCancellable *cancellable, GError **error) { gs_unref_object NMClient *self = NULL; NMClientPrivate * priv; GMainContext * dbus_context; GError * local_error = NULL; GMainLoop * main_loop; GObject * parent_context_busy_watcher; g_return_val_if_fail(NM_IS_CLIENT(initable), FALSE); self = g_object_ref(NM_CLIENT(initable)); /* keep instance alive. */ priv = NM_CLIENT_GET_PRIVATE(self); g_return_val_if_fail(!priv->dbus_context, FALSE); /* when using init_sync(), we use a separate internal GMainContext for * all D-Bus operations and use our regular async-init code. That means, * also in sync-init, we don't actually block waiting for our D-Bus requests, * instead, we only block (g_main_loop_run()) for the overall result. * * Doing this has a performance overhead. Also, we cannot ever fall back * to the regular main-context (not unless we lose the main-owner and * need to re-initialize). The reason is that we receive events on our * dbus_context, and this cannot be brought in sync -- short of full * reinitalizing. Therefor, using sync init not only is slower during * construction of the object, but NMClient will stick to the dual GMainContext * mode. * * Aside from this downside, the solution is good: * * - we don't duplicate the implementation of async-init. * - we don't iterate the main-context of the caller while waiting for * initialization to happen * - we still invoke all changes under the main_context of the caller. * - all D-Bus events strictly go through dbus_context and are in order. */ dbus_context = g_main_context_new(); priv->dbus_context = g_main_context_ref(dbus_context); /* We have an inner context. Note that if we loose the name owner, we have a chance * to resync and drop the inner context. That means, requests made against the inner * context have a different lifetime. Hence, we create a separate tracking * object. This "wraps" the outer context-busy-watcher and references it, so * that the work together. Grep for nm_context_busy_watcher_quark() to * see how this works. */ parent_context_busy_watcher = g_steal_pointer(&priv->context_busy_watcher); priv->context_busy_watcher = g_object_new(G_TYPE_OBJECT, NULL); g_object_set_qdata_full(priv->context_busy_watcher, nm_context_busy_watcher_quark(), parent_context_busy_watcher, g_object_unref); g_main_context_push_thread_default(dbus_context); main_loop = g_main_loop_new(dbus_context, FALSE); priv->init_data = nml_init_data_new_sync(cancellable, main_loop, &local_error); _init_start(self); g_main_loop_run(main_loop); g_main_loop_unref(main_loop); g_main_context_pop_thread_default(dbus_context); if (priv->main_context != priv->dbus_context) { nm_context_busy_watcher_integrate_source(priv->main_context, priv->dbus_context, priv->context_busy_watcher); } g_main_context_unref(dbus_context); if (local_error) { g_propagate_error(error, local_error); return FALSE; } return TRUE; } /*****************************************************************************/ static void init_async(GAsyncInitable * initable, int io_priority, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMClientPrivate * priv; NMClient * self; nm_auto_pop_gmaincontext GMainContext *context = NULL; GTask * task; g_return_if_fail(NM_IS_CLIENT(initable)); self = NM_CLIENT(initable); priv = NM_CLIENT_GET_PRIVATE(self); g_return_if_fail(!priv->dbus_context); priv->dbus_context = g_main_context_ref(priv->main_context); context = nm_g_main_context_push_thread_default_if_necessary(priv->main_context); task = nm_g_task_new(self, cancellable, init_async, callback, user_data); g_task_set_priority(task, io_priority); priv->init_data = nml_init_data_new_async(cancellable, g_steal_pointer(&task)); _init_start(self); } static gboolean init_finish(GAsyncInitable *initable, GAsyncResult *result, GError **error) { g_return_val_if_fail(NM_IS_CLIENT(initable), FALSE); g_return_val_if_fail(nm_g_task_is_valid(result, initable, init_async), FALSE); return g_task_propagate_boolean(G_TASK(result), error); } /*****************************************************************************/ static void nm_client_init(NMClient *self) { NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); priv->permissions_state = NM_TERNARY_DEFAULT; priv->context_busy_watcher = g_object_new(G_TYPE_OBJECT, NULL); c_list_init(&self->obj_base.queue_notify_lst); c_list_init(&priv->queue_notify_lst_head); c_list_init(&priv->notify_event_lst_head); priv->dbus_objects = g_hash_table_new(nm_pdirect_hash, nm_pdirect_equal); c_list_init(&priv->dbus_objects_lst_head_watched_only); c_list_init(&priv->dbus_objects_lst_head_on_dbus); c_list_init(&priv->dbus_objects_lst_head_with_nmobj_not_ready); c_list_init(&priv->dbus_objects_lst_head_with_nmobj_ready); c_list_init(&priv->obj_changed_lst_head); } /** * nm_client_new: * @cancellable: a #GCancellable, or %NULL * @error: location for a #GError, or %NULL * * Creates a new #NMClient. * * Note that this will do blocking D-Bus calls to initialize the * client. You can use nm_client_new_async() if you want to avoid * that. * * Returns: a new #NMClient or NULL on an error **/ NMClient * nm_client_new(GCancellable *cancellable, GError **error) { return g_initable_new(NM_TYPE_CLIENT, cancellable, error, NULL); } /** * nm_client_new_async: * @cancellable: a #GCancellable, or %NULL * @callback: callback to call when the client is created * @user_data: data for @callback * * Creates a new #NMClient and begins asynchronously initializing it. * @callback will be called when it is done; use * nm_client_new_finish() to get the result. Note that on an error, * the callback can be invoked with two first parameters as NULL. **/ void nm_client_new_async(GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_async_initable_new_async(NM_TYPE_CLIENT, G_PRIORITY_DEFAULT, cancellable, callback, user_data, NULL); } /** * nm_client_new_finish: * @result: a #GAsyncResult * @error: location for a #GError, or %NULL * * Gets the result of an nm_client_new_async() call. * * Returns: a new #NMClient, or %NULL on error **/ NMClient * nm_client_new_finish(GAsyncResult *result, GError **error) { gs_unref_object GObject *source_object = NULL; GObject * object; source_object = g_async_result_get_source_object(result); g_return_val_if_fail(source_object, NULL); object = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object), result, error); g_return_val_if_fail(!object || NM_IS_CLIENT(object), FALSE); return NM_CLIENT(object); } static void constructed(GObject *object) { NMClient * self = NM_CLIENT(object); NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); priv->main_context = g_main_context_ref_thread_default(); G_OBJECT_CLASS(nm_client_parent_class)->constructed(object); NML_NMCLIENT_LOG_D(self, "new NMClient instance"); } static void dispose(GObject *object) { NMClient * self = NM_CLIENT(object); NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); nm_assert(!priv->init_data); self->obj_base.is_disposing = TRUE; nm_clear_g_cancellable(&priv->name_owner_get_cancellable); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); nm_clear_g_free(&priv->name_owner); _init_release_all(self); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_watched_only)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_on_dbus)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_with_nmobj_not_ready)); nm_assert(c_list_is_empty(&priv->dbus_objects_lst_head_with_nmobj_ready)); nm_assert(c_list_is_empty(&priv->queue_notify_lst_head)); nm_assert(c_list_is_empty(&priv->notify_event_lst_head)); nm_assert(c_list_is_empty(&self->obj_base.queue_notify_lst)); nm_assert(nm_g_hash_table_size(priv->dbus_objects) == 0); nml_dbus_property_o_clear_many(priv->nm.property_o, G_N_ELEMENTS(priv->nm.property_o), NULL); nml_dbus_property_ao_clear_many(priv->nm.property_ao, G_N_ELEMENTS(priv->nm.property_ao), NULL); nm_clear_g_free(&priv->nm.connectivity_check_uri); nm_clear_g_free(&priv->nm.version); nml_dbus_property_ao_clear(&priv->settings.connections, NULL); nm_clear_g_free(&priv->settings.hostname); nm_clear_pointer(&priv->dns_manager.configuration, g_ptr_array_unref); nm_clear_g_free(&priv->dns_manager.mode); nm_clear_g_free(&priv->dns_manager.rc_manager); nm_clear_pointer(&priv->dbus_objects, g_hash_table_destroy); G_OBJECT_CLASS(nm_client_parent_class)->dispose(object); nm_clear_pointer(&priv->udev, udev_unref); if (priv->context_busy_watcher && priv->dbus_context) { nml_cleanup_context_busy_watcher_on_idle(g_steal_pointer(&priv->context_busy_watcher), priv->dbus_context); } nm_clear_pointer(&priv->dbus_context, g_main_context_unref); nm_clear_pointer(&priv->main_context, g_main_context_unref); nm_clear_g_free(&priv->permissions); g_clear_object(&priv->dbus_connection); g_clear_object(&priv->context_busy_watcher); nm_clear_g_free(&priv->name_owner); priv->nm.capabilities_len = 0; nm_clear_g_free(&priv->nm.capabilities_arr); NML_NMCLIENT_LOG_D(self, "disposed"); } const NMLDBusMetaIface _nml_dbus_meta_iface_nm_agentmanager = NML_DBUS_META_IFACE_INIT(NM_DBUS_INTERFACE_AGENT_MANAGER, NULL, NML_DBUS_META_INTERFACE_PRIO_NONE, ); const NMLDBusMetaIface _nml_dbus_meta_iface_nm = NML_DBUS_META_IFACE_INIT_PROP( NM_DBUS_INTERFACE, nm_client_get_type, NML_DBUS_META_INTERFACE_PRIO_NMCLIENT, NML_DBUS_META_IFACE_DBUS_PROPERTIES( NML_DBUS_META_PROPERTY_INIT_O_PROP( "ActivatingConnection", PROP_ACTIVATING_CONNECTION, NMClient, _priv.nm.property_o[PROPERTY_O_IDX_NM_ACTIVATING_CONNECTION], nm_active_connection_get_type), NML_DBUS_META_PROPERTY_INIT_AO_PROP( "ActiveConnections", PROP_ACTIVE_CONNECTIONS, NMClient, _priv.nm.property_ao[PROPERTY_AO_IDX_ACTIVE_CONNECTIONS], nm_active_connection_get_type, .notify_changed_ao = _property_ao_notify_changed_active_connections_cb), NML_DBUS_META_PROPERTY_INIT_AO_PROP("AllDevices", PROP_ALL_DEVICES, NMClient, _priv.nm.property_ao[PROPERTY_AO_IDX_ALL_DEVICES], nm_device_get_type, .notify_changed_ao = _property_ao_notify_changed_all_devices_cb), NML_DBUS_META_PROPERTY_INIT_FCN("Capabilities", PROP_CAPABILITIES, "au", _notify_update_prop_nm_capabilities, ), NML_DBUS_META_PROPERTY_INIT_AO_PROP("Checkpoints", PROP_CHECKPOINTS, NMClient, _priv.nm.property_ao[PROPERTY_AO_IDX_CHECKPOINTS], nm_checkpoint_get_type), NML_DBUS_META_PROPERTY_INIT_U("Connectivity", PROP_CONNECTIVITY, NMClient, _priv.nm.connectivity), NML_DBUS_META_PROPERTY_INIT_B("ConnectivityCheckAvailable", PROP_CONNECTIVITY_CHECK_AVAILABLE, NMClient, _priv.nm.connectivity_check_available), NML_DBUS_META_PROPERTY_INIT_B("ConnectivityCheckEnabled", PROP_CONNECTIVITY_CHECK_ENABLED, NMClient, _priv.nm.connectivity_check_enabled), NML_DBUS_META_PROPERTY_INIT_S("ConnectivityCheckUri", PROP_CONNECTIVITY_CHECK_URI, NMClient, _priv.nm.connectivity_check_uri), NML_DBUS_META_PROPERTY_INIT_AO_PROP("Devices", PROP_DEVICES, NMClient, _priv.nm.property_ao[PROPERTY_AO_IDX_DEVICES], nm_device_get_type, .notify_changed_ao = _property_ao_notify_changed_devices_cb), NML_DBUS_META_PROPERTY_INIT_IGNORE("GlobalDnsConfiguration", "a{sv}"), NML_DBUS_META_PROPERTY_INIT_U("Metered", PROP_METERED, NMClient, _priv.nm.metered), NML_DBUS_META_PROPERTY_INIT_B("NetworkingEnabled", PROP_NETWORKING_ENABLED, NMClient, _priv.nm.networking_enabled), NML_DBUS_META_PROPERTY_INIT_O_PROP("PrimaryConnection", PROP_PRIMARY_CONNECTION, NMClient, _priv.nm.property_o[PROPERTY_O_IDX_NM_PRIMAY_CONNECTION], nm_active_connection_get_type), NML_DBUS_META_PROPERTY_INIT_IGNORE("PrimaryConnectionType", "s"), NML_DBUS_META_PROPERTY_INIT_B("Startup", PROP_STARTUP, NMClient, _priv.nm.startup), NML_DBUS_META_PROPERTY_INIT_U("State", PROP_STATE, NMClient, _priv.nm.state), NML_DBUS_META_PROPERTY_INIT_S("Version", PROP_VERSION, NMClient, _priv.nm.version), NML_DBUS_META_PROPERTY_INIT_IGNORE("WimaxEnabled", "b"), NML_DBUS_META_PROPERTY_INIT_IGNORE("WimaxHardwareEnabled", "b"), NML_DBUS_META_PROPERTY_INIT_B("WirelessEnabled", PROP_WIRELESS_ENABLED, NMClient, _priv.nm.wireless_enabled), NML_DBUS_META_PROPERTY_INIT_B("WirelessHardwareEnabled", PROP_WIRELESS_HARDWARE_ENABLED, NMClient, _priv.nm.wireless_hardware_enabled), NML_DBUS_META_PROPERTY_INIT_B("WwanEnabled", PROP_WWAN_ENABLED, NMClient, _priv.nm.wwan_enabled), NML_DBUS_META_PROPERTY_INIT_B("WwanHardwareEnabled", PROP_WWAN_HARDWARE_ENABLED, NMClient, _priv.nm.wwan_hardware_enabled), ), ); const NMLDBusMetaIface _nml_dbus_meta_iface_nm_settings = NML_DBUS_META_IFACE_INIT_PROP( NM_DBUS_INTERFACE_SETTINGS, nm_client_get_type, NML_DBUS_META_INTERFACE_PRIO_NMCLIENT, NML_DBUS_META_IFACE_DBUS_PROPERTIES( NML_DBUS_META_PROPERTY_INIT_B("CanModify", PROP_CAN_MODIFY, NMClient, _priv.settings.can_modify), NML_DBUS_META_PROPERTY_INIT_AO_PROP( "Connections", PROP_CONNECTIONS, NMClient, _priv.settings.connections, nm_remote_connection_get_type, .notify_changed_ao = _property_ao_notify_changed_connections_cb, .check_nmobj_visible_fcn = (gboolean(*)(GObject *)) nm_remote_connection_get_visible), NML_DBUS_META_PROPERTY_INIT_S("Hostname", PROP_HOSTNAME, NMClient, _priv.settings.hostname), ), ); const NMLDBusMetaIface _nml_dbus_meta_iface_nm_dnsmanager = NML_DBUS_META_IFACE_INIT_PROP( NM_DBUS_INTERFACE_DNS_MANAGER, nm_client_get_type, NML_DBUS_META_INTERFACE_PRIO_NMCLIENT, NML_DBUS_META_IFACE_DBUS_PROPERTIES( NML_DBUS_META_PROPERTY_INIT_FCN("Configuration", PROP_DNS_CONFIGURATION, "aa{sv}", _notify_update_prop_dns_manager_configuration), NML_DBUS_META_PROPERTY_INIT_S("Mode", PROP_DNS_MODE, NMClient, _priv.dns_manager.mode), NML_DBUS_META_PROPERTY_INIT_S("RcManager", PROP_DNS_RC_MANAGER, NMClient, _priv.dns_manager.rc_manager), ), ); static void nm_client_class_init(NMClientClass *client_class) { GObjectClass *object_class = G_OBJECT_CLASS(client_class); _dbus_path_nm = nm_ref_string_new(NM_DBUS_PATH); _dbus_path_settings = nm_ref_string_new(NM_DBUS_PATH_SETTINGS); _dbus_path_dns_manager = nm_ref_string_new(NM_DBUS_PATH_DNS_MANAGER); object_class->get_property = get_property; object_class->set_property = set_property; object_class->constructed = constructed; object_class->dispose = dispose; /** * NMClient:dbus-connection: * * The #GDBusConnection to use. * * If this is not set during object construction, the D-Bus connection will * automatically be chosen during async/sync initalization via g_bus_get(). * * Since: 1.22 */ obj_properties[PROP_DBUS_CONNECTION] = g_param_spec_object( NM_CLIENT_DBUS_CONNECTION, "", "", G_TYPE_DBUS_CONNECTION, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); /** * NMClient:instance-flags: * * #NMClientInstanceFlags for the instance. These affect behavior of #NMClient. * This is a construct property and you may only set most flags only during * construction. * * The flag %NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS can be toggled any time, * even after constructing the instance. Note that you may want to watch NMClient:permissions-state * property to know whether permissions are ready. Note that permissions are only fetched * when NMClient has a D-Bus name owner. * * Since: 1.24 */ obj_properties[PROP_INSTANCE_FLAGS] = g_param_spec_uint( NM_CLIENT_INSTANCE_FLAGS, "", "", 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); /** * NMClient:dbus-name-owner: * * The name owner of the NetworkManager D-Bus service. * * Since: 1.22 **/ obj_properties[PROP_DBUS_NAME_OWNER] = g_param_spec_string(NM_CLIENT_DBUS_NAME_OWNER, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:version: * * The NetworkManager version. **/ obj_properties[PROP_VERSION] = g_param_spec_string(NM_CLIENT_VERSION, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:state: * * The current daemon state. **/ obj_properties[PROP_STATE] = g_param_spec_enum(NM_CLIENT_STATE, "", "", NM_TYPE_STATE, NM_STATE_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:startup: * * Whether the daemon is still starting up. **/ obj_properties[PROP_STARTUP] = g_param_spec_boolean(NM_CLIENT_STARTUP, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:nm-running: * * Whether the daemon is running. **/ obj_properties[PROP_NM_RUNNING] = g_param_spec_boolean(NM_CLIENT_NM_RUNNING, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:networking-enabled: * * Whether networking is enabled. * * The property setter is a synchronous D-Bus call. This is deprecated since 1.22. */ obj_properties[PROP_NETWORKING_ENABLED] = g_param_spec_boolean(NM_CLIENT_NETWORKING_ENABLED, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMClient:wireless-enabled: * * Whether wireless is enabled. * * The property setter is a synchronous D-Bus call. This is deprecated since 1.22. **/ obj_properties[PROP_WIRELESS_ENABLED] = g_param_spec_boolean(NM_CLIENT_WIRELESS_ENABLED, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMClient:wireless-hardware-enabled: * * Whether the wireless hardware is enabled. **/ obj_properties[PROP_WIRELESS_HARDWARE_ENABLED] = g_param_spec_boolean(NM_CLIENT_WIRELESS_HARDWARE_ENABLED, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:wwan-enabled: * * Whether WWAN functionality is enabled. * * The property setter is a synchronous D-Bus call. This is deprecated since 1.22. */ obj_properties[PROP_WWAN_ENABLED] = g_param_spec_boolean(NM_CLIENT_WWAN_ENABLED, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMClient:wwan-hardware-enabled: * * Whether the WWAN hardware is enabled. **/ obj_properties[PROP_WWAN_HARDWARE_ENABLED] = g_param_spec_boolean(NM_CLIENT_WWAN_HARDWARE_ENABLED, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:wimax-enabled: * * Whether WiMAX functionality is enabled. * * Deprecated: 1.22: WiMAX is no longer supported and this always returns FALSE. The setter has no effect. */ obj_properties[PROP_WIMAX_ENABLED] = g_param_spec_boolean(NM_CLIENT_WIMAX_ENABLED, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMClient:wimax-hardware-enabled: * * Whether the WiMAX hardware is enabled. * * Deprecated: 1.22: WiMAX is no longer supported and this always returns FALSE. **/ obj_properties[PROP_WIMAX_HARDWARE_ENABLED] = g_param_spec_boolean(NM_CLIENT_WIMAX_HARDWARE_ENABLED, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:active-connections: (type GPtrArray(NMActiveConnection)) * * The active connections. **/ obj_properties[PROP_ACTIVE_CONNECTIONS] = g_param_spec_boxed(NM_CLIENT_ACTIVE_CONNECTIONS, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:connectivity: * * The network connectivity state. */ obj_properties[PROP_CONNECTIVITY] = g_param_spec_enum(NM_CLIENT_CONNECTIVITY, "", "", NM_TYPE_CONNECTIVITY_STATE, NM_CONNECTIVITY_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient::connectivity-check-available * * Whether a connectivity checking service has been configured. * * Since: 1.10 */ obj_properties[PROP_CONNECTIVITY_CHECK_AVAILABLE] = g_param_spec_boolean(NM_CLIENT_CONNECTIVITY_CHECK_AVAILABLE, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient::connectivity-check-enabled * * Whether a connectivity checking service has been enabled. * * Since: 1.10 * * The property setter is a synchronous D-Bus call. This is deprecated since 1.22. */ obj_properties[PROP_CONNECTIVITY_CHECK_ENABLED] = g_param_spec_boolean(NM_CLIENT_CONNECTIVITY_CHECK_ENABLED, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMClient:connectivity-check-uri: * * The used URI for connectivity checking. * * Since: 1.22 **/ obj_properties[PROP_CONNECTIVITY_CHECK_URI] = g_param_spec_string(NM_CLIENT_CONNECTIVITY_CHECK_URI, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:primary-connection: * * The #NMActiveConnection of the device with the default route; * see nm_client_get_primary_connection() for more details. **/ obj_properties[PROP_PRIMARY_CONNECTION] = g_param_spec_object(NM_CLIENT_PRIMARY_CONNECTION, "", "", NM_TYPE_ACTIVE_CONNECTION, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:activating-connection: * * The #NMActiveConnection of the activating connection that is * likely to become the new #NMClient:primary-connection. **/ obj_properties[PROP_ACTIVATING_CONNECTION] = g_param_spec_object(NM_CLIENT_ACTIVATING_CONNECTION, "", "", NM_TYPE_ACTIVE_CONNECTION, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:devices: (type GPtrArray(NMDevice)) * * List of real network devices. Does not include placeholder devices. **/ obj_properties[PROP_DEVICES] = g_param_spec_boxed(NM_CLIENT_DEVICES, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:all-devices: (type GPtrArray(NMDevice)) * * List of both real devices and device placeholders. * Since: 1.2 **/ obj_properties[PROP_ALL_DEVICES] = g_param_spec_boxed(NM_CLIENT_ALL_DEVICES, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:connections: (type GPtrArray(NMRemoteConnection)) * * The list of configured connections that are available to the user. (Note * that this differs from the underlying D-Bus property, which may also * contain the object paths of connections that the user does not have * permission to read the details of.) */ obj_properties[PROP_CONNECTIONS] = g_param_spec_boxed(NM_CLIENT_CONNECTIONS, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:hostname: * * The machine hostname stored in persistent configuration. This can be * modified by calling nm_client_save_hostname(). */ obj_properties[PROP_HOSTNAME] = g_param_spec_string(NM_CLIENT_HOSTNAME, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:can-modify: * * If %TRUE, adding and modifying connections is supported. */ obj_properties[PROP_CAN_MODIFY] = g_param_spec_boolean(NM_CLIENT_CAN_MODIFY, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:metered: * * Whether the connectivity is metered. * * Since: 1.2 **/ obj_properties[PROP_METERED] = g_param_spec_uint(NM_CLIENT_METERED, "", "", 0, G_MAXUINT32, NM_METERED_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:dns-mode: * * The current DNS processing mode. * * Since: 1.6 **/ obj_properties[PROP_DNS_MODE] = g_param_spec_string(NM_CLIENT_DNS_MODE, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:dns-rc-manager: * * The current resolv.conf management mode. * * Since: 1.6 **/ obj_properties[PROP_DNS_RC_MANAGER] = g_param_spec_string(NM_CLIENT_DNS_RC_MANAGER, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:dns-configuration: (type GPtrArray(NMDnsEntry)) * * The current DNS configuration, represented as an array * of #NMDnsEntry objects. * * Since: 1.6 **/ obj_properties[PROP_DNS_CONFIGURATION] = g_param_spec_boxed(NM_CLIENT_DNS_CONFIGURATION, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:checkpoints: (type GPtrArray(NMCheckpoint)) * * The list of active checkpoints. * * Since: 1.12 */ obj_properties[PROP_CHECKPOINTS] = g_param_spec_boxed(NM_CLIENT_CHECKPOINTS, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:capabilities: (type GArray(guint32)) * * The list of capabilities numbers as guint32 or %NULL if * there are no capabilities. The numeric value correspond * to %NMCapability enum. * * Since: 1.24 */ obj_properties[PROP_CAPABILITIES] = g_param_spec_boxed(NM_CLIENT_CAPABILITIES, "", "", G_TYPE_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMClient:permissions-state: * * The state of the cached permissions. The value %NM_TERNARY_DEFAULT * means that no permissions are yet received (or not yet requested). * %NM_TERNARY_TRUE means that permissions are received, cached and up * to date. %NM_TERNARY_FALSE means that permissions were received and are * cached, but in the meantime a "CheckPermissions" signal was received * that invalidated the cached permissions. * Note that NMClient will always emit a notify::permissions-state signal * when a "CheckPermissions" signal got received or after new permissions * got received (that is regardless whether the value of the permission state * actually changed). With this you can watch the permissions-state property * to know whether the permissions are ready. Note that while NMClient has * no D-Bus name owner, no permissions are fetched (and this property won't * change). * * Since: 1.24 */ obj_properties[PROP_PERMISSIONS_STATE] = g_param_spec_enum(NM_CLIENT_PERMISSIONS_STATE, "", "", NM_TYPE_TERNARY, NM_TERNARY_DEFAULT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); _nml_dbus_meta_class_init_with_properties(object_class, &_nml_dbus_meta_iface_nm, &_nml_dbus_meta_iface_nm_settings, &_nml_dbus_meta_iface_nm_dnsmanager); /** * NMClient::device-added: * @client: the client that received the signal * @device: (type NMDevice): the new device * * Notifies that a #NMDevice is added. This signal is not emitted for * placeholder devices. **/ signals[DEVICE_ADDED] = g_signal_new(NM_CLIENT_DEVICE_ADDED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * NMClient::device-removed: * @client: the client that received the signal * @device: (type NMDevice): the removed device * * Notifies that a #NMDevice is removed. This signal is not emitted for * placeholder devices. **/ signals[DEVICE_REMOVED] = g_signal_new(NM_CLIENT_DEVICE_REMOVED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * NMClient::any-device-added: * @client: the client that received the signal * @device: (type NMDevice): the new device * * Notifies that a #NMDevice is added. This signal is emitted for both * regular devices and placeholder devices. **/ signals[ANY_DEVICE_ADDED] = g_signal_new(NM_CLIENT_ANY_DEVICE_ADDED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * NMClient::any-device-removed: * @client: the client that received the signal * @device: (type NMDevice): the removed device * * Notifies that a #NMDevice is removed. This signal is emitted for both * regular devices and placeholder devices. **/ signals[ANY_DEVICE_REMOVED] = g_signal_new(NM_CLIENT_ANY_DEVICE_REMOVED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * NMClient::permission-changed: * @client: the client that received the signal * @permission: a permission from #NMClientPermission * @result: the permission's result, one of #NMClientPermissionResult * * Notifies that a permission has changed **/ signals[PERMISSION_CHANGED] = g_signal_new(NM_CLIENT_PERMISSION_CHANGED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); /** * NMClient::connection-added: * @client: the settings object that received the signal * @connection: the new connection * * Notifies that a #NMConnection has been added. **/ signals[CONNECTION_ADDED] = g_signal_new(NM_CLIENT_CONNECTION_ADDED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_REMOTE_CONNECTION); /** * NMClient::connection-removed: * @client: the settings object that received the signal * @connection: the removed connection * * Notifies that a #NMConnection has been removed. **/ signals[CONNECTION_REMOVED] = g_signal_new(NM_CLIENT_CONNECTION_REMOVED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_REMOTE_CONNECTION); /** * NMClient::active-connection-added: * @client: the settings object that received the signal * @active_connection: the new active connection * * Notifies that a #NMActiveConnection has been added. **/ signals[ACTIVE_CONNECTION_ADDED] = g_signal_new(NM_CLIENT_ACTIVE_CONNECTION_ADDED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION); /** * NMClient::active-connection-removed: * @client: the settings object that received the signal * @active_connection: the removed active connection * * Notifies that a #NMActiveConnection has been removed. **/ signals[ACTIVE_CONNECTION_REMOVED] = g_signal_new(NM_CLIENT_ACTIVE_CONNECTION_REMOVED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION); } static void nm_client_initable_iface_init(GInitableIface *iface) { iface->init = init_sync; } static void nm_client_async_initable_iface_init(GAsyncInitableIface *iface) { iface->init_async = init_async; iface->init_finish = init_finish; } /***************************************************************************** * Backported symbols. Usually, new API is only added in new major versions * of NetworkManager (that is, on "master" branch). Sometimes however, we might * have to backport some API to an older stable branch. In that case, we backport * the symbols with a different version corresponding to the minor API. * * To allow upgrading from such a extended minor-release, "master" contains these * backported symbols too. * * For example, 1.2.0 added nm_setting_connection_autoconnect_slaves_get_type. * This was backported for 1.0.4 as nm_setting_connection_autoconnect_slaves_get_type@libnm_1_0_4 * To allow an application that was linked against 1.0.4 to seamlessly upgrade to * a newer major version, the same symbols is also exposed on "master". Note, that * a user can only seamlessly upgrade to a newer major version, that is released * *after* 1.0.4 is out. In this example, 1.2.0 was released after 1.4.0, and thus * a 1.0.4 user can upgrade to 1.2.0 ABI. *****************************************************************************/ NM_BACKPORT_SYMBOL(libnm_1_0_4, NMSettingConnectionAutoconnectSlaves, nm_setting_connection_get_autoconnect_slaves, (NMSettingConnection * setting), (setting)); NM_BACKPORT_SYMBOL(libnm_1_0_4, GType, nm_setting_connection_autoconnect_slaves_get_type, (void), ()); NM_BACKPORT_SYMBOL(libnm_1_0_6, NMMetered, nm_setting_connection_get_metered, (NMSettingConnection * setting), (setting)); NM_BACKPORT_SYMBOL(libnm_1_0_6, GType, nm_metered_get_type, (void), ()); NM_BACKPORT_SYMBOL(libnm_1_0_6, NMSettingWiredWakeOnLan, nm_setting_wired_get_wake_on_lan, (NMSettingWired * setting), (setting)); NM_BACKPORT_SYMBOL(libnm_1_0_6, const char *, nm_setting_wired_get_wake_on_lan_password, (NMSettingWired * setting), (setting)); NM_BACKPORT_SYMBOL(libnm_1_0_6, GType, nm_setting_wired_wake_on_lan_get_type, (void), ()); NM_BACKPORT_SYMBOL(libnm_1_0_6, const guint *, nm_utils_wifi_2ghz_freqs, (void), ()); NM_BACKPORT_SYMBOL(libnm_1_0_6, const guint *, nm_utils_wifi_5ghz_freqs, (void), ()); NM_BACKPORT_SYMBOL(libnm_1_0_6, char *, nm_utils_enum_to_str, (GType type, int value), (type, value)); NM_BACKPORT_SYMBOL(libnm_1_0_6, gboolean, nm_utils_enum_from_str, (GType type, const char *str, int *out_value, char **err_token), (type, str, out_value, err_token)); NM_BACKPORT_SYMBOL(libnm_1_2_4, int, nm_setting_ip_config_get_dns_priority, (NMSettingIPConfig * setting), (setting)); NM_BACKPORT_SYMBOL(libnm_1_10_14, NMSettingConnectionMdns, nm_setting_connection_get_mdns, (NMSettingConnection * setting), (setting)); NM_BACKPORT_SYMBOL(libnm_1_10_14, GType, nm_setting_connection_mdns_get_type, (void), ());