RzShell: Remove possibility to switch to oldshell (#2420)
Due to some commands/tests still using the legacy "..." commands, we can't fully remove oldshell code yet, but with this patch we just hide it from the end users. Rzshell is now the default and only shell and rzshell autocompletion is the default and only autocompletion engine.
This commit is contained in:
parent
78bbf5ba80
commit
86d20b2d16
16 changed files with 12 additions and 217 deletions
|
|
@ -119,7 +119,6 @@ To enter visual mode use the 'V' command. Then press '?' for help
|
|||
.Sh DEBUGGER
|
||||
In rizin the debugger commands are implemented under the 'd' command. Type 'd?' for help
|
||||
.Sh ENVIRONMENT
|
||||
RZ_CFG_OLDSHELL sets cfg.oldshell=true
|
||||
RZ_DEBUG if defined, show error messages and crash signal
|
||||
RZ_DEBUG_ASSERT=1 set a breakpoint when hitting an assert
|
||||
RZ_MAGICPATH /Users/pancake/.local/share/rizin/share/rizin/4.5.0-git/magic
|
||||
|
|
|
|||
|
|
@ -2156,20 +2156,6 @@ static bool cb_scrhtml(void *user, void *data) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool cb_oldshell(void *user, void *data) {
|
||||
RzConfigNode *node = (RzConfigNode *)data;
|
||||
RzCore *core = (RzCore *)user;
|
||||
core->use_tree_sitter_rzcmd = !node->i_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cb_oldshell_autocompletion(void *user, void *data) {
|
||||
RzConfigNode *node = (RzConfigNode *)data;
|
||||
RzCore *core = (RzCore *)user;
|
||||
core->use_rzshell_autocompletion = !node->i_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cb_scrhighlight(void *user, void *data) {
|
||||
RzConfigNode *node = (RzConfigNode *)data;
|
||||
rz_cons_highlight(node->value);
|
||||
|
|
@ -3301,10 +3287,6 @@ RZ_API int rz_core_config_init(RzCore *core) {
|
|||
SETICB("cfg.seek.histsize", 63, NULL, "Maximum size of the seek history");
|
||||
SETCB("cfg.seek.silent", "false", NULL, "When true, seek movements are not logged in seek history");
|
||||
SETCB("cfg.bigendian", "false", &cb_bigendian, "Use little (false) or big (true) endianness");
|
||||
p = rz_sys_getenv("RZ_CFG_OLDSHELL");
|
||||
SETCB("cfg.oldshell", p ? "true" : "false", &cb_oldshell, "Use old radare2 parser");
|
||||
free(p);
|
||||
SETCB("cfg.oldshell.autocompletion", "true", &cb_oldshell_autocompletion, "Use old radare2 autocompletion");
|
||||
SETI("cfg.cpuaffinity", 0, "Run on cpuid");
|
||||
|
||||
/* log */
|
||||
|
|
|
|||
|
|
@ -5345,79 +5345,7 @@ RZ_API RzCmdStatus rz_core_cmd_rzshell(RzCore *core, const char *cstr, int log)
|
|||
}
|
||||
|
||||
RZ_API int rz_core_cmd(RzCore *core, const char *cstr, int log) {
|
||||
if (core->use_tree_sitter_rzcmd) {
|
||||
return rz_cmd_status2int(core_cmd_tsrzcmd(core, cstr, false, log));
|
||||
}
|
||||
|
||||
int ret = false, i;
|
||||
|
||||
if (core->cmdfilter) {
|
||||
const char *invalid_chars = ";|>`@";
|
||||
for (i = 0; invalid_chars[i]; i++) {
|
||||
if (strchr(cstr, invalid_chars[i])) {
|
||||
ret = true;
|
||||
goto beach;
|
||||
}
|
||||
}
|
||||
if (strncmp(cstr, core->cmdfilter, strlen(core->cmdfilter))) {
|
||||
ret = true;
|
||||
goto beach;
|
||||
}
|
||||
}
|
||||
if (core->cmdremote) {
|
||||
if (*cstr == 'q') {
|
||||
RZ_FREE(core->cmdremote);
|
||||
goto beach; // false
|
||||
} else if (*cstr != '=' && strncmp(cstr, "!=", 2)) {
|
||||
if (core->cmdremote[0]) {
|
||||
char *s = rz_str_newf("%s %s", core->cmdremote, cstr);
|
||||
rz_core_rtr_cmd(core, s);
|
||||
free(s);
|
||||
} else {
|
||||
char *res = rz_io_system(core->io, cstr);
|
||||
if (res) {
|
||||
rz_cons_printf("%s\n", res);
|
||||
free(res);
|
||||
}
|
||||
}
|
||||
if (log) {
|
||||
rz_line_hist_add(cstr);
|
||||
}
|
||||
goto beach; // false
|
||||
}
|
||||
}
|
||||
|
||||
if (!cstr || (*cstr == '|' && cstr[1] != '?')) {
|
||||
// raw comment syntax
|
||||
goto beach; // false;
|
||||
}
|
||||
if (!strncmp(cstr, "/*", 2)) {
|
||||
core->incomment = true;
|
||||
} else if (!strncmp(cstr, "*/", 2)) {
|
||||
core->incomment = false;
|
||||
goto beach; // false
|
||||
}
|
||||
if (core->incomment) {
|
||||
goto beach; // false
|
||||
}
|
||||
if (log && (*cstr && (*cstr != '.' || !strncmp(cstr, ".(", 2)))) {
|
||||
free(core->lastcmd);
|
||||
core->lastcmd = strdup(cstr);
|
||||
}
|
||||
|
||||
char *cmd = malloc(strlen(cstr) + 4096);
|
||||
if (!cmd) {
|
||||
goto beach;
|
||||
}
|
||||
rz_str_cpy(cmd, cstr);
|
||||
if (log) {
|
||||
rz_line_hist_add(cstr);
|
||||
}
|
||||
|
||||
ret = run_cmd_depth(core, cmd);
|
||||
free(cmd);
|
||||
beach:
|
||||
return ret;
|
||||
return rz_cmd_status2int(core_cmd_tsrzcmd(core, cstr, false, log));
|
||||
}
|
||||
|
||||
RZ_API RzCmdStatus rz_core_cmd_lines_rzshell(RzCore *core, const char *lines) {
|
||||
|
|
@ -5425,58 +5353,8 @@ RZ_API RzCmdStatus rz_core_cmd_lines_rzshell(RzCore *core, const char *lines) {
|
|||
}
|
||||
|
||||
RZ_API int rz_core_cmd_lines(RzCore *core, const char *lines) {
|
||||
if (core->use_tree_sitter_rzcmd) {
|
||||
RzCmdStatus status = core_cmd_tsrzcmd(core, lines, true, false);
|
||||
return status == RZ_CMD_STATUS_OK;
|
||||
}
|
||||
int r, ret = true;
|
||||
char *nl, *data, *odata;
|
||||
|
||||
if (!lines || !*lines) {
|
||||
return true;
|
||||
}
|
||||
data = odata = strdup(lines);
|
||||
if (!odata) {
|
||||
return false;
|
||||
}
|
||||
nl = strchr(odata, '\n');
|
||||
if (nl) {
|
||||
rz_cons_break_push(NULL, NULL);
|
||||
do {
|
||||
if (rz_cons_is_breaked()) {
|
||||
free(odata);
|
||||
rz_cons_break_pop();
|
||||
return ret;
|
||||
}
|
||||
*nl = '\0';
|
||||
r = rz_core_cmd(core, data, 0);
|
||||
if (r < 0) { //== -1) {
|
||||
data = nl + 1;
|
||||
ret = -1; // r; //false;
|
||||
break;
|
||||
}
|
||||
rz_cons_flush();
|
||||
if (data[0] == 'q') {
|
||||
if (data[1] == '!') {
|
||||
ret = -1;
|
||||
} else {
|
||||
eprintf("'q': quit ignored. Use 'q!'\n");
|
||||
}
|
||||
data = nl + 1;
|
||||
break;
|
||||
}
|
||||
data = nl + 1;
|
||||
rz_core_task_yield(&core->tasks);
|
||||
} while ((nl = strchr(data, '\n')));
|
||||
rz_cons_break_pop();
|
||||
}
|
||||
if (ret >= 0 && data && *data) {
|
||||
rz_core_cmd(core, data, 0);
|
||||
rz_cons_flush();
|
||||
rz_core_task_yield(&core->tasks);
|
||||
}
|
||||
free(odata);
|
||||
return ret;
|
||||
RzCmdStatus status = core_cmd_tsrzcmd(core, lines, true, false);
|
||||
return status == RZ_CMD_STATUS_OK;
|
||||
}
|
||||
|
||||
RZ_API int rz_core_cmd_file(RzCore *core, const char *file) {
|
||||
|
|
@ -5591,11 +5469,7 @@ RZ_API char *rz_core_cmd_str_pipe(RzCore *core, const char *cmd) {
|
|||
return rz_core_cmd_str(core, cmd);
|
||||
}
|
||||
char *_cmd = strdup(cmd);
|
||||
if (core->use_tree_sitter_rzcmd) {
|
||||
rz_core_cmd(core, _cmd, 0);
|
||||
} else {
|
||||
rz_core_cmd_subst(core, _cmd);
|
||||
}
|
||||
rz_core_cmd(core, _cmd, 0);
|
||||
rz_cons_flush();
|
||||
rz_cons_pipe_close(pipefd);
|
||||
if (rz_file_exists(tmp)) {
|
||||
|
|
|
|||
|
|
@ -1633,12 +1633,6 @@ RZ_API void rz_core_autocomplete(RZ_NULLABLE RzCore *core, RzLineCompletion *com
|
|||
}
|
||||
}
|
||||
|
||||
static int autocomplete(RzLineCompletion *completion, RzLineBuffer *buf, RzLinePromptType prompt_type, void *user) {
|
||||
RzCore *core = user;
|
||||
rz_core_autocomplete(core, completion, buf, prompt_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
static RzLineNSCompletionResult *rzshell_autocomplete(RzLineBuffer *buf, RzLinePromptType prompt_type, void *user) {
|
||||
return rz_core_autocomplete_rzshell((RzCore *)user, buf, prompt_type);
|
||||
}
|
||||
|
|
@ -1650,16 +1644,9 @@ RZ_API int rz_core_fgets(char *buf, int len, void *user) {
|
|||
bool prompt = cons->context->is_interactive;
|
||||
buf[0] = '\0';
|
||||
if (prompt) {
|
||||
if (core->use_rzshell_autocompletion) {
|
||||
rzline->ns_completion.run = rzshell_autocomplete;
|
||||
rzline->ns_completion.run_user = core;
|
||||
rzline->completion.run = NULL;
|
||||
} else {
|
||||
rz_line_completion_set(&rzline->completion, rizin_argc, rizin_argv);
|
||||
rzline->completion.run = autocomplete;
|
||||
rzline->completion.run_user = core;
|
||||
rzline->ns_completion.run = NULL;
|
||||
}
|
||||
rzline->ns_completion.run = rzshell_autocomplete;
|
||||
rzline->ns_completion.run_user = core;
|
||||
rzline->completion.run = NULL;
|
||||
} else {
|
||||
rzline->history.data = NULL;
|
||||
rz_line_completion_set(&rzline->completion, 0, NULL);
|
||||
|
|
@ -2358,8 +2345,6 @@ RZ_API bool rz_core_init(RzCore *core) {
|
|||
core->incomment = false;
|
||||
core->config = NULL;
|
||||
core->http_up = false;
|
||||
core->use_tree_sitter_rzcmd = false;
|
||||
core->use_rzshell_autocompletion = false;
|
||||
ZERO_FILL(core->root_cmd_descriptor);
|
||||
core->print = rz_print_new();
|
||||
core->ropchain = rz_list_newf((RzListFree)free);
|
||||
|
|
|
|||
|
|
@ -384,8 +384,6 @@ struct rz_core_t {
|
|||
bool scr_gadgets;
|
||||
bool log_events; // core.c:cb_event_handler : log actions from events if cfg.log.events is set
|
||||
RzList *ropchain;
|
||||
bool use_tree_sitter_rzcmd;
|
||||
bool use_rzshell_autocompletion;
|
||||
RzCoreSeekHistory seek_history;
|
||||
|
||||
bool marks_init;
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ static int main_help(int line) {
|
|||
" RZ_LIBR_PLUGINS %s\n"
|
||||
" RZ_USER_ZIGNS %s\n"
|
||||
"Environment:\n"
|
||||
" RZ_CFG_OLDSHELL sets cfg.oldshell=true\n"
|
||||
" RZ_DEBUG if defined, show error messages and crash signal\n"
|
||||
" RZ_DEBUG_ASSERT=1 set a breakpoint when hitting an assert\n"
|
||||
" RZ_MAGICPATH %s\n"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ $foo
|
|||
$foo
|
||||
?e --
|
||||
$-*
|
||||
e cfg.oldshell=false
|
||||
$foo=\#!pipe echo hello
|
||||
$foo
|
||||
?e --
|
||||
|
|
|
|||
|
|
@ -1390,7 +1390,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=pdJ string
|
||||
ARGS=-ecfg.oldshell=false
|
||||
FILE=malloc://128
|
||||
CMDS=<<EOF
|
||||
w "Snoo\"ping as\" usual,"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=& + grep
|
||||
ARGS=-e cfg.oldshell=false
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
& "?e Hello\nfrom\na task!~task"
|
||||
|
|
|
|||
|
|
@ -322,7 +322,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=w_escape
|
||||
ARGS=-ecfg.oldshell=false
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
wv8\@10
|
||||
|
|
|
|||
|
|
@ -735,7 +735,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=af ; af ; zfs libc-v7.sig - rzshell
|
||||
ARGS=-ecfg.oldshell=false
|
||||
FILE=bins/elf/analysis/pid_stripped
|
||||
CMDS=s 0x4e2420 ; af ; s 0x4e25c7 ; af ; zfs bins/other/sigs/libc-v7.sig ; afl ~4e2420
|
||||
EXPECT=<<EOF
|
||||
|
|
@ -782,7 +781,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=af ; af ; zfs libc-v10.sig - rzshell
|
||||
ARGS=-ecfg.oldshell=false
|
||||
FILE=bins/elf/analysis/pid_stripped
|
||||
CMDS=s 0x4e2420 ; af ; s 0x4e25c7 ; af ; zfs bins/other/sigs/libc-v10.sig ; afl ~4e2420
|
||||
EXPECT=<<EOF
|
||||
|
|
|
|||
|
|
@ -350,7 +350,6 @@ RUN
|
|||
|
||||
NAME=multi-command single-line grep
|
||||
FILE==
|
||||
ARGS=-ecfg.oldshell=false
|
||||
CMDS=<<EOF
|
||||
?e Hello World; ?e 4e2420~4e2420
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
NAME=Double quotes - rzshell
|
||||
ARGS=-e cfg.oldshell=false
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
w "Hello World"
|
||||
|
|
@ -23,7 +22,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=Single quotes - rzshell
|
||||
ARGS=-e cfg.oldshell=false
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
w 'Hello World'
|
||||
|
|
@ -51,7 +49,6 @@ EOF
|
|||
RUN
|
||||
|
||||
NAME=Quotes and substitution - rzshell
|
||||
ARGS=-e cfg.oldshell=false
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
w "Hello $(?e World)"
|
||||
|
|
@ -69,4 +66,4 @@ Hello World
|
|||
Hello $(?e World)
|
||||
Hello $(?e World)
|
||||
EOF
|
||||
RUN
|
||||
RUN
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ NAME=load with file without moving anything
|
|||
FILE=bins/elf/crackme0x05
|
||||
CMDS=<<EOF
|
||||
e asm.bytes=true
|
||||
e cfg.oldshell=false
|
||||
f i_do_hope_that_no_entity_knocks_over_my_beverage @ 0x080483d8
|
||||
Ps .tmp_load_no_move.rzdb
|
||||
o--
|
||||
|
|
@ -21,7 +20,6 @@ RUN
|
|||
NAME=load with file using saved absolute file path after project file was moved
|
||||
FILE=bins/elf/crackme0x05
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
e asm.bytes=true
|
||||
f i_do_hope_that_no_entity_knocks_over_my_beverage @ 0x080483d8
|
||||
Ps .tmp_load_move_project.rzdb
|
||||
|
|
@ -47,7 +45,6 @@ NAME=load with file using saved relative file path after project file and binary
|
|||
FILE=--
|
||||
CMDS=<<EOF
|
||||
e asm.bytes=true
|
||||
e cfg.oldshell=false
|
||||
cp bins/elf/crackme0x05 .tmp_load_move_both.bin
|
||||
o .tmp_load_move_both.bin
|
||||
f i_do_hope_that_no_entity_knocks_over_my_beverage @ 0x080483d8
|
||||
|
|
@ -74,7 +71,6 @@ RUN
|
|||
NAME=load with file using saved relative file path after project file and binary were moved together with different relation
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
e asm.bytes=true
|
||||
mkdir -p .tmp_load_move_both2/.tmp_load_move_both2
|
||||
cp bins/elf/crackme0x05 .tmp_load_move_both2/somebin
|
||||
|
|
@ -104,7 +100,6 @@ NAME=load with file using saved raw file path after everything has been messed u
|
|||
FILE=--
|
||||
CMDS=<<EOF
|
||||
e asm.bytes=true
|
||||
e cfg.oldshell=false
|
||||
mkdir -p .tmp_load_move_bin
|
||||
cp bins/elf/crackme0x05 .tmp_load_move_bin.bin
|
||||
o .tmp_load_move_bin.bin
|
||||
|
|
@ -133,7 +128,6 @@ NAME=load without file
|
|||
FILE=bins/elf/crackme0x05
|
||||
CMDS=<<EOF
|
||||
e asm.bytes=true
|
||||
e cfg.oldshell=false
|
||||
f a_nice_refreshing_un-moisten @ 0x080483d8
|
||||
Ps .tmp_load_without_file.rzdb
|
||||
ol
|
||||
|
|
@ -168,7 +162,6 @@ RUN
|
|||
NAME=load on top of file
|
||||
FILE=bins/elf/crackme0x05
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
e asm.bytes=true
|
||||
f a_nice_refreshing_un-moisten @ 0x080483d8
|
||||
Ps .tmp_load_on_top_of_file.rzdb
|
||||
|
|
@ -204,7 +197,6 @@ RUN
|
|||
NAME=flags
|
||||
FILE=bins/elf/crackme0x05
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
fr sym.main sym.renamedmain
|
||||
fl~?
|
||||
fl~main
|
||||
|
|
@ -237,7 +229,6 @@ RUN
|
|||
NAME=config
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
e asm.describe=1
|
||||
e asm.describe
|
||||
e asm.indentspace=8
|
||||
|
|
@ -277,7 +268,6 @@ RUN
|
|||
NAME=seek
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
s 0x1337
|
||||
s
|
||||
Ps .tmp_seek.rzdb
|
||||
|
|
@ -296,7 +286,6 @@ RUN
|
|||
NAME=analysis
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
af+ 0x100 windowpane
|
||||
afb+ windowpane 0x100 0x30
|
||||
afl
|
||||
|
|
@ -321,7 +310,6 @@ NAME=remember saved project file during session
|
|||
FILE=bins/elf/crackme0x05
|
||||
CMDS=<<EOF
|
||||
e asm.bytes=true
|
||||
e cfg.oldshell=false
|
||||
Ps .tmp_remember.rzdb
|
||||
e prj.file
|
||||
f cashews @ 0x080483d8
|
||||
|
|
@ -346,7 +334,6 @@ RUN
|
|||
NAME=raw file permissions after reload
|
||||
FILE=bins/other/0x3
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=false
|
||||
oml
|
||||
Ps .tmp_perms.rzdb
|
||||
o--
|
||||
|
|
|
|||
|
|
@ -29,26 +29,7 @@ EXPECT=<<EOF
|
|||
EOF
|
||||
RUN
|
||||
|
||||
NAME="/e /t\wst\d\d\d\s\w\w/i" - oldshell
|
||||
FILE=malloc://1024
|
||||
CMDS=<<EOF
|
||||
e cfg.oldshell=true
|
||||
w "test123 ab"
|
||||
w "Test123 ab" @ 444
|
||||
?e
|
||||
e search.in=block
|
||||
b 777
|
||||
"/e /t\wst\d\d\d\s\w\w/i"
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
|
||||
0x00000000 hit0_0 "test123 ab"
|
||||
0x000001bc hit0_1 "Test123 ab"
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME="/e /t\wst\d\d\d\s\w\w/i" - rzshell
|
||||
ARGS=-ecfg.oldshell=false
|
||||
FILE=malloc://1024
|
||||
CMDS=<<EOF
|
||||
w '"test123 ab"'
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ static bool test_autocmplt_eval(void) {
|
|||
mu_assert_notnull(core, "core should be created");
|
||||
RzLineBuffer *buf = &core->cons->line->buffer;
|
||||
|
||||
const char *s = "xd 1 2 3 4 cfg.oldsh";
|
||||
const char *s = "xd 1 2 3 4 asm.lines.w";
|
||||
strcpy(buf->data, s);
|
||||
buf->length = strlen(s);
|
||||
buf->index = buf->length;
|
||||
|
|
@ -332,9 +332,9 @@ static bool test_autocmplt_eval(void) {
|
|||
mu_assert_notnull(r, "r should not be null");
|
||||
mu_assert_eq(r->start, strlen("xd 1 2 3 4 "), "should autocomplete the last arg");
|
||||
mu_assert_eq(r->end, buf->length, "should autocomplete ending at end of buffer");
|
||||
mu_assert_eq(rz_pvector_len(&r->options), 2, "there are 2 config evals starting with cfg.oldsh");
|
||||
mu_assert_streq(rz_pvector_at(&r->options, 0), "cfg.oldshell", "cfg.oldshell found");
|
||||
mu_assert_streq(rz_pvector_at(&r->options, 1), "cfg.oldshell.autocompletion", "cfg.oldshell.autocompletion found");
|
||||
mu_assert_eq(rz_pvector_len(&r->options), 2, "there are 2 config evals starting with asm.lines.w");
|
||||
mu_assert_streq(rz_pvector_at(&r->options, 0), "asm.lines.wide", "asm.lines.wide found");
|
||||
mu_assert_streq(rz_pvector_at(&r->options, 1), "asm.lines.width", "asm.lines.width found");
|
||||
rz_line_ns_completion_result_free(r);
|
||||
|
||||
s = "xd 1 2 3 4 search.in=io.maps.r";
|
||||
|
|
|
|||
Loading…
Reference in a new issue