nm-sudo,dispatcher: merge branch 'th/nm-sudo-exit-on-idle-race'

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/950
This commit is contained in:
Thomas Haller 2021-08-04 09:53:51 +02:00
commit 4513d4db63
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
12 changed files with 720 additions and 261 deletions

View file

@ -5,6 +5,13 @@ Description=Network Manager Script Dispatcher Service
Type=dbus
BusName=org.freedesktop.nm_dispatcher
ExecStart=@libexecdir@/nm-dispatcher
NotifyAccess=main
# Enable debug logging in dispatcher service. Note that dispatcher
# also honors debug logging requests from NetworkManager, so you
# can also control logging requests with
# `nmcli general logging domain DISPATCHER level TRACE`.
#Environment=NM_DISPATCHER_DEBUG_LOG=1
# We want to allow scripts to spawn long-running daemons, so tell
# systemd to not clean up when nm-dispatcher exits

View file

@ -18,6 +18,7 @@ Description=NetworkManager Sudo Helper
Type=dbus
BusName=org.freedesktop.nm.sudo
ExecStart=@libexecdir@/nm-sudo
NotifyAccess=main
# Extra configuration options. Set via `systemctl edit nm-sudo.service`:
#

View file

@ -1,4 +1,5 @@
contrib/fedora/rpm/
data/NetworkManager-dispatcher.service.in
data/NetworkManager-wait-online.service.in
data/NetworkManager.service.in
data/nm-sudo.service.in

View file

@ -7,6 +7,8 @@
#include <sys/socket.h>
#include <sys/un.h>
#include "libnm-glib-aux/nm-io-utils.h"
/*****************************************************************************/
int
@ -20,13 +22,11 @@ nm_sudo_utils_open_fd(NMSudoGetFDType fd_type, GError **error)
case NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET:
{
struct sockaddr_un sock;
int sock_len;
memset(&sock, 0, sizeof(sock));
sock.sun_family = AF_UNIX;
G_STATIC_ASSERT_EXPR(NM_STRLEN(NM_OVSDB_SOCKET) + 1 < sizeof(sock.sun_path));
if (g_strlcpy(sock.sun_path, NM_OVSDB_SOCKET, sizeof(sock.sun_path))
>= sizeof(sock.sun_path))
nm_assert_not_reached();
sock_len = nm_io_sockaddr_un_set(&sock, FALSE, NM_OVSDB_SOCKET);
nm_assert(sock_len > 0);
fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
@ -35,9 +35,7 @@ nm_sudo_utils_open_fd(NMSudoGetFDType fd_type, GError **error)
return -errsv;
}
r = connect(fd,
(const struct sockaddr *) &sock,
G_STRUCT_OFFSET(struct sockaddr_un, sun_path) + NM_STRLEN(NM_OVSDB_SOCKET) + 1);
r = connect(fd, (const struct sockaddr *) &sock, sock_len);
if (r != 0) {
errsv = NM_ERRNO_NATIVE(errno);
g_set_error(error,

View file

@ -403,3 +403,90 @@ _nm_dbus_error_is(GError *error, ...)
return FALSE;
}
/*****************************************************************************/
typedef struct {
GDBusConnection **p_dbus_connection;
GError ** p_error;
} BusGetData;
static void
_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
BusGetData *data = user_data;
*data->p_dbus_connection = g_bus_get_finish(result, data->p_error);
}
/**
* nm_g_bus_get_blocking:
* @cancellable: (allow-none): a #GCancellable to abort the operation.
* @error: (allow-none): the error.
*
* This calls g_bus_get(), but iterates the current (thread-default) GMainContext
* until the response is ready. As such, it's similar to g_bus_get_sync(),
* but it allows to cancel the operation (without having multiple threads).
*
* Returns: (transfer full): the new #GDBusConnection or %NULL on error.
*/
GDBusConnection *
nm_g_bus_get_blocking(GCancellable *cancellable, GError **error)
{
gs_free_error GError *local_error = NULL;
gs_unref_object GDBusConnection *dbus_connection = NULL;
GMainContext * main_context = g_main_context_get_thread_default();
BusGetData data = {
.p_dbus_connection = &dbus_connection,
.p_error = &local_error,
};
g_bus_get(G_BUS_TYPE_SYSTEM, cancellable, _bus_get_cb, &data);
while (!dbus_connection && !local_error)
g_main_context_iteration(main_context, TRUE);
if (!dbus_connection) {
g_propagate_error(error, g_steal_pointer(&local_error));
return NULL;
}
return g_steal_pointer(&dbus_connection);
}
/*****************************************************************************/
void
nm_dbus_connection_call_blocking_callback(GObject *source, GAsyncResult *res, gpointer user_data)
{
NMDBusConnectionCallBlockingData *data = user_data;
nm_assert(data);
nm_assert(!data->result);
nm_assert(!data->error);
data->result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &data->error);
}
GVariant *
nm_dbus_connection_call_blocking(NMDBusConnectionCallBlockingData *data, GError **error)
{
GMainContext *main_context = g_main_context_get_thread_default();
gs_free_error GError *local_error = NULL;
gs_unref_variant GVariant *result = NULL;
nm_assert(data);
while (!data->result && !data->error)
g_main_context_iteration(main_context, TRUE);
local_error = g_steal_pointer(&data->error);
result = g_steal_pointer(&data->result);
if (!result) {
g_propagate_error(error, g_steal_pointer(&local_error));
return NULL;
}
return g_steal_pointer(&result);
}

