diff options
Diffstat (limited to 'src/libnm-client-impl/nm-client.c')
| -rw-r--r-- | src/libnm-client-impl/nm-client.c | 443 |
1 files changed, 421 insertions, 22 deletions
diff --git a/src/libnm-client-impl/nm-client.c b/src/libnm-client-impl/nm-client.c index 871c5466..3ad4b9d6 100644 --- a/src/libnm-client-impl/nm-client.c +++ b/src/libnm-client-impl/nm-client.c @@ -31,6 +31,7 @@ #include "nm-device-generic.h" #include "nm-device-infiniband.h" #include "nm-device-ip-tunnel.h" +#include "nm-device-loopback.h" #include "nm-device-macsec.h" #include "nm-device-macvlan.h" #include "nm-device-modem.h" @@ -216,6 +217,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMClient, PROP_DNS_RC_MANAGER, PROP_DNS_CONFIGURATION, PROP_CHECKPOINTS, + PROP_VERSION_INFO, PROP_CAPABILITIES, PROP_PERMISSIONS_STATE, ); @@ -287,7 +289,7 @@ typedef struct { guint dbsid_nm_vpn_connection_state_changed; guint dbsid_nm_check_permissions; - NMClientInstanceFlags instance_flags : 3; + NMClientInstanceFlags instance_flags : 5; NMTernary permissions_state : 3; @@ -304,7 +306,9 @@ typedef struct { char *connectivity_check_uri; char *version; guint32 *capabilities_arr; + guint32 *version_info_arr; gsize capabilities_len; + gsize version_info_len; guint32 connectivity; guint32 state; guint32 metered; @@ -1253,9 +1257,9 @@ nml_dbus_object_get_property_location(NMLDBusObject *dbobj, { char *target_c; - target_c = (char *) dbobj->nmobj; + target_c = ((gpointer) dbobj->nmobj); if (meta_iface->base_struct_offset > 0) - target_c = *((gpointer *) (&target_c[meta_iface->base_struct_offset])); + target_c = *NM_CAST_ALIGN(gpointer, &target_c[meta_iface->base_struct_offset]); return &target_c[meta_property->prop_struct_offset]; } @@ -1785,7 +1789,7 @@ nml_dbus_property_o_notify_watch_cb(NMClient *self, gpointer obj_watcher) } } -static NMLDBusNotifyUpdatePropFlags +NMLDBusNotifyUpdatePropFlags nml_dbus_property_o_notify(NMClient *self, NMLDBusPropertyO *pr_o, NMLDBusObject *dbobj, @@ -2630,7 +2634,16 @@ _obj_handle_dbus_iface_changes(NMClient *self, 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); + const GVariantType *dbus_type = + db_iface_data->dbus_iface.meta->dbus_properties[i_prop].dbus_type; + + /* Unset properties that can potentially contain objects, to release them, + * but keep the rest around, because it might still make sense to know what + * they were (e.g. when a device has been removed we'd like know what interface + * name it had, or keep the state to avoid spurious state change into UNKNOWN). */ + if (g_variant_type_is_array(dbus_type) + || g_variant_type_equal(dbus_type, G_VARIANT_TYPE_OBJECT_PATH)) + _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, @@ -6290,6 +6303,57 @@ nm_client_get_capabilities(NMClient *client, gsize *length) return priv->nm.capabilities_arr; } +/** + * nm_client_get_version_info: + * @client: the #NMClient instance + * @length: (out): the number of returned capabilities. + * + * If available, the first element in the array is NM_VERSION which + * encodes the daemon version as "(major << 16 | minor << 8 | micro)". + * The following elements are a bitfield of %NMVersionInfoCapabilities + * that indicate that the daemon supports a certain capability. + * + * Returns: (transfer none) (array length=length): the + * list of capabilities reported by the server or %NULL + * if the capabilities are unknown. + * + * Since: 1.42 + */ +const guint32 * +nm_client_get_version_info(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); + + *length = priv->nm.version_info_len; + return priv->nm.version_info_arr; +} + +static NMLDBusNotifyUpdatePropFlags +_notify_update_prop_nm_au(guint32 **p_arr, gsize *p_len, GVariant *value) +{ + nm_clear_g_free(p_arr); + *p_len = 0; + + if (value) { + const guint32 *arr; + gsize len; + + arr = g_variant_get_fixed_array(value, &len, sizeof(guint32)); + *p_len = len; + *p_arr = g_new(guint32, len + 1); + if (len > 0) + memcpy(*p_arr, arr, len * sizeof(guint32)); + (*p_arr)[len] = 0; + } + + return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY; +} + static NMLDBusNotifyUpdatePropFlags _notify_update_prop_nm_capabilities(NMClient *self, NMLDBusObject *dbobj, @@ -6301,22 +6365,21 @@ _notify_update_prop_nm_capabilities(NMClient *self, nm_assert(G_OBJECT(self) == dbobj->nmobj); - nm_clear_g_free(&priv->nm.capabilities_arr); - priv->nm.capabilities_len = 0; + return _notify_update_prop_nm_au(&priv->nm.capabilities_arr, &priv->nm.capabilities_len, value); +} - if (value) { - const guint32 *arr; - gsize len; +static NMLDBusNotifyUpdatePropFlags +_notify_update_prop_nm_version_info(NMClient *self, + NMLDBusObject *dbobj, + const NMLDBusMetaIface *meta_iface, + guint dbus_property_idx, + GVariant *value) +{ + NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); - 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; - } + nm_assert(G_OBJECT(self) == dbobj->nmobj); - return NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY; + return _notify_update_prop_nm_au(&priv->nm.version_info_arr, &priv->nm.version_info_len, value); } /*****************************************************************************/ @@ -7279,7 +7342,8 @@ nml_cleanup_context_busy_watcher_on_idle(GObject *context_busy_watcher_take, GMa static void _init_start_complete(NMClient *self, GError *error_take) { - NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); + gs_unref_object NMClient *self_keep_alive = g_object_ref(self); + NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self); NML_NMCLIENT_LOG_D( self, @@ -7287,6 +7351,11 @@ _init_start_complete(NMClient *self, GError *error_take) priv->init_data->is_sync ? "sync" : "async", NM_PRINT_FMT_QUOTED(error_take, "error: ", error_take->message, "", "success")); + priv->instance_flags |= (error_take ? NM_CLIENT_INSTANCE_FLAGS_INITIALIZED_BAD + : NM_CLIENT_INSTANCE_FLAGS_INITIALIZED_GOOD); + + _notify(self, PROP_INSTANCE_FLAGS); + nml_init_data_return(g_steal_pointer(&priv->init_data), error_take); } @@ -7525,13 +7594,15 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) case PROP_CHECKPOINTS: g_value_take_boxed(value, _nm_utils_copy_object_array(nm_client_get_checkpoints(self))); break; + case PROP_VERSION_INFO: case PROP_CAPABILITIES: { const guint32 *arr; GArray *out; gsize len; - arr = nm_client_get_capabilities(self, &len); + arr = (prop_id == PROP_VERSION_INFO) ? nm_client_get_version_info(self, &len) + : nm_client_get_capabilities(self, &len); if (arr) { out = g_array_new(TRUE, FALSE, sizeof(guint32)); g_array_append_vals(out, arr, len); @@ -7587,8 +7658,18 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps /* 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); + + /* Silently ignore "initialized-{good,bad}" flags. They are only set internally + * and cannot be change by the user. However, accept the caller to set them, + * so that + * nmc.props.instance_flags = nmc.props.instance_flags | NM.ClientInstanceFlags.NO_AUTO_FETCH_PERMISSIONS + * works. */ + v_uint &= ~((guint) (NM_CLIENT_INSTANCE_FLAGS_INITIALIZED_GOOD + | NM_CLIENT_INSTANCE_FLAGS_INITIALIZED_BAD)); + + g_return_if_fail(!NM_FLAGS_ANY(v_uint, ~((guint) NM_CLIENT_INSTANCE_FLAGS_ALL_WRITABLE))); + + v_uint &= ((guint) NM_CLIENT_INSTANCE_FLAGS_ALL_WRITABLE); if (!priv->instance_flags_constructed) { priv->instance_flags_constructed = TRUE; @@ -8078,6 +8159,10 @@ const NMLDBusMetaIface _nml_dbus_meta_iface_nm = NML_DBUS_META_IFACE_INIT_PROP( 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_FCN("VersionInfo", + PROP_VERSION_INFO, + "au", + _notify_update_prop_nm_version_info, ), NML_DBUS_META_PROPERTY_INIT_IGNORE("WimaxEnabled", "b"), NML_DBUS_META_PROPERTY_INIT_IGNORE("WimaxHardwareEnabled", "b"), NML_DBUS_META_PROPERTY_INIT_B("WirelessEnabled", @@ -8177,6 +8262,9 @@ nm_client_class_init(NMClientClass *client_class) * property to know whether permissions are ready. Note that permissions are only fetched * when NMClient has a D-Bus name owner. * + * The flags %NM_CLIENT_INSTANCE_FLAGS_INITIALIZED_GOOD and %NM_CLIENT_INSTANCE_FLAGS_INITIALIZED_BAD + * cannot be set, however they will be returned by the getter after initialization completes. + * * Since: 1.24 */ obj_properties[PROP_INSTANCE_FLAGS] = g_param_spec_uint( @@ -8214,6 +8302,24 @@ nm_client_class_init(NMClientClass *client_class) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** + * NMClient:version-info: (type GArray(guint32)) + * + * Expose version info and capabilities of NetworkManager. If non-empty, + * the first element is NM_VERSION, which encodes the version of the + * daemon as "(major << 16 | minor << 8 | micro)". The following elements + * is a bitfields of %NMVersionInfoCapabilities. If a bit is set, then + * the running NetworkManager has the respective capability. + * + * Since: 1.42 + */ + obj_properties[PROP_VERSION_INFO] = + g_param_spec_boxed(NM_CLIENT_VERSION_INFO, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** * NMClient:state: * * The current daemon state. @@ -8815,6 +8921,299 @@ nm_client_async_initable_iface_init(GAsyncInitableIface *iface) iface->init_finish = init_finish; } +/*****************************************************************************/ + +typedef struct { + GCancellable *cancellable; + GSource *integration_source; + GTask *task; + GSource *idle_source; + + /* A weakref to nm_client_get_context_busy_watcher() */ + GWeakRef weak_ref; + + gulong cancellable_id; + + guint64 log_ptr; + + int result; +} WaitShutdownData; + +G_LOCK_DEFINE_STATIC(wait_shutdown_mutex); + +static NM_CACHED_QUARK_FCN("nm.client.wait-shutdown", _wait_shutdown_get_quark); + +static void +_wait_shutdown_data_free(gpointer user_data) +{ + WaitShutdownData *data = user_data; + + nm_g_slice_free(data); +} + +static gboolean +_wait_shutdown_idle_cb(gpointer user_data) +{ + WaitShutdownData *data = user_data; + gs_unref_object GTask *task = NULL; + int result; + + nm_clear_g_source_inst(&data->idle_source); + + task = g_steal_pointer(&data->task); + + result = g_atomic_int_get(&data->result); + nm_assert(NM_IN_SET(result, 0, 1)); + + NML_DBUS_LOG(_NML_NMCLIENT_LOG_LEVEL_COERCE(NML_DBUS_LOG_LEVEL_TRACE), + "nmclient[" NM_HASH_OBFUSCATE_PTR_FMT + "]: wait-shutdown (" NM_HASH_OBFUSCATE_PTR_FMT ")" + "%s", + data->log_ptr, + NM_HASH_OBFUSCATE_PTR(data), + !result ? " cancelled" : " completed"); + + if (!result) { + GError *error = NULL; + + nm_utils_error_set_cancelled(&error, FALSE, NULL); + g_task_return_error(task, error); + } else + g_task_return_boolean(task, TRUE); + + return G_SOURCE_CONTINUE; +} + +static void +_wait_shutdown_data_clear(WaitShutdownData *data, gboolean result) +{ + gs_unref_object GObject *context_busy_watcher = NULL; + + if (!g_atomic_int_compare_and_exchange(&data->result, -1, !!result)) { + /* There was a race and the result is already provided. Nothing to + * do, except, if "result" indicates cancellation (FALSE), set data->result + * to FALSE to. Aside that, the completion is already in progress. */ + if (!result) + g_atomic_int_compare_and_exchange(&data->result, TRUE, FALSE); + return; + } + + nm_clear_g_signal_handler(data->cancellable, &data->cancellable_id); + nm_clear_g_source_inst(&data->integration_source); + g_clear_object(&data->cancellable); + + if (!result) { + /* This was a cancellation. We likely still have the qdata tracked. + * We need to remove it. */ + + context_busy_watcher = g_weak_ref_get(&data->weak_ref); + if (context_busy_watcher) { + GPtrArray *qdata_arr; + + G_LOCK(wait_shutdown_mutex); + qdata_arr = g_object_get_qdata(context_busy_watcher, _wait_shutdown_get_quark()); + if (qdata_arr && g_ptr_array_remove_fast(qdata_arr, data)) { + /* data->task had an additional reference, we return it now. */ + g_object_unref(data->task); + } + G_UNLOCK(wait_shutdown_mutex); + } + } + + g_weak_ref_clear(&data->weak_ref); + + /* We don't complete right away, instead always schedule an idle action + * on the caller's context. */ + data->idle_source = nm_g_source_attach( + nm_g_idle_source_new(G_PRIORITY_DEFAULT_IDLE, _wait_shutdown_idle_cb, data, NULL), + g_task_get_context(data->task)); +} + +static void +_wait_shutdown_qdata_cb(gpointer user_data) +{ + gs_unref_ptrarray GPtrArray *qdata_arr = user_data; + + while (qdata_arr->len > 0) { + WaitShutdownData *data; + + data = g_ptr_array_remove_index_fast(qdata_arr, qdata_arr->len - 1); + _wait_shutdown_data_clear(data, TRUE); + + /* data->task had an additional reference, we return it now. */ + g_object_unref(data->task); + } +} + +static void +_wait_shutdown_cancelled_cb(GCancellable *cancellable, gpointer user_data) +{ + _wait_shutdown_data_clear(g_task_get_task_data(user_data), FALSE); +} + +/** + * nm_client_wait_shutdown: + * @client: the #NMClient to shutdown. + * @integrate_maincontext: whether to hook the client's maincontext + * in the current thread default. Otherwise, you must ensure + * that the client's maincontext gets iterated so that it can complete. + * By integrating the maincontext in the current thread default, you + * may instead only iterate the latter. + * @cancellable: (allow-none): the #GCancellable to abort the shutdown. + * @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 + * + * The way to stop #NMClient is by unrefing it. That will cancel all + * internally pending async operations. However, as async operations in + * NMClient use GTask, hence they cannot complete right away. Instead, + * their (internal) result callback still needs to be dispatched by iterating + * the client's main context. + * + * You thus cannot stop iterating the client's main context until + * everything is wrapped up. nm_client_get_context_busy_watcher() + * helps to watch how long that will be. + * + * This function automates that waiting. Like all glib async operations + * this honors the current g_main_context_get_thread_default(). + * + * In any case, to complete the shutdown, nm_client_get_main_context() + * must be iterated. If the current g_main_context_get_thread_default() is + * the same as nm_client_get_main_context(), then @integrate_maincontext + * is ignored. In that case, the caller is required to iterate the context + * for shutdown to complete. Otherwise, if g_main_context_get_thread_default() + * differs from nm_client_get_main_context() and @integrate_maincontext + * is %FALSE, the caller must make sure that both contexts are iterated + * until completion. Otherwise, if @integrate_maincontext is %TRUE, then + * nm_client_get_main_context() will be integrated in g_main_context_get_thread_default(). + * This means, the caller gives nm_client_get_main_context() up until the waiting + * completes, the function will acquire the context and hook it into + * g_main_context_get_thread_default(). + * It is a bug to request @integrate_maincontext while having nm_client_get_main_context() + * acquired or iterated otherwise because a context can only be acquired once + * at a time. + * + * Shutdown can only complete after all references to @client were released. + * + * It is possible to call this function multiple times for the same client. + * But note that with @integrate_maincontext the client's context is acquired, + * which can only be done once at a time. + * + * It is permissible to start waiting before the objects is fully initialized. + * + * The function really allows two separate things. To get a notification (callback) when + * shutdown is complete, and to integrate the client's context in another context. + * The latter case is useful if the client has a separate context and you hand it + * over to another GMainContext to wrap up. + * + * The main use is to have a NMClient and a separate GMainContext on a worker + * thread. When being done, you can hand over the cleanup of the context + * to g_main_context_default(), assuming that the main thread iterates + * the default context. In that case, you don't need to care about passing + * a callback to know when shutdown completed. + * + * Since: 1.42 + */ +void +nm_client_wait_shutdown(NMClient *client, + gboolean integrate_maincontext, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMClientPrivate *priv; + WaitShutdownData *data; + gs_unref_object GTask *task = NULL; + GQuark quark = _wait_shutdown_get_quark(); + GPtrArray *qdata_arr; + GSource *integration_source = NULL; + + g_return_if_fail(NM_IS_CLIENT(client)); + g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); + + priv = NM_CLIENT_GET_PRIVATE(client); + + task = nm_g_task_new(NULL, cancellable, nm_client_wait_shutdown, callback, user_data); + + if (integrate_maincontext && g_task_get_context(task) != priv->main_context) { + integration_source = nm_utils_g_main_context_create_integrate_source(priv->main_context); + g_return_if_fail(integration_source); + g_source_attach(integration_source, g_task_get_context(task)); + } + + data = g_slice_new(WaitShutdownData); + *data = (WaitShutdownData){ + .cancellable = nm_g_object_ref(cancellable), + .task = g_object_ref(task), + .result = -1, + .integration_source = integration_source, + .log_ptr = NM_HASH_OBFUSCATE_PTR(client), + }; + /* The "data" itself stays alive as long as the task lives. That's important, because + * the callbacks _wait_shutdown_weak_ref_cb and _wait_shutdown_cancelled_cb + * rely on accessing "data", which must live long enough. */ + g_task_set_task_data(task, data, _wait_shutdown_data_free); + + g_weak_ref_init(&data->weak_ref, priv->context_busy_watcher); + + NML_DBUS_LOG(_NML_NMCLIENT_LOG_LEVEL_COERCE(NML_DBUS_LOG_LEVEL_TRACE), + "nmclient[" NM_HASH_OBFUSCATE_PTR_FMT + "]: wait-shutdown (" NM_HASH_OBFUSCATE_PTR_FMT ")" + "%s", + data->log_ptr, + NM_HASH_OBFUSCATE_PTR(data), + integration_source ? " (integrated main source)" : ""); + + /* I don't think g_object_weak_ref() + GWeakRef can actually be used in + * a race-free way here, because g_object_weak_ref() has no GDestroyNotify + * and cannot keep task alive to avoid races. Instead, implement a weak pointer + * notification via the qdata. + * + * Yes, getting cancellation thread-safe is rather complicated here! I think + * the code is correct though, and you can cancel the operation from + * any thread without races. */ + G_LOCK(wait_shutdown_mutex); + qdata_arr = g_object_get_qdata(priv->context_busy_watcher, quark); + if (!qdata_arr) { + qdata_arr = g_ptr_array_new(); + g_object_set_qdata_full(priv->context_busy_watcher, + quark, + qdata_arr, + _wait_shutdown_qdata_cb); + } + /* data->task gets an additional reference, take it now. */ + g_object_ref(data->task); + g_ptr_array_add(qdata_arr, data); + G_UNLOCK(wait_shutdown_mutex); + + if (data->cancellable) { + /* Take an additional reference on task. */ + data->cancellable_id = g_cancellable_connect(data->cancellable, + G_CALLBACK(_wait_shutdown_cancelled_cb), + g_object_ref(task), + g_object_unref); + } +} + +/** + * nm_client_wait_shutdown_finish: + * @result: a #GAsyncResult obtained from the #GAsyncReadyCallback passed to nm_client_wait_shutdown() + * @error: return location for error or %NULL + * + * Returns: %TRUE if waiting is complete successfully. In that case, all resources of the + * nmclient are wrapped up and released. This can only fail by user cancellation. + * + * Since: 1.42 + */ +gboolean +nm_client_wait_shutdown_finish(GAsyncResult *result, GError **error) +{ + g_return_val_if_fail(nm_g_task_is_valid(result, NULL, nm_client_wait_shutdown), FALSE); + + return g_task_propagate_boolean(G_TASK(result), error); +} + /***************************************************************************** * Backported symbols. Usually, new API is only added in new major versions * of NetworkManager (that is, on "master" branch). Sometimes however, we might |