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:
Beniamino Galvani 2015-08-25 16:24:49 +02:00
commit 127a4c5d9e
8 changed files with 393 additions and 134 deletions

View file

@ -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)

View file

@ -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"

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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>