From 552e00990d9c61b3a364bd0b773047ce4c277ac4 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Fri, 12 Mar 2021 19:37:53 +0100 Subject: [PATCH 1/7] m4: ax_lib_readline.m4: Use readline libraries when linking add_history test program Up till now the AC_LANG_CALL() was just checking if program which uses the add_history call can be correctly linked without passing required libraries (e.g. -libreadline to LIBS variable). This commit fixes this issue as now the add_history can be found as it is now available in any of passed libs. --- m4/ax_lib_readline.m4 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 index 7efa947e47..d8090da2ac 100644 --- a/m4/ax_lib_readline.m4 +++ b/m4/ax_lib_readline.m4 @@ -94,11 +94,15 @@ AC_DEFUN([AX_LIB_READLINE], [ AC_DEFINE(HAVE_LIBREADLINE, 1, [Define if you have a readline compatible library]) AC_CHECK_HEADERS(readline.h readline/readline.h) + ORIG_LIBS="$LIBS" + LIBS="$ORIG_LIBS $ax_cv_lib_readline" AC_CACHE_CHECK([whether readline supports history], ax_cv_lib_readline_history, [ ax_cv_lib_readline_history="no" AC_LINK_IFELSE([AC_LANG_CALL([], [add_history])], [ax_cv_lib_readline_history="yes"]) ]) + LIBS=$ORIG_LIBS + if test "$ax_cv_lib_readline_history" = "yes"; then AC_DEFINE(HAVE_READLINE_HISTORY, 1, [Define if your readline library has \`add_history']) From 0c5adc6938447cdec62c599fe58fe48b27e1a986 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Thu, 1 Jul 2021 16:00:33 +0200 Subject: [PATCH 2/7] m4: Check for history_set_history_state instead of add_history The add_history function is available on both - libreadline and libedit. The read difference between those two libraries is that for libedit the history_set_history_state is missing. On that basis one can assess if we do have history from libreadline or from libedit. --- m4/ax_lib_readline.m4 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 index d8090da2ac..1fcf504141 100644 --- a/m4/ax_lib_readline.m4 +++ b/m4/ax_lib_readline.m4 @@ -99,7 +99,8 @@ AC_DEFUN([AX_LIB_READLINE], [ AC_CACHE_CHECK([whether readline supports history], ax_cv_lib_readline_history, [ ax_cv_lib_readline_history="no" - AC_LINK_IFELSE([AC_LANG_CALL([], [add_history])], [ax_cv_lib_readline_history="yes"]) + AC_LINK_IFELSE([AC_LANG_CALL([], [history_set_history_state])], + [ax_cv_lib_readline_history="yes"]) ]) LIBS=$ORIG_LIBS From 85f3030e4b9c41435143fd4d157fc9f95d6fbcac Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Mon, 5 Apr 2021 16:07:53 +0200 Subject: [PATCH 3/7] cli: meson: Support building nmcli with libedit After this change the nmcli program built with meson will have the possibility to use libedit (BSD license) instead of libreadline (GPLv3). Meson configuration line: meson configure -Dreadline=libedit -C ../nm-build/ or meson --reconfigure -Dreadline=libedit ../nm-build/ ninja -C ../nm-build/ The new 'readline' option is set to 'auto' by default, so the current behavior shall be preserved (and the libreadline is used). Two new config.h flags (always defined) have been introduced - HAVE_EDITLINE_READLINE and HAVE_READLINE_HISTORY. --- config.h.meson | 6 ++++++ meson.build | 42 ++++++++++++++++++++++++++---------------- meson_options.txt | 1 + 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/config.h.meson b/config.h.meson index 432402d5b4..5979793e5c 100644 --- a/config.h.meson +++ b/config.h.meson @@ -255,3 +255,9 @@ #mesondefine HAVE_PIDFD_OPEN #mesondefine HAVE_PIDFD_SEND_SIGNAL #mesondefine HAVE_RT_SIGQUEUEINFO + +/* Define to 1 if you want to use -ledit, otherwise 0 for default -lreadline. */ +#mesondefine HAVE_EDITLINE_READLINE + +/* Define to 1 if you have history support from -lreadline. */ +#mesondefine HAVE_READLINE_HISTORY diff --git a/meson.build b/meson.build index 6082828194..0c5a28fbc6 100644 --- a/meson.build +++ b/meson.build @@ -733,24 +733,33 @@ if enable_concheck endif config_h.set10('WITH_CONCHECK', enable_concheck) +config_h.set10('HAVE_READLINE_HISTORY', false) +config_h.set10('HAVE_EDITLINE_READLINE', false) +enable_readline = get_option('readline') +if enable_readline != 'none' + if enable_readline == 'libreadline' or enable_readline == 'auto' + readline = cc.find_library('readline') + if readline.found() + readline_dep = declare_dependency(link_args: '-lreadline') + config_h.set10('HAVE_READLINE_HISTORY', true) + else + assert(enable_readline == 'auto', 'libreadline was not found') + endif + endif + if enable_readline == 'libedit' or (enable_readline == 'auto' and not readline.found()) + edit = dependency('libedit') + if edit.found() + readline_dep = declare_dependency(link_args: '-ledit') + config_h.set10('HAVE_EDITLINE_READLINE', true) + else + assert(enable_readline == 'auto', 'libedit was not found') + endif + endif +endif + enable_nmcli = get_option('nmcli') if enable_nmcli - # FIXME: check for readline - # AX_LIB_READLINE - readline_dep = declare_dependency(link_args: '-lreadline') - ''' - foreach readline_lib: ['-lreadline', '-ledit', '-leditline'] - if not is_variable('readline_dep') - foreach termcap_lib: ['', '-lncurses', '-ltermcap', '-lcurses'] - test_dep = declare_dependency(link_args: ' '.join([readline_lib, termcap_lib])) - if cc.has_function('readline', dependencies: test_dep) and cc.has_header('readline', dependencies: test_dep) - readline_dep = test_dep - endif - endforeach - endif - endforeach - ''' - assert(readline_dep.found(), 'readline library with terminfo support is required (one of readline, edit, or editline, AND one of ncurses, curses, or termcap)') + assert(enable_readline != 'none', 'readline library with terminfo support is required (one of readline, edit, or editline, AND one of ncurses, curses, or termcap)') endif enable_nmtui = get_option('nmtui') @@ -1086,4 +1095,5 @@ output += ' sanitizers: ' + get_option('b_sanitize') + '\n' output += ' Mozilla Public Suffix List: ' + enable_libpsl.to_string() + '\n' output += ' vapi: ' + enable_vapi.to_string() + '\n' output += ' ebpf: ' + enable_ebpf.to_string() + '\n' +output += ' readline: ' + enable_readline + '\n' message(output) diff --git a/meson_options.txt b/meson_options.txt index 14ed4077a0..76a131ad05 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -72,3 +72,4 @@ option('ld_gc', type: 'boolean', value: true, description: 'Enable garbage colle option('libpsl', type: 'boolean', value: true, description: 'Link against libpsl') option('crypto', type: 'combo', choices: ['nss', 'gnutls'], value: 'nss', description: 'Cryptography library to use for certificate and key operations') option('qt', type: 'boolean', value: true, description: 'enable Qt examples') +option('readline', type: 'combo', choices: ['auto', 'libreadline', 'libedit', 'none'], description: 'Using readline (auto) or libedit)') From 8ea9da85b3934c9258d8e2900d5706ebf933f8f1 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Thu, 1 Jul 2021 15:47:40 +0200 Subject: [PATCH 4/7] m4: cli: Add support for --with-readline=auto|libreadline|libedit|none configuration options This commit provides support for --with-readline=auto|libreadline|libedit|none option for the configure script. It allows building the NetworkManager's nmcli tool with libedit instead of libreadline. With --with-readline=auto the system looks for any eligible readline library to use. Moreover, in this commit all required defines are provided (e.g. HAVE_EDITLINE_READLINE) to allow correct buil of the code. --- configure.ac | 17 ++++++++++++++++- m4/ax_lib_readline.m4 | 29 +++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index c4e345224c..8f40066be0 100644 --- a/configure.ac +++ b/configure.ac @@ -1043,10 +1043,24 @@ fi PKG_CHECK_MODULES(LIBNDP, [libndp]) +AC_ARG_WITH(readline, + AS_HELP_STRING([--with-readline=auto|libreadline|libedit|none], [Using libreadline (auto) or libedit]), + [], [with_readline=auto]) + +if test "$with_readline" != "none" -a "$with_readline" != "auto" -a "$with_readline" != "libreadline" -a "$with_readline" != "libedit"; then + AC_MSG_ERROR([invalid --with-readline option]) +fi + +if test "$with_readline" != "none"; then + AX_LIB_READLINE + if test -z "${READLINE_LIBS}"; then + AC_MSG_FAILURE([readline library not found]) + fi +fi + AC_ARG_WITH(nmcli, AS_HELP_STRING([--with-nmcli=yes|no], [Build nmcli])) if test "$with_nmcli" != no; then - AX_LIB_READLINE build_nmcli=yes else build_nmcli=no @@ -1420,4 +1434,5 @@ echo " crypto: $with_crypto (have-gnutls: $have_crypto_gnutls, have-nss: $have_ echo " sanitizers: $sanitizers" echo " Mozilla Public Suffix List: $with_libpsl" echo " eBPF: $have_ebpf" +echo " readline: $with_readline" echo diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 index 1fcf504141..3365e0b979 100644 --- a/m4/ax_lib_readline.m4 +++ b/m4/ax_lib_readline.m4 @@ -65,7 +65,16 @@ AC_DEFUN([AX_LIB_READLINE], [ AC_CACHE_CHECK([for a readline compatible library], ax_cv_lib_readline, [ ORIG_LIBS="$LIBS" - for readline_lib in readline edit editline; do + + search_readlines="readline edit" + if test "$with_readline" = "libreadline"; then + search_readlines="readline" + fi + if test "$with_readline" = "libedit"; then + search_readlines="edit" + fi + for readline_lib in $search_readlines; do + # prefer ncurses since we use it for nmtui too for termcap_lib in "" termcap curses ncurses; do if test -z "$termcap_lib"; then TRY_LIB="-l$readline_lib" @@ -93,7 +102,16 @@ AC_DEFUN([AX_LIB_READLINE], [ AC_SUBST(READLINE_LIBS) AC_DEFINE(HAVE_LIBREADLINE, 1, [Define if you have a readline compatible library]) - AC_CHECK_HEADERS(readline.h readline/readline.h) + + if test "$with_readline" = "libedit"; then + AC_DEFINE(HAVE_EDITLINE_READLINE, 1, + [Explicitly set to 1 when libedit shall be used]) + else + AC_DEFINE(HAVE_EDITLINE_READLINE, 0, + [By default the libreadline is used as readline library]) + + fi + ORIG_LIBS="$LIBS" LIBS="$ORIG_LIBS $ax_cv_lib_readline" AC_CACHE_CHECK([whether readline supports history], @@ -106,8 +124,11 @@ AC_DEFUN([AX_LIB_READLINE], [ if test "$ax_cv_lib_readline_history" = "yes"; then AC_DEFINE(HAVE_READLINE_HISTORY, 1, - [Define if your readline library has \`add_history']) - AC_CHECK_HEADERS(history.h readline/history.h) + [Define if your readline library has \`history_set_history_state']) + AC_CHECK_HEADERS(readline/history.h histedit.h) + else + AC_DEFINE(HAVE_READLINE_HISTORY, 0, + [Explicitly set to 0 when libreadline shall not be used]) fi fi ])dnl From f47d55fc66bb4873d14d37d77f651ab271637c1c Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 30 Jun 2021 14:42:21 +0200 Subject: [PATCH 5/7] cli: Fix for rl_startup_hook function signatures mismatch (-lreadline vs -ledit) The rl_startup_hook function has different prototype in libreadline and in the libedit. To fix this issue, arguments of hook function has been wrapped to C preprocessor macro and properly adjusted. --- src/nmcli/agent.c | 3 +-- src/nmcli/common.c | 3 +-- src/nmcli/common.h | 10 +++++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/nmcli/agent.c b/src/nmcli/agent.c index a0b23dd19b..a71e40594b 100644 --- a/src/nmcli/agent.c +++ b/src/nmcli/agent.c @@ -53,8 +53,7 @@ usage_agent_all(void) /* for pre-filling a string to readline prompt */ static char *pre_input_deftext; -static int -set_deftext(void) +static int set_deftext(_NMC_RL_STARTUPHOOK_ARGS) { if (pre_input_deftext && rl_startup_hook) { rl_insert_text(pre_input_deftext); diff --git a/src/nmcli/common.c b/src/nmcli/common.c index a01a2f7247..690bcf0d03 100644 --- a/src/nmcli/common.c +++ b/src/nmcli/common.c @@ -1136,8 +1136,7 @@ nmc_rl_gen_func_ifnames(const char *text, int state) /* for pre-filling a string to readline prompt */ char *nmc_rl_pre_input_deftext; -int -nmc_rl_set_deftext(void) +int nmc_rl_set_deftext(_NMC_RL_STARTUPHOOK_ARGS) { if (nmc_rl_pre_input_deftext && rl_startup_hook) { rl_insert_text(nmc_rl_pre_input_deftext); diff --git a/src/nmcli/common.h b/src/nmcli/common.h index a479a45569..908dad0672 100644 --- a/src/nmcli/common.h +++ b/src/nmcli/common.h @@ -50,9 +50,17 @@ char * nmc_rl_gen_func_ifnames(const char *text, int state); gboolean nmc_get_in_readline(void); void nmc_set_in_readline(gboolean in_readline); +#if HAVE_EDITLINE_READLINE +/* libedit has different signature for rl_startup_hook function */ +#define _NMC_RL_STARTUPHOOK_ARGS const char *c, int i +#else +/* By default the libreadline shall be used */ +#define _NMC_RL_STARTUPHOOK_ARGS void +#endif + /* for pre-filling a string to readline prompt */ extern char *nmc_rl_pre_input_deftext; -int nmc_rl_set_deftext(void); +int nmc_rl_set_deftext(_NMC_RL_STARTUPHOOK_ARGS); char *nmc_parse_lldp_capabilities(guint value); From 823445021ab69c4a59b02994696484e13ba76536 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 30 Jun 2021 15:53:44 +0200 Subject: [PATCH 6/7] cli: Provide rl_completion_display_matches_hook function only for libreadline When one wants to compile the nmcli with libedit (GPLv2 replacement of libreadline) the rl_completion_display_matches_hook hook shall be left untouched (as NULL) as it is not supported in libedit. --- src/nmcli/connections.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index 87cd2a8b94..4f291408af 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -5755,6 +5755,9 @@ finish: /*****************************************************************************/ /* Functions for readline TAB completion in editor */ +#if HAVE_EDITLINE_READLINE +#define uuid_display_hook ((void (*)(void)) NULL) +#else static void uuid_display_hook(char **array, int len, int max_len) { @@ -5778,6 +5781,7 @@ uuid_display_hook(char **array, int len, int max_len) rl_display_match_list(array, len, max_len + max + 3); rl_forced_update_display(); } +#endif static char * gen_nmcli_cmds_menu(const char *text, int state) From d1dad6ae27e05df2c580d81ced808f81498a92eb Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Fri, 2 Jul 2021 10:30:22 +0200 Subject: [PATCH 7/7] cli: Provide optional support for libedit instead of readline The libreadline starting from version 6 is licensed as GPLv3. For some use cases it is not acceptable to use this license. In the NetworkManager the libreadline is used by nmcli. This change allows using libedit instead of libreadline. Following adjustments were made: 1. The history_set_history_state() is not supported in the libedit. Instead, the where_history() with remove_history() were used to remove the history content if needed. 2. rl_complete_with_tilde_expansion - it is the binary flag used only when one wants to have the expansion support. The libedit is not supporting and hence exporting this flag. --- src/nmcli/agent.c | 5 ++++- src/nmcli/common.c | 21 +++++++++++++++++++-- src/nmcli/connections.c | 12 +++++++++++- src/nmcli/devices.c | 4 ++++ src/nmcli/nmcli.c | 4 ++++ 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/nmcli/agent.c b/src/nmcli/agent.c index a71e40594b..842843995a 100644 --- a/src/nmcli/agent.c +++ b/src/nmcli/agent.c @@ -7,9 +7,12 @@ #include #include +#if HAVE_EDITLINE_READLINE +#include +#else #include #include - +#endif #include "common.h" #include "utils.h" #include "libnmc-base/nm-secret-agent-simple.h" diff --git a/src/nmcli/common.c b/src/nmcli/common.c index 690bcf0d03..67bbb6a179 100644 --- a/src/nmcli/common.c +++ b/src/nmcli/common.c @@ -10,9 +10,12 @@ #include #include #include -#include +#if HAVE_EDITLINE_READLINE +#include +#else #include - +#include +#endif #include "libnm-client-aux-extern/nm-libnm-aux.h" #include "libnmc-base/nm-vpn-helpers.h" @@ -1007,10 +1010,14 @@ nmc_readline_echo(const NmcConfig *nmc_config, gboolean echo_on, const char *pro va_list args; gs_free char *prompt = NULL; char * str; +#if HAVE_READLINE_HISTORY nm_auto_free HISTORY_STATE *saved_history = NULL; HISTORY_STATE passwd_history = { 0, }; +#else + int start, curpos; +#endif va_start(args, prompt_fmt); prompt = g_strdup_vprintf(prompt_fmt, args); @@ -1020,8 +1027,12 @@ nmc_readline_echo(const NmcConfig *nmc_config, gboolean echo_on, const char *pro /* Hide the actual password */ if (!echo_on) { +#if HAVE_READLINE_HISTORY saved_history = history_get_history_state(); history_set_history_state(&passwd_history); +#else + start = where_history(); +#endif /* stifling history is important as it tells readline to * not store anything, otherwise sensitive data could be * leaked */ @@ -1034,7 +1045,13 @@ nmc_readline_echo(const NmcConfig *nmc_config, gboolean echo_on, const char *pro /* Restore the non-hiding behavior */ if (!echo_on) { rl_redisplay_function = rl_redisplay; +#if HAVE_READLINE_HISTORY history_set_history_state(saved_history); +#else + curpos = where_history(); + while (curpos > start) + remove_history(curpos--); +#endif } return str; diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index 4f291408af..70fa89608b 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -11,8 +11,12 @@ #include #include #include +#if HAVE_EDITLINE_READLINE +#include +#else #include #include +#endif #include #include "libnm-glib-aux/nm-dbus-aux.h" @@ -6453,8 +6457,10 @@ gen_property_values(const char *text, int state) return nmc_rl_gen_func_basic(text, state, avals); } +#if !HAVE_EDITLINE_READLINE /* from readline */ extern int rl_complete_with_tilde_expansion; +#endif /* * Attempt to complete on the contents of TEXT. START and END show the @@ -6482,8 +6488,10 @@ nmcli_editor_tab_completion(const char *text, int start, int end) /* Disable default filename completion */ rl_attempted_completion_over = 1; +#if !HAVE_EDITLINE_READLINE /* Enable tilde expansion when filenames are completed */ rl_complete_with_tilde_expansion = 1; +#endif /* Filter out possible ANSI color escape sequences */ prompt_tmp = nmc_filter_out_colors((const char *) rl_prompt); @@ -9617,8 +9625,10 @@ nmcli_con_tab_completion(const char *text, int start, int end) nmc_tab_completion.words = _meta_abstract_complete(info, text); generator_func = _meta_abstract_generator; } else if (nm_streq0(rl_prompt, PROMPT_IMPORT_FILE)) { - rl_attempted_completion_over = 0; + rl_attempted_completion_over = 0; +#if !HAVE_EDITLINE_READLINE rl_complete_with_tilde_expansion = 1; +#endif } else if (nm_streq0(rl_prompt, PROMPT_VPN_CONNECTION)) { generator_func = gen_vpn_ids; } diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 33be4e7a6d..00339868b6 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -9,7 +9,11 @@ #include #include +#if HAVE_EDITLINE_READLINE +#include +#else #include +#endif #include #include "libnm-glib-aux/nm-secret-utils.h" diff --git a/src/nmcli/nmcli.c b/src/nmcli/nmcli.c index 9c6a791187..b2b2cd8143 100644 --- a/src/nmcli/nmcli.c +++ b/src/nmcli/nmcli.c @@ -14,8 +14,12 @@ #include #include #include +#if HAVE_EDITLINE_READLINE +#include +#else #include #include +#endif #include "libnmc-base/nm-client-utils.h"