diff --git a/data/NetworkManager-dispatcher.service.in b/data/NetworkManager-dispatcher.service.in index c450478bac..1a45f35367 100644 --- a/data/NetworkManager-dispatcher.service.in +++ b/data/NetworkManager-dispatcher.service.in @@ -5,6 +5,13 @@ Description=Network Manager Script Dispatcher Service Type=dbus BusName=org.freedesktop.nm_dispatcher ExecStart=@libexecdir@/nm-dispatcher +NotifyAccess=main + +# Enable debug logging in dispatcher service. Note that dispatcher +# also honors debug logging requests from NetworkManager, so you +# can also control logging requests with +# `nmcli general logging domain DISPATCHER level TRACE`. +#Environment=NM_DISPATCHER_DEBUG_LOG=1 # We want to allow scripts to spawn long-running daemons, so tell # systemd to not clean up when nm-dispatcher exits diff --git a/data/nm-sudo.service.in b/data/nm-sudo.service.in index 4ed8b67ba9..22abdd236b 100644 --- a/data/nm-sudo.service.in +++ b/data/nm-sudo.service.in @@ -18,6 +18,7 @@ Description=NetworkManager Sudo Helper Type=dbus BusName=org.freedesktop.nm.sudo ExecStart=@libexecdir@/nm-sudo +NotifyAccess=main # Extra configuration options. Set via `systemctl edit nm-sudo.service`: # diff --git a/po/POTFILES.skip b/po/POTFILES.skip index 3f70738f8b..399b1e6b5c 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -1,4 +1,5 @@ contrib/fedora/rpm/ +data/NetworkManager-dispatcher.service.in data/NetworkManager-wait-online.service.in data/NetworkManager.service.in data/nm-sudo.service.in diff --git a/src/libnm-base/nm-sudo-utils.c b/src/libnm-base/nm-sudo-utils.c index 18bd0051bd..fd9bac94f6 100644 --- a/src/libnm-base/nm-sudo-utils.c +++ b/src/libnm-base/nm-sudo-utils.c @@ -7,6 +7,8 @@ #include #include +#include "libnm-glib-aux/nm-io-utils.h" + /*****************************************************************************/ int @@ -20,13 +22,11 @@ nm_sudo_utils_open_fd(NMSudoGetFDType fd_type, GError **error) case NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET: { struct sockaddr_un sock; + int sock_len; - memset(&sock, 0, sizeof(sock)); - sock.sun_family = AF_UNIX; G_STATIC_ASSERT_EXPR(NM_STRLEN(NM_OVSDB_SOCKET) + 1 < sizeof(sock.sun_path)); - if (g_strlcpy(sock.sun_path, NM_OVSDB_SOCKET, sizeof(sock.sun_path)) - >= sizeof(sock.sun_path)) - nm_assert_not_reached(); + sock_len = nm_io_sockaddr_un_set(&sock, FALSE, NM_OVSDB_SOCKET); + nm_assert(sock_len > 0); fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { @@ -35,9 +35,7 @@ nm_sudo_utils_open_fd(NMSudoGetFDType fd_type, GError **error) return -errsv; } - r = connect(fd, - (const struct sockaddr *) &sock, - G_STRUCT_OFFSET(struct sockaddr_un, sun_path) + NM_STRLEN(NM_OVSDB_SOCKET) + 1); + r = connect(fd, (const struct sockaddr *) &sock, sock_len); if (r != 0) { errsv = NM_ERRNO_NATIVE(errno); g_set_error(error, diff --git a/src/libnm-glib-aux/nm-dbus-aux.c b/src/libnm-glib-aux/nm-dbus-aux.c index f4f53a7d58..454bd2d8c6 100644 --- a/src/libnm-glib-aux/nm-dbus-aux.c +++ b/src/libnm-glib-aux/nm-dbus-aux.c @@ -403,3 +403,90 @@ _nm_dbus_error_is(GError *error, ...) return FALSE; } + +/*****************************************************************************/ + +typedef struct { + GDBusConnection **p_dbus_connection; + GError ** p_error; +} BusGetData; + +static void +_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + BusGetData *data = user_data; + + *data->p_dbus_connection = g_bus_get_finish(result, data->p_error); +} + +/** + * nm_g_bus_get_blocking: + * @cancellable: (allow-none): a #GCancellable to abort the operation. + * @error: (allow-none): the error. + * + * This calls g_bus_get(), but iterates the current (thread-default) GMainContext + * until the response is ready. As such, it's similar to g_bus_get_sync(), + * but it allows to cancel the operation (without having multiple threads). + * + * Returns: (transfer full): the new #GDBusConnection or %NULL on error. + */ +GDBusConnection * +nm_g_bus_get_blocking(GCancellable *cancellable, GError **error) +{ + gs_free_error GError *local_error = NULL; + gs_unref_object GDBusConnection *dbus_connection = NULL; + GMainContext * main_context = g_main_context_get_thread_default(); + BusGetData data = { + .p_dbus_connection = &dbus_connection, + .p_error = &local_error, + }; + + g_bus_get(G_BUS_TYPE_SYSTEM, cancellable, _bus_get_cb, &data); + + while (!dbus_connection && !local_error) + g_main_context_iteration(main_context, TRUE); + + if (!dbus_connection) { + g_propagate_error(error, g_steal_pointer(&local_error)); + return NULL; + } + + return g_steal_pointer(&dbus_connection); +} + +/*****************************************************************************/ + +void +nm_dbus_connection_call_blocking_callback(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDBusConnectionCallBlockingData *data = user_data; + + nm_assert(data); + nm_assert(!data->result); + nm_assert(!data->error); + + data->result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &data->error); +} + +GVariant * +nm_dbus_connection_call_blocking(NMDBusConnectionCallBlockingData *data, GError **error) +{ + GMainContext *main_context = g_main_context_get_thread_default(); + gs_free_error GError *local_error = NULL; + gs_unref_variant GVariant *result = NULL; + + nm_assert(data); + + while (!data->result && !data->error) + g_main_context_iteration(main_context, TRUE); + + local_error = g_steal_pointer(&data->error); + result = g_steal_pointer(&data->result); + + if (!result) { + g_propagate_error(error, g_steal_pointer(&local_error)); + return NULL; + } + + return g_steal_pointer(&result); +} diff --git a/src/libnm-glib-aux/nm-dbus-aux.h b/src/libnm-glib-aux/nm-dbus-aux.h index 65a91311e7..f71b05b568 100644 --- a/src/libnm-glib-aux/nm-dbus-aux.h +++ b/src/libnm-glib-aux/nm-dbus-aux.h @@ -84,6 +84,29 @@ void nm_dbus_connection_call_get_name_owner(GDBusConnection * d NMDBusConnectionCallGetNameOwnerCb callback, gpointer user_data); +static inline void +nm_dbus_connection_call_request_name(GDBusConnection * dbus_connection, + const char * name, + guint32 flags, + int timeout_msec, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_connection_call(dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RequestName", + g_variant_new("(su)", name, flags), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + callback, + user_data); +} + static inline guint nm_dbus_connection_signal_subscribe_properties_changed(GDBusConnection * dbus_connection, const char * bus_name, @@ -215,4 +238,18 @@ gboolean _nm_dbus_error_is(GError *error, ...) G_GNUC_NULL_TERMINATED; /*****************************************************************************/ +GDBusConnection *nm_g_bus_get_blocking(GCancellable *cancellable, GError **error); + +/*****************************************************************************/ + +typedef struct { + GVariant *result; + GError * error; +} NMDBusConnectionCallBlockingData; + +void +nm_dbus_connection_call_blocking_callback(GObject *source, GAsyncResult *res, gpointer user_data); + +GVariant *nm_dbus_connection_call_blocking(NMDBusConnectionCallBlockingData *data, GError **error); + #endif /* __NM_DBUS_AUX_H__ */ diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c index 87478a2dc2..85a81f69bc 100644 --- a/src/libnm-glib-aux/nm-io-utils.c +++ b/src/libnm-glib-aux/nm-io-utils.c @@ -7,9 +7,11 @@ #include "nm-io-utils.h" -#include -#include #include +#include +#include +#include +#include #include "nm-str-buf.h" #include "nm-shared-utils.h" @@ -628,3 +630,96 @@ next:; g_ptr_array_add(arr, NULL); return (char **) g_ptr_array_free(arr, FALSE); } + +/*****************************************************************************/ + +/* taken from systemd's sockaddr_un_set_path(). */ +int +nm_io_sockaddr_un_set(struct sockaddr_un *ret, NMOptionBool is_abstract, const char *path) +{ + gsize l; + + g_return_val_if_fail(ret, -EINVAL); + g_return_val_if_fail(path, -EINVAL); + nm_assert_is_ternary(is_abstract); + + if (is_abstract == NM_OPTION_BOOL_DEFAULT) + is_abstract = nm_io_sockaddr_un_path_is_abstract(path, &path); + + l = strlen(path); + if (l < 1) + return -EINVAL; + if (l > sizeof(ret->sun_path) - 1) + return -EINVAL; + + if (!is_abstract) { + if (path[0] != '/') { + /* non-abstract paths must be absolute. */ + return -EINVAL; + } + } + + memset(ret, 0, nm_offsetof(struct sockaddr_un, sun_path)); + ret->sun_family = AF_UNIX; + + if (is_abstract) { + ret->sun_path[0] = '\0'; + memcpy(&ret->sun_path[1], path, NM_MIN(l + 1, sizeof(ret->sun_path) - 1)); + } else + memcpy(&ret->sun_path, path, l + 1); + + /* For pathname addresses, we return the size with the trailing NUL. + * For abstract addresses, we return the size without the trailing NUL + * (which may not be even written). But as abstract sockets also have + * a NUL at the beginning of sun_path, the total length is always + * calculated the same. */ + return (nm_offsetof(struct sockaddr_un, sun_path) + 1) + l; +} + +/*****************************************************************************/ + +/* taken from systemd's sd_notify(). */ +int +nm_sd_notify(const char *state) +{ + struct sockaddr_un sockaddr; + struct iovec iovec; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_name = &sockaddr, + }; + nm_auto_close int fd = -1; + const char * e; + int r; + + if (!state) + g_return_val_if_reached(-EINVAL); + + e = getenv("NOTIFY_SOCKET"); + if (!e) + return 0; + + r = nm_io_sockaddr_un_set(&sockaddr, NM_OPTION_BOOL_DEFAULT, e); + if (r < 0) + return r; + msghdr.msg_namelen = r; + + fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -NM_ERRNO_NATIVE(errno); + + /* systemd calls here fd_set_sndbuf(fd, SNDBUF_SIZE) .We don't bother. */ + + iovec = (struct iovec){ + .iov_base = (gpointer) state, + .iov_len = strlen(state), + }; + + /* systemd sends ucred, if geteuid()/getegid() does not match getuid()/getgid(). We don't bother. */ + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) + return -NM_ERRNO_NATIVE(errno); + + return 0; +} diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h index 31ff6d0569..479d0e5100 100644 --- a/src/libnm-glib-aux/nm-io-utils.h +++ b/src/libnm-glib-aux/nm-io-utils.h @@ -60,4 +60,21 @@ void nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeou char **nm_utils_find_mkstemp_files(const char *dirname, const char *filename); +static inline gboolean +nm_io_sockaddr_un_path_is_abstract(const char *path, const char **out_path) +{ + if (path && path[0] == '@') { + NM_SET_OUT(out_path, &path[1]); + return TRUE; + } + NM_SET_OUT(out_path, path); + return FALSE; +} + +struct sockaddr_un; + +int nm_io_sockaddr_un_set(struct sockaddr_un *ret, NMOptionBool is_abstract, const char *path); + +int nm_sd_notify(const char *state); + #endif /* __NM_IO_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index d9928e324e..3fa00570b4 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -5080,12 +5080,12 @@ nm_g_unix_signal_source_new(int signum, } GSource * -nm_g_unix_fd_source_new(int fd, - GIOCondition io_condition, - int priority, - gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data), - gpointer user_data, - GDestroyNotify destroy_notify) +nm_g_unix_fd_source_new(int fd, + GIOCondition io_condition, + int priority, + GUnixFDSourceFunc source_func, + gpointer user_data, + GDestroyNotify destroy_notify) { GSource *source; @@ -5097,6 +5097,23 @@ nm_g_unix_fd_source_new(int fd, return source; } +GSource * +nm_g_child_watch_source_new(GPid pid, + int priority, + GChildWatchFunc handler, + gpointer user_data, + GDestroyNotify notify) +{ + GSource *source; + + source = g_child_watch_source_new(pid); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority(source, priority); + g_source_set_callback(source, G_SOURCE_FUNC(handler), user_data, notify); + return source; +} + /*****************************************************************************/ #define _CTX_LOG(fmt, ...) \ diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index d5894e82a6..ef4d3937f1 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -1715,19 +1715,25 @@ GSource *nm_g_timeout_source_new_seconds(guint timeout_sec, gpointer user_data, GDestroyNotify destroy_notify); -GSource * - nm_g_unix_fd_source_new(int fd, - GIOCondition io_condition, - int priority, - gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data), - gpointer user_data, - GDestroyNotify destroy_notify); +GSource *nm_g_unix_fd_source_new(int fd, + GIOCondition io_condition, + int priority, + GUnixFDSourceFunc source_func, + gpointer user_data, + GDestroyNotify destroy_notify); + GSource *nm_g_unix_signal_source_new(int signum, int priority, GSourceFunc handler, gpointer user_data, GDestroyNotify notify); +GSource *nm_g_child_watch_source_new(GPid pid, + int priority, + GChildWatchFunc handler, + gpointer user_data, + GDestroyNotify notify); + static inline GSource * nm_g_source_attach(GSource *source, GMainContext *context) { @@ -1824,6 +1830,14 @@ nm_g_unix_signal_add_source(int signum, GSourceFunc handler, gpointer user_data) NULL); } +static inline GSource * +nm_g_child_watch_add_source(GPid pid, GChildWatchFunc handler, gpointer user_data) +{ + return nm_g_source_attach( + nm_g_child_watch_source_new(pid, G_PRIORITY_DEFAULT, handler, user_data, NULL), + NULL); +} + NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_unref_gmaincontext, g_main_context_unref); #define nm_auto_unref_gmaincontext nm_auto(_nm_auto_unref_gmaincontext) @@ -1879,6 +1893,14 @@ nm_g_main_context_push_thread_default_if_necessary(GMainContext *context) return context; } +static inline void +nm_g_main_context_iterate_ready(GMainContext *context) +{ + while (g_main_context_iteration(context, FALSE)) { + ; + } +} + /*****************************************************************************/ static inline int diff --git a/src/nm-dispatcher/nm-dispatcher.c b/src/nm-dispatcher/nm-dispatcher.c index 6751a2cb0b..b96cc82ea9 100644 --- a/src/nm-dispatcher/nm-dispatcher.c +++ b/src/nm-dispatcher/nm-dispatcher.c @@ -18,21 +18,39 @@ #include #include "libnm-core-aux-extern/nm-dispatcher-api.h" +#include "libnm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" #include "nm-dispatcher-utils.h" /*****************************************************************************/ +/* Serves only the purpose to mark environment variables that are honored by + * the application. You can search for this macro, and find what options are supported. */ +#define _ENV(var) ("" var "") + +/*****************************************************************************/ + typedef struct Request Request; static struct { GDBusConnection *dbus_connection; - GMainLoop * loop; - gboolean debug; + GCancellable * quit_cancellable; + bool log_verbose; + bool log_stdout; gboolean persist; - guint quit_id; + GSource * quit_source; guint request_id_counter; - gboolean ever_acquired_name; - bool exit_with_failure; + guint dbus_regist_id; + + gint64 start_timestamp_msec; + + bool name_requested; + + bool exit_with_failure; + + bool shutdown_timeout; + bool shutdown_quitting; Request *current_request; GQueue * requests_waiting; @@ -48,8 +66,8 @@ typedef struct { char * error; gboolean wait; gboolean dispatched; - guint watch_id; - guint timeout_id; + GSource * watch_source; + GSource * timeout_source; } ScriptInfo; struct Request { @@ -137,7 +155,7 @@ struct Request { } \ G_STMT_END -#define _LOG_X_D_enabled() (gl.debug) +#define _LOG_X_D_enabled() (gl.log_verbose) #define _LOG_X_T_enabled() _LOG_X_D_enabled() #define _LOG_R_D_enabled(request) (_NM_ENSURE_TYPE_CONST(Request *, request)->debug) @@ -189,18 +207,26 @@ request_free(Request *request) static gboolean quit_timeout_cb(gpointer user_data) { - gl.quit_id = 0; - g_main_loop_quit(gl.loop); - return G_SOURCE_REMOVE; + nm_clear_g_source_inst(&gl.quit_source); + gl.shutdown_timeout = TRUE; + return G_SOURCE_CONTINUE; } static void quit_timeout_reschedule(void) { - if (!gl.persist) { - nm_clear_g_source(&gl.quit_id); - gl.quit_id = g_timeout_add_seconds(10, quit_timeout_cb, NULL); - } + nm_clear_g_source_inst(&gl.quit_source); + + if (gl.persist) + return; + + if (gl.shutdown_quitting) + return; + + if (gl.num_requests_pending > 0) + return; + + gl.quit_source = nm_g_timeout_add_source(10000, quit_timeout_cb, NULL); } /** @@ -286,7 +312,7 @@ complete_request(Request *request) request_free(request); - g_assert_cmpuint(gl.num_requests_pending, >, 0); + nm_assert(gl.num_requests_pending > 0); if (--gl.num_requests_pending <= 0) { nm_assert(!gl.current_request && !g_queue_peek_head(gl.requests_waiting)); quit_timeout_reschedule(); @@ -365,8 +391,8 @@ script_watch_cb(GPid pid, int status, gpointer user_data) g_assert(pid == script->pid); - script->watch_id = 0; - nm_clear_g_source(&script->timeout_id); + nm_clear_g_source_inst(&script->watch_source); + nm_clear_g_source_inst(&script->timeout_source); script->request->num_scripts_done++; if (!script->wait) script->request->num_scripts_nowait--; @@ -395,8 +421,8 @@ script_timeout_cb(gpointer user_data) { ScriptInfo *script = user_data; - script->timeout_id = 0; - nm_clear_g_source(&script->watch_id); + nm_clear_g_source_inst(&script->timeout_source); + nm_clear_g_source_inst(&script->watch_source); script->request->num_scripts_done++; if (!script->wait) script->request->num_scripts_nowait--; @@ -417,7 +443,7 @@ again: complete_script(script); - return FALSE; + return G_SOURCE_CONTINUE; } static gboolean @@ -514,8 +540,9 @@ script_dispatch(ScriptInfo *script) return FALSE; } - script->watch_id = g_child_watch_add(script->pid, (GChildWatchFunc) script_watch_cb, script); - script->timeout_id = g_timeout_add_seconds(SCRIPT_TIMEOUT, script_timeout_cb, script); + script->watch_source = nm_g_child_watch_add_source(script->pid, script_watch_cb, script); + script->timeout_source = + nm_g_timeout_add_source_seconds(SCRIPT_TIMEOUT, script_timeout_cb, script); if (!script->wait) request->num_scripts_nowait++; return TRUE; @@ -726,7 +753,7 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters) request = g_slice_new0(Request); request->request_id = ++gl.request_id_counter; - request->debug = debug || gl.debug; + request->debug = debug || gl.log_verbose; request->context = invocation; request->action = g_strdup(action); @@ -782,9 +809,9 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters) return; } - nm_clear_g_source(&gl.quit_id); - gl.num_requests_pending++; + gl.shutdown_timeout = FALSE; + nm_clear_g_source_inst(&gl.quit_source); for (i = 0; i < request->scripts->len; i++) { ScriptInfo *s = g_ptr_array_index(request->scripts, i); @@ -830,28 +857,23 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters) } static void -on_name_acquired(GDBusConnection *connection, const char *name, gpointer user_data) +_method_call_ping(GDBusMethodInvocation *invocation, GVariant *parameters) { - gl.ever_acquired_name = TRUE; -} + gs_free char *msg = NULL; + gint64 running_msec; + const char * arg_s; -static void -on_name_lost(GDBusConnection *connection, const char *name, gpointer user_data) -{ - if (!connection) { - if (!gl.ever_acquired_name) { - _LOG_X_W("Could not get the system bus. Make sure the message bus daemon is running!"); - gl.exit_with_failure = TRUE; - } else { - _LOG_X_I("System bus stopped. Exiting"); - } - } else if (!gl.ever_acquired_name) { - _LOG_X_W("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service."); - gl.exit_with_failure = TRUE; - } else - _LOG_X_I("Lost the " NM_DISPATCHER_DBUS_SERVICE " name. Exiting"); + g_variant_get(parameters, "(&s)", &arg_s); - g_main_loop_quit(gl.loop); + running_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME) - gl.start_timestamp_msec; + + msg = g_strdup_printf("pid=%lu, unique-name=%s, since=%" G_GINT64_FORMAT ".%03d, pong=%s", + (unsigned long) getpid(), + g_dbus_connection_get_unique_name(gl.dbus_connection), + (gint64) (running_msec / 1000), + (int) (running_msec % 1000), + arg_s); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", msg)); } static void @@ -869,6 +891,10 @@ _method_call(GDBusConnection * connection, _method_call_action(invocation, parameters); return; } + if (nm_streq(method_name, "Ping")) { + _method_call_ping(invocation, parameters); + return; + } } g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, @@ -880,6 +906,10 @@ _method_call(GDBusConnection * connection, static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO( NM_DISPATCHER_DBUS_INTERFACE, .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO( + "Ping", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), ), NM_DEFINE_GDBUS_METHOD_INFO( "Action", .in_args = NM_DEFINE_GDBUS_ARG_INFOS( @@ -901,9 +931,75 @@ static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("results", "a(sus)"), ), ), ), ); -static const GDBusInterfaceVTable interface_vtable = { - .method_call = _method_call, -}; +static gboolean +_bus_register_service(void) +{ + static const GDBusInterfaceVTable interface_vtable = { + .method_call = _method_call, + }; + gs_free_error GError * error = NULL; + NMDBusConnectionCallBlockingData data = { + .result = NULL, + }; + gs_unref_variant GVariant *ret = NULL; + guint32 ret_val; + + gl.dbus_regist_id = + g_dbus_connection_register_object(gl.dbus_connection, + NM_DISPATCHER_DBUS_PATH, + interface_info, + NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable), + NULL, + NULL, + &error); + if (gl.dbus_regist_id == 0) { + _LOG_X_W("dbus: could not export dispatcher D-Bus interface %s: %s", + NM_DISPATCHER_DBUS_PATH, + error->message); + return FALSE; + } + + _LOG_X_D("dbus: dispatcher D-Bus interface %s registered", NM_DISPATCHER_DBUS_PATH); + + gl.name_requested = TRUE; + + nm_dbus_connection_call_request_name(gl.dbus_connection, + NM_DISPATCHER_DBUS_SERVICE, + DBUS_NAME_FLAG_ALLOW_REPLACEMENT + | DBUS_NAME_FLAG_REPLACE_EXISTING, + 10000, + gl.quit_cancellable, + nm_dbus_connection_call_blocking_callback, + &data); + + /* Note that with D-Bus activation, the first request will already hit us before RequestName + * completes. So when we start iterating the main context, the first request may already come + * in. */ + + ret = nm_dbus_connection_call_blocking(&data, &error); + + if (nm_utils_error_is_cancelled(error)) + return FALSE; + + if (error) { + _LOG_X_W("d-bus: failed to request name %s: %s", + NM_DISPATCHER_DBUS_SERVICE, + error->message); + return FALSE; + } + + g_variant_get(ret, "(u)", &ret_val); + + if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + _LOG_X_W("dbus: request name for %s failed to take name (response %u)", + NM_DISPATCHER_DBUS_SERVICE, + ret_val); + return FALSE; + } + + _LOG_X_D("dbus: request name for %s succeeded", NM_DISPATCHER_DBUS_SERVICE); + return TRUE; +} /*****************************************************************************/ @@ -956,23 +1052,52 @@ logging_shutdown(void) static gboolean signal_handler(gpointer user_data) { - int signo = GPOINTER_TO_INT(user_data); - - _LOG_X_I("Caught signal %d, shutting down...", signo); - g_main_loop_quit(gl.loop); - + if (!gl.shutdown_quitting) { + gl.shutdown_quitting = TRUE; + _LOG_X_I("Caught signal %d, shutting down...", GPOINTER_TO_INT(user_data)); + g_cancellable_cancel(gl.quit_cancellable); + } return G_SOURCE_CONTINUE; } +static void +_bus_release_name_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + nm_assert(gl.num_requests_pending > 0); + gl.num_requests_pending--; + g_main_context_wakeup(NULL); +} + static gboolean parse_command_line(int *p_argc, char ***p_argv, GError **error) { GOptionContext *opt_ctx; - GOptionEntry entries[] = { - {"debug", 0, 0, G_OPTION_ARG_NONE, &gl.debug, "Output to console rather than syslog", NULL}, - {"persist", 0, 0, G_OPTION_ARG_NONE, &gl.persist, "Don't quit after a short timeout", NULL}, - {NULL}}; - gboolean success; + gboolean arg_debug = FALSE; + GOptionEntry entries[] = {{ + "debug", + 0, + 0, + G_OPTION_ARG_NONE, + &arg_debug, + "Output to console rather than syslog", + NULL, + }, + { + "persist", + 0, + 0, + G_OPTION_ARG_NONE, + &gl.persist, + "Don't quit after a short timeout", + NULL, + }, + { + NULL, + }}; + gboolean success; + + gl.log_stdout = FALSE; + gl.log_verbose = _nm_utils_ascii_str_to_bool(g_getenv(_ENV("NM_DISPATCHER_DEBUG_LOG")), FALSE); opt_ctx = g_option_context_new(NULL); g_option_context_set_summary(opt_ctx, "Executes scripts upon actions by NetworkManager."); @@ -982,17 +1107,28 @@ parse_command_line(int *p_argc, char ***p_argv, GError **error) g_option_context_free(opt_ctx); + if (success && arg_debug) { + gl.log_stdout = TRUE; + gl.log_verbose = TRUE; + } + return success; } int main(int argc, char **argv) { - gs_free_error GError *error = NULL; - guint signal_id_term = 0; - guint signal_id_int = 0; - guint dbus_regist_id = 0; - guint dbus_own_name_id = 0; + gs_free_error GError *error = NULL; + GSource * source_term = NULL; + GSource * source_int = NULL; + + signal(SIGPIPE, SIG_IGN); + source_term = nm_g_unix_signal_add_source(SIGTERM, signal_handler, GINT_TO_POINTER(SIGTERM)); + source_int = nm_g_unix_signal_add_source(SIGINT, signal_handler, GINT_TO_POINTER(SIGINT)); + + gl.start_timestamp_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME); + + gl.quit_cancellable = g_cancellable_new(); if (!parse_command_line(&argc, &argv, &error)) { _LOG_X_W("Error parsing command line arguments: %s", error->message); @@ -1000,10 +1136,7 @@ main(int argc, char **argv) goto done; } - signal_id_term = g_unix_signal_add(SIGTERM, signal_handler, GINT_TO_POINTER(SIGTERM)); - signal_id_int = g_unix_signal_add(SIGINT, signal_handler, GINT_TO_POINTER(SIGINT)); - - if (gl.debug) { + if (gl.log_stdout) { if (!g_getenv("G_MESSAGES_DEBUG")) { /* we log our regular messages using g_debug() and g_info(). * When we redirect glib logging to syslog, there is no problem. @@ -1014,77 +1147,111 @@ main(int argc, char **argv) } else logging_setup(); - gl.loop = g_main_loop_new(NULL, FALSE); - - gl.dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + gl.dbus_connection = nm_g_bus_get_blocking(gl.quit_cancellable, &error); if (!gl.dbus_connection) { - _LOG_X_W("Could not get the system bus (%s). Make sure the message bus daemon is running!", - error->message); - gl.exit_with_failure = TRUE; + if (!nm_utils_error_is_cancelled(error)) { + _LOG_X_W("dbus: failure to get D-Bus connection: %s", error->message); + gl.exit_with_failure = TRUE; + } goto done; } + /* On bus-disconnect, GDBus will raise(SIGTERM), which we handle like a + * regular request to quit. */ + g_dbus_connection_set_exit_on_close(gl.dbus_connection, TRUE); + + _LOG_X_D("dbus: unique name: %s", g_dbus_connection_get_unique_name(gl.dbus_connection)); + gl.requests_waiting = g_queue_new(); - dbus_regist_id = - g_dbus_connection_register_object(gl.dbus_connection, - NM_DISPATCHER_DBUS_PATH, - interface_info, - NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable), - NULL, - NULL, - &error); - if (dbus_regist_id == 0) { - _LOG_X_W("Could not export Dispatcher D-Bus interface: %s", error->message); - gl.exit_with_failure = 1; - goto done; - } - - dbus_own_name_id = g_bus_own_name_on_connection(gl.dbus_connection, - NM_DISPATCHER_DBUS_SERVICE, - G_BUS_NAME_OWNER_FLAGS_NONE, - on_name_acquired, - on_name_lost, - NULL, - NULL); - quit_timeout_reschedule(); - g_main_loop_run(gl.loop); - -done: - - if (gl.num_requests_pending > 0) { - /* this only happens when we quit due to SIGTERM (not due to the idle timer). - * - * Log a warning about pending scripts. - * - * Maybe we should notify NetworkManager that these scripts are left in an unknown state. - * But this is either a bug of a dispatcher script (not terminating in time). - * - * FIXME(shutdown): Also, currently NetworkManager behaves wrongly on shutdown. - * Note that systemd would not terminate NetworkManager-dispatcher before NetworkManager. - * It's NetworkManager's responsibility to keep running long enough so that all requests - * can complete (with a watchdog timer, and a warning that user provided scripts hang). */ - _LOG_X_W("exiting but there are still %u requests pending", gl.num_requests_pending); + if (!_bus_register_service()) { + /* we failed to start the D-Bus service, and will shut down. However, + * first see whether there are any requests that we should process. + * Even if RequestName fails, we might already have requests pending. */ + if (!g_cancellable_is_cancelled(gl.quit_cancellable)) + gl.exit_with_failure = TRUE; + gl.shutdown_quitting = TRUE; } - if (dbus_own_name_id != 0) - g_bus_unown_name(nm_steal_int(&dbus_own_name_id)); + while (TRUE) { + if (gl.num_requests_pending > 0) { + /* while we have requests pending, we cannot stop processing them... */ + } else if (gl.shutdown_timeout || gl.shutdown_quitting) { + if (gl.name_requested) { + int r; - if (dbus_regist_id != 0) - g_dbus_connection_unregister_object(gl.dbus_connection, nm_steal_int(&dbus_regist_id)); + /* We already requested a name. To exit-on-idle without race, we need to dance. + * See https://lists.freedesktop.org/archives/dbus/2015-May/016671.html . */ + + gl.name_requested = FALSE; + gl.shutdown_quitting = TRUE; + + _LOG_X_T("shutdown: release-name"); + + /* we create a fake pending request. */ + gl.num_requests_pending++; + nm_clear_g_source_inst(&gl.quit_source); + + r = nm_sd_notify("STOPPING=1"); + if (r < 0) + _LOG_X_W("shutdown: sd_notifiy(STOPPING=1) failed: %s", nm_strerror_native(-r)); + else + _LOG_X_T("shutdown: sd_notifiy(STOPPING=1) succeeded"); + + g_dbus_connection_call(gl.dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ReleaseName", + g_variant_new("(s)", NM_DISPATCHER_DBUS_SERVICE), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + 10000, + NULL, + _bus_release_name_cb, + NULL); + continue; + } + + break; + } + + g_main_context_iteration(NULL, TRUE); + } + +done: + nm_g_main_context_iterate_ready(NULL); + + gl.shutdown_quitting = TRUE; + g_cancellable_cancel(gl.quit_cancellable); + + nm_assert(gl.num_requests_pending == 0); + + if (gl.dbus_regist_id != 0) + g_dbus_connection_unregister_object(gl.dbus_connection, nm_steal_int(&gl.dbus_regist_id)); nm_clear_pointer(&gl.requests_waiting, g_queue_free); - nm_clear_g_source(&signal_id_term); - nm_clear_g_source(&signal_id_int); - nm_clear_g_source(&gl.quit_id); - nm_clear_pointer(&gl.loop, g_main_loop_unref); - g_clear_object(&gl.dbus_connection); + nm_clear_g_source_inst(&gl.quit_source); - if (!gl.debug) + if (gl.dbus_connection) { + g_dbus_connection_flush_sync(gl.dbus_connection, NULL, NULL); + g_clear_object(&gl.dbus_connection); + } + + nm_g_main_context_iterate_ready(NULL); + + _LOG_X_T("shutdown: exiting with %s", gl.exit_with_failure ? "failure" : "success"); + + if (gl.log_stdout) logging_shutdown(); + nm_clear_g_source_inst(&source_term); + nm_clear_g_source_inst(&source_int); + + g_clear_object(&gl.quit_cancellable); + return gl.exit_with_failure ? 1 : 0; } diff --git a/src/nm-sudo/nm-sudo.c b/src/nm-sudo/nm-sudo.c index dd1581de18..d8cf42f3c8 100644 --- a/src/nm-sudo/nm-sudo.c +++ b/src/nm-sudo/nm-sudo.c @@ -5,10 +5,11 @@ #include #include "c-list/src/c-list.h" +#include "libnm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "libnm-glib-aux/nm-logging-base.h" #include "libnm-glib-aux/nm-shared-utils.h" #include "libnm-glib-aux/nm-time-utils.h" -#include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-base/nm-sudo-utils.h" /* nm-sudo doesn't link with libnm-core nor libnm-base, but these headers @@ -49,7 +50,7 @@ struct _GlobalData { gint64 start_timestamp_msec; guint32 timeout_msec; bool name_owner_initialized; - bool service_registered; + bool name_requested; /* This is controlled by $NM_SUDO_NO_AUTH_FOR_TESTING. It disables authentication * of the request, so it is ONLY for testing. */ @@ -66,7 +67,7 @@ static void _pending_job_register_object(GlobalData *gl, GObject *obj); /*****************************************************************************/ -#define _nm_log(level, ...) _nm_log_simple_printf((level), __VA_ARGS__); +#define _nm_log(level, ...) _nm_log_simple_printf((level), __VA_ARGS__) #define _NMLOG(level, ...) \ G_STMT_START \ @@ -140,33 +141,13 @@ _signal_callback_term(gpointer user_data) /*****************************************************************************/ -typedef struct { - GDBusConnection **p_dbus_connection; - GError ** p_error; -} BusGetData; - -static void -_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data) -{ - BusGetData *data = user_data; - - *data->p_dbus_connection = g_bus_get_finish(result, data->p_error); -} - static GDBusConnection * _bus_get(GCancellable *cancellable, int *out_exit_code) { gs_free_error GError *error = NULL; gs_unref_object GDBusConnection *dbus_connection = NULL; - BusGetData data = { - .p_dbus_connection = &dbus_connection, - .p_error = &error, - }; - g_bus_get(G_BUS_TYPE_SYSTEM, cancellable, _bus_get_cb, &data); - - while (!dbus_connection && !error) - g_main_context_iteration(NULL, TRUE); + dbus_connection = nm_g_bus_get_blocking(cancellable, &error); if (!dbus_connection) { gboolean was_cancelled = nm_utils_error_is_cancelled(error); @@ -309,14 +290,26 @@ _bus_method_call(GDBusConnection * connection, method_name, g_variant_get_type_string(parameters)); + if (!nm_streq(interface_name, NM_SUDO_DBUS_IFACE_NAME)) + goto out_unknown_method; + + if (nm_streq(method_name, "GetFD")) { + g_variant_get(parameters, "(u)", &arg_u); + _handle_get_fd(gl, invocation, arg_u); + return; + } if (nm_streq(method_name, "Ping")) { g_variant_get(parameters, "(&s)", &arg_s); _handle_ping(gl, invocation, arg_s); - } else if (nm_streq(method_name, "GetFD")) { - g_variant_get(parameters, "(u)", &arg_u); - _handle_get_fd(gl, invocation, arg_u); - } else - nm_assert_not_reached(); + return; + } + +out_unknown_method: + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s", + method_name); } static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO( @@ -330,56 +323,18 @@ static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO .in_args = NM_DEFINE_GDBUS_ARG_INFOS( NM_DEFINE_GDBUS_ARG_INFO("fd_type", "u"), ), ), ), ); -typedef struct { - GlobalData *gl; - gboolean is_waiting; -} BusRegisterServiceRequestNameData; - -static void -_bus_register_service_request_name_cb(GObject *source, GAsyncResult *res, gpointer user_data) -{ - BusRegisterServiceRequestNameData *data = user_data; - gs_free_error GError *error = NULL; - gs_unref_variant GVariant *ret = NULL; - gboolean success = FALSE; - - ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); - - if (nm_utils_error_is_cancelled(error)) - goto out; - - if (error) - _LOGE("d-bus: failed to request name %s: %s", NM_SUDO_DBUS_BUS_NAME, error->message); - else { - guint32 ret_val; - - g_variant_get(ret, "(u)", &ret_val); - if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { - _LOGW("dbus: request name for %s failed to take name (response %u)", - NM_SUDO_DBUS_BUS_NAME, - ret_val); - } else { - _LOGD("dbus: request name for %s succeeded", NM_SUDO_DBUS_BUS_NAME); - success = TRUE; - } - } - -out: - if (success) - data->gl->service_registered = TRUE; - data->is_waiting = FALSE; -} - -static void +static gboolean _bus_register_service(GlobalData *gl) { static const GDBusInterfaceVTable interface_vtable = { .method_call = _bus_method_call, }; - gs_free_error GError * error = NULL; - BusRegisterServiceRequestNameData data; - - nm_assert(!gl->service_registered); + gs_free_error GError * error = NULL; + NMDBusConnectionCallBlockingData data = { + .result = NULL, + }; + gs_unref_variant GVariant *ret = NULL; + guint32 ret_val; gl->service_regist_id = g_dbus_connection_register_object(gl->dbus_connection, @@ -391,37 +346,49 @@ _bus_register_service(GlobalData *gl) &error); if (gl->service_regist_id == 0) { _LOGE("dbus: error registering object %s: %s", NM_SUDO_DBUS_OBJECT_PATH, error->message); - return; + return FALSE; } _LOGD("dbus: object %s registered", NM_SUDO_DBUS_OBJECT_PATH); - data = (BusRegisterServiceRequestNameData){ - .gl = gl, - .is_waiting = TRUE, - }; + /* regardless whether the request is successful, after we start calling + * RequestName, we remember that we need to ReleaseName it. */ + gl->name_requested = TRUE; - g_dbus_connection_call( - gl->dbus_connection, - DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS, - "RequestName", - g_variant_new("(su)", - NM_SUDO_DBUS_BUS_NAME, - (guint) (DBUS_NAME_FLAG_ALLOW_REPLACEMENT | DBUS_NAME_FLAG_REPLACE_EXISTING)), - G_VARIANT_TYPE("(u)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - gl->quit_cancellable, - _bus_register_service_request_name_cb, - &data); + nm_dbus_connection_call_request_name(gl->dbus_connection, + NM_SUDO_DBUS_BUS_NAME, + DBUS_NAME_FLAG_ALLOW_REPLACEMENT + | DBUS_NAME_FLAG_REPLACE_EXISTING, + 10000, + gl->quit_cancellable, + nm_dbus_connection_call_blocking_callback, + &data); /* Note that with D-Bus activation, the first request will already hit us before RequestName - * completes. */ + * completes. So when we start iterating the main context, the first request may already come + * in. */ - while (data.is_waiting) - g_main_context_iteration(NULL, TRUE); + ret = nm_dbus_connection_call_blocking(&data, &error); + + if (nm_utils_error_is_cancelled(error)) + return FALSE; + + if (error) { + _LOGE("d-bus: failed to request name %s: %s", NM_SUDO_DBUS_BUS_NAME, error->message); + return FALSE; + } + + g_variant_get(ret, "(u)", &ret_val); + + if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + _LOGW("dbus: request name for %s failed to take name (response %u)", + NM_SUDO_DBUS_BUS_NAME, + ret_val); + return FALSE; + } + + _LOGD("dbus: request name for %s succeeded", NM_SUDO_DBUS_BUS_NAME); + return TRUE; } /*****************************************************************************/ @@ -505,6 +472,16 @@ _pending_job_register_object(GlobalData *gl, GObject *obj) /*****************************************************************************/ +static void +_bus_release_name_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + _nm_unused gs_unref_object GObject *keep_alive_object = user_data; + + g_main_context_wakeup(NULL); +} + +/*****************************************************************************/ + static void _initial_setup(GlobalData *gl) { @@ -568,36 +545,73 @@ main(int argc, char **argv) exit_code = EXIT_SUCCESS; - _bus_register_service(gl); - if (!gl->service_registered) { + if (!_bus_register_service(gl)) { /* We failed to RequestName, but due to D-Bus activation we * might have a pending request still (on the unique name). * Process it below. * * Let's fake a shutdown signal, and still process the request below. */ - exit_code = EXIT_FAILURE; + if (!g_cancellable_is_cancelled(gl->quit_cancellable)) + exit_code = EXIT_FAILURE; gl->is_shutting_down_quitting = TRUE; } while (TRUE) { if (!c_list_is_empty(&gl->pending_jobs_lst_head)) { /* we must first reply to all requests. No matter what. */ - } else { - if (gl->is_shutting_down_quitting || gl->is_shutting_down_timeout) { - /* we either hit the idle timeout or received SIGTERM. Note that - * if we received an idle-timeout and the very moment afterwards - * a new request, then _bus_method_call() will clear gl->is_shutting_down_timeout - * (via _pending_job_register_object()). */ - break; + } else if (gl->is_shutting_down_quitting || gl->is_shutting_down_timeout) { + /* we either hit the idle timeout or received SIGTERM. Note that + * if we received an idle-timeout and the very moment afterwards + * a new request, then _bus_method_call() will clear gl->is_shutting_down_timeout + * (via _pending_job_register_object()). */ + + if (gl->name_requested) { + gs_unref_object GObject *keep_alive_object = g_object_new(G_TYPE_OBJECT, NULL); + + /* We already requested a name. To exit-on-idle without race, we need to dance. + * See https://lists.freedesktop.org/archives/dbus/2015-May/016671.html . */ + + gl->name_requested = FALSE; + gl->is_shutting_down_quitting = TRUE; + + _LOGT("shutdown: release-name"); + + /* we use the _pending_job_register_object() mechanism to make the loop busy during + * shutdown. */ + _pending_job_register_object(gl, keep_alive_object); + + r = nm_sd_notify("STOPPING=1"); + if (r < 0) + _LOGW("shutdown: sd_notifiy(STOPPING=1) failed: %s", nm_strerror_native(-r)); + else + _LOGT("shutdown: sd_notifiy(STOPPING=1) succeeded"); + + g_dbus_connection_call(gl->dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ReleaseName", + g_variant_new("(s)", NM_SUDO_DBUS_BUS_NAME), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + 10000, + NULL, + _bus_release_name_cb, + g_steal_pointer(&keep_alive_object)); + continue; } + + break; } g_main_context_iteration(NULL, TRUE); } done: + _LOGD("shutdown: cleanup"); + gl->is_shutting_down_cleanup = TRUE; - _LOGD("exiting..."); + g_cancellable_cancel(gl->quit_cancellable); nm_assert(c_list_is_empty(&gl->pending_jobs_lst_head)); @@ -609,24 +623,20 @@ done: g_dbus_connection_signal_unsubscribe(gl->dbus_connection, nm_steal_int(&gl->name_owner_changed_id)); } - nm_clear_g_cancellable(&gl->quit_cancellable); nm_clear_g_source_inst(&gl->source_sigterm); nm_clear_g_source_inst(&gl->source_idle_timeout); nm_clear_g_free(&gl->name_owner); - while (g_main_context_iteration(NULL, FALSE)) { - ; - } + nm_g_main_context_iterate_ready(NULL); if (gl->dbus_connection) { g_dbus_connection_flush_sync(gl->dbus_connection, NULL, NULL); g_clear_object(&gl->dbus_connection); - - while (g_main_context_iteration(NULL, FALSE)) { - ; - } + nm_g_main_context_iterate_ready(NULL); } + nm_clear_g_cancellable(&gl->quit_cancellable); + _LOGD("exit (%d)", exit_code); return exit_code; }