mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-07 20:40:21 +01:00
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:
commit
4513d4db63
12 changed files with 720 additions and 261 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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, ...) \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue