about summary refs log tree commit diff
path: root/src/nm-dispatcher/nm-dispatcher.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-dispatcher/nm-dispatcher.c')
-rw-r--r--src/nm-dispatcher/nm-dispatcher.c492
1 files changed, 348 insertions, 144 deletions
diff --git a/src/nm-dispatcher/nm-dispatcher.c b/src/nm-dispatcher/nm-dispatcher.c
index 5df32959..b29f5aca 100644
--- a/src/nm-dispatcher/nm-dispatcher.c
+++ b/src/nm-dispatcher/nm-dispatcher.c
@@ -7,38 +7,62 @@
 
 #include "libnm-client-aux-extern/nm-default-client.h"
 
-#include <syslog.h>
+#include <arpa/inet.h>
+#include <signal.h>
 #include <stdio.h>
-#include <unistd.h>
 #include <stdlib.h>
-#include <sys/types.h>
-#include <signal.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/wait.h>
-#include <arpa/inet.h>
-#include <glib-unix.h>
+#include <syslog.h>
+#include <unistd.h>
 
 #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 {
+typedef struct {
     GDBusConnection *dbus_connection;
-    GMainLoop *      loop;
-    gboolean         debug;
-    gboolean         persist;
-    guint            quit_id;
-    guint            request_id_counter;
-    gboolean         ever_acquired_name;
-    bool             exit_with_failure;
+    GCancellable *   quit_cancellable;
+
+    bool log_verbose;
+    bool log_stdout;
+
+    GSource *source_idle_timeout;
+
+    gint64 start_timestamp_msec;
+
+    guint request_id_counter;
+    guint service_regist_id;
+
+    gboolean persist;
 
     Request *current_request;
     GQueue * requests_waiting;
     int      num_requests_pending;
-} gl;
+
+    bool exit_with_failure;
+
+    bool name_requested;
+    bool reject_new_requests;
+
+    bool shutdown_timeout;
+    bool shutdown_quitting;
+} GlobalData;
+
+GlobalData gl;
 
 typedef struct {
     Request *request;
@@ -49,8 +73,8 @@ typedef struct {
     char *         error;
     gboolean       wait;
     gboolean       dispatched;
-    guint          watch_id;
-    guint          timeout_id;
+    GSource *      watch_source;
+    GSource *      timeout_source;
 } ScriptInfo;
 
 struct Request {
@@ -138,7 +162,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)
@@ -187,23 +211,35 @@ request_free(Request *request)
     g_slice_free(Request, request);
 }
 
+/*****************************************************************************/
+
 static gboolean
-quit_timeout_cb(gpointer user_data)
+_idle_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.source_idle_timeout);
+    gl.shutdown_timeout = TRUE;
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-quit_timeout_reschedule(void)
+_idle_timeout_restart(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.source_idle_timeout);
+
+    if (gl.persist)
+        return;
+
+    if (gl.shutdown_quitting)
+        return;
+
+    if (gl.num_requests_pending > 0)
+        return;
+
+    gl.source_idle_timeout = nm_g_timeout_add_source(10000, _idle_timeout_cb, NULL);
 }
 
+/*****************************************************************************/
+
 /**
  * next_request:
  *
@@ -251,7 +287,7 @@ next_request(Request *request)
  * Checks if all the scripts for the request have terminated and in such case
  * it sends the D-Bus response and releases the request resources.
  *
- * It also decreases @num_requests_pending and possibly does quit_timeout_reschedule().
+ * It also decreases @num_requests_pending and possibly does _idle_timeout_restart().
  */
 static void
 complete_request(Request *request)
@@ -287,10 +323,10 @@ 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();
+        _idle_timeout_restart();
     }
 }
 
@@ -366,8 +402,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--;
@@ -396,8 +432,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--;
@@ -418,7 +454,7 @@ again:
 
     complete_script(script);
 
-    return FALSE;
+    return G_SOURCE_CONTINUE;
 }
 
 static gboolean
@@ -515,8 +551,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_seconds_source(SCRIPT_TIMEOUT, script_timeout_cb, script);
     if (!script->wait)
         request->num_scripts_nowait++;
     return TRUE;
@@ -667,7 +704,7 @@ script_must_wait(const char *path)
 }
 
 static void
-_method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters)
+_handle_action(GDBusMethodInvocation *invocation, GVariant *parameters)
 {
     const char *     action;
     gs_unref_variant GVariant *connection              = NULL;
@@ -727,7 +764,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);
 
@@ -783,9 +820,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.source_idle_timeout);
 
     for (i = 0; i < request->scripts->len; i++) {
         ScriptInfo *s = g_ptr_array_index(request->scripts, i);
@@ -831,43 +868,49 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters)
 }
 
 static void