View file

@ -84,6 +84,29 @@ void nm_dbus_connection_call_get_name_owner(GDBusConnection * d
NMDBusConnectionCallGetNameOwnerCb callback,
gpointer user_data);
static inline void
nm_dbus_connection_call_request_name(GDBusConnection * dbus_connection,
const char * name,
guint32 flags,
int timeout_msec,
GCancellable * cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_dbus_connection_call(dbus_connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"RequestName",
g_variant_new("(su)", name, flags),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
timeout_msec,
cancellable,
callback,
user_data);
}
static inline guint
nm_dbus_connection_signal_subscribe_properties_changed(GDBusConnection * dbus_connection,
const char * bus_name,
@ -215,4 +238,18 @@ gboolean _nm_dbus_error_is(GError *error, ...) G_GNUC_NULL_TERMINATED;
/*****************************************************************************/
GDBusConnection *nm_g_bus_get_blocking(GCancellable *cancellable, GError **error);
/*****************************************************************************/
typedef struct {
GVariant *result;
GError * error;
} NMDBusConnectionCallBlockingData;
void
nm_dbus_connection_call_blocking_callback(GObject *source, GAsyncResult *res, gpointer user_data);
GVariant *nm_dbus_connection_call_blocking(NMDBusConnectionCallBlockingData *data, GError **error);
#endif /* __NM_DBUS_AUX_H__ */

View file

@ -7,9 +7,11 @@
#include "nm-io-utils.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include "nm-str-buf.h"
#include "nm-shared-utils.h"
@ -628,3 +630,96 @@ next:;
g_ptr_array_add(arr, NULL);
return (char **) g_ptr_array_free(arr, FALSE);
}
/*****************************************************************************/
/* taken from systemd's sockaddr_un_set_path(). */
int
nm_io_sockaddr_un_set(struct sockaddr_un *ret, NMOptionBool is_abstract, const char *path)
{
gsize l;
g_return_val_if_fail(ret, -EINVAL);
g_return_val_if_fail(path, -EINVAL);
nm_assert_is_ternary(is_abstract);
if (is_abstract == NM_OPTION_BOOL_DEFAULT)
is_abstract = nm_io_sockaddr_un_path_is_abstract(path, &path);
l = strlen(path);
if (l < 1)
return -EINVAL;
if (l > sizeof(ret->sun_path) - 1)
return -EINVAL;
if (!is_abstract) {
if (path[0] != '/') {
/* non-abstract paths must be absolute. */
return -EINVAL;
}
}
memset(ret, 0, nm_offsetof(struct sockaddr_un, sun_path));
ret->sun_family = AF_UNIX;
if (is_abstract) {
ret->sun_path[0] = '\0';
memcpy(&ret->sun_path[1], path, NM_MIN(l + 1, sizeof(ret->sun_path) - 1));
} else
memcpy(&ret->sun_path, path, l + 1);
/* For pathname addresses, we return the size with the trailing NUL.
* For abstract addresses, we return the size without the trailing NUL
* (which may not be even written). But as abstract sockets also have
* a NUL at the beginning of sun_path, the total length is always
* calculated the same. */
return (nm_offsetof(struct sockaddr_un, sun_path) + 1) + l;
}
/*****************************************************************************/
/* taken from systemd's sd_notify(). */
int
nm_sd_notify(const char *state)
{
struct sockaddr_un sockaddr;
struct iovec iovec;
struct msghdr msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_name = &sockaddr,
};
nm_auto_close int fd = -1;
const char * e;
int r;
if (!state)
g_return_val_if_reached(-EINVAL);
e = getenv("NOTIFY_SOCKET");
if (!e)
return 0;
r = nm_io_sockaddr_un_set(&sockaddr, NM_OPTION_BOOL_DEFAULT, e);
if (r < 0)
return r;
msghdr.msg_namelen = r;
fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (fd < 0)
return -NM_ERRNO_NATIVE(errno);
/* systemd calls here fd_set_sndbuf(fd, SNDBUF_SIZE) .We don't bother. */
iovec = (struct iovec){
.iov_base = (gpointer) state,
.iov_len = strlen(state),
};
/* systemd sends ucred, if geteuid()/getegid() does not match getuid()/getgid(). We don't bother. */
if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0)
return -NM_ERRNO_NATIVE(errno);
return 0;
}

View file

@ -60,4 +60,21 @@ void nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeou
char **nm_utils_find_mkstemp_files(const char *dirname, const char *filename);
static inline gboolean
nm_io_sockaddr_un_path_is_abstract(const char *path, const char **out_path)
{
if (path && path[0] == '@') {
NM_SET_OUT(out_path, &path[1]);
return TRUE;
}
NM_SET_OUT(out_path, path);
return FALSE;
}
struct sockaddr_un;
int nm_io_sockaddr_un_set(struct sockaddr_un *ret, NMOptionBool is_abstract, const char *path);
int nm_sd_notify(const char *state);
#endif /* __NM_IO_UTILS_H__ */

View file

@ -5080,12 +5080,12 @@ nm_g_unix_signal_source_new(int signum,
}
GSource *
nm_g_unix_fd_source_new(int fd,
GIOCondition io_condition,
int priority,
gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data),
gpointer user_data,
GDestroyNotify destroy_notify)
nm_g_unix_fd_source_new(int fd,
GIOCondition io_condition,
int priority,
GUnixFDSourceFunc source_func,
gpointer user_data,
GDestroyNotify destroy_notify)
{
GSource *source;
@ -5097,6 +5097,23 @@ nm_g_unix_fd_source_new(int fd,
return source;
}
GSource *
nm_g_child_watch_source_new(GPid pid,
int priority,
GChildWatchFunc handler,
gpointer user_data,
GDestroyNotify notify)
{
GSource *source;
source = g_child_watch_source_new(pid);
if (priority != G_PRIORITY_DEFAULT)
g_source_set_priority(source, priority);
g_source_set_callback(source, G_SOURCE_FUNC(handler), user_data, notify);
return source;
}
/*****************************************************************************/
#define _CTX_LOG(fmt, ...) \

View file

@ -1715,19 +1715,25 @@ GSource *nm_g_timeout_source_new_seconds(guint timeout_sec,
gpointer user_data,
GDestroyNotify destroy_notify);
GSource *
nm_g_unix_fd_source_new(int fd,
GIOCondition io_condition,
int priority,
gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data),
gpointer user_data,
GDestroyNotify destroy_notify);
GSource *nm_g_unix_fd_source_new(int fd,
GIOCondition io_condition,
int priority,
GUnixFDSourceFunc source_func,
gpointer user_data,
GDestroyNotify destroy_notify);
GSource *nm_g_unix_signal_source_new(int signum,
int priority,
GSourceFunc handler,
gpointer user_data,
GDestroyNotify notify);
GSource *nm_g_child_watch_source_new(GPid pid,
int priority,
GChildWatchFunc handler,
gpointer user_data,
GDestroyNotify notify);
static inline GSource *
nm_g_source_attach(GSource *source, GMainContext *context)
{
@ -1824,6 +1830,14 @@ nm_g_unix_signal_add_source(int signum, GSourceFunc handler, gpointer user_data)
NULL);
}
static inline GSource *
nm_g_child_watch_add_source(GPid pid, GChildWatchFunc handler, gpointer user_data)
{
return nm_g_source_attach(
nm_g_child_watch_source_new(pid, G_PRIORITY_DEFAULT, handler, user_data, NULL),
NULL);
}
NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_unref_gmaincontext, g_main_context_unref);
#define nm_auto_unref_gmaincontext nm_auto(_nm_auto_unref_gmaincontext)
@ -1879,6 +1893,14 @@ nm_g_main_context_push_thread_default_if_necessary(GMainContext *context)
return context;
}
static inline void
nm_g_main_context_iterate_ready(GMainContext *context)
{
while (g_main_context_iteration(context, FALSE)) {
;
}
}
/*****************************************************************************/
static inline int

View file

@ -18,21 +18,39 @@
#include <arpa/inet.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 {
GDBusConnection *dbus_connection;
GMainLoop * loop;
gboolean debug;
GCancellable * quit_cancellable;
bool log_verbose;
bool log_stdout;
gboolean persist;
guint quit_id;
GSource * quit_source;
guint request_id_counter;
gboolean ever_acquired_name;
bool exit_with_failure;
guint dbus_regist_id;
gint64 start_timestamp_msec;
bool name_requested;
bool exit_with_failure;
bool shutdown_timeout;
bool shutdown_quitting;
Request *current_request;
GQueue * requests_waiting;
@ -48,8 +66,8 @@ typedef struct {
char * error;
gboolean wait;
gboolean dispatched;
guint watch_id;
guint timeout_id;
GSource * watch_source;
GSource * timeout_source;
} ScriptInfo;
struct Request {
@ -137,7 +155,7 @@ struct Request {
} \
G_STMT_END
#define _LOG_X_D_enabled() (gl.debug)
#define _LOG_X_D_enabled() (gl.log_verbose)
#define _LOG_X_T_enabled() _LOG_X_D_enabled()
#define _LOG_R_D_enabled(request) (_NM_ENSURE_TYPE_CONST(Request *, request)->debug)
@ -189,18 +207,26 @@ request_free(Request *request)
static gboolean
quit_timeout_cb(gpointer user_data)
{
gl.quit_id = 0;
g_main_loop_quit(gl.loop);
return G_SOURCE_REMOVE;
nm_clear_g_source_inst(&gl.quit_source);
gl.shutdown_timeout = TRUE;
return G_SOURCE_CONTINUE;
}
static void
quit_timeout_reschedule(void)
{
if (!gl.persist) {
nm_clear_g_source(&gl.quit_id);
gl.quit_id = g_timeout_add_seconds(10, quit_timeout_cb, NULL);
}
nm_clear_g_source_inst(&gl.quit_source);
if (gl.persist)
return;
if (gl.shutdown_quitting)
return;
if (gl.num_requests_pending > 0)
return;
gl.quit_source = nm_g_timeout_add_source(10000, quit_timeout_cb, NULL);
}
/**
@ -286,7 +312,7 @@ complete_request(Request *request)
request_free(request);
g_assert_cmpuint(gl.num_requests_pending, >, 0);
nm_assert(gl.num_requests_pending > 0);
if (--gl.num_requests_pending <= 0) {
nm_assert(!gl.current_request && !g_queue_peek_head(gl.requests_waiting));
quit_timeout_reschedule();
@ -365,8 +391,8 @@ script_watch_cb(GPid pid, int status, gpointer user_data)
g_assert(pid == script->pid);
script->watch_id = 0;
nm_clear_g_source(&script->timeout_id);
nm_clear_g_source_inst(&script->watch_source);
nm_clear_g_source_inst(&script->timeout_source);
script->request->num_scripts_done++;
if (!script->wait)
script->request->num_scripts_nowait--;
@ -395,8 +421,8 @@ script_timeout_cb(gpointer user_data)
{
ScriptInfo *script = user_data;
script->timeout_id = 0;
nm_clear_g_source(&script->watch_id);
nm_clear_g_source_inst(&script->timeout_source);
nm_clear_g_source_inst(&script->watch_source);
script->request->num_scripts_done++;
if (!script->wait)
script->request->num_scripts_nowait--;
@ -417,7 +443,7 @@ again:
complete_script(script);
return FALSE;
return G_SOURCE_CONTINUE;
}
static gboolean
@ -514,8 +540,9 @@ script_dispatch(ScriptInfo *script)
return FALSE;
}
script->watch_id = g_child_watch_add(script->pid, (GChildWatchFunc) script_watch_cb, script);
script->timeout_id = g_timeout_add_seconds(SCRIPT_TIMEOUT, script_timeout_cb, script);
script->watch_source = nm_g_child_watch_add_source(script->pid, script_watch_cb, script);
script->timeout_source =
nm_g_timeout_add_source_seconds(SCRIPT_TIMEOUT, script_timeout_cb, script);
if (!script->wait)
request->num_scripts_nowait++;
return TRUE;
@ -726,7 +753,7 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters)
request = g_slice_new0(Request);
request->request_id = ++gl.request_id_counter;
request->debug = debug || gl.debug;
request->debug = debug || gl.log_verbose;
request->context = invocation;
request->action = g_strdup(action);
@ -782,9 +809,9 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters)
return;
}
nm_clear_g_source(&gl.quit_id);
gl.num_requests_pending++;
gl.shutdown_timeout = FALSE;
nm_clear_g_source_inst(&gl.quit_source);
for (i = 0; i < request->scripts->len; i++) {
ScriptInfo *s = g_ptr_array_index(request->scripts, i);
@ -830,28 +857,23 @@ _method_call_action(GDBusMethodInvocation *invocation, GVariant *parameters)
}
static void
on_name_acquired(GDBusConnection *connection, const char *name, gpointer user_data)
_method_call_ping(GDBusMethodInvocation *invocation, GVariant *parameters)
{
gl.ever_acquired_name = TRUE;
}
gs_free char *msg = NULL;
gint64 running_msec;
const char * arg_s;
static void
on_name_lost(GDBusConnection *connection, const char *name, gpointer user_data)
{
if (!connection) {
if (!gl.ever_acquired_name) {
_LOG_X_W("Could not get the system bus. Make sure the message bus daemon is running!");
gl.exit_with_failure = TRUE;
} else {
_LOG_X_I("System bus stopped. Exiting");
}
} else if (!gl.ever_acquired_name) {
_LOG_X_W("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service.");
gl.exit_with_failure = TRUE;
} else
_LOG_X_I("Lost the " NM_DISPATCHER_DBUS_SERVICE " name. Exiting");
g_variant_get(parameters, "(&s)", &arg_s);
g_main_loop_quit(gl.loop);
running_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME) - gl.start_timestamp_msec;
msg = g_strdup_printf("pid=%lu, unique-name=%s, since=%" G_GINT64_FORMAT ".%03d, pong=%s",
(unsigned long) getpid(),
g_dbus_connection_get_unique_name(gl.dbus_connection),
(gint64) (running_msec / 1000),
(int) (running_msec % 1000),
arg_s);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", msg));
}
static void
@ -869,6 +891,10 @@ _method_call(GDBusConnection * connection,
_method_call_action(invocation, parameters);
return;
}
if (nm_streq(method_name, "Ping")) {
_method_call_ping(invocation, parameters);
return;
}
}
g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR,
@ -880,6 +906,10 @@ _method_call(GDBusConnection * connection,
static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO(
NM_DISPATCHER_DBUS_INTERFACE,
.methods = NM_DEFINE_GDBUS_METHOD_INFOS(
NM_DEFINE_GDBUS_METHOD_INFO(
"Ping",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), ),
NM_DEFINE_GDBUS_METHOD_INFO(
"Action",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(
@ -901,9 +931,75 @@ static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO
.out_args =
NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("results", "a(sus)"), ), ), ), );
static const GDBusInterfaceVTable interface_vtable = {
.method_call = _method_call,
};
static gboolean
_bus_register_service(void)
{
static const GDBusInterfaceVTable interface_vtable = {
.method_call = _method_call,
};
gs_free_error GError * error = NULL;
NMDBusConnectionCallBlockingData data = {
.result = NULL,
};
gs_unref_variant GVariant *ret = NULL;
guint32 ret_val;
gl.dbus_regist_id =
g_dbus_connection_register_object(gl.dbus_connection,
NM_DISPATCHER_DBUS_PATH,
interface_info,
NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable),
NULL,
NULL,
&error);
if (gl.dbus_regist_id == 0) {
_LOG_X_W("dbus: could not export dispatcher D-Bus interface %s: %s",
NM_DISPATCHER_DBUS_PATH,
error->message);
return FALSE;
}
_LOG_X_D("dbus: dispatcher D-Bus interface %s registered", NM_DISPATCHER_DBUS_PATH);
gl.name_requested = TRUE;
nm_dbus_connection_call_request_name(gl.dbus_connection,
NM_DISPATCHER_DBUS_SERVICE,
DBUS_NAME_FLAG_ALLOW_REPLACEMENT
| DBUS_NAME_FLAG_REPLACE_EXISTING,
10000,
gl.quit_cancellable,
nm_dbus_connection_call_blocking_callback,
&data);
/* Note that with D-Bus activation, the first request will already hit us before RequestName
* completes. So when we start iterating the main context, the first request may already come
* in. */
ret = nm_dbus_connection_call_blocking(&data, &error);
if (nm_utils_error_is_cancelled(error))
return FALSE;
if (error) {
_LOG_X_W("d-bus: failed to request name %s: %s",
NM_DISPATCHER_DBUS_SERVICE,
error->message);
return FALSE;
}
g_variant_get(ret, "(u)", &ret_val);
if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
_LOG_X_W("dbus: request name for %s failed to take name (response %u)",
NM_DISPATCHER_DBUS_SERVICE,
ret_val);
return FALSE;
}
_LOG_X_D("dbus: request name for %s succeeded", NM_DISPATCHER_DBUS_SERVICE);
return TRUE;
}
/*****************************************************************************/
@ -956,23 +1052,52 @@ logging_shutdown(void)
static gboolean
signal_handler(gpointer user_data)
{
int signo = GPOINTER_TO_INT(user_data);
_LOG_X_I("Caught signal %d, shutting down...", signo);
g_main_loop_quit(gl.loop);
if (!gl.shutdown_quitting) {
gl.shutdown_quitting = TRUE;
_LOG_X_I("Caught signal %d, shutting down...", GPOINTER_TO_INT(user_data));
g_cancellable_cancel(gl.quit_cancellable);
}
return G_SOURCE_CONTINUE;
}
static void
_bus_release_name_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
nm_assert(gl.num_requests_pending > 0);
gl.num_requests_pending--;
g_main_context_wakeup(NULL);
}
static gboolean
parse_command_line(int *p_argc, char ***p_argv, GError **error)
{
GOptionContext *opt_ctx;
GOptionEntry entries[] = {
{"debug", 0, 0, G_OPTION_ARG_NONE, &gl.debug, "Output to console rather than syslog", NULL},
{"persist", 0, 0, G_OPTION_ARG_NONE, &gl.persist, "Don't quit after a short timeout", NULL},
{NULL}};
gboolean success;
gboolean arg_debug = FALSE;
GOptionEntry entries[] = {{
"debug",
0,
0,
G_OPTION_ARG_NONE,
&arg_debug,
"Output to console rather than syslog",
NULL,
},
{
"persist",
0,
0,
G_OPTION_ARG_NONE,
&gl.persist,
"Don't quit after a short timeout",
NULL,
},
{
NULL,
}};
gboolean success;
gl.log_stdout = FALSE;
gl.log_verbose = _nm_utils_ascii_str_to_bool(g_getenv(_ENV("NM_DISPATCHER_DEBUG_LOG")), FALSE);
opt_ctx = g_option_context_new(NULL);
g_option_context_set_summary(opt_ctx, "Executes scripts upon actions by NetworkManager.");
@ -982,17 +1107,28 @@ parse_command_line(int *p_argc, char ***p_argv, GError **error)
g_option_context_free(opt_ctx);
if (success && arg_debug) {
gl.log_stdout = TRUE;
gl.log_verbose = TRUE;
}
return success;
}
int
main(int argc, char **argv)
{
gs_free_error GError *error = NULL;
guint signal_id_term = 0;
guint signal_id_int = 0;
guint dbus_regist_id = 0;
guint dbus_own_name_id = 0;
gs_free_error GError *error = NULL;
GSource * source_term = NULL;
GSource * source_int = NULL;
signal(SIGPIPE, SIG_IGN);
source_term = nm_g_unix_signal_add_source(SIGTERM, signal_handler, GINT_TO_POINTER(SIGTERM));
source_int = nm_g_unix_signal_add_source(SIGINT, signal_handler, GINT_TO_POINTER(SIGINT));
gl.start_timestamp_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME);
gl.quit_cancellable = g_cancellable_new();
if (!parse_command_line(&argc, &argv, &error)) {
_LOG_X_W("Error parsing command line arguments: %s", error->message);
@ -1000,10 +1136,7 @@ main(int argc, char **argv)
goto done;
}
signal_id_term = g_unix_signal_add(SIGTERM, signal_handler, GINT_TO_POINTER(SIGTERM));
signal_id_int = g_unix_signal_add(SIGINT, signal_handler, GINT_TO_POINTER(SIGINT));
if (gl.debug) {
if (gl.log_stdout) {
if (!g_getenv("G_MESSAGES_DEBUG")) {
/* we log our regular messages using g_debug() and g_info().
* When we redirect glib logging to syslog, there is no problem.
@ -1014,77 +1147,111 @@ main(int argc, char **argv)
} else
logging_setup();
gl.loop = g_main_loop_new(NULL, FALSE);
gl.dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
gl.dbus_connection = nm_g_bus_get_blocking(gl.quit_cancellable, &error);
if (!gl.dbus_connection) {
_LOG_X_W("Could not get the system bus (%s). Make sure the message bus daemon is running!",
error->message);
gl.exit_with_failure = TRUE;
if (!nm_utils_error_is_cancelled(error)) {
_LOG_X_W("dbus: failure to get D-Bus connection: %s", error->message);
gl.exit_with_failure = TRUE;
}
goto done;
}
/* On bus-disconnect, GDBus will raise(SIGTERM), which we handle like a
* regular request to quit. */
g_dbus_connection_set_exit_on_close(gl.dbus_connection, TRUE);
_LOG_X_D("dbus: unique name: %s", g_dbus_connection_get_unique_name(gl.dbus_connection));
gl.requests_waiting = g_queue_new();
dbus_regist_id =
g_dbus_connection_register_object(gl.dbus_connection,
NM_DISPATCHER_DBUS_PATH,
interface_info,
NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable),
NULL,
NULL,
&error);
if (dbus_regist_id == 0) {
_LOG_X_W("Could not export Dispatcher D-Bus interface: %s", error->message);
gl.exit_with_failure = 1;
goto done;
}
dbus_own_name_id = g_bus_own_name_on_connection(gl.dbus_connection,
NM_DISPATCHER_DBUS_SERVICE,
G_BUS_NAME_OWNER_FLAGS_NONE,
on_name_acquired,
on_name_lost,
NULL,
NULL);
quit_timeout_reschedule();
g_main_loop_run(gl.loop);
done:
if (gl.num_requests_pending > 0) {
/* this only happens when we quit due to SIGTERM (not due to the idle timer).
*
* Log a warning about pending scripts.
*
* Maybe we should notify NetworkManager that these scripts are left in an unknown state.
* But this is either a bug of a dispatcher script (not terminating in time).
*
* FIXME(shutdown): Also, currently NetworkManager behaves wrongly on shutdown.
* Note that systemd would not terminate NetworkManager-dispatcher before NetworkManager.
* It's NetworkManager's responsibility to keep running long enough so that all requests
* can complete (with a watchdog timer, and a warning that user provided scripts hang). */
_LOG_X_W("exiting but there are still %u requests pending", gl.num_requests_pending);
if (!_bus_register_service()) {
/* we failed to start the D-Bus service, and will shut down. However,
* first see whether there are any requests that we should process.
* Even if RequestName fails, we might already have requests pending. */
if (!g_cancellable_is_cancelled(gl.quit_cancellable))
gl.exit_with_failure = TRUE;
gl.shutdown_quitting = TRUE;
}
if (dbus_own_name_id != 0)
g_bus_unown_name(nm_steal_int(&dbus_own_name_id));
while (TRUE) {
if (gl.num_requests_pending > 0) {
/* while we have requests pending, we cannot stop processing them... */
} else if (gl.shutdown_timeout || gl.shutdown_quitting) {
if (gl.name_requested) {
int r;
if (dbus_regist_id != 0)
g_dbus_connection_unregister_object(gl.dbus_connection, nm_steal_int(&dbus_regist_id));
/* We already requested a name. To exit-on-idle without race, we need to dance.
* See https://lists.freedesktop.org/archives/dbus/2015-May/016671.html . */
gl.name_requested = FALSE;
gl.shutdown_quitting = TRUE;
_LOG_X_T("shutdown: release-name");
/* we create a fake pending request. */
gl.num_requests_pending++;
nm_clear_g_source_inst(&gl.quit_source);
r = nm_sd_notify("STOPPING=1");
if (r < 0)
_LOG_X_W("shutdown: sd_notifiy(STOPPING=1) failed: %s", nm_strerror_native(-r));
else
_LOG_X_T("shutdown: sd_notifiy(STOPPING=1) succeeded");
g_dbus_connection_call(gl.dbus_connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"ReleaseName",
g_variant_new("(s)", NM_DISPATCHER_DBUS_SERVICE),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
10000,
NULL,
_bus_release_name_cb,
NULL);
continue;
}
break;
}
g_main_context_iteration(NULL, TRUE);
}
done:
nm_g_main_context_iterate_ready(NULL);
gl.shutdown_quitting = TRUE;
g_cancellable_cancel(gl.quit_cancellable);
nm_assert(gl.num_requests_pending == 0);
if (gl.dbus_regist_id != 0)
g_dbus_connection_unregister_object(gl.dbus_connection, nm_steal_int(&gl.dbus_regist_id));
nm_clear_pointer(&gl.requests_waiting, g_queue_free);
nm_clear_g_source(&signal_id_term);
nm_clear_g_source(&signal_id_int);
nm_clear_g_source(&gl.quit_id);
nm_clear_pointer(&gl.loop, g_main_loop_unref);
g_clear_object(&gl.dbus_connection);
nm_clear_g_source_inst(&gl.quit_source);
if (!gl.debug)
if (gl.dbus_connection) {
g_dbus_connection_flush_sync(gl.dbus_connection, NULL, NULL);
g_clear_object(&gl.dbus_connection);
}
nm_g_main_context_iterate_ready(NULL);
_LOG_X_T("shutdown: exiting with %s", gl.exit_with_failure ? "failure" : "success");
if (gl.log_stdout)
logging_shutdown();
nm_clear_g_source_inst(&source_term);
nm_clear_g_source_inst(&source_int);
g_clear_object(&gl.quit_cancellable);
return gl.exit_with_failure ? 1 : 0;
}

View file

@ -5,10 +5,11 @@
#include <gio/gunixfdlist.h>
#include "c-list/src/c-list.h"
#include "libnm-glib-aux/nm-dbus-aux.h"
#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-glib-aux/nm-logging-base.h"
#include "libnm-glib-aux/nm-shared-utils.h"
#include "libnm-glib-aux/nm-time-utils.h"
#include "libnm-glib-aux/nm-dbus-aux.h"
#include "libnm-base/nm-sudo-utils.h"
/* nm-sudo doesn't link with libnm-core nor libnm-base, but these headers
@ -49,7 +50,7 @@ struct _GlobalData {
gint64 start_timestamp_msec;
guint32 timeout_msec;
bool name_owner_initialized;
bool service_registered;
bool name_requested;
/* This is controlled by $NM_SUDO_NO_AUTH_FOR_TESTING. It disables authentication
* of the request, so it is ONLY for testing. */
@ -66,7 +67,7 @@ static void _pending_job_register_object(GlobalData *gl, GObject *obj);
/*****************************************************************************/
#define _nm_log(level, ...) _nm_log_simple_printf((level), __VA_ARGS__);
#define _nm_log(level, ...) _nm_log_simple_printf((level), __VA_ARGS__)
#define _NMLOG(level, ...) \
G_STMT_START \
@ -140,33 +141,13 @@ _signal_callback_term(gpointer user_data)
/*****************************************************************************/
typedef struct {
GDBusConnection **p_dbus_connection;
GError ** p_error;
} BusGetData;
static void
_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
BusGetData *data = user_data;
*data->p_dbus_connection = g_bus_get_finish(result, data->p_error);
}
static GDBusConnection *
_bus_get(GCancellable *cancellable, int *out_exit_code)
{
gs_free_error GError *error = NULL;
gs_unref_object GDBusConnection *dbus_connection = NULL;
BusGetData data = {
.p_dbus_connection = &dbus_connection,
.p_error = &error,
};
g_bus_get(G_BUS_TYPE_SYSTEM, cancellable, _bus_get_cb, &data);
while (!dbus_connection && !error)
g_main_context_iteration(NULL, TRUE);
dbus_connection = nm_g_bus_get_blocking(cancellable, &error);
if (!dbus_connection) {
gboolean was_cancelled = nm_utils_error_is_cancelled(error);
@ -309,14 +290,26 @@ _bus_method_call(GDBusConnection * connection,
method_name,
g_variant_get_type_string(parameters));
if (!nm_streq(interface_name, NM_SUDO_DBUS_IFACE_NAME))
goto out_unknown_method;
if (nm_streq(method_name, "GetFD")) {
g_variant_get(parameters, "(u)", &arg_u);
_handle_get_fd(gl, invocation, arg_u);
return;
}
if (nm_streq(method_name, "Ping")) {
g_variant_get(parameters, "(&s)", &arg_s);
_handle_ping(gl, invocation, arg_s);
} else if (nm_streq(method_name, "GetFD")) {
g_variant_get(parameters, "(u)", &arg_u);
_handle_get_fd(gl, invocation, arg_u);
} else
nm_assert_not_reached();
return;
}
out_unknown_method:
g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_UNKNOWN_METHOD,
"Unknown method %s",
method_name);
}
static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO(
@ -330,56 +323,18 @@ static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(
NM_DEFINE_GDBUS_ARG_INFO("fd_type", "u"), ), ), ), );
typedef struct {
GlobalData *gl;
gboolean is_waiting;
} BusRegisterServiceRequestNameData;
static void
_bus_register_service_request_name_cb(GObject *source, GAsyncResult *res, gpointer user_data)
{
BusRegisterServiceRequestNameData *data = user_data;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *ret = NULL;
gboolean success = FALSE;
ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error);
if (nm_utils_error_is_cancelled(error))
goto out;
if (error)
_LOGE("d-bus: failed to request name %s: %s", NM_SUDO_DBUS_BUS_NAME, error->message);
else {
guint32 ret_val;
g_variant_get(ret, "(u)", &ret_val);
if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
_LOGW("dbus: request name for %s failed to take name (response %u)",
NM_SUDO_DBUS_BUS_NAME,
ret_val);
} else {
_LOGD("dbus: request name for %s succeeded", NM_SUDO_DBUS_BUS_NAME);
success = TRUE;
}
}
out:
if (success)
data->gl->service_registered = TRUE;
data->is_waiting = FALSE;
}
static void
static gboolean
_bus_register_service(GlobalData *gl)
{
static const GDBusInterfaceVTable interface_vtable = {
.method_call = _bus_method_call,
};
gs_free_error GError * error = NULL;
BusRegisterServiceRequestNameData data;
nm_assert(!gl->service_registered);
gs_free_error GError * error = NULL;
NMDBusConnectionCallBlockingData data = {
.result = NULL,
};
gs_unref_variant GVariant *ret = NULL;
guint32 ret_val;
gl->service_regist_id =
g_dbus_connection_register_object(gl->dbus_connection,
@ -391,37 +346,49 @@ _bus_register_service(GlobalData *gl)
&error);
if (gl->service_regist_id == 0) {
_LOGE("dbus: error registering object %s: %s", NM_SUDO_DBUS_OBJECT_PATH, error->message);
return;
return FALSE;
}
_LOGD("dbus: object %s registered", NM_SUDO_DBUS_OBJECT_PATH);
data = (BusRegisterServiceRequestNameData){
.gl = gl,
.is_waiting = TRUE,
};
/* regardless whether the request is successful, after we start calling
* RequestName, we remember that we need to ReleaseName it. */
gl->name_requested = TRUE;
g_dbus_connection_call(
gl->dbus_connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"RequestName",
g_variant_new("(su)",
NM_SUDO_DBUS_BUS_NAME,
(guint) (DBUS_NAME_FLAG_ALLOW_REPLACEMENT | DBUS_NAME_FLAG_REPLACE_EXISTING)),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
gl->quit_cancellable,
_bus_register_service_request_name_cb,
&data);
nm_dbus_connection_call_request_name(gl->dbus_connection,
NM_SUDO_DBUS_BUS_NAME,
DBUS_NAME_FLAG_ALLOW_REPLACEMENT
| DBUS_NAME_FLAG_REPLACE_EXISTING,
10000,
gl->quit_cancellable,
nm_dbus_connection_call_blocking_callback,
&data);
/* Note that with D-Bus activation, the first request will already hit us before RequestName
* completes. */
* completes. So when we start iterating the main context, the first request may already come
* in. */
while (data.is_waiting)
g_main_context_iteration(NULL, TRUE);
ret = nm_dbus_connection_call_blocking(&data, &error);
if (nm_utils_error_is_cancelled(error))
return FALSE;
if (error) {
_LOGE("d-bus: failed to request name %s: %s", NM_SUDO_DBUS_BUS_NAME, error->message);
return FALSE;
}
g_variant_get(ret, "(u)", &ret_val);
if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
_LOGW("dbus: request name for %s failed to take name (response %u)",
NM_SUDO_DBUS_BUS_NAME,
ret_val);
return FALSE;
}
_LOGD("dbus: request name for %s succeeded", NM_SUDO_DBUS_BUS_NAME);
return TRUE;
}
/*****************************************************************************/
@ -505,6 +472,16 @@ _pending_job_register_object(GlobalData *gl, GObject *obj)
/*****************************************************************************/
static void
_bus_release_name_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
_nm_unused gs_unref_object GObject *keep_alive_object = user_data;
g_main_context_wakeup(NULL);
}
/*****************************************************************************/
static void
_initial_setup(GlobalData *gl)
{
@ -568,36 +545,73 @@ main(int argc, char **argv)
exit_code = EXIT_SUCCESS;
_bus_register_service(gl);
if (!gl->service_registered) {
if (!_bus_register_service(gl)) {
/* We failed to RequestName, but due to D-Bus activation we
* might have a pending request still (on the unique name).
* Process it below.
*
* Let's fake a shutdown signal, and still process the request below. */
exit_code = EXIT_FAILURE;
if (!g_cancellable_is_cancelled(gl->quit_cancellable))
exit_code = EXIT_FAILURE;
gl->is_shutting_down_quitting = TRUE;
}
while (TRUE) {
if (!c_list_is_empty(&gl->pending_jobs_lst_head)) {
/* we must first reply to all requests. No matter what. */
} else {
if (gl->is_shutting_down_quitting || gl->is_shutting_down_timeout) {
/* we either hit the idle timeout or received SIGTERM. Note that
* if we received an idle-timeout and the very moment afterwards
* a new request, then _bus_method_call() will clear gl->is_shutting_down_timeout
* (via _pending_job_register_object()). */
break;
} else if (gl->is_shutting_down_quitting || gl->is_shutting_down_timeout) {
/* we either hit the idle timeout or received SIGTERM. Note that
* if we received an idle-timeout and the very moment afterwards
* a new request, then _bus_method_call() will clear gl->is_shutting_down_timeout
* (via _pending_job_register_object()). */
if (gl->name_requested) {
gs_unref_object GObject *keep_alive_object = g_object_new(G_TYPE_OBJECT, NULL);
/* We already requested a name. To exit-on-idle without race, we need to dance.
* See https://lists.freedesktop.org/archives/dbus/2015-May/016671.html . */
gl->name_requested = FALSE;
gl->is_shutting_down_quitting = TRUE;
_LOGT("shutdown: release-name");
/* we use the _pending_job_register_object() mechanism to make the loop busy during
* shutdown. */
_pending_job_register_object(gl, keep_alive_object);
r = nm_sd_notify("STOPPING=1");
if (r < 0)
_LOGW("shutdown: sd_notifiy(STOPPING=1) failed: %s", nm_strerror_native(-r));
else
_LOGT("shutdown: sd_notifiy(STOPPING=1) succeeded");
g_dbus_connection_call(gl->dbus_connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"ReleaseName",
g_variant_new("(s)", NM_SUDO_DBUS_BUS_NAME),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
10000,
NULL,
_bus_release_name_cb,
g_steal_pointer(&keep_alive_object));
continue;
}
break;
}
g_main_context_iteration(NULL, TRUE);
}
done:
_LOGD("shutdown: cleanup");
gl->is_shutting_down_cleanup = TRUE;
_LOGD("exiting...");
g_cancellable_cancel(gl->quit_cancellable);
nm_assert(c_list_is_empty(&gl->pending_jobs_lst_head));
@ -609,24 +623,20 @@ done:
g_dbus_connection_signal_unsubscribe(gl->dbus_connection,
nm_steal_int(&gl->name_owner_changed_id));
}
nm_clear_g_cancellable(&gl->quit_cancellable);
nm_clear_g_source_inst(&gl->source_sigterm);
nm_clear_g_source_inst(&gl->source_idle_timeout);
nm_clear_g_free(&gl->name_owner);
while (g_main_context_iteration(NULL, FALSE)) {
;
}
nm_g_main_context_iterate_ready(NULL);
if (gl->dbus_connection) {
g_dbus_connection_flush_sync(gl->dbus_connection, NULL, NULL);
g_clear_object(&gl->dbus_connection);
while (g_main_context_iteration(NULL, FALSE)) {
;
}
nm_g_main_context_iterate_ready(NULL);
}
nm_clear_g_cancellable(&gl->quit_cancellable);
_LOGD("exit (%d)", exit_code);
return exit_code;
}