[njs] Shell: added signal support in CLI.
Dmitry Volyntsev
xeioex at nginx.com
Fri Nov 18 05:04:34 UTC 2022
details: https://hg.nginx.org/njs/rev/8163612eb950
branches:
changeset: 2006:8163612eb950
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Thu Nov 17 20:38:23 2022 -0800
description:
Shell: added signal support in CLI.
diffstat:
src/njs_shell.c | 184 +++++++++++++++++++++++++++++++++++++++++++--------
test/shell_test.exp | 32 ++++----
2 files changed, 169 insertions(+), 47 deletions(-)
diffs (414 lines):
diff -r b3eeac9ee9f4 -r 8163612eb950 src/njs_shell.c
--- a/src/njs_shell.c Wed Nov 16 18:47:36 2022 -0800
+++ b/src/njs_shell.c Thu Nov 17 20:38:23 2022 -0800
@@ -10,6 +10,8 @@
#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
#include <locale.h>
+#include <signal.h>
+#include <sys/select.h>
#if (NJS_HAVE_EDITLINE)
#include <editline/readline.h>
#elif (NJS_HAVE_EDIT_READLINE)
@@ -24,13 +26,15 @@
#endif
+typedef void (*njs_console_output_pt)(njs_vm_t *vm, njs_int_t ret);
+
+
typedef struct {
uint8_t disassemble;
uint8_t denormals;
uint8_t interactive;
uint8_t module;
uint8_t quiet;
- uint8_t silent;
uint8_t sandbox;
uint8_t safe;
uint8_t version;
@@ -90,10 +94,12 @@ typedef struct {
static njs_int_t njs_console_init(njs_vm_t *vm, njs_console_t *console);
+static void njs_console_output(njs_vm_t *vm, njs_int_t ret);
static njs_int_t njs_externals_init(njs_vm_t *vm);
static njs_vm_t *njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options);
-static njs_int_t njs_process_script(njs_vm_t *vm, njs_opts_t *opts,
- void *runtime, const njs_str_t *script);
+static void njs_process_output(njs_vm_t *vm, njs_int_t ret);
+static njs_int_t njs_process_script(njs_vm_t *vm, void *runtime,
+ const njs_str_t *script);
#ifndef NJS_FUZZER_TARGET
@@ -243,6 +249,15 @@ main(int argc, char **argv)
njs_str_t command;
njs_vm_opt_t vm_options;
+ static uintptr_t uptr[] = {
+ (uintptr_t) njs_console_output,
+ };
+
+ static njs_vm_meta_t metas = {
+ .size = njs_nitems(uptr),
+ .values = uptr
+ };
+
njs_memzero(&opts, sizeof(njs_opts_t));
opts.interactive = 1;
@@ -287,6 +302,7 @@ main(int argc, char **argv)
vm_options.ops = &njs_console_ops;
vm_options.addons = njs_console_addon_modules;
+ vm_options.metas = &metas;
vm_options.external = &njs_console;
vm_options.argv = opts.argv;
vm_options.argc = opts.argc;
@@ -311,7 +327,7 @@ main(int argc, char **argv)
if (vm != NULL) {
command.start = (u_char *) opts.command;
command.length = njs_strlen(opts.command);
- ret = njs_process_script(vm, &opts, vm_options.external, &command);
+ ret = njs_process_script(vm, vm->external, &command);
njs_vm_destroy(vm);
}
@@ -659,7 +675,7 @@ njs_process_file(njs_opts_t *opts, njs_v
}
}
- ret = njs_process_script(vm, opts, vm_options->external, &script);
+ ret = njs_process_script(vm, vm_options->external, &script);
if (ret != NJS_OK) {
ret = NJS_ERROR;
goto done;
@@ -696,18 +712,26 @@ LLVMFuzzerTestOneInput(const uint8_t* da
njs_str_t script;
njs_vm_opt_t vm_options;
+ static uintptr_t uptr[] = {
+ (uintptr_t) NULL,
+ };
+
+ static njs_vm_meta_t metas = {
+ .size = njs_nitems(uptr),
+ .values = uptr
+ };
+
if (size == 0) {
return 0;
}
njs_memzero(&opts, sizeof(njs_opts_t));
- opts.silent = 1;
-
njs_vm_opt_init(&vm_options);
vm_options.init = 1;
vm_options.backtrace = 0;
+ vm_options.metas = &metas;
vm_options.ops = &njs_console_ops;
vm = njs_create_vm(&opts, &vm_options);
@@ -716,7 +740,7 @@ LLVMFuzzerTestOneInput(const uint8_t* da
script.length = size;
script.start = (u_char *) data;
- (void) njs_process_script(vm, &opts, NULL, &script);
+ (void) njs_process_script(vm, NULL, &script);
njs_vm_destroy(vm);
}
@@ -853,14 +877,10 @@ njs_create_vm(njs_opts_t *opts, njs_vm_o
static void
-njs_output(njs_opts_t *opts, njs_vm_t *vm, njs_int_t ret)
+njs_console_output(njs_vm_t *vm, njs_int_t ret)
{
njs_str_t out;
- if (opts->silent) {
- return;
- }
-
if (ret == NJS_OK) {
if (njs_vm_retval_dump(vm, &out, 1) != NJS_OK) {
njs_stderror("Shell:failed to get retval from VM\n");
@@ -917,8 +937,7 @@ njs_process_events(void *runtime)
static njs_int_t
-njs_process_script(njs_vm_t *vm, njs_opts_t *opts, void *runtime,
- const njs_str_t *script)
+njs_process_script(njs_vm_t *vm, void *runtime, const njs_str_t *script)
{
u_char *start, *end;
njs_int_t ret;
@@ -938,14 +957,15 @@ njs_process_script(njs_vm_t *vm, njs_opt
}
}
- njs_output(opts, vm, ret);
+ njs_process_output(vm, ret);
- if (!opts->interactive && ret == NJS_ERROR) {
+ if (!vm->options.interactive && ret == NJS_ERROR) {
return NJS_ERROR;
}
for ( ;; ) {
if (!njs_vm_pending(vm) && !njs_vm_unhandled_rejection(vm)) {
+ ret = NJS_OK;
break;
}
@@ -967,9 +987,9 @@ njs_process_script(njs_vm_t *vm, njs_opt
ret = njs_vm_run(vm);
if (ret == NJS_ERROR) {
- njs_output(opts, vm, ret);
+ njs_process_output(vm, ret);
- if (!opts->interactive) {
+ if (!vm->options.interactive) {
return NJS_ERROR;
}
}
@@ -979,13 +999,64 @@ njs_process_script(njs_vm_t *vm, njs_opt
}
+static void
+njs_process_output(njs_vm_t *vm, njs_int_t ret)
+{
+ njs_console_output_pt pt;
+
+ pt = (njs_console_output_pt) njs_vm_meta(vm, 0);
+
+ if (pt != NULL) {
+ pt(vm, ret);
+ }
+}
+
+
#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
+
+volatile sig_atomic_t njs_running;
+volatile sig_atomic_t njs_sigint_count;
+volatile sig_atomic_t njs_sigint_received;
+
+
+static void
+njs_cb_line_handler(char *line_in)
+{
+ njs_int_t ret;
+ njs_str_t line;
+
+ if (line_in == NULL || strcmp(line_in, ".exit") == 0) {
+ njs_running = NJS_DONE;
+ return;
+ }
+
+ njs_sigint_count = 0;
+
+ line.start = (u_char *) line_in;
+ line.length = njs_strlen(line.start);
+
+ if (line.length == 0) {
+ return;
+ }
+
+ add_history((char *) line.start);
+
+ ret = njs_process_script(njs_console.vm, &njs_console, &line);
+ if (ret == NJS_ERROR) {
+ njs_running = NJS_ERROR;
+ }
+
+ free(line.start);
+}
+
+
static njs_int_t
njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options)
{
+ fd_set fds;
njs_vm_t *vm;
- njs_str_t line;
+ njs_int_t ret;
if (njs_editline_init() != NJS_OK) {
njs_stderror("failed to init completions\n");
@@ -1003,27 +1074,64 @@ njs_interactive_shell(njs_opts_t *opts,
njs_printf("v.<Tab> -> the properties and prototype methods of v.\n\n");
}
- for ( ;; ) {
- line.start = (u_char *) readline(">> ");
- if (line.start == NULL) {
+ rl_callback_handler_install(">> ", njs_cb_line_handler);
+
+ njs_running = NJS_OK;
+
+ while (njs_running == NJS_OK) {
+ FD_ZERO(&fds);
+ FD_SET(fileno(rl_instream), &fds);
+
+ ret = select(FD_SETSIZE, &fds, NULL, NULL, NULL);
+ if (ret < 0 && errno != EINTR) {
+ njs_stderror("select() failed\n");
+ njs_running = NJS_ERROR;
break;
}
- line.length = njs_strlen(line.start);
+ if (njs_sigint_received) {
+ if (njs_sigint_count > 1) {
+ njs_running = NJS_DONE;
+ break;
+ }
+
+ if (rl_end != 0) {
+ njs_printf("\n");
+
+ njs_sigint_count = 0;
- if (line.length != 0) {
- add_history((char *) line.start);
+ } else {
+ njs_printf("(To exit, press Ctrl+C again or Ctrl+D "
+ "or type .exit)\n");
- njs_process_script(vm, opts, vm_options->external, &line);
+ njs_sigint_count = 1;
+ }
+
+ rl_point = rl_end = 0;
+ rl_on_new_line();
+ rl_redisplay();
+
+ njs_sigint_received = 0;
}
- /* editline allocs a new buffer every time. */
- free(line.start);
+ if (ret < 0) {
+ continue;
+ }
+
+ if (FD_ISSET(fileno(rl_instream), &fds)) {
+ rl_callback_read_char();
+ }
+ }
+
+ rl_callback_handler_remove();
+
+ if (njs_running == NJS_DONE) {
+ njs_printf("exiting\n");
}
njs_vm_destroy(vm);
- return NJS_OK;
+ return njs_running == NJS_DONE ? NJS_OK : njs_running;
}
@@ -1036,6 +1144,20 @@ njs_completion_handler(const char *text,
}
+static void
+njs_signal_handler(int signal)
+{
+ switch (signal) {
+ case SIGINT:
+ njs_sigint_received = 1;
+ njs_sigint_count += 1;
+ break;
+ default:
+ break;
+ }
+}
+
+
static njs_int_t
njs_editline_init(void)
{
@@ -1045,6 +1167,8 @@ njs_editline_init(void)
setlocale(LC_ALL, "");
+ signal(SIGINT, njs_signal_handler);
+
return NJS_OK;
}
diff -r b3eeac9ee9f4 -r 8163612eb950 test/shell_test.exp
--- a/test/shell_test.exp Wed Nov 16 18:47:36 2022 -0800
+++ b/test/shell_test.exp Thu Nov 17 20:38:23 2022 -0800
@@ -36,8 +36,8 @@ proc njs_test {body {opts ""}} {
expect [lindex $pair 1]
}
- # Ctrl-C
- send \x03
+ send "\n"
+ send ".exit\r\n"
expect eof
}
@@ -62,10 +62,11 @@ njs_test {
}
# Global completions, no
-njs_test {
- {"\t\tn"
- "\a\r\nDisplay all*possibilities? (y or n)*>> "}
-}
+# Disabled: readline does not support it in callback mode
+# njs_test {
+# {"\t\tn"
+# "\a\r\nDisplay all*possibilities? (y or n)*>> "}
+# }
# Global completions, yes
njs_test {
@@ -87,13 +88,10 @@ njs_test {
"Ma\a*th"}
}
-# FIXME: completions for external objects
-# are not supported
-
-# njs_test {
-# {"conso\t"
-# "conso\a*le"}
-# }
+ njs_test {
+ {"conso\t"
+ "conso\a*le"}
+ }
# Global completions, multiple partial match
njs_test {
@@ -145,10 +143,10 @@ njs_test {
# Global completions, global vars
njs_test {
- {"var a = 1; var aa = 2\r\n"
- "var a = 1; var aa = 2\r\nundefined\r\n>> "}
- {"a\t\t"
- "a*aa*arguments*await"}
+ {"var AA = 1; var AAA = 2\r\n"
+ "var AA = 1; var AAA = 2\r\nundefined\r\n>> "}
+ {"AA\t\t"
+ "AA*AAA*"}
}
# z*z is WORKAROUND for libedit-20170329.3.1-r3
More information about the nginx-devel
mailing list