mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager
synced 2024-07-09 04:05:56 +00:00
core: add infrastructure for spawning a helper process
(cherry picked from commit 6ac21ba916
)
This commit is contained in:
parent
5e5baa0f05
commit
00126e57b4
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -427,6 +427,7 @@ test-*.trs
|
|||
/src/initrd/tests/test-ibft-reader
|
||||
/src/ndisc/tests/test-ndisc-fake
|
||||
/src/ndisc/tests/test-ndisc-linux
|
||||
/src/nm-daemon-helper/nm-daemon-helper
|
||||
/src/nm-iface-helper
|
||||
/src/platform/tests/dump
|
||||
/src/platform/tests/monitor
|
||||
|
|
25
Makefile.am
25
Makefile.am
|
@ -4598,6 +4598,31 @@ EXTRA_DIST += \
|
|||
src/nm-dispatcher/tests/meson.build \
|
||||
$(NULL)
|
||||
|
||||
###############################################################################
|
||||
# src/nm-daemon-helper
|
||||
###############################################################################
|
||||
|
||||
libexec_PROGRAMS += src/nm-daemon-helper/nm-daemon-helper
|
||||
|
||||
src_nm_daemon_helper_nm_daemon_helper_CPPFLAGS = \
|
||||
$(dflt_cppflags) \
|
||||
-I$(srcdir)/src \
|
||||
-I$(builddir)/src \
|
||||
$(NULL)
|
||||
|
||||
src_nm_daemon_helper_nm_daemon_helper_LDFLAGS = \
|
||||
-Wl,--version-script="$(srcdir)/linker-script-binary.ver" \
|
||||
$(SANITIZER_EXEC_LDFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
src_nm_daemon_helper_nm_daemon_helper_LDADD = \
|
||||
src/libnm-std-aux/libnm-std-aux.la \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST += \
|
||||
src/nm-daemon-helper/meson.build \
|
||||
$(NULL)
|
||||
|
||||
###############################################################################
|
||||
# src/nm-online
|
||||
###############################################################################
|
||||
|
|
|
@ -998,6 +998,7 @@ fi
|
|||
%{_libexecdir}/nm-dispatcher
|
||||
%{_libexecdir}/nm-iface-helper
|
||||
%{_libexecdir}/nm-initrd-generator
|
||||
%{_libexecdir}/nm-daemon-helper
|
||||
%dir %{_libdir}/%{name}
|
||||
%dir %{nmplugindir}
|
||||
%{nmplugindir}/libnm-settings-plugin*.so
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "libnm-glib-aux/nm-io-utils.h"
|
||||
#include "libnm-glib-aux/nm-secret-utils.h"
|
||||
#include "libnm-glib-aux/nm-time-utils.h"
|
||||
#include "libnm-glib-aux/nm-str-buf.h"
|
||||
#include "nm-utils.h"
|
||||
#include "libnm-core-intern/nm-core-internal.h"
|
||||
#include "nm-setting-connection.h"
|
||||
|
@ -4653,3 +4654,323 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_activation_type_to_string,
|
|||
NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_MANAGED, "managed"),
|
||||
NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_ASSUME, "assume"),
|
||||
NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_EXTERNAL, "external"), );
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
typedef struct {
|
||||
GPid pid;
|
||||
GTask * task;
|
||||
gulong cancellable_id;
|
||||
GSource *child_watch_source;
|
||||
GSource *timeout_source;
|
||||
|
||||
int child_stdin;
|
||||
int child_stdout;
|
||||
GSource *input_source;
|
||||
GSource *output_source;
|
||||
|
||||
NMStrBuf in_buffer;
|
||||
NMStrBuf out_buffer;
|
||||
gsize out_buffer_offset;
|
||||
} HelperInfo;
|
||||
|
||||
#define _NMLOG_PREFIX_NAME "helper"
|
||||
#define _NMLOG_DOMAIN LOGD_CORE
|
||||
#define _NMLOG2(level, info, ...) \
|
||||
G_STMT_START \
|
||||
{ \
|
||||
if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \
|
||||
HelperInfo *_info = (info); \
|
||||
\
|
||||
_nm_log((level), \
|
||||
(_NMLOG_DOMAIN), \
|
||||
0, \
|
||||
NULL, \
|
||||
NULL, \
|
||||
_NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \
|
||||
",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
|
||||
NM_HASH_OBFUSCATE_PTR(_info), \
|
||||
_info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
|
||||
} \
|
||||
} \
|
||||
G_STMT_END
|
||||
|
||||
static void
|
||||
helper_info_free(gpointer data)
|
||||
{
|
||||
HelperInfo *info = data;
|
||||
|
||||
nm_clear_g_source_inst(&info->child_watch_source);
|
||||
nm_clear_g_source_inst(&info->timeout_source);
|
||||
g_object_unref(info->task);
|
||||
|
||||
nm_str_buf_destroy(&info->in_buffer);
|
||||
nm_str_buf_destroy(&info->out_buffer);
|
||||
nm_clear_g_source_inst(&info->input_source);
|
||||
nm_clear_g_source_inst(&info->output_source);
|
||||
|
||||
if (info->child_stdout != -1)
|
||||
nm_close(info->child_stdout);
|
||||
if (info->child_stdin != -1)
|
||||
nm_close(info->child_stdin);
|
||||
|
||||
if (info->pid != -1) {
|
||||
nm_assert(info->pid > 1);
|
||||
nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, _NMLOG_PREFIX_NAME, 0, NULL, NULL);
|
||||
}
|
||||
|
||||
g_free(info);
|
||||
}
|
||||
|
||||
static void
|
||||
helper_complete(HelperInfo *info, GError *error)
|
||||
{
|
||||
if (error) {
|
||||
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task),
|
||||
&info->cancellable_id);
|
||||
g_task_return_error(info->task, error);
|
||||
helper_info_free(info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->input_source || info->output_source || info->pid != -1) {
|
||||
/* Wait that pipes are closed and process has terminated */
|
||||
return;
|
||||
}
|
||||
|
||||
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id);
|
||||
g_task_return_pointer(info->task, nm_str_buf_finalize(&info->in_buffer, NULL), g_free);
|
||||
helper_info_free(info);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
helper_can_write(int fd, GIOCondition condition, gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
gssize n_written;
|
||||
int errsv;
|
||||
|
||||
if (NM_FLAGS_HAS(condition, G_IO_ERR)) {
|
||||
errsv = EIO;
|
||||
goto out_error;
|
||||
} else if (NM_FLAGS_HAS(condition, G_IO_HUP)) {
|
||||
errsv = EPIPE;
|
||||
goto out_error;
|
||||
}
|
||||
|
||||
n_written = write(info->child_stdin,
|
||||
&((nm_str_buf_get_str_unsafe(&info->out_buffer))[info->out_buffer_offset]),
|
||||
info->out_buffer.len - info->out_buffer_offset);
|
||||
errsv = errno;
|
||||
|
||||
if (n_written < 0 && errsv != EAGAIN)
|
||||
goto out_error;
|
||||
|
||||
if (n_written > 0) {
|
||||
if ((gsize) n_written >= (info->out_buffer.len - info->out_buffer_offset)) {
|
||||
nm_assert((gsize) n_written == (info->out_buffer.len - info->out_buffer_offset));
|
||||
nm_clear_g_source_inst(&info->output_source);
|
||||
nm_close(info->child_stdin);
|
||||
info->child_stdin = -1;
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
info->out_buffer_offset += (gsize) n_written;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
|
||||
out_error:
|
||||
nm_clear_g_source_inst(&info->output_source);
|
||||
helper_complete(info,
|
||||
g_error_new(NM_UTILS_ERROR,
|
||||
NM_UTILS_ERROR_UNKNOWN,
|
||||
"error writing to helper: %d (%s)",
|
||||
errsv,
|
||||
nm_strerror_native(errsv)));
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
helper_have_data(int fd, GIOCondition condition, gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
gssize n_read;
|
||||
GError * error = NULL;
|
||||
|
||||
n_read = nm_utils_fd_read(fd, &info->in_buffer);
|
||||
_LOG2T(info, "read returns %ld", (long) n_read);
|
||||
|
||||
if (n_read > 0)
|
||||
return G_SOURCE_CONTINUE;
|
||||
|
||||
nm_clear_g_source_inst(&info->input_source);
|
||||
nm_close(info->child_stdout);
|
||||
info->child_stdout = -1;
|
||||
|
||||
_LOG2T(info, "stdout closed");
|
||||
|
||||
if (n_read < 0) {
|
||||
error = g_error_new(NM_UTILS_ERROR,
|
||||
NM_UTILS_ERROR_UNKNOWN,
|
||||
"read from process returned %d (%s)",
|
||||
(int) -n_read,
|
||||
nm_strerror_native((int) -n_read));
|
||||
}
|
||||
|
||||
helper_complete(info, error);
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
helper_child_terminated(GPid pid, int status, gpointer user_data)
|
||||
{
|
||||
HelperInfo * info = user_data;
|
||||
GError * error = NULL;
|
||||
gs_free char *status_desc = NULL;
|
||||
|
||||
_LOG2D(info, "process %s", (status_desc = nm_utils_get_process_exit_status_desc(status)));
|
||||
|
||||
info->pid = -1;
|
||||
nm_clear_g_source_inst(&info->child_watch_source);
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
if (!status_desc)
|
||||
status_desc = nm_utils_get_process_exit_status_desc(status);
|
||||
error =
|
||||
g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc);
|
||||
}
|
||||
|
||||
helper_complete(info, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
helper_timeout(gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
|
||||
nm_clear_g_source_inst(&info->timeout_source);
|
||||
helper_complete(info, g_error_new_literal(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "timed out"));
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
helper_cancelled(GObject *object, gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
GError * error = NULL;
|
||||
|
||||
nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id);
|
||||
nm_utils_error_set_cancelled(&error, FALSE, NULL);
|
||||
helper_complete(info, error);
|
||||
}
|
||||
|
||||
void
|
||||
nm_utils_spawn_helper(const char *const * args,
|
||||
GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer cb_data)
|
||||
{
|
||||
gs_free_error GError *error = NULL;
|
||||
gs_free char * commands = NULL;
|
||||
HelperInfo * info;
|
||||
int fd_flags;
|
||||
const char *const * arg;
|
||||
|
||||
nm_assert(args && args[0]);
|
||||
|
||||
info = g_new(HelperInfo, 1);
|
||||
*info = (HelperInfo){
|
||||
.task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
|
||||
.child_stdin = -1,
|
||||
.child_stdout = -1,
|
||||
.pid = -1,
|
||||
};
|
||||
|
||||
if (!g_spawn_async_with_pipes("/",
|
||||
(char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"),
|
||||
(char **) NM_MAKE_STRV(),
|
||||
G_SPAWN_DO_NOT_REAP_CHILD,
|
||||
NULL,
|
||||
NULL,
|
||||
&info->pid,
|
||||
&info->child_stdin,
|
||||
&info->child_stdout,
|
||||
NULL,
|
||||
&error)) {
|
||||
info->child_stdin = -1;
|
||||
info->child_stdout = -1;
|
||||
info->pid = -1;
|
||||
g_task_return_error(info->task,
|
||||
g_error_new(NM_UTILS_ERROR,
|
||||
NM_UTILS_ERROR_UNKNOWN,
|
||||
"error spawning nm-helper: %s",
|
||||
error->message));
|
||||
helper_info_free(info);
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG2D(info, "spawned process with args: %s", (commands = g_strjoinv(" ", (char **) args)));
|
||||
|
||||
info->child_watch_source = g_child_watch_source_new(info->pid);
|
||||
g_source_set_callback(info->child_watch_source,
|
||||
G_SOURCE_FUNC(helper_child_terminated),
|
||||
info,
|
||||
NULL);
|
||||
g_source_attach(info->child_watch_source, g_main_context_get_thread_default());
|
||||
|
||||
info->timeout_source =
|
||||
nm_g_timeout_source_new_seconds(20, G_PRIORITY_DEFAULT, helper_timeout, info, NULL);
|
||||
g_source_attach(info->timeout_source, g_main_context_get_thread_default());
|
||||
|
||||
/* Set file descriptors as non-blocking */
|
||||
fd_flags = fcntl(info->child_stdin, F_GETFD, 0);
|
||||
fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
|
||||
fd_flags = fcntl(info->child_stdout, F_GETFD, 0);
|
||||
fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
|
||||
|
||||
/* Watch process stdin */
|
||||
nm_str_buf_init(&info->out_buffer, 32, TRUE);
|
||||
for (arg = args; *arg; arg++) {
|
||||
nm_str_buf_append(&info->out_buffer, *arg);
|
||||
nm_str_buf_append_c(&info->out_buffer, '\0');
|
||||
}
|
||||
info->output_source = nm_g_unix_fd_source_new(info->child_stdin,
|
||||
G_IO_OUT | G_IO_ERR | G_IO_HUP,
|
||||
G_PRIORITY_DEFAULT,
|
||||
helper_can_write,
|
||||
info,
|
||||
NULL);
|
||||
g_source_attach(info->output_source, g_main_context_get_thread_default());
|
||||
|
||||
/* Watch process stdout */
|
||||
nm_str_buf_init(&info->in_buffer, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
|
||||
info->input_source = nm_g_unix_fd_source_new(info->child_stdout,
|
||||
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
||||
G_PRIORITY_DEFAULT,
|
||||
helper_have_data,
|
||||
info,
|
||||
NULL);
|
||||
g_source_attach(info->input_source, g_main_context_get_thread_default());
|
||||
|
||||
if (cancellable) {
|
||||
gulong signal_id;
|
||||
|
||||
signal_id = g_cancellable_connect(cancellable, G_CALLBACK(helper_cancelled), info, NULL);
|
||||
if (signal_id == 0) {
|
||||
/* the request is already cancelled. Return. */
|
||||
return;
|
||||
}
|
||||
info->cancellable_id = signal_id;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error)
|
||||
{
|
||||
GTask *task = G_TASK(result);
|
||||
|
||||
nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper));
|
||||
|
||||
return g_task_propagate_pointer(task, error);
|
||||
}
|
||||
|
|
|
@ -420,4 +420,13 @@ guint8 nm_wifi_utils_level_to_quality(int val);
|
|||
#define NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN "Unable to determine UID of the request"
|
||||
#define NM_UTILS_ERROR_MSG_INSUFF_PRIV "Insufficient privileges"
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
void nm_utils_spawn_helper(const char *const * args,
|
||||
GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer cb_data);
|
||||
|
||||
char *nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error);
|
||||
|
||||
#endif /* __NM_CORE_UTILS_H__ */
|
||||
|
|
|
@ -93,6 +93,7 @@ if enable_nmtui
|
|||
endif
|
||||
subdir('nmcli')
|
||||
subdir('nm-dispatcher')
|
||||
subdir('nm-daemon-helper')
|
||||
subdir('nm-online')
|
||||
if enable_nmtui
|
||||
subdir('nmtui')
|
||||
|
|
15
src/nm-daemon-helper/meson.build
Normal file
15
src/nm-daemon-helper/meson.build
Normal file
|
@ -0,0 +1,15 @@
|
|||
executable(
|
||||
'nm-daemon-helper',
|
||||
'nm-daemon-helper.c',
|
||||
include_directories : [
|
||||
src_inc,
|
||||
top_inc,
|
||||
],
|
||||
link_with: [
|
||||
libnm_std_aux,
|
||||
],
|
||||
link_args: ldflags_linker_script_binary,
|
||||
link_depends: linker_script_binary,
|
||||
install: true,
|
||||
install_dir: nm_libexecdir,
|
||||
)
|
63
src/nm-daemon-helper/nm-daemon-helper.c
Normal file
63
src/nm-daemon-helper/nm-daemon-helper.c
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/* Copyright (C) 2021 Red Hat, Inc. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "libnm-std-aux/nm-std-aux.h"
|
||||
|
||||
enum {
|
||||
RETURN_SUCCESS = 0,
|
||||
RETURN_INVALID_CMD = 1,
|
||||
RETURN_INVALID_ARGS = 2,
|
||||
RETURN_ERROR = 3,
|
||||
};
|
||||
|
||||
static char *
|
||||
read_arg(void)
|
||||
{
|
||||
nm_auto_free char *arg = NULL;
|
||||
size_t len = 0;
|
||||
|
||||
if (getdelim(&arg, &len, '\0', stdin) < 0)
|
||||
return NULL;
|
||||
|
||||
return nm_steal_pointer(&arg);
|
||||
}
|
||||
|
||||
static int
|
||||
more_args(void)
|
||||
{
|
||||
nm_auto_free char *arg = NULL;
|
||||
|
||||
arg = read_arg();
|
||||
|
||||
return !!arg;
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_version(void)
|
||||
{
|
||||
if (more_args())
|
||||
return RETURN_INVALID_ARGS;
|
||||
|
||||
printf("1");
|
||||
return RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
nm_auto_free char *cmd = NULL;
|
||||
|
||||
cmd = read_arg();
|
||||
if (!cmd)
|
||||
return RETURN_INVALID_CMD;
|
||||
|
||||
if (nm_streq(cmd, "version")) {
|
||||
return cmd_version();
|
||||
}
|
||||
|
||||
return RETURN_INVALID_CMD;
|
||||
}
|
Loading…
Reference in New Issue
Block a user