Add pm3line API to hide readline, allowing easier implementation of alternatives

This commit is contained in:
Philippe Teuwen 2022-01-10 23:53:01 +01:00
parent 4e78034a6e
commit fc13b6b20c
12 changed files with 265 additions and 227 deletions

View file

@ -287,7 +287,7 @@ style:
[ -x client/proxmark3 ] && client/proxmark3 --fulltext | sed 's#com[0-9]#/dev/ttyacm0#'|python3 client/pyscripts/pm3_help2json.py - doc/commands.json
# Update the readline autocomplete autogenerated code
[ -x client/proxmark3 ] && client/proxmark3 --fulltext | python3 client/pyscripts/pm3_help2list.py - client/src/rl_vocabulory.h
[ -x client/proxmark3 ] && client/proxmark3 --fulltext | python3 client/pyscripts/pm3_help2list.py - client/src/pm3line_vocabulory.h
# Detecting weird codepages and tabs.

View file

@ -340,6 +340,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/pm3.c
${PM3_ROOT}/client/src/pm3_binlib.c
${PM3_ROOT}/client/src/pm3_bitlib.c
${PM3_ROOT}/client/src/pm3line.c
${PM3_ROOT}/client/src/prng.c
${PM3_ROOT}/client/src/scandir.c
${PM3_ROOT}/client/src/scripting.c
@ -578,6 +579,7 @@ endif (NOT APPLE)
if (NOT JANSSON_FOUND)
set(ADDITIONAL_LNK pm3rrg_rdv4_jansson ${ADDITIONAL_LNK})
endif (NOT JANSSON_FOUND)
if (NOT WHEREAMI_FOUND)
set(ADDITIONAL_LNK pm3rrg_rdv4_whereami ${ADDITIONAL_LNK})
endif (NOT WHEREAMI_FOUND)

View file

@ -635,6 +635,7 @@ SRCS = mifare/aiddesfire.c \
pm3_bitlib.c \
preferences.c \
prng.c \
pm3line.c \
proxmark3.c \
scandir.c \
uart/uart_posix.c \

View file

@ -341,6 +341,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/pm3.c
${PM3_ROOT}/client/src/pm3_binlib.c
${PM3_ROOT}/client/src/pm3_bitlib.c
${PM3_ROOT}/client/src/pm3line.c
${PM3_ROOT}/client/src/prng.c
${PM3_ROOT}/client/src/scandir.c
${PM3_ROOT}/client/src/scripting.c
@ -581,6 +582,7 @@ endif (NOT APPLE)
if (NOT JANSSON_FOUND)
set(ADDITIONAL_LNK pm3rrg_rdv4_jansson ${ADDITIONAL_LNK})
endif (NOT JANSSON_FOUND)
if (NOT WHEREAMI_FOUND)
set(ADDITIONAL_LNK pm3rrg_rdv4_whereami ${ADDITIONAL_LNK})
endif (NOT WHEREAMI_FOUND)

View file

@ -12,7 +12,7 @@ This version
- Iceman
Note:
This script is used as a helper script to generate the rl_vocabulory.h file.
This script is used as a helper script to generate the pm3line_vocabulory.h file.
It need a working proxmark3 client to extract the help text.
Ie: this script can't be used inside the normal build sequence.
@ -65,22 +65,14 @@ def main():
// readline auto complete utilities
//-----------------------------------------------------------------------------
#ifndef RL_VOCABULORY_H__
#define RL_VOCABULORY_H__
#ifndef PM3LINE_VOCABULORY_H__
#define PM3LINE_VOCABULORY_H__
#ifdef __cplusplus
extern "C" {
#endif
#if defined(HAVE_READLINE)
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include "ui.h" // g_session
#include "util.h" // str_ndup
char* rl_command_generator(const char *text, int state);
char **rl_command_completion(const char *text, int start, int end);
#include <stdbool.h>
typedef struct vocabulory_s {
bool offline;
@ -100,50 +92,6 @@ const static vocabulory_t vocabulory[] = {\n""")
args.output_file.write(""" {0, NULL}\n};
char **rl_command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 0;
return rl_completion_matches (text, rl_command_generator);
}
char* rl_command_generator(const char *text, int state) {
static int index;
static size_t len;
size_t rlen = strlen(rl_line_buffer);
const char *command;
if (!state) {
index = 0;
len = strlen(text);
}
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, rl_line_buffer, rlen) == 0) {
const char *next = command + (rlen - len);
const char *space = strstr(next, " ");
if (space != NULL) {
return str_ndup(next, space - next);
}
return str_dup(next);
}
}
return NULL;
}
#endif
#ifdef __cplusplus
}
#endif

View file

@ -17,13 +17,9 @@
//-----------------------------------------------------------------------------
#include "cmdhflegic.h"
#include <stdio.h> // for Mingw readline
#include <ctype.h> // tolower
#ifdef HAVE_READLINE
#include <readline/readline.h>
#endif
#include "pm3line.h" // pm3line_read, pm3line_free
#include "cliparser.h"
#include "cmdparser.h" // command_t
#include "comms.h" // clearCommandBuffer
@ -553,19 +549,9 @@ static int CmdLegicWrbl(const char *Cmd) {
PrintAndLogEx(INFO, "#####################################");
const char *confirm = "Do you really want to continue? y(es)/n(o) : ";
bool overwrite = false;
#ifdef HAVE_READLINE
char *answer = readline(confirm);
char *answer = pm3line_read(confirm);
overwrite = (answer[0] == 'y' || answer[0] == 'Y');
#else
PrintAndLogEx(NORMAL, "%s" NOLF, confirm);
char *answer = NULL;
size_t anslen = 0;
if (getline(&answer, &anslen, stdin) > 0) {
overwrite = (answer[0] == 'y' || answer[0] == 'Y');
}
PrintAndLogEx(NORMAL, "");
#endif
free(answer);
pm3line_free(answer);
if (overwrite == false) {
PrintAndLogEx(WARNING, "command cancelled");
return PM3_EOPABORTED;

View file

@ -993,7 +993,7 @@ void pm3_version(bool verbose, bool oneliner) {
PrintAndLogEx(NORMAL, "%s", temp);
PrintAndLogEx(NORMAL, " compiled with............. " PM3CLIENTCOMPILER __VERSION__);
PrintAndLogEx(NORMAL, " platform.................. " PM3HOSTOS " / " PM3HOSTARCH);
#ifdef HAVE_READLINE
#if defined(HAVE_READLINE)
PrintAndLogEx(NORMAL, " Readline support.......... " _GREEN_("present"));
#else
PrintAndLogEx(NORMAL, " Readline support.......... " _YELLOW_("absent"));

200
client/src/pm3line.c Normal file
View file

@ -0,0 +1,200 @@
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// API to abstract Readline / Linenoise support
//-----------------------------------------------------------------------------
#include "pm3line.h"
#include <stdlib.h>
#include <stdio.h> // for Mingw readline and for getline
#include <string.h>
#include <signal.h>
#if defined(HAVE_READLINE)
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include "pm3line_vocabulory.h"
#include "pm3_cmd.h"
#include "ui.h" // g_session
#include "util.h" // str_ndup
#if defined(HAVE_READLINE)
static char* rl_command_generator(const char *text, int state) {
static int index;
static size_t len;
size_t rlen = strlen(rl_line_buffer);
const char *command;
if (!state) {
index = 0;
len = strlen(text);
}
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, rl_line_buffer, rlen) == 0) {
const char *next = command + (rlen - len);
const char *space = strstr(next, " ");
if (space != NULL) {
return str_ndup(next, space - next);
}
return str_dup(next);
}
}
return NULL;
}
static char **rl_command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 0;
return rl_completion_matches (text, rl_command_generator);
}
#endif // HAVE_READLINE
# if defined(_WIN32)
/*
static bool WINAPI terminate_handler(DWORD t) {
if (t == CTRL_C_EVENT) {
flush_history();
return true;
}
return false;
}
*/
# else
static struct sigaction gs_old_sigint_action;
static void sigint_handler(int signum) {
sigaction(SIGINT, &gs_old_sigint_action, NULL);
pm3line_flush_history();
kill(0, SIGINT);
}
#endif
void pm3line_install_signals(void){
# if defined(_WIN32)
// SetConsoleCtrlHandler((PHANDLER_ROUTINE)terminate_handler, true);
# else
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = &sigint_handler;
sigaction(SIGINT, &action, &gs_old_sigint_action);
# endif
#if defined(HAVE_READLINE)
rl_catch_signals = 1;
rl_set_signals();
#endif // HAVE_READLINE
}
void pm3line_init(void) {
#if defined(HAVE_READLINE)
/* initialize history */
using_history();
rl_readline_name = "PM3";
rl_attempted_completion_function = rl_command_completion;
#ifdef RL_STATE_READCMD
rl_extend_line_buffer(1024);
#endif // RL_STATE_READCMD
#endif // HAVE_READLINE
}
char *pm3line_read(const char *s) {
#if defined(HAVE_READLINE)
return readline(s);
#else
printf("%s", s);
char *answer = NULL;
size_t anslen = 0;
int ret;
if ((ret = getline(&answer, &anslen, stdin)) < 0) {
// TODO this happens also when kbd_enter_pressed() is used, with a key pressed or not
printf("DEBUG: getline returned %i", ret);
free(answer);
answer = NULL;
}
return answer;
#endif
}
void pm3line_free(void *ref) {
free(ref);
}
void pm3line_update_prompt(const char *prompt) {
#if defined(HAVE_READLINE)
rl_set_prompt(prompt);
rl_forced_update_display();
#else
(void) prompt;
#endif
}
int pm3line_load_history(const char *path) {
#if defined(HAVE_READLINE)
if (read_history(path) == 0) {
return PM3_SUCCESS;
} else {
return PM3_ESOFT;
}
#else
(void) path;
return PM3_ENOTIMPL;
#endif
}
void pm3line_add_history(const char *line) {
#if defined(HAVE_READLINE)
HIST_ENTRY *entry = history_get(history_length);
// add if not identical to latest recorded line
if ((!entry) || (strcmp(entry->line, line) != 0)) {
add_history(line);
}
#else
(void) line;
#endif
}
void pm3line_flush_history(void) {
if (g_session.history_path) {
#if defined(HAVE_READLINE)
write_history(g_session.history_path);
#endif // HAVE_READLINE
free(g_session.history_path);
g_session.history_path = NULL;
}
}
void pm3line_check(int (check)(void)) {
#if defined(HAVE_READLINE)
rl_event_hook = check;
#else
check();
#endif
}
// TODO:
// src/ui.c print_progress()

32
client/src/pm3line.h Normal file
View file

@ -0,0 +1,32 @@
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// API to abstract Readline / Linenoise support
//-----------------------------------------------------------------------------
#ifndef PM3LINE_H__
#define PM3LINE_H__
void pm3line_init(void);
void pm3line_install_signals(void);
char *pm3line_read(const char* s);
void pm3line_free(void *ref);
void pm3line_update_prompt(const char *prompt);
int pm3line_load_history(const char *path);
void pm3line_add_history(const char *line);
void pm3line_flush_history(void);
void pm3line_check(int (check)(void));
#endif // PM3LINE_H__

View file

@ -16,22 +16,14 @@
// readline auto complete utilities
//-----------------------------------------------------------------------------
#ifndef RL_VOCABULORY_H__
#define RL_VOCABULORY_H__
#ifndef PM3LINE_VOCABULORY_H__
#define PM3LINE_VOCABULORY_H__
#ifdef __cplusplus
extern "C" {
#endif
#if defined(HAVE_READLINE)
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include "ui.h" // g_session
#include "util.h" // str_ndup
char* rl_command_generator(const char *text, int state);
char **rl_command_completion(const char *text, int start, int end);
#include <stdbool.h>
typedef struct vocabulory_s {
bool offline;
@ -722,50 +714,6 @@ const static vocabulory_t vocabulory[] = {
{0, NULL}
};
char **rl_command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 0;
return rl_completion_matches (text, rl_command_generator);
}
char* rl_command_generator(const char *text, int state) {
static int index;
static size_t len;
size_t rlen = strlen(rl_line_buffer);
const char *command;
if (!state) {
index = 0;
len = strlen(text);
}
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, rl_line_buffer, rlen) == 0) {
const char *next = command + (rlen - len);
const char *space = strstr(next, " ");
if (space != NULL) {
return str_ndup(next, space - next);
}
return str_dup(next);
}
}
return NULL;
}
#endif
#ifdef __cplusplus
}
#endif

