mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-09 00:40:19 +01:00
dispatcher: merge branch 'bg/dispatch-sync-requests-rh746703'
Allow scripts to be marked as "no-wait", so that the dispatcher will schedule them immediately and in parallel with other no-wait scripts. This is particularly useful for pre-up scripts to avoid excessive delays when activating multiple interfaces in parallel. https://bugzilla.gnome.org/show_bug.cgi?id=746703
This commit is contained in:
commit
127a4c5d9e
8 changed files with 393 additions and 134 deletions
|
|
@ -94,6 +94,7 @@ install-data-hook:
|
|||
$(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)
|
||||
$(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/pre-down.d
|
||||
$(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/pre-up.d
|
||||
$(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/no-wait.d
|
||||
|
||||
CLEANFILES = $(nodist_libnmdbus_dispatcher_la_SOURCES) $(dbusactivation_DATA)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#define NMD_SCRIPT_DIR_DEFAULT NMCONFDIR "/dispatcher.d"
|
||||
#define NMD_SCRIPT_DIR_PRE_UP NMD_SCRIPT_DIR_DEFAULT "/pre-up.d"
|
||||
#define NMD_SCRIPT_DIR_PRE_DOWN NMD_SCRIPT_DIR_DEFAULT "/pre-down.d"
|
||||
#define NMD_SCRIPT_DIR_NO_WAIT NMD_SCRIPT_DIR_DEFAULT "/no-wait.d"
|
||||
|
||||
#define NM_DISPATCHER_DBUS_SERVICE "org.freedesktop.nm_dispatcher"
|
||||
#define NM_DISPATCHER_DBUS_INTERFACE "org.freedesktop.nm_dispatcher"
|
||||
|
|
|
|||
|
|
@ -331,7 +331,8 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
const char *vpn_ip_iface,
|
||||
GVariant *vpn_ip4_props,
|
||||
GVariant *vpn_ip6_props,
|
||||
char **out_iface)
|
||||
char **out_iface,
|
||||
const char **out_error_message)
|
||||
{
|
||||
const char *iface = NULL, *ip_iface = NULL;
|
||||
const char *uuid = NULL, *id = NULL, *path = NULL;
|
||||
|
|
@ -343,6 +344,10 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
GSList *items = NULL, *iter;
|
||||
guint i;
|
||||
GVariant *con_setting;
|
||||
const char *error_message_backup;
|
||||
|
||||
if (!out_error_message)
|
||||
out_error_message = &error_message_backup;
|
||||
|
||||
g_return_val_if_fail (action != NULL, NULL);
|
||||
g_return_val_if_fail (out_iface != NULL, NULL);
|
||||
|
|
@ -354,7 +359,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
|
||||
/* Connection properties */
|
||||
if (!g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_PATH, "&o", &path)) {
|
||||
g_warning ("Missing or invalid required value " NMD_CONNECTION_PROPS_PATH "!");
|
||||
*out_error_message = "Missing or invalid required value " NMD_CONNECTION_PROPS_PATH "!";
|
||||
return NULL;
|
||||
}
|
||||
items = g_slist_prepend (items, g_strdup_printf ("CONNECTION_DBUS_PATH=%s", path));
|
||||
|
|
@ -374,7 +379,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
|
||||
/* interface name */
|
||||
if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE, "&s", &iface)) {
|
||||
g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!");
|
||||
*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!";
|
||||
return NULL;
|
||||
}
|
||||
if (!*iface)
|
||||
|
|
@ -384,7 +389,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
value = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, NULL);
|
||||
if (value) {
|
||||
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
|
||||
g_warning ("Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!");
|
||||
*out_error_message = "Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!";
|
||||
return NULL;
|
||||
}
|
||||
g_variant_unref (value);
|
||||
|
|
@ -393,14 +398,14 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
|
||||
/* Device type */
|
||||
if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_TYPE, "u", NULL)) {
|
||||
g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!");
|
||||
*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Device state */
|
||||
value = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_STATE, G_VARIANT_TYPE_UINT32);
|
||||
if (!value) {
|
||||
g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!");
|
||||
*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!";
|
||||
return NULL;
|
||||
}
|
||||
dev_state = g_variant_get_uint32 (value);
|
||||
|
|
@ -408,25 +413,25 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
|
||||
/* device itself */
|
||||
if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_PATH, "o", NULL)) {
|
||||
g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!");
|
||||
*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* UUID and ID */
|
||||
con_setting = g_variant_lookup_value (connection_dict, NM_SETTING_CONNECTION_SETTING_NAME, NM_VARIANT_TYPE_SETTING);
|
||||
if (!con_setting) {
|
||||
g_warning ("Failed to read connection setting");
|
||||
*out_error_message = "Failed to read connection setting";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_UUID, "&s", &uuid)) {
|
||||
g_warning ("Connection hash did not contain the UUID");
|
||||
*out_error_message = "Connection hash did not contain the UUID";
|
||||
g_variant_unref (con_setting);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_ID, "&s", &id)) {
|
||||
g_warning ("Connection hash did not contain the ID");
|
||||
*out_error_message = "Connection hash did not contain the ID";
|
||||
g_variant_unref (con_setting);
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -473,6 +478,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
envp[i] = (char *) iter->data;
|
||||
g_slist_free (items);
|
||||
|
||||
*out_error_message = NULL;
|
||||
return envp;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ nm_dispatcher_utils_construct_envp (const char *action,
|
|||
const char *vpn_ip_iface,
|
||||
GVariant *vpn_ip4_props,
|
||||
GVariant *vpn_ip6_props,
|
||||
char **out_iface);
|
||||
char **out_iface,
|
||||
const char **out_error_message);
|
||||
|
||||
#endif /* __NETWORKMANAGER_DISPATCHER_UTILS_H__ */
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
#include "nm-default.h"
|
||||
#include "nm-dispatcher-api.h"
|
||||
#include "nm-dispatcher-utils.h"
|
||||
#include "nm-macros-internal.h"
|
||||
#include "gsystem-local-alloc.h"
|
||||
|
||||
#include "nmdbus-dispatcher.h"
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ static GMainLoop *loop = NULL;
|
|||
static gboolean debug = FALSE;
|
||||
static gboolean persist = FALSE;
|
||||
static guint quit_id;
|
||||
static guint request_id_counter = 0;
|
||||
|
||||
typedef struct Request Request;
|
||||
|
||||
|
|
@ -54,7 +57,8 @@ typedef struct {
|
|||
NMDBusDispatcher *dbus_dispatcher;
|
||||
|
||||
Request *current_request;
|
||||
GQueue *pending_requests;
|
||||
GQueue *requests_waiting;
|
||||
gint num_requests_pending;
|
||||
} Handler;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -89,7 +93,7 @@ handle_action (NMDBusDispatcher *dbus_dispatcher,
|
|||
static void
|
||||
handler_init (Handler *h)
|
||||
{
|
||||
h->pending_requests = g_queue_new ();
|
||||
h->requests_waiting = g_queue_new ();
|
||||
h->dbus_dispatcher = nmdbus_dispatcher_skeleton_new ();
|
||||
g_signal_connect (h->dbus_dispatcher, "handle-action",
|
||||
G_CALLBACK (handle_action), h);
|
||||
|
|
@ -100,7 +104,7 @@ handler_class_init (HandlerClass *h_class)
|
|||
{
|
||||
}
|
||||
|
||||
static void dispatch_one_script (Request *request);
|
||||
static gboolean dispatch_one_script (Request *request);
|
||||
|
||||
typedef struct {
|
||||
Request *request;
|
||||
|
|
@ -109,11 +113,17 @@ typedef struct {
|
|||
GPid pid;
|
||||
DispatchResult result;
|
||||
char *error;
|
||||
gboolean wait;
|
||||
gboolean dispatched;
|
||||
guint watch_id;
|
||||
guint timeout_id;
|
||||
} ScriptInfo;
|
||||
|
||||
struct Request {
|
||||
Handler *handler;
|
||||
|
||||
guint request_id;
|
||||
|
||||
GDBusMethodInvocation *context;
|
||||
char *action;
|
||||
char *iface;
|
||||
|
|
@ -122,11 +132,61 @@ struct Request {
|
|||
|
||||
GPtrArray *scripts; /* list of ScriptInfo */
|
||||
guint idx;
|
||||
|
||||
guint script_watch_id;
|
||||
guint script_timeout_id;
|
||||
gint num_scripts_done;
|
||||
gint num_scripts_nowait;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
#define __LOG_print(print_cmd, _request, _script, ...) \
|
||||
G_STMT_START { \
|
||||
nm_assert ((_request) && (!(_script) || (_script)->request == (_request))); \
|
||||
print_cmd ("#%u '%s'%s%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
|
||||
(_request)->request_id, \
|
||||
(_request)->action, \
|
||||
(_request)->iface ? " [" : "", \
|
||||
(_request)->iface ? (_request)->iface : "", \
|
||||
(_request)->iface ? "]" : "", \
|
||||
(_script) ? ", \"" : "", \
|
||||
(_script) ? (_script)->script : "", \
|
||||
(_script) ? "\"" : "" \
|
||||
_NM_UTILS_MACRO_REST (__VA_ARGS__)); \
|
||||
} G_STMT_END
|
||||
|
||||
#define _LOG(_request, _script, log_always, print_cmd, ...) \
|
||||
G_STMT_START { \
|
||||
const Request *__request = (_request); \
|
||||
const ScriptInfo *__script = (_script); \
|
||||
\
|
||||
if (!__request) \
|
||||
__request = __script->request; \
|
||||
nm_assert (__request && (!__script || __script->request == __request)); \
|
||||
if ((log_always) || __request->debug) { \
|
||||
if (FALSE) { \
|
||||
/* g_message() alone does not warn about invalid format. Add a dummy printf() statement to
|
||||
* get a compiler warning about wrong format. */ \
|
||||
__LOG_print (printf, __request, __script, __VA_ARGS__); \
|
||||
} \
|
||||
__LOG_print (print_cmd, __request, __script, __VA_ARGS__); \
|
||||
} \
|
||||
} G_STMT_END
|
||||
|
||||
static gboolean
|
||||
_LOG_R_D_enabled (const Request *request)
|
||||
{
|
||||
return request->debug;
|
||||
}
|
||||
|
||||
#define _LOG_R_D(_request, ...) _LOG(_request, NULL, FALSE, g_message, __VA_ARGS__)
|
||||
#define _LOG_R_I(_request, ...) _LOG(_request, NULL, TRUE, g_message, __VA_ARGS__)
|
||||
#define _LOG_R_W(_request, ...) _LOG(_request, NULL, TRUE, g_warning, __VA_ARGS__)
|
||||
|
||||
#define _LOG_S_D(_script, ...) _LOG(NULL, _script, FALSE, g_message, __VA_ARGS__)
|
||||
#define _LOG_S_I(_script, ...) _LOG(NULL, _script, TRUE, g_message, __VA_ARGS__)
|
||||
#define _LOG_S_W(_script, ...) _LOG(NULL, _script, TRUE, g_warning, __VA_ARGS__)
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
script_info_free (gpointer ptr)
|
||||
{
|
||||
|
|
@ -134,17 +194,22 @@ script_info_free (gpointer ptr)
|
|||
|
||||
g_free (info->script);
|
||||
g_free (info->error);
|
||||
g_free (info);
|
||||
g_slice_free (ScriptInfo, info);
|
||||
}
|
||||
|
||||
static void
|
||||
request_free (Request *request)
|
||||
{
|
||||
g_assert_cmpuint (request->num_scripts_done, ==, request->scripts->len);
|
||||
g_assert_cmpuint (request->num_scripts_nowait, ==, 0);
|
||||
|
||||
g_free (request->action);
|
||||
g_free (request->iface);
|
||||
g_strfreev (request->envp);
|
||||
if (request->scripts)
|
||||
g_ptr_array_free (request->scripts, TRUE);
|
||||
|
||||
g_slice_free (Request, request);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
|
@ -154,65 +219,79 @@ quit_timeout_cb (gpointer user_data)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
quit_timeout_cancel (void)
|
||||
{
|
||||
if (quit_id) {
|
||||
g_source_remove (quit_id);
|
||||
quit_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
quit_timeout_reschedule (void)
|
||||
{
|
||||
quit_timeout_cancel ();
|
||||
if (!persist)
|
||||
if (!persist) {
|
||||
nm_clear_g_source (&quit_id);
|
||||
quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_request (Request *request)
|
||||
/**
|
||||
* next_request:
|
||||
*
|
||||
* @h: the handler
|
||||
* @request: (allow-none): the request to set as next. If %NULL, dequeue the next
|
||||
* waiting request. Otherwise, try to set the given request.
|
||||
*
|
||||
* Sets the currently active request (@current_request). The current request
|
||||
* is a request that has at least on "wait" script, because requests that only
|
||||
* consist of "no-wait" scripts are handled right away and not enqueued to
|
||||
* @requests_waiting nor set as @current_request.
|
||||
*
|
||||
* Returns: %TRUE, if there was currently not request in process and it set
|
||||
* a new request as current.
|
||||
*/
|
||||
static gboolean
|
||||
next_request (Handler *h, Request *request)
|
||||
{
|
||||
if (request->iface)
|
||||
g_message ("Dispatching action '%s' for %s", request->action, request->iface);
|
||||
else
|
||||
g_message ("Dispatching action '%s'", request->action);
|
||||
|
||||
request->handler->current_request = request;
|
||||
dispatch_one_script (request);
|
||||
}
|
||||
|
||||
static void
|
||||
next_request (Handler *h)
|
||||
{
|
||||
Request *request = g_queue_pop_head (h->pending_requests);
|
||||
|
||||
if (request) {
|
||||
start_request (request);
|
||||
return;
|
||||
if (h->current_request) {
|
||||
g_queue_push_tail (h->requests_waiting, request);
|
||||
return FALSE;
|
||||
}
|
||||
} else {
|
||||
/* when calling next_request() without explicit @request, we always
|
||||
* forcefully clear @current_request. That one is certainly
|
||||
* handled already. */
|
||||
h->current_request = NULL;
|
||||
|
||||
request = g_queue_pop_head (h->requests_waiting);
|
||||
if (!request)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
h->current_request = NULL;
|
||||
quit_timeout_reschedule ();
|
||||
_LOG_R_I (request, "start running ordered scripts...");
|
||||
|
||||
h->current_request = request;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
next_script (gpointer user_data)
|
||||
/**
|
||||
* complete_request:
|
||||
* @request: the 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().
|
||||
*/
|
||||
static void
|
||||
complete_request (Request *request)
|
||||
{
|
||||
Request *request = user_data;
|
||||
Handler *h = request->handler;
|
||||
GVariantBuilder results;
|
||||
GVariant *ret;
|
||||
guint i;
|
||||
Handler *handler = request->handler;
|
||||
|
||||
request->idx++;
|
||||
if (request->idx < request->scripts->len) {
|
||||
dispatch_one_script (request);
|
||||
return FALSE;
|
||||
}
|
||||
nm_assert (request);
|
||||
|
||||
/* Are there still pending scripts? Then do nothing (for now). */
|
||||
if (request->num_scripts_done < request->scripts->len)
|
||||
return;
|
||||
|
||||
/* All done */
|
||||
g_variant_builder_init (&results, G_VARIANT_TYPE ("a(sus)"));
|
||||
for (i = 0; i < request->scripts->len; i++) {
|
||||
ScriptInfo *script = g_ptr_array_index (request->scripts, i);
|
||||
|
|
@ -226,16 +305,74 @@ next_script (gpointer user_data)
|
|||
ret = g_variant_new ("(a(sus))", &results);
|
||||
g_dbus_method_invocation_return_value (request->context, ret);
|
||||
|
||||
if (request->debug) {
|
||||
if (request->iface)
|
||||
g_message ("Dispatch '%s' on %s complete", request->action, request->iface);
|
||||
else
|
||||
g_message ("Dispatch '%s' complete", request->action);
|
||||
}
|
||||
_LOG_R_D (request, "completed (%u scripts)", request->scripts->len);
|
||||
|
||||
if (handler->current_request == request)
|
||||
handler->current_request = NULL;
|
||||
|
||||
request_free (request);
|
||||
|
||||
next_request (h);
|
||||
return FALSE;
|
||||
g_assert_cmpuint (handler->num_requests_pending, >, 0);
|
||||
if (--handler->num_requests_pending <= 0) {
|
||||
nm_assert (!handler->current_request && !g_queue_peek_head (handler->requests_waiting));
|
||||
quit_timeout_reschedule ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
complete_script (ScriptInfo *script)
|
||||
{
|
||||
Handler *handler;
|
||||
gboolean wait = script->wait;
|
||||
|
||||
if (wait) {
|
||||
/* for "wait" scripts, try to schedule the next blocking script.
|
||||
* If that is successful, return (as we must wait for its completion). */
|
||||
if (dispatch_one_script (script->request))
|
||||
return;
|
||||
}
|
||||
|
||||
handler = script->request->handler;
|
||||
|
||||
nm_assert (!wait || handler->current_request == script->request);
|
||||
|
||||
/* Try to complete the request. */
|
||||
complete_request (script->request);
|
||||
|
||||
if (!wait) {
|
||||
/* this was a "no-wait" script. We either completed the request,
|
||||
* or there is nothing to do. Especially, there is no need to
|
||||
* queue the next_request() -- because no-wait scripts don't block
|
||||
* requests. However, if this was the last "no-wait" script and
|
||||
* there are "wait" scripts ready to run, launch them.
|
||||
*/
|
||||
if ( script->request->num_scripts_nowait == 0
|
||||
&& handler->current_request == script->request) {
|
||||
|
||||
if (dispatch_one_script (script->request))
|
||||
return;
|
||||
|
||||
complete_request (script->request);
|
||||
} else
|
||||
return;
|
||||
}
|
||||
|
||||
while (next_request (handler, NULL)) {
|
||||
Request *request;
|
||||
|
||||
request = handler->current_request;
|
||||
|
||||
if (dispatch_one_script (request))
|
||||
return;
|
||||
|
||||
/* Try to complete the request. It will be either completed
|
||||
* now, or when all pending "no-wait" scripts return. */
|
||||
complete_request (request);
|
||||
|
||||
/* We can immediately start next_request(), because our current
|
||||
* @request has obviously no more "wait" scripts either.
|
||||
* Repeat... */
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -246,9 +383,11 @@ script_watch_cb (GPid pid, gint status, gpointer user_data)
|
|||
|
||||
g_assert (pid == script->pid);
|
||||
|
||||
script->request->script_watch_id = 0;
|
||||
g_source_remove (script->request->script_timeout_id);
|
||||
script->request->script_timeout_id = 0;
|
||||
script->watch_id = 0;
|
||||
nm_clear_g_source (&script->timeout_id);
|
||||
script->request->num_scripts_done++;
|
||||
if (!script->wait)
|
||||
script->request->num_scripts_nowait--;
|
||||
|
||||
if (WIFEXITED (status)) {
|
||||
err = WEXITSTATUS (status);
|
||||
|
|
@ -270,15 +409,15 @@ script_watch_cb (GPid pid, gint status, gpointer user_data)
|
|||
}
|
||||
|
||||
if (script->result == DISPATCH_RESULT_SUCCESS) {
|
||||
if (script->request->debug)
|
||||
g_message ("Script '%s' complete", script->script);
|
||||
_LOG_S_D (script, "complete");
|
||||
} else {
|
||||
script->result = DISPATCH_RESULT_FAILED;
|
||||
g_warning ("%s", script->error);
|
||||
_LOG_S_W (script, "complete: failed with %s", script->error);
|
||||
}
|
||||
|
||||
g_spawn_close_pid (script->pid);
|
||||
next_script (script->request);
|
||||
|
||||
complete_script (script);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
|
@ -286,11 +425,13 @@ script_timeout_cb (gpointer user_data)
|
|||
{
|
||||
ScriptInfo *script = user_data;
|
||||
|
||||
g_source_remove (script->request->script_watch_id);
|
||||
script->request->script_watch_id = 0;
|
||||
script->request->script_timeout_id = 0;
|
||||
script->timeout_id = 0;
|
||||
nm_clear_g_source (&script->watch_id);
|
||||
script->request->num_scripts_done++;
|
||||
if (!script->wait)
|
||||
script->request->num_scripts_nowait--;
|
||||
|
||||
g_warning ("Script '%s' took too long; killing it.", script->script);
|
||||
_LOG_S_W (script, "complete: timeout (kill script)");
|
||||
|
||||
kill (script->pid, SIGKILL);
|
||||
again:
|
||||
|
|
@ -303,7 +444,9 @@ again:
|
|||
script->result = DISPATCH_RESULT_TIMEOUT;
|
||||
|
||||
g_spawn_close_pid (script->pid);
|
||||
g_idle_add (next_script, script->request);
|
||||
|
||||
complete_script (script);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
@ -364,12 +507,17 @@ check_filename (const char *file_name)
|
|||
|
||||
#define SCRIPT_TIMEOUT 600 /* 10 minutes */
|
||||
|
||||
static void
|
||||
dispatch_one_script (Request *request)
|
||||
static gboolean
|
||||
script_dispatch (ScriptInfo *script)
|
||||
{
|
||||
GError *error = NULL;
|
||||
gchar *argv[4];
|
||||
ScriptInfo *script = g_ptr_array_index (request->scripts, request->idx);
|
||||
Request *request = script->request;
|
||||
|
||||
if (script->dispatched)
|
||||
return FALSE;
|
||||
|
||||
script->dispatched = TRUE;
|
||||
|
||||
argv[0] = script->script;
|
||||
argv[1] = request->iface
|
||||
|
|
@ -378,24 +526,41 @@ dispatch_one_script (Request *request)
|
|||
argv[2] = request->action;
|
||||
argv[3] = NULL;
|
||||
|
||||
if (request->debug)
|
||||
g_message ("Running script '%s'", script->script);
|
||||
_LOG_S_D (script, "run script%s", script->wait ? "" : " (no-wait)");
|
||||
|
||||
if (g_spawn_async ("/", argv, request->envp, G_SPAWN_DO_NOT_REAP_CHILD, NULL, request, &script->pid, &error)) {
|
||||
request->script_watch_id = g_child_watch_add (script->pid, (GChildWatchFunc) script_watch_cb, script);
|
||||
request->script_timeout_id = g_timeout_add_seconds (SCRIPT_TIMEOUT, script_timeout_cb, script);
|
||||
if (g_spawn_async ("/", argv, request->envp, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &script->pid, &error)) {
|
||||
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);
|
||||
if (!script->wait)
|
||||
request->num_scripts_nowait++;
|
||||
return TRUE;
|
||||
} else {
|
||||
g_warning ("Failed to execute script '%s': (%d) %s",
|
||||
script->script, error->code, error->message);
|
||||
_LOG_S_W (script, "complete: failed to execute script: %s (%d)",
|
||||
error->message, error->code);
|
||||
script->result = DISPATCH_RESULT_EXEC_FAILED;
|
||||
script->error = g_strdup (error->message);
|
||||
request->num_scripts_done++;
|
||||
g_clear_error (&error);
|
||||
|
||||
/* Try the next script */
|
||||
g_idle_add (next_script, request);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dispatch_one_script (Request *request)
|
||||
{
|
||||
if (request->num_scripts_nowait > 0)
|
||||
return TRUE;
|
||||
|
||||
while (request->idx < request->scripts->len) {
|
||||
ScriptInfo *script;
|
||||
|
||||
script = g_ptr_array_index (request->scripts, request->idx++);
|
||||
if (script_dispatch (script))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GSList *
|
||||
find_scripts (const char *str_action)
|
||||
{
|
||||
|
|
@ -415,7 +580,7 @@ find_scripts (const char *str_action)
|
|||
dirname = NMD_SCRIPT_DIR_DEFAULT;
|
||||
|
||||
if (!(dir = g_dir_open (dirname, 0, &error))) {
|
||||
g_message ("Failed to open dispatcher directory '%s': (%d) %s",
|
||||
g_message ("find-scripts: Failed to open dispatcher directory '%s': (%d) %s",
|
||||
dirname, error->code, error->message);
|
||||
g_error_free (error);
|
||||
return NULL;
|
||||
|
|
@ -434,11 +599,11 @@ find_scripts (const char *str_action)
|
|||
|
||||
err = stat (path, &st);
|
||||
if (err)
|
||||
g_warning ("Failed to stat '%s': %d", path, err);
|
||||
g_warning ("find-scripts: Failed to stat '%s': %d", path, err);
|
||||
else if (S_ISDIR (st.st_mode))
|
||||
; /* silently skip. */
|
||||
else if (!check_permissions (&st, &err_msg))
|
||||
g_warning ("Cannot execute '%s': %s", path, err_msg);
|
||||
g_warning ("find-scripts: Cannot execute '%s': %s", path, err_msg);
|
||||
else {
|
||||
/* success */
|
||||
sorted = g_slist_insert_sorted (sorted, path, (GCompareFunc) g_strcmp0);
|
||||
|
|
@ -451,6 +616,34 @@ find_scripts (const char *str_action)
|
|||
return sorted;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
script_must_wait (const char *path)
|
||||
{
|
||||
gs_free char *link = NULL;
|
||||
gs_free char *dir = NULL;
|
||||
gs_free char *real = NULL;
|
||||
char *tmp;
|
||||
|
||||
link = g_file_read_link (path, NULL);
|
||||
if (link) {
|
||||
if (!g_path_is_absolute (link)) {
|
||||
dir = g_path_get_dirname (path);
|
||||
tmp = g_build_path ("/", dir, link, NULL);
|
||||
g_free (link);
|
||||
g_free (dir);
|
||||
link = tmp;
|
||||
}
|
||||
|
||||
dir = g_path_get_dirname (link);
|
||||
real = realpath (dir, NULL);
|
||||
|
||||
if (real && !strcmp (real, NMD_SCRIPT_DIR_NO_WAIT))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_action (NMDBusDispatcher *dbus_dispatcher,
|
||||
GDBusMethodInvocation *context,
|
||||
|
|
@ -473,21 +666,13 @@ handle_action (NMDBusDispatcher *dbus_dispatcher,
|
|||
GSList *iter;
|
||||
Request *request;
|
||||
char **p;
|
||||
char *iface = NULL;
|
||||
guint i, num_nowait = 0;
|
||||
const char *error_message = NULL;
|
||||
|
||||
sorted_scripts = find_scripts (str_action);
|
||||
|
||||
if (!sorted_scripts) {
|
||||
GVariant *results;
|
||||
|
||||
results = g_variant_new_array (G_VARIANT_TYPE ("(sus)"), NULL, 0);
|
||||
g_dbus_method_invocation_return_value (context, g_variant_new ("(@a(sus))", results));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
quit_timeout_cancel ();
|
||||
|
||||
request = g_malloc0 (sizeof (*request));
|
||||
request = g_slice_new0 (Request);
|
||||
request->request_id = ++request_id_counter;
|
||||
request->handler = h;
|
||||
request->debug = request_debug || debug;
|
||||
request->context = context;
|
||||
|
|
@ -504,31 +689,86 @@ handle_action (NMDBusDispatcher *dbus_dispatcher,
|
|||
vpn_ip_iface,
|
||||
vpn_ip4_props,
|
||||
vpn_ip6_props,
|
||||
&iface);
|
||||
|
||||
if (request->debug) {
|
||||
g_message ("------------ Action ID %p '%s' Interface %s Environment ------------",
|
||||
context, str_action, iface ? iface : "(none)");
|
||||
for (p = request->envp; *p; p++)
|
||||
g_message (" %s", *p);
|
||||
g_message ("\n");
|
||||
}
|
||||
|
||||
request->iface = g_strdup (iface);
|
||||
&request->iface,
|
||||
&error_message);
|
||||
|
||||
request->scripts = g_ptr_array_new_full (5, script_info_free);
|
||||
for (iter = sorted_scripts; iter; iter = g_slist_next (iter)) {
|
||||
ScriptInfo *s = g_malloc0 (sizeof (*s));
|
||||
ScriptInfo *s;
|
||||
|
||||
s = g_slice_new0 (ScriptInfo);
|
||||
s->request = request;
|
||||
s->script = iter->data;
|
||||
s->wait = script_must_wait (s->script);
|
||||
g_ptr_array_add (request->scripts, s);
|
||||
}
|
||||
g_slist_free (sorted_scripts);
|
||||
|
||||
if (h->current_request)
|
||||
g_queue_push_tail (h->pending_requests, request);
|
||||
else
|
||||
start_request (request);
|
||||
_LOG_R_I (request, "new request (%u scripts)", request->scripts->len);
|
||||
if (_LOG_R_D_enabled (request)) {
|
||||
for (p = request->envp; *p; p++)
|
||||
_LOG_R_D (request, "environment: %s", *p);
|
||||
}
|
||||
|
||||
if (error_message || request->scripts->len == 0) {
|
||||
GVariant *results;
|
||||
|
||||
if (error_message)
|
||||
_LOG_R_W (request, "completed: invalid request: %s", error_message);
|
||||
else
|
||||
_LOG_R_I (request, "completed: no scripts");
|
||||
|
||||
results = g_variant_new_array (G_VARIANT_TYPE ("(sus)"), NULL, 0);
|
||||
g_dbus_method_invocation_return_value (context, g_variant_new ("(@a(sus))", results));
|
||||
request_free (request);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
nm_clear_g_source (&quit_id);
|
||||
|
||||
h->num_requests_pending++;
|
||||
|
||||
for (i = 0; i < request->scripts->len; i++) {
|
||||
ScriptInfo *s = g_ptr_array_index (request->scripts, i);
|
||||
|
||||
if (!s->wait) {
|
||||
script_dispatch (s);
|
||||
num_nowait++;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_nowait < request->scripts->len) {
|
||||
/* The request has at least one wait script.
|
||||
* Try next_request() to schedule the request for
|
||||
* execution. This either enqueues the request or
|
||||
* sets it as h->current_request. */
|
||||
if (next_request (h, request)) {
|
||||
/* @request is now @current_request. Go ahead and
|
||||
* schedule the first wait script. */
|
||||
if (!dispatch_one_script (request)) {
|
||||
/* If that fails, we might be already finished with the
|
||||
* request. Try complete_request(). */
|
||||
complete_request (request);
|
||||
|
||||
if (next_request (h, NULL)) {
|
||||
/* As @request was successfully scheduled as next_request(), there is no
|
||||
* other request in queue that can be scheduled afterwards. Assert against
|
||||
* that, but call next_request() to clear current_request. */
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* The request contains only no-wait scripts. Try to complete
|
||||
* the request right away (we might have failed to schedule any
|
||||
* of the scripts). It will be either completed now, or later
|
||||
* when the pending scripts return.
|
||||
* We don't enqueue it to h->requests_waiting.
|
||||
* There is no need to handle next_request(), because @request is
|
||||
* not the current request anyway and does not interfere with requests
|
||||
* that have any "wait" scripts. */
|
||||
complete_request (request);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -571,7 +811,7 @@ log_handler (const gchar *log_domain,
|
|||
const gchar *message,
|
||||
gpointer ignored)
|
||||
{
|
||||
int syslog_priority;
|
||||
int syslog_priority;
|
||||
|
||||
switch (log_level) {
|
||||
case G_LOG_LEVEL_ERROR:
|
||||
|
|
@ -603,7 +843,7 @@ static void
|
|||
logging_setup (void)
|
||||
{
|
||||
openlog (G_LOG_DOMAIN, LOG_CONS, LOG_DAEMON);
|
||||
g_log_set_handler (G_LOG_DOMAIN,
|
||||
g_log_set_handler (G_LOG_DOMAIN,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
|
||||
log_handler,
|
||||
NULL);
|
||||
|
|
@ -645,7 +885,7 @@ main (int argc, char **argv)
|
|||
g_option_context_add_main_entries (opt_ctx, entries, NULL);
|
||||
|
||||
if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) {
|
||||
g_warning ("%s\n", error->message);
|
||||
g_warning ("Error parsing command line arguments: %s", error->message);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -689,12 +929,11 @@ main (int argc, char **argv)
|
|||
NULL, NULL);
|
||||
g_object_unref (bus);
|
||||
|
||||
if (!persist)
|
||||
quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
|
||||
quit_timeout_reschedule ();
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
g_queue_free (handler->pending_requests);
|
||||
g_queue_free (handler->requests_waiting);
|
||||
g_object_unref (handler);
|
||||
|
||||
if (!debug)
|
||||
|
|
|
|||
|
|
@ -458,6 +458,7 @@ test_generic (const char *file, const char *override_vpn_ip_iface)
|
|||
char *expected_iface = NULL;
|
||||
char *action = NULL;
|
||||
char *out_iface = NULL;
|
||||
const char *error_message = NULL;
|
||||
GHashTable *expected_env = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
|
|
@ -497,7 +498,13 @@ test_generic (const char *file, const char *override_vpn_ip_iface)
|
|||
override_vpn_ip_iface ? override_vpn_ip_iface : vpn_ip_iface,
|
||||
vpn_ip4_props,
|
||||
vpn_ip6_props,
|
||||
&out_iface);
|
||||
&out_iface,
|
||||
&error_message);
|
||||
|
||||
g_assert ((!denv && error_message) || (denv && !error_message));
|
||||
|
||||
if (error_message)
|
||||
g_warning (error_message);
|
||||
|
||||
/* Print out environment for now */
|
||||
#ifdef DEBUG
|
||||
|
|
|
|||
|
|
@ -459,6 +459,7 @@ mkdir -p $RPM_BUILD_ROOT%{nmlibdir}/VPN
|
|||
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/dispatcher.d
|
||||
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/dispatcher.d/pre-up.d
|
||||
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/dispatcher.d/pre-down.d
|
||||
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/dispatcher.d/no-wait.d
|
||||
%{__cp} examples/dispatcher/10-ifcfg-rh-routes.sh $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/dispatcher.d/
|
||||
%{__ln_s} ../10-ifcfg-rh-routes.sh $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/dispatcher.d/pre-up.d/
|
||||
|
||||
|
|
@ -527,6 +528,7 @@ fi
|
|||
%{_sysconfdir}/%{name}/dispatcher.d/10-ifcfg-rh-routes.sh
|
||||
%dir %{_sysconfdir}/%{name}/dispatcher.d/pre-down.d
|
||||
%dir %{_sysconfdir}/%{name}/dispatcher.d/pre-up.d
|
||||
%dir %{_sysconfdir}/%{name}/dispatcher.d/no-wait.d
|
||||
%{_sysconfdir}/%{name}/dispatcher.d/pre-up.d/10-ifcfg-rh-routes.sh
|
||||
%dir %{_sysconfdir}/%{name}/dnsmasq.d
|
||||
%dir %{_sysconfdir}/%{name}/VPN
|
||||
|
|
|
|||
|
|
@ -270,10 +270,12 @@
|
|||
Dispatcher scripts are run one at a time, but asynchronously from the main
|
||||
NetworkManager process, and will be killed if they run for too long. If your script
|
||||
might take arbitrarily long to complete, you should spawn a child process and have the
|
||||
parent return immediately. Also beware that once a script is queued, it will always be
|
||||
run, even if a later event renders it obsolete. (Eg, if an interface goes up, and then
|
||||
back down again quickly, it is possible that one or more "up" scripts will be run
|
||||
after the interface has gone down.)
|
||||
parent return immediately. Scripts that are symbolic links pointing inside the
|
||||
/etc/NetworkManager/dispatcher.d/no-wait.d/ directory are run immediately, without
|
||||
waiting for the termination of previous scripts, and in parallel. Also beware that
|
||||
once a script is queued, it will always be run, even if a later event renders it
|
||||
obsolete. (Eg, if an interface goes up, and then back down again quickly, it is
|
||||
possible that one or more "up" scripts will be run after the interface has gone down.)
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue