diff options
Diffstat (limited to 'src/nm-dispatcher/nm-dispatcher.c')
| -rw-r--r-- | src/nm-dispatcher/nm-dispatcher.c | 492 |
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; } |