diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index e377616f8..f6a0c9e9f 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -140,6 +140,12 @@ if (NOT SKIPREADLINE EQUAL 1) if (READLINE_INCLUDE_DIRS AND READLINE_LIBRARIES) set(READLINE_FOUND ON) endif (READLINE_INCLUDE_DIRS AND READLINE_LIBRARIES) +else (NOT SKIPREADLINE EQUAL 1) + if (NOT SKIPLINENOISE EQUAL 1) + if (EXISTS ${PM3_ROOT}/client/deps/linenoise/) + set(LINENOISE_LOCAL_FOUND ON) + endif (EXISTS ${PM3_ROOT}/client/deps/linenoise/) + endif (NOT SKIPLINENOISE EQUAL 1) endif (NOT SKIPREADLINE EQUAL 1) if (NOT SKIPJANSSONSYSTEM EQUAL 1) @@ -500,6 +506,18 @@ else (SKIPREADLINE EQUAL 1) endif (READLINE_FOUND) endif(SKIPREADLINE EQUAL 1) +if (NOT READLINE_FOUND) + if (SKIPLINENOISE EQUAL 1) + message(STATUS "Linenoise library: skipped") + else (SKIPLINENOISE EQUAL 1) + if (LINENOISE_LOCAL_FOUND) + message(STATUS "Linenoise library: enabled") + else (LINENOISE_LOCAL_FOUND) + message(STATUS "Linenoise library: Linenoise not found, disabled") + endif (LINENOISE_LOCAL_FOUND) + endif (SKIPLINENOISE EQUAL 1) +endif (NOT READLINE_FOUND) + if (SKIPWHEREAMISYSTEM EQUAL 1) message(STATUS "Whereami library: local library forced") else (SKIPWHEREAMISYSTEM EQUAL 1) @@ -578,6 +596,13 @@ if (NOT JANSSON_FOUND) set(ADDITIONAL_LNK pm3rrg_rdv4_jansson ${ADDITIONAL_LNK}) endif (NOT JANSSON_FOUND) +if (NOT READLINE_FOUND) + if (LINENOISE_LOCAL_FOUND) + add_definitions("-DHAVE_LINENOISE") + set(ADDITIONAL_LNK pm3rrg_rdv4_linenoise ${ADDITIONAL_LNK}) + endif (LINENOISE_LOCAL_FOUND) +endif (NOT READLINE_FOUND) + if (NOT WHEREAMI_FOUND) set(ADDITIONAL_LNK pm3rrg_rdv4_whereami ${ADDITIONAL_LNK}) endif (NOT WHEREAMI_FOUND) diff --git a/client/Makefile b/client/Makefile index 905609b36..4debb130f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -78,6 +78,14 @@ JANSSONLIBINC = -I$(JANSSONLIBPATH) JANSSONLIB = $(JANSSONLIBPATH)/libjansson.a JANSSONLIBLD = +## Linenoise +# Can be used if Readline is unavailable or explicitely disabled +# Requires to be unpacked, see deps/get_linenoise.sh +LINENOISELIBPATH = ./deps/linenoise +LINENOISELIBINC = -I$(LINENOISELIBPATH) +LINENOISELIB = $(LINENOISELIBPATH)/liblinenoise.a +LINENOISELIBLD = + ## Lua LUALIBPATH = ./deps/liblua LUALIBINC = -I$(LUALIBPATH) @@ -146,6 +154,9 @@ STATICLIBS += $(HARDNESTEDLIB) LDLIBS +=$(HARDNESTEDLIBLD) PM3INCLUDES += $(HARDNESTEDLIBINC) +## Linenoise +# wait to see if Readline is available + ## Lua ifneq ($(SKIPLUASYSTEM),1) ifdef MACPORTS_PREFIX @@ -325,6 +336,16 @@ ifneq ($(SKIPREADLINE),1) LDLIBS += -lreadline READLINE_FOUND = 1 +else +## Linenoise + ifneq ($(SKIPLINENOISE),1) + ifneq (,$(wildcard $(LINENOISELIBPATH))) + STATICLIBS += $(LINENOISELIB) + LDLIBS += $(LINENOISELIBLD) + PM3INCLUDES += $(LINENOISELIBINC) + LINENOISE_LOCAL_FOUND = 1 + endif + endif endif ######## @@ -362,6 +383,10 @@ ifeq ($(READLINE_FOUND),1) PM3CFLAGS += -DHAVE_READLINE endif +ifeq ($(LINENOISE_LOCAL_FOUND),1) + PM3CFLAGS += -DHAVE_LINENOISE +endif + ifeq ($(BT_FOUND),1) PM3CFLAGS += -DHAVE_BLUEZ endif @@ -466,6 +491,18 @@ else endif endif +ifneq ($(READLINE_FOUND),1) + ifeq ($(SKIPLINENOISE),1) + $(info Linenoise library: skipped) + else + ifeq ($(LINENOISE_LOCAL_FOUND),1) + $(info Linenoise library: enabled) + else + $(info Linenoise library: Linenoise not found, disabled) + endif + endif +endif + ifeq ($(SKIPWHEREAMISYSTEM),1) $(info Whereami library: local library forced) else @@ -737,6 +774,9 @@ clean: $(Q)$(MAKE) --no-print-directory -C $(CLIPARSERLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(JANSSONLIBPATH) clean +ifeq ($(LINENOISE_LOCAL_FOUND), 1) + $(Q)$(MAKE) --no-print-directory -C $(LINENOISELIBPATH) clean +endif $(Q)$(MAKE) --no-print-directory -C $(LUALIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(REVENGLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(TINYCBORLIBPATH) clean @@ -792,6 +832,12 @@ ifneq ($(JANSSON_FOUND),1) $(Q)$(MAKE) --no-print-directory -C $(JANSSONLIBPATH) all endif +$(LINENOISELIB): .FORCE +ifeq ($(LINENOISE_LOCAL_FOUND), 1) + $(info [*] MAKE $@) + $(Q)$(MAKE) --no-print-directory -C $(LINENOISELIBPATH) all +endif + $(LUALIB): .FORCE ifneq ($(LUA_FOUND),1) $(info [*] MAKE $@ for $(LUAPLATFORM)) diff --git a/client/deps/.gitignore b/client/deps/.gitignore new file mode 100644 index 000000000..c3eede102 --- /dev/null +++ b/client/deps/.gitignore @@ -0,0 +1,2 @@ +linenoise/ +linenoise.cmake diff --git a/client/deps/CMakeLists.txt b/client/deps/CMakeLists.txt index f7b96a7ea..87613c0fe 100644 --- a/client/deps/CMakeLists.txt +++ b/client/deps/CMakeLists.txt @@ -10,6 +10,9 @@ endif() if (NOT TARGET pm3rrg_rdv4_jansson) include(jansson.cmake) endif() +if ((NOT TARGET pm3rrg_rdv4_linenoise) AND (EXISTS ${PM3_ROOT}/client/deps/linenoise/)) + include(linenoise.cmake) +endif() if (NOT TARGET pm3rrg_rdv4_lua) include(lua.cmake) endif() diff --git a/client/deps/get_linenoise.sh b/client/deps/get_linenoise.sh new file mode 100755 index 000000000..4bb6dbc81 --- /dev/null +++ b/client/deps/get_linenoise.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Can be used if Readline is unavailable or explicitely disabled +# Note that ConvertUTF.cpp is under redis-only license therefore +# if you are maintainer, think twice before including it + +version=1.0.1 +mkdir -p linenoise +ZP=linenoise-ng-$version +if [ ! -f "${ZP}.zip" ]; then + wget -O "${ZP}.zip" https://github.com/arangodb/linenoise-ng/archive/v$version.zip +fi +unzip -o -j "${ZP}.zip" $ZP/src/ConvertUTF.cpp $ZP/src/ConvertUTF.h $ZP/LICENSE $ZP/src/linenoise.cpp $ZP/include/linenoise.h $ZP/README.md $ZP/src/wcwidth.cpp -d linenoise +#echo "Please do make style" + +echo "Generating linenoise.cmake..." +cat > linenoise.cmake << EOF +add_library(pm3rrg_rdv4_linenoise STATIC + linenoise/ConvertUTF.cpp + linenoise/linenoise.cpp + linenoise/wcwidth.cpp +) + +target_compile_definitions(pm3rrg_rdv4_linenoise PRIVATE NDEBUG) +target_include_directories(pm3rrg_rdv4_linenoise INTERFACE linenoise) +target_compile_options(pm3rrg_rdv4_linenoise PRIVATE -Wall -Werror -O3) +set_property(TARGET pm3rrg_rdv4_linenoise PROPERTY POSITION_INDEPENDENT_CODE ON) +EOF + +cd linenoise +echo "Generating linenoise/Makefile..." +cat > Makefile << EOF +MYSRCPATHS = +MYINCLUDES = +MYCXXFLAGS = -DNDEBUG -std=c++11 -fomit-frame-pointer +MYDEFS = +MYCXXSRCS = ConvertUTF.cpp linenoise.cpp wcwidth.cpp + +LIB_A = liblinenoise.a + +include ../../../Makefile.host +EOF + +# Patch to get proper autocompletion of subcommands +patch << EOF +diff -Naur linenoise.cpp linenoise.cpp ++++ linenoise.cpp 2017-03-06 17:01:33.000000000 +0100 +--- linenoise.cpp 2022-01-29 10:37:19.656202922 +0100 +@@ -1956,7 +1956,7 @@ + // character and + // extract a copy to parse. we also handle the case where tab is hit while + // not at end-of-line. +- int startIndex = pos; ++ int startIndex = 0; + while (--startIndex >= 0) { + if (strchr(breakChars, buf32[startIndex])) { + break; +EOF diff --git a/client/deps/linenoise-ng-1.0.1.zip b/client/deps/linenoise-ng-1.0.1.zip new file mode 100644 index 000000000..8093590c0 Binary files /dev/null and b/client/deps/linenoise-ng-1.0.1.zip differ diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index a31a3110d..618b5af28 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -141,6 +141,12 @@ if (NOT SKIPREADLINE EQUAL 1) if (READLINE_INCLUDE_DIRS AND READLINE_LIBRARIES) set(READLINE_FOUND ON) endif (READLINE_INCLUDE_DIRS AND READLINE_LIBRARIES) +else (NOT SKIPREADLINE EQUAL 1) + if (NOT SKIPLINENOISE EQUAL 1) + if (EXISTS ${PM3_ROOT}/client/deps/linenoise/) + set(LINENOISE_LOCAL_FOUND ON) + endif (EXISTS ${PM3_ROOT}/client/deps/linenoise/) + endif (NOT SKIPLINENOISE EQUAL 1) endif (NOT SKIPREADLINE EQUAL 1) if (NOT SKIPJANSSONSYSTEM EQUAL 1) @@ -501,6 +507,18 @@ else (SKIPREADLINE EQUAL 1) endif (READLINE_FOUND) endif(SKIPREADLINE EQUAL 1) +if (NOT READLINE_FOUND) + if (SKIPLINENOISE EQUAL 1) + message(STATUS "Linenoise library: skipped") + else (SKIPLINENOISE EQUAL 1) + if (LINENOISE_LOCAL_FOUND) + message(STATUS "Linenoise library: enabled") + else (LINENOISE_LOCAL_FOUND) + message(STATUS "Linenoise library: Linenoise not found, disabled") + endif (LINENOISE_LOCAL_FOUND) + endif (SKIPLINENOISE EQUAL 1) +endif (NOT READLINE_FOUND) + if (SKIPWHEREAMISYSTEM EQUAL 1) message(STATUS "Whereami library: local library forced") else (SKIPWHEREAMISYSTEM EQUAL 1) @@ -581,6 +599,13 @@ if (NOT JANSSON_FOUND) set(ADDITIONAL_LNK pm3rrg_rdv4_jansson ${ADDITIONAL_LNK}) endif (NOT JANSSON_FOUND) +if (NOT READLINE_FOUND) + if (LINENOISE_LOCAL_FOUND) + add_definitions("-DHAVE_LINENOISE") + set(ADDITIONAL_LNK pm3rrg_rdv4_linenoise ${ADDITIONAL_LNK}) + endif (LINENOISE_LOCAL_FOUND) +endif (NOT READLINE_FOUND) + if (NOT WHEREAMI_FOUND) set(ADDITIONAL_LNK pm3rrg_rdv4_whereami ${ADDITIONAL_LNK}) endif (NOT WHEREAMI_FOUND) diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index 991815e46..5e462ea8d 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -995,8 +995,10 @@ void pm3_version(bool verbose, bool oneliner) { PrintAndLogEx(NORMAL, " platform.................. " PM3HOSTOS " / " PM3HOSTARCH); #if defined(HAVE_READLINE) PrintAndLogEx(NORMAL, " Readline support.......... " _GREEN_("present")); +#elif defined(HAVE_LINENOISE) + PrintAndLogEx(NORMAL, " Linenoise support......... " _GREEN_("present")); #else - PrintAndLogEx(NORMAL, " Readline support.......... " _YELLOW_("absent")); + PrintAndLogEx(NORMAL, " Readline/Linenoise support." _YELLOW_("absent")); #endif #ifdef HAVE_GUI PrintAndLogEx(NORMAL, " QT GUI support............ " _GREEN_("present")); diff --git a/client/src/pm3line.c b/client/src/pm3line.c index f070d3813..91e7df6c2 100644 --- a/client/src/pm3line.c +++ b/client/src/pm3line.c @@ -24,6 +24,8 @@ #if defined(HAVE_READLINE) #include #include +#elif defined(HAVE_LINENOISE) +#include "linenoise.h" #endif #include "pm3line_vocabulory.h" #include "pm3_cmd.h" @@ -73,6 +75,39 @@ static char **rl_command_completion(const char *text, int start, int end) { return rl_completion_matches(text, rl_command_generator); } +#elif defined(HAVE_LINENOISE) +static void ln_command_completion(const char *text, linenoiseCompletions *lc) { + int index = 0; + const char *prev_match = ""; + size_t prev_match_len = 0; + size_t len = strlen(text); + const char *command; + while ((command = vocabulory[index].name)) { + + // When no pm3 device present + // and the command is not available offline, + // we skip it. + if ((g_session.pm3_present == false) && (vocabulory[index].offline == false )) { + index++; + continue; + } + + index++; + + if (strncmp (command, text, len) == 0) { + const char *space = strstr(command + len, " "); + if (space != NULL) { + if ((prev_match_len == 0) || (strncmp (prev_match, command, prev_match_len < space - command ? prev_match_len : space - command) != 0)) { + linenoiseAddCompletion(lc, str_ndup(command, space - command + 1)); + prev_match = command; + prev_match_len = space - command + 1; + } + } else { + linenoiseAddCompletion(lc, command); + } + } + } +} #endif // HAVE_READLINE # if defined(_WIN32) @@ -119,12 +154,17 @@ void pm3line_init(void) { #ifdef RL_STATE_READCMD rl_extend_line_buffer(1024); #endif // RL_STATE_READCMD +#elif defined(HAVE_LINENOISE) + linenoiseInstallWindowChangeHandler(); + linenoiseSetCompletionCallback(ln_command_completion); #endif // HAVE_READLINE } char *pm3line_read(const char *s) { #if defined(HAVE_READLINE) return readline(s); +#elif defined(HAVE_LINENOISE) + return linenoise(s); #else printf("%s", s); char *answer = NULL; @@ -160,6 +200,12 @@ int pm3line_load_history(const char *path) { } else { return PM3_ESOFT; } +#elif defined(HAVE_LINENOISE) + if (linenoiseHistoryLoad(path) == 0) { + return PM3_SUCCESS; + } else { + return PM3_ESOFT; + } #else (void) path; return PM3_ENOTIMPL; @@ -173,6 +219,9 @@ void pm3line_add_history(const char *line) { if ((!entry) || (strcmp(entry->line, line) != 0)) { add_history(line); } +#elif defined(HAVE_LINENOISE) + // linenoiseHistoryAdd takes already care of duplicate entries + linenoiseHistoryAdd(line); #else (void) line; #endif @@ -182,6 +231,8 @@ void pm3line_flush_history(void) { if (g_session.history_path) { #if defined(HAVE_READLINE) write_history(g_session.history_path); +#elif defined(HAVE_LINENOISE) + linenoiseHistorySave(g_session.history_path); #endif // HAVE_READLINE free(g_session.history_path); g_session.history_path = NULL; diff --git a/doc/md/Development/Maintainers.md b/doc/md/Development/Maintainers.md index 2055e5bd2..2a72e500f 100644 --- a/doc/md/Development/Maintainers.md +++ b/doc/md/Development/Maintainers.md @@ -66,6 +66,11 @@ It's also possible to skip parts even if libraries are present in the compilatio * `make client SKIPJANSSONSYSTEM=1` to skip system Jansson lib even if libjansson is present, use embedded Jansson lib instead * `make client SKIPWHEREAMISYSTEM=1` to skip system Whereami lib even if libwhereami is present, use embedded whereami lib instead +By default, the client is using Readline, but this can be disabled: +* `make client SKIPREADLINE=1` to skip system Readline lib even if libreadline is present + +When Readline is disabled, it is possible to use Linenoise instead. Note that Linenoise-ng contains `ConvertUTF.cpp` which is under a redistribution-only license, therefore think twice before including it in a release. To get Linenoise-ng, see `client/deps/get_linenoise.sh`. + If you're cross-compiling, these ones might be useful: * `make client SKIPREVENGTEST=1` to skip compilation and execution of a consistency test for reveng, which can be problematic in case of cross-compilation diff --git a/doc/md/Development/Makefile-vs-CMake.md b/doc/md/Development/Makefile-vs-CMake.md index 79ac43e2c..c54e911ff 100644 --- a/doc/md/Development/Makefile-vs-CMake.md +++ b/doc/md/Development/Makefile-vs-CMake.md @@ -62,7 +62,7 @@ At the moment both are maintained because they don't perfectly overlap yet. | dep libm | sys | sys | | | libm detection | **none** | **none** (1) | (1) cf https://cmake.org/pipermail/cmake/2019-March/069168.html ? | | dep mbedtls | in_common | in_common | no sys lib: missing support for CMAC in def conf (btw no .pc available) | -| dep python3 | opt, sys, <3.8 & 3.8 | opt, sys, <3.8 & 3.8 | | +| dep python3 | opt, sys, < 3.8 & 3.8 | opt, sys, < 3.8 & 3.8 | | | python3 detection | pc | pkg_search_module | | | `SKIPPYTHON` | yes | yes | | | dep pthread | sys | sys | | @@ -74,6 +74,7 @@ At the moment both are maintained because they don't perfectly overlap yet. | dep readline | sys | sys | | | readline detection | **none** (1) | find*(2), Cross:getzip | (1) OSX: hardcoded path (2) additional paths for OSX | | `SKIPREADLINE` | yes | yes | CLI not fully functional without Readline | +| `SKIPLINENOISE` | yes | yes | replacement of Readline, not as complete | | dep reveng | in_deps | in_deps | | | `SKIPREVENGTEST` | yes(1) | **no**(2) | (1) e.g. if cross-compilation (2) tests aren't compiled/ran with cmake | | dep tinycbor | in_deps | in_deps | |