View file

@ -19,18 +19,12 @@
#include "proxmark3.h"
#include <stdlib.h>
#include <stdio.h> // for Mingw readline
#include <limits.h>
#include <unistd.h>
#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#include "rl_vocabulory.h"
#include <signal.h>
#endif
#include <ctype.h>
#include <libgen.h> // basename
#include "pm3line.h"
#include "usart_defs.h"
#include "util_posix.h"
#include "proxgui.h"
@ -137,48 +131,16 @@ static int check_comm(void) {
if (IsCommunicationThreadDead() && g_session.pm3_present) {
PrintAndLogEx(INFO, "Running in " _YELLOW_("OFFLINE") " mode. Use "_YELLOW_("\"hw connect\"") " to reconnect\n");
prompt_dev = PROXPROMPT_DEV_OFFLINE;
#ifdef HAVE_READLINE
char prompt[PROXPROMPT_MAX_SIZE] = {0};
prompt_compose(prompt, sizeof(prompt), prompt_ctx, prompt_dev);
char prompt_filtered[PROXPROMPT_MAX_SIZE] = {0};
memcpy_filter_ansi(prompt_filtered, prompt, sizeof(prompt_filtered), !g_session.supports_colors);
rl_set_prompt(prompt_filtered);
rl_redisplay();
#endif
pm3line_update_prompt(prompt_filtered);
CloseProxmark(g_session.current_device);
}
msleep(10);
return 0;
}
#ifdef HAVE_READLINE
static void flush_history(void) {
if (g_session.history_path) {
write_history(g_session.history_path);
free(g_session.history_path);
g_session.history_path = NULL;
}
}
# if defined(_WIN32)
/*
static bool WINAPI terminate_handler(DWORD t) {
if (t == CTRL_C_EVENT) {
flush_history();
return true;
}
return false;
}
*/
# else
static struct sigaction gs_old_sigint_action;
static void sigint_handler(int signum) {
sigaction(SIGINT, &gs_old_sigint_action, NULL);
flush_history();
kill(0, SIGINT);
}
#endif
#endif
#if defined(_WIN32)
static bool DetectWindowsAnsiSupport(void) {
@ -278,30 +240,22 @@ main_loop(char *script_cmds_file, char *script_cmd, bool stayInCommandLoop) {
}
}
#ifdef HAVE_READLINE
g_session.history_path = NULL;
if (g_session.incognito) {
PrintAndLogEx(INFO, "No history will be recorded");
} else {
bool loaded_history = false;
if (searchHomeFilePath(&g_session.history_path, NULL, PROXHISTORY, true) != PM3_SUCCESS) {
PrintAndLogEx(ERR, "No history will be recorded");
g_session.history_path = NULL;
} else {
# if defined(_WIN32)
// SetConsoleCtrlHandler((PHANDLER_ROUTINE)terminate_handler, true);
# else
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = &sigint_handler;
sigaction(SIGINT, &action, &gs_old_sigint_action);
# endif
rl_catch_signals = 1;
rl_set_signals();
read_history(g_session.history_path);
loaded_history = (pm3line_load_history(g_session.history_path) == PM3_SUCCESS);
}
if (loaded_history) {
pm3line_install_signals();
} else {
PrintAndLogEx(ERR, "No history will be recorded");
}
}
#endif
// loops every time enter is pressed...
while (1) {
@ -378,19 +332,14 @@ check_script:
strcleanrn(script_cmd, script_cmd_len);
goto check_script;
} else {
#ifdef HAVE_READLINE
rl_event_hook = check_comm;
#else
check_comm();
#endif
pm3line_check(check_comm);
prompt_ctx = PROXPROMPT_CTX_INTERACTIVE;
char prompt[PROXPROMPT_MAX_SIZE] = {0};
prompt_compose(prompt, sizeof(prompt), prompt_ctx, prompt_dev);
char prompt_filtered[PROXPROMPT_MAX_SIZE] = {0};
memcpy_filter_ansi(prompt_filtered, prompt, sizeof(prompt_filtered), !g_session.supports_colors);
g_pendingPrompt = true;
#ifdef HAVE_READLINE
script_cmd = readline(prompt_filtered);
cmd = pm3line_read(prompt_filtered);
#if defined(_WIN32)
//Check if color support needs to be enabled again in case the window buffer did change
g_session.supports_colors = DetectWindowsAnsiSupport();
@ -405,18 +354,6 @@ check_script:
strcleanrn(script_cmd, script_cmd_len);
goto check_script;
}
#else
printf("%s", prompt_filtered);
cmd = NULL;
size_t len = 0;
int ret;
if ((ret = getline(&cmd, &len, stdin)) < 0) {
// TODO this happens also when kbd_enter_pressed() is used, with a key pressed or not
printf("GETLINE ERR %i", ret);
free(cmd);
cmd = NULL;
}
#endif
fflush(NULL);
}
}
@ -455,16 +392,10 @@ check_script:
PrintAndLogEx(NORMAL, "%s%s", prompt_filtered, cmd);
g_printAndLog = old_printAndLog;
#ifdef HAVE_READLINE
// add to history if not from a script
if (!current_cmdscriptfile()) {
HIST_ENTRY *entry = history_get(history_length);
// add if not identical to latest recorded cmd
if ((!entry) || (strcmp(entry->line, cmd) != 0)) {
add_history(cmd);
}
pm3line_add_history(cmd);
}
#endif
// process cmd
g_pendingPrompt = false;
int ret = CommandReceived(cmd);
@ -495,9 +426,7 @@ check_script:
while (current_cmdscriptfile())
pop_cmdscriptfile();
#ifdef HAVE_READLINE
flush_history();
#endif
pm3line_flush_history();
if (cmd) {
free(cmd);
@ -780,17 +709,7 @@ int main(int argc, char *argv[]) {
char *port = NULL;
uint32_t speed = 0;
#ifdef HAVE_READLINE
/* initialize history */
using_history();
rl_readline_name = "PM3";
rl_attempted_completion_function = rl_command_completion;
#ifdef RL_STATE_READCMD
rl_extend_line_buffer(1024);
#endif // RL_STATE_READCMD
#endif // HAVE_READLINE
pm3line_init();
char exec_name[100] = {0};
strncpy(exec_name, basename(argv[0]), sizeof(exec_name) - 1);

View file

@ -28,7 +28,7 @@
#include <stdarg.h>
#include <stdlib.h>
#ifdef HAVE_READLINE
#if defined(HAVE_READLINE)
//Load readline after stdio.h
#include <readline/readline.h>
#endif
@ -643,7 +643,7 @@ void iceSimple_Filter(int *data, const size_t len, uint8_t k) {
void print_progress(size_t count, uint64_t max, barMode_t style) {
int cols = 100 + 35;
#ifdef HAVE_READLINE
#if defined(HAVE_READLINE)
static int prev_cols = 0;
int rows;
rl_reset_screen_size(); // refresh Readline idea of the actual screen width