-on_name_acquired(GDBusConnection *connection, const char *name, gpointer user_data)
+_handle_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
-_method_call(GDBusConnection *      connection,
-             const char *           sender,
-             const char *           object_path,
-             const char *           interface_name,
-             const char *           method_name,
-             GVariant *             parameters,
-             GDBusMethodInvocation *invocation,
-             gpointer               user_data)
+_bus_method_call(GDBusConnection *      connection,
+                 const char *           sender,
+                 const char *           object_path,
+                 const char *           interface_name,
+                 const char *           method_name,
+                 GVariant *             parameters,
+                 GDBusMethodInvocation *invocation,
+                 gpointer               user_data)
 {
+    if (gl.reject_new_requests) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              G_DBUS_ERROR,
+                                              G_DBUS_ERROR_NO_SERVER,
+                                              "Server is exiting");
+        return;
+    }
     if (nm_streq(interface_name, NM_DISPATCHER_DBUS_INTERFACE)) {
         if (nm_streq(method_name, "Action")) {
-            _method_call_action(invocation, parameters);
+            _handle_action(invocation, parameters);
+            return;
+        }
+        if (nm_streq(method_name, "Ping")) {
+            _handle_ping(invocation, parameters);
             return;
         }
     }
@@ -882,6 +925,10 @@ 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(
                 NM_DEFINE_GDBUS_ARG_INFO("action", "s"),
@@ -902,9 +949,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 = _bus_method_call,
+    };
+    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,
+                                          NM_DISPATCHER_DBUS_PATH,
+                                          interface_info,
+                                          NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable),
+                                          NULL,
+                                          NULL,
+                                          &error);
+    if (gl.service_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;
+}
 
 /*****************************************************************************/
 
@@ -955,25 +1068,100 @@ logging_shutdown(void)
 }
 
 static gboolean
-signal_handler(gpointer user_data)
+_signal_callback_term(gpointer user_data)
 {
-    int signo = GPOINTER_TO_INT(user_data);
+    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;
+}
 
-    _LOG_X_I("Caught signal %d, shutting down...", signo);
-    g_main_loop_quit(gl.loop);
+/*****************************************************************************/
 
-    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.reject_new_requests = TRUE;
+    gl.num_requests_pending--;
+    g_main_context_wakeup(NULL);
+}
+
+static gboolean
+_bus_release_name(void)
+{
+    int r;
+
+    /* 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 . */
+
+    if (!gl.name_requested)
+        return FALSE;
+
+    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.source_idle_timeout);
+
+    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);
+    return TRUE;
 }
 
+/*****************************************************************************/
+
 static gboolean
-parse_command_line(int *p_argc, char ***p_argv, GError **error)
+_initial_setup(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.");
@@ -983,28 +1171,40 @@ 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_callback_term, GINT_TO_POINTER(SIGTERM));
+    source_int =
+        nm_g_unix_signal_add_source(SIGINT, _signal_callback_term, GINT_TO_POINTER(SIGINT));
+
+    gl.start_timestamp_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME);
 
-    if (!parse_command_line(&argc, &argv, &error)) {
+    gl.quit_cancellable = g_cancellable_new();
+
+    if (!_initial_setup(&argc, &argv, &error)) {
         _LOG_X_W("Error parsing command line arguments: %s", error->message);
         gl.exit_with_failure = TRUE;
         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.
@@ -1015,77 +1215,81 @@ 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;
+    _idle_timeout_restart();
+
+    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 (!gl.name_requested)
+            gl.reject_new_requests = TRUE;
     }
 
-    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);
+    while (TRUE) {
+        if (gl.shutdown_quitting)
+            _bus_release_name();
 
-    quit_timeout_reschedule();
+        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 (!_bus_release_name())
+                break;
+        }
 
-    g_main_loop_run(gl.loop);
+        g_main_context_iteration(NULL, TRUE);
+    }
 
 done:
+    gl.shutdown_quitting = TRUE;
+    g_cancellable_cancel(gl.quit_cancellable);
 
-    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);
+    nm_assert(gl.num_requests_pending == 0);
+
+    if (gl.service_regist_id != 0) {
+        g_dbus_connection_unregister_object(gl.dbus_connection,
+                                            nm_steal_int(&gl.service_regist_id));
     }
 
-    if (dbus_own_name_id != 0)
-        g_bus_unown_name(nm_steal_int(&dbus_own_name_id));
+    nm_clear_pointer(&gl.requests_waiting, g_queue_free);
 
-    if (dbus_regist_id != 0)
-        g_dbus_connection_unregister_object(gl.dbus_connection, nm_steal_int(&dbus_regist_id));
+    nm_clear_g_source_inst(&gl.source_idle_timeout);
 
-    nm_clear_pointer(&gl.requests_waiting, g_queue_free);
+    if (gl.dbus_connection) {
+        g_dbus_connection_flush_sync(gl.dbus_connection, NULL, NULL);
+        g_clear_object(&gl.dbus_connection);
+    }
 
-    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_g_main_context_iterate_ready(NULL);
 
-    if (!gl.debug)
+    _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;
 }