From 4dc3f3da171a12fe591920c35ca593b592543695 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 27 May 2022 11:45:50 +0200 Subject: [PATCH] core/hostname: avoid blocking calls in NMHostnameManager setting static hostname Of course, blocking and synchronous code is much simpler. But it's also fundamentally wrong to block while we talk to systemd-hostnamed. Refactor to use async operations. --- src/core/nm-hostname-manager.c | 99 ++++++++++++++++++++++++------ src/core/nm-hostname-manager.h | 10 ++- src/core/settings/nm-settings.c | 105 ++++++++++++++++++++++++-------- 3 files changed, 170 insertions(+), 44 deletions(-) diff --git a/src/core/nm-hostname-manager.c b/src/core/nm-hostname-manager.c index 3720bfe7a..be1ba77be 100644 --- a/src/core/nm-hostname-manager.c +++ b/src/core/nm-hostname-manager.c @@ -321,10 +321,30 @@ nm_hostname_manager_get_transient_hostname(NMHostnameManager *self, char **hostn return TRUE; } -gboolean -nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname) +/*****************************************************************************/ + +static void +_write_hostname_dbus_cb(GObject *source, GAsyncResult *result, gpointer user_data) { + gs_unref_object GTask *task = G_TASK(user_data); + gs_unref_variant GVariant *res = NULL; + GError *error = NULL; + + res = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error); + if (!res) { + g_task_return_error(task, error); + return; + } + g_task_return_boolean(task, TRUE); +} + +static void +_write_hostname_on_idle_cb(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object GTask *task = G_TASK(user_data); + NMHostnameManager *self; NMHostnameManagerPrivate *priv; + const char *hostname; gs_free char *hostname_eol = NULL; gboolean ret; gs_free_error GError *error = NULL; @@ -337,23 +357,15 @@ nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname char *fcon_prev = NULL; #endif - g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), FALSE); + if (g_task_return_error_if_cancelled(task)) + return; + self = g_task_get_source_object(task); priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); - if (priv->hostnamed_proxy) { - var = g_dbus_proxy_call_sync(priv->hostnamed_proxy, - "SetStaticHostname", - g_variant_new("(sb)", hostname ?: "", FALSE), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - &error); - if (error) - _LOGW("could not set hostname: %s", error->message); + nm_assert(!priv->hostnamed_proxy); - return !error; - } + hostname = g_task_get_task_data(task); /* If the hostname file is a symbolic link, follow it to find where the * real file is located, otherwise g_file_set_contents will attempt to @@ -409,11 +421,62 @@ nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname #endif if (!ret) { - _LOGW("could not save hostname to %s: %s", file, error->message); - return FALSE; + g_task_return_new_error(task, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "could not save hostname to %s: %s", + file, + error->message); + return; } - return TRUE; + g_task_return_boolean(task, TRUE); +} + +void +nm_hostname_manager_write_hostname(NMHostnameManager *self, + const char *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMHostnameManagerPrivate *priv; + GTask *task; + + g_return_if_fail(NM_IS_HOSTNAME_MANAGER(self)); + + priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + + task = + nm_g_task_new(self, cancellable, nm_hostname_manager_write_hostname, callback, user_data); + + g_task_set_task_data(task, g_strdup(hostname), g_free); + + if (priv->hostnamed_proxy) { + g_dbus_proxy_call(priv->hostnamed_proxy, + "SetStaticHostname", + g_variant_new("(sb)", hostname ?: "", FALSE), + G_DBUS_CALL_FLAGS_NONE, + 15000, + cancellable, + _write_hostname_dbus_cb, + task); + return; + } + + nm_utils_invoke_on_idle(cancellable, _write_hostname_on_idle_cb, task); +} + +gboolean +nm_hostname_manager_write_hostname_finish(NMHostnameManager *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), FALSE); + g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_hostname_manager_write_hostname), + FALSE); + + return g_task_propagate_boolean(G_TASK(result), error); } /*****************************************************************************/ diff --git a/src/core/nm-hostname-manager.h b/src/core/nm-hostname-manager.h index b871d1779..31596819a 100644 --- a/src/core/nm-hostname-manager.h +++ b/src/core/nm-hostname-manager.h @@ -36,7 +36,15 @@ NMHostnameManager *nm_hostname_manager_get(void); const char *nm_hostname_manager_get_static_hostname(NMHostnameManager *self); -gboolean nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname); +void nm_hostname_manager_write_hostname(NMHostnameManager *self, + const char *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean nm_hostname_manager_write_hostname_finish(NMHostnameManager *self, + GAsyncResult *result, + GError **error); void nm_hostname_manager_set_transient_hostname(NMHostnameManager *self, const char *hostname, diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c index 8de81c5d9..b7d846c6d 100644 --- a/src/core/settings/nm-settings.c +++ b/src/core/settings/nm-settings.c @@ -376,6 +376,8 @@ typedef struct { GHashTable *sce_idx; + GCancellable *shutdown_cancellable; + CList sce_dirty_lst_head; CList connections_lst_head; @@ -3466,46 +3468,93 @@ load_plugins(NMSettings *self, const char *const *plugins, GError **error) /*****************************************************************************/ static void -pk_hostname_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +_save_hostname_write_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSettings *self; + GDBusMethodInvocation *context; + gs_free char *hostname = NULL; + gs_unref_object NMAuthSubject *auth_subject = NULL; + gs_unref_object GCancellable *cancellable = NULL; + gs_free_error GError *error = NULL; + + nm_utils_user_data_unpack(user_data, &self, &context, &auth_subject, &hostname, &cancellable); + + nm_hostname_manager_write_hostname_finish(NM_HOSTNAME_MANAGER(source), result, &error); + + nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, + hostname ?: "", + !error, + auth_subject, + error ? error->message : NULL); + + if (nm_utils_error_is_cancelled(error)) { + g_dbus_method_invocation_return_error_literal(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "NetworkManager is shutting down"); + return; + } + + if (error) { + g_dbus_method_invocation_take_error(context, + g_error_new(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Saving the hostname failed: %s", + error->message)); + return; + } + + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +_save_hostname_pk_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMSettings *self = NM_SETTINGS(user_data); NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); NMAuthCallResult result; - GError *error = NULL; - const char *hostname; + gs_free char *hostname = NULL; nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); c_list_unlink(nm_auth_chain_parent_lst_list(chain)); result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME); - hostname = nm_auth_chain_get_data(chain, "hostname"); + hostname = nm_auth_chain_steal_data(chain, "hostname"); - /* If our NMSettingsConnection is already gone, do nothing */ if (result != NM_AUTH_CALL_RESULT_YES) { - error = g_error_new_literal(NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_PERMISSION_DENIED, - NM_UTILS_ERROR_MSG_INSUFF_PRIV); - goto done; + nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, + hostname ?: "", + FALSE, + nm_auth_chain_get_subject(chain), + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + g_dbus_method_invocation_return_error_literal(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + return; } - if (!nm_hostname_manager_write_hostname(priv->hostname_manager, hostname)) { - error = g_error_new_literal(NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_FAILED, - "Saving the hostname failed."); + if (!priv->shutdown_cancellable) { + /* we only keep a weak pointer on the cancellable, so we can + * wrap it up after use. We almost never require this, because + * SaveHostname is almost never called. */ + priv->shutdown_cancellable = g_cancellable_new(); + g_object_add_weak_pointer(G_OBJECT(priv->shutdown_cancellable), + (gpointer *) &priv->shutdown_cancellable); } -done: - nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, - hostname, - !error, - nm_auth_chain_get_subject(chain), - error ? error->message : NULL); - - if (error) - g_dbus_method_invocation_take_error(context, error); - else - g_dbus_method_invocation_return_value(context, NULL); + nm_hostname_manager_write_hostname( + priv->hostname_manager, + hostname, + priv->shutdown_cancellable, + _save_hostname_write_cb, + nm_utils_user_data_pack(self, + context, + g_object_ref(nm_auth_chain_get_subject(chain)), + hostname, + g_object_ref(priv->shutdown_cancellable))); + g_steal_pointer(&hostname); } static void @@ -3533,7 +3582,7 @@ impl_settings_save_hostname(NMDBusObject *obj, goto err; } - chain = nm_auth_chain_new_context(invocation, pk_hostname_cb, self); + chain = nm_auth_chain_new_context(invocation, _save_hostname_pk_cb, self); if (!chain) { error_code = NM_SETTINGS_ERROR_PERMISSION_DENIED; error_reason = NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED; @@ -4126,6 +4175,12 @@ dispose(GObject *object) g_clear_object(&priv->session_monitor); } + if (priv->shutdown_cancellable) { + g_object_remove_weak_pointer(G_OBJECT(priv->shutdown_cancellable), + (gpointer *) &priv->shutdown_cancellable); + g_cancellable_cancel(g_steal_pointer(&priv->shutdown_cancellable)); + } + G_OBJECT_CLASS(nm_settings_parent_class)->dispose(object); }