mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager
synced 2024-07-22 10:46:59 +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/initrd/tests/test-ibft-reader
|
||||||
/src/ndisc/tests/test-ndisc-fake
|
/src/ndisc/tests/test-ndisc-fake
|
||||||
/src/ndisc/tests/test-ndisc-linux
|
/src/ndisc/tests/test-ndisc-linux
|
||||||
|
/src/nm-daemon-helper/nm-daemon-helper
|
||||||
/src/nm-iface-helper
|
/src/nm-iface-helper
|
||||||
/src/platform/tests/dump
|
/src/platform/tests/dump
|
||||||
/src/platform/tests/monitor
|
/src/platform/tests/monitor
|
||||||
|
|
25
Makefile.am
25
Makefile.am
|
@ -4598,6 +4598,31 @@ EXTRA_DIST += \
|
||||||
src/nm-dispatcher/tests/meson.build \
|
src/nm-dispatcher/tests/meson.build \
|
||||||
$(NULL)
|
$(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
|
# src/nm-online
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
|
@ -998,6 +998,7 @@ fi
|
||||||
%{_libexecdir}/nm-dispatcher
|
%{_libexecdir}/nm-dispatcher
|
||||||
%{_libexecdir}/nm-iface-helper
|
%{_libexecdir}/nm-iface-helper
|
||||||
%{_libexecdir}/nm-initrd-generator
|
%{_libexecdir}/nm-initrd-generator
|
||||||
|
%{_libexecdir}/nm-daemon-helper
|
||||||
%dir %{_libdir}/%{name}
|
%dir %{_libdir}/%{name}
|
||||||
%dir %{nmplugindir}
|
%dir %{nmplugindir}
|
||||||
%{nmplugindir}/libnm-settings-plugin*.so
|
%{nmplugindir}/libnm-settings-plugin*.so
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "libnm-glib-aux/nm-io-utils.h"
|
#include "libnm-glib-aux/nm-io-utils.h"
|
||||||
#include "libnm-glib-aux/nm-secret-utils.h"
|
#include "libnm-glib-aux/nm-secret-utils.h"
|
||||||
#include "libnm-glib-aux/nm-time-utils.h"
|
#include "libnm-glib-aux/nm-time-utils.h"
|
||||||
|
#include "libnm-glib-aux/nm-str-buf.h"
|
||||||
#include "nm-utils.h"
|
#include "nm-utils.h"
|
||||||
#include "libnm-core-intern/nm-core-internal.h"
|
#include "libnm-core-intern/nm-core-internal.h"
|
||||||
#include "nm-setting-connection.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_MANAGED, "managed"),
|
||||||
NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_ASSUME, "assume"),
|
NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_ASSUME, "assume"),
|
||||||
NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_EXTERNAL, "external"), );
|
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_REQ_UID_UKNOWN "Unable to determine UID of the request"
|
||||||
#define NM_UTILS_ERROR_MSG_INSUFF_PRIV "Insufficient privileges"
|
#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__ */
|
#endif /* __NM_CORE_UTILS_H__ */
|
||||||
|
|
|
@ -93,6 +93,7 @@ if enable_nmtui
|
||||||
endif
|
endif
|
||||||
subdir('nmcli')
|
subdir('nmcli')
|
||||||
subdir('nm-dispatcher')
|
subdir('nm-dispatcher')
|
||||||
|
subdir('nm-daemon-helper')
|
||||||
subdir('nm-online')
|
subdir('nm-online')
|
||||||
if enable_nmtui
|
if enable_nmtui
|
||||||
subdir('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 a new issue