Compare commits

...

6 Commits

Author SHA1 Message Date
Christopher Faulet
95ff3a4918 BUG/MINOR: ring: Fix I/O handler of "show event" command to not rely on the SC
Thanks to the CLI refactoring ("MAJOR: cli: Refacor parsing and execution of
pipelined commands"), it is possible to fix "show event" I/O handle function
to no longer use the SC.

When the applet API was refactored to no longer manipulate the channels or
the stream-connectors, this part was missed. However, without the patch
above, it could not be fixed. It is now possible so let's do it.

This patch must not be backported becaues it depends on refactoring of the
CLI applet.
2025-04-24 08:54:09 +02:00
Christopher Faulet
1a74169549 BUG/MINOR: hlua: Fix I/O handler of lua CLI commands to not rely on the SC
Thanks to the CLI refactoring ("MAJOR: cli: Refacor parsing and execution of
pipelined commands"), it is possible to fix the I/O handler function used by
lua CLI commands to no longer use the SC.

When the applet API was refactored to no longer manipulate the channels or
the stream-connectors, this part was missed. However, without the patch
above, it could not be fixed. It is now possible so let's do it.

This patch must not be backported becaues it depends on refactoring of the
CLI applet.
2025-04-24 08:54:09 +02:00
Christopher Faulet
134188ee7c CLEANUP: applet: Update st0/st1 comment in appctx structure
Today, these states are used by almost all applets. So update the comments
of these fields.
2025-04-24 08:54:09 +02:00
Christopher Faulet
3b6884b9ab MINOR: cli: Rename some CLI applet states to reflect recent refactoring
CLI_ST_GETREQ state was renamed into CLI_ST_PARSE_CMDLINE and CLI_ST_PARSEREQ
into CLI_ST_PROCESS_CMDLINE to reflect the real action performed in these
states.
2025-04-24 08:54:09 +02:00
Christopher Faulet
0e44ef48c5 MAJOR: cli: Refacor parsing and execution of pipelined commands
Before this patch, when pipelined commands were received, each command was
parsed and then excuted before moving to the next command. Pending commands
were not copied in the input buffer of the applet. The major issue with this
way to handle commands is the impossibility to consume inputs from commands
with an I/O handler, like "show events" for instance. It was working thanks
to a "bug" if such commands were the last one on the command line. But it
was impossible to use them followed by another command. And this prevents us
to implement any streaming support for CLI commands.

So we decided to refactor the command line parsing to have something similar
to a basic shell. Now an entire line is parsed, including the payload,
before starting commands execution. The command line is copied in a
dedicated buffer. "appctx->chunk" buffer is used for this purpose. It was an
unsed field, so it is safe to use it here. Once the command line copied, the
commands found on this line are executed. Because the applet input buffer
was flushed, any input can be safely consumed by the CLI applet and is
available for the command I/O handler. Thanks to this change, "show event
-w" command can be followed by a command. And in theory, it should be
possible to implement commands supporting input data streaming. For
instance, the Tetris like lua applet can be used on the CLI now.

Note that the payload, if any, is part of the command line and must be fully
received before starting the commands processing. It means there is still
the limitation to a buffer, but not only for the payload but for the whole
command line. The payload is still necessarily at the end of the command
line and is passed as argument to the last command. Internally, the
"appctx->cli_payload" field was introduced to point on the payload in the
command line buffer.

This patch is quite huge but it cannot easily be splitted. It should not
introduced significant changes.
2025-04-24 08:54:09 +02:00
Christopher Faulet
3479f230e0 BUG/MINOR: cli: Issue an error when too many args are passed for a command
When a command is parsed to split it in an array of arguments, by default,
at most 64 arguments are supported. But no warning was emitted when there
were too many arguments. Instead, the arguments above the limit were
silently ignored. It could be an issue for some commands, like "add server",
because there was no way to know some arguments were ignored.

Now an error is issued when too many arguments are passed and the command is
not executed.

This patch should be backported to all stable versions.
2025-04-24 08:54:09 +02:00
5 changed files with 183 additions and 164 deletions

View File

@ -101,8 +101,8 @@ struct applet {
struct appctx { struct appctx {
enum obj_type obj_type; /* OBJ_TYPE_APPCTX */ enum obj_type obj_type; /* OBJ_TYPE_APPCTX */
/* 3 unused bytes here */ /* 3 unused bytes here */
unsigned int st0; /* CLI state for stats, session state for peers */ unsigned int st0; /* Main applet state. May be used by any applet */
unsigned int st1; /* prompt/payload (bitwise OR of APPCTX_CLI_ST1_*) for stats, session error for peers */ unsigned int st1; /* Applet substate. Mau be used by any applet */
unsigned int flags; /* APPCTX_FL_* */ unsigned int flags; /* APPCTX_FL_* */
struct buffer inbuf; struct buffer inbuf;
@ -120,6 +120,7 @@ struct appctx {
int cli_severity_output; /* used within the cli_io_handler to format severity output of informational feedback */ int cli_severity_output; /* used within the cli_io_handler to format severity output of informational feedback */
int cli_level; /* the level of CLI which can be lowered dynamically */ int cli_level; /* the level of CLI which can be lowered dynamically */
char cli_payload_pat[8]; /* Payload pattern */ char cli_payload_pat[8]; /* Payload pattern */
char *cli_payload; /* Pointer on the payload. NULL if no payload */
uint32_t cli_anon_key; /* the key to anonymise with the hash in cli */ uint32_t cli_anon_key; /* the key to anonymise with the hash in cli */
struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */ struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */
struct task *t; /* task associated to the applet */ struct task *t; /* task associated to the applet */

View File

@ -55,8 +55,8 @@
enum { enum {
CLI_ST_INIT = 0, /* initial state, must leave to zero ! */ CLI_ST_INIT = 0, /* initial state, must leave to zero ! */
CLI_ST_END, /* final state, let's close */ CLI_ST_END, /* final state, let's close */
CLI_ST_GETREQ, /* wait for a request */ CLI_ST_PARSE_CMDLINE, /* wait for a full command line */
CLI_ST_PARSEREQ, /* parse a request */ CLI_ST_PROCESS_CMDLINE, /* process all commands on the command line */
CLI_ST_OUTPUT, /* all states after this one are responses */ CLI_ST_OUTPUT, /* all states after this one are responses */
CLI_ST_PROMPT, /* display the prompt (first output, same code) */ CLI_ST_PROMPT, /* display the prompt (first output, same code) */
CLI_ST_PRINT, /* display const message in cli->msg */ CLI_ST_PRINT, /* display const message in cli->msg */

310
src/cli.c
View File

@ -728,22 +728,22 @@ static int cli_get_severity_output(struct appctx *appctx)
/* Processes the CLI interpreter on the stats socket. This function is called /* Processes the CLI interpreter on the stats socket. This function is called
* from the CLI's IO handler running in an appctx context. The function returns * from the CLI's IO handler running in an appctx context. The function returns
* 1 if the request was understood, otherwise zero (in which case an error * 1 if the request was understood, otherwise zero (in which case an error
* message will be displayed). It is called with appctx->st0 * message will be displayed). It is called with appctx->st0 set to
* set to CLI_ST_GETREQ and presets ->st2 to 0 so that parsers don't have to do * CLI_ST_PROMPT. It will possilbly leave st0 to CLI_ST_CALLBACK if the
* it. It will possilbly leave st0 to CLI_ST_CALLBACK if the keyword needs to * keyword needs to have its own I/O handler called again. Most of the time,
* have its own I/O handler called again. Most of the time, parsers will only * parsers will only set st0 to CLI_ST_PRINT and put their message to be
* set st0 to CLI_ST_PRINT and put their message to be displayed into cli.msg. * displayed into cli.msg. If a keyword parser is NULL and an I/O handler is
* If a keyword parser is NULL and an I/O handler is declared, the I/O handler * declared, the I/O handler will automatically be used.
* will automatically be used.
*/ */
static int cli_parse_request(struct appctx *appctx) static int cli_process_cmdline(struct appctx *appctx)
{ {
char *args[MAX_CLI_ARGS + 1], *p, *end, *payload = NULL; char *args[MAX_CLI_ARGS + 1], *orig, *p, *end, *payload = NULL;
int i = 0; int i = 0, ret = 0;
struct cli_kw *kw; struct cli_kw *kw;
p = b_head(&appctx->inbuf); orig = p = b_head(appctx->chunk);
end = b_tail(&appctx->inbuf); end = p + strlen(p);
/* /*
* Get pointers on words. * Get pointers on words.
* One extra slot is reserved to store a pointer on a null byte. * One extra slot is reserved to store a pointer on a null byte.
@ -756,23 +756,6 @@ static int cli_parse_request(struct appctx *appctx)
if (!*p) if (!*p)
break; break;
/* first check if the '<<' is present, but this is not enough
* because we don't know if this is the end of the string */
if (strncmp(p, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) {
int pat_len = strlen(appctx->cli_payload_pat);
/* then if the customized pattern is empty, check if the next character is '\0' */
if (pat_len == 0 && p[strlen(PAYLOAD_PATTERN)] == '\0') {
payload = p + strlen(PAYLOAD_PATTERN) + 1;
break;
}
/* else if we found the customized pattern at the end of the string */
if (strcmp(p + strlen(PAYLOAD_PATTERN), appctx->cli_payload_pat) == 0) {
payload = p + strlen(PAYLOAD_PATTERN) + pat_len + 1;
break;
}
}
args[i] = p; args[i] = p;
while (1) { while (1) {
@ -804,13 +787,31 @@ static int cli_parse_request(struct appctx *appctx)
i++; i++;
} }
/* fill unused slots */ /* Pass the payload to the last command. It happens when the end of the
p = b_tail(&appctx->inbuf); * commend is just before the payload pattern.
*/
if (appctx->cli_payload && appctx->cli_payload == end + strlen(appctx->cli_payload_pat) + 3) {
appctx->st1 |= APPCTX_CLI_ST1_LASTCMD;
payload = appctx->cli_payload;
}
if (end+1 == b_tail(appctx->chunk))
appctx->st1 |= APPCTX_CLI_ST1_LASTCMD;
/* throw an error if too many args are provided */
if (*p && i == MAX_CLI_ARGS) {
char *err = NULL;
cli_err(appctx, memprintf(&err, "Too many arguments. Commands must have at most %d arguments.\n", MAX_CLI_ARGS));
goto end;
}
/* fill unused slots to point on the \0 at the end of the command */
for (; i < MAX_CLI_ARGS + 1; i++) for (; i < MAX_CLI_ARGS + 1; i++)
args[i] = p; args[i] = end;
if (!**args) if (!**args)
return 0; goto end;
kw = cli_find_kw(args); kw = cli_find_kw(args);
if (!kw || if (!kw ||
@ -819,18 +820,18 @@ static int cli_parse_request(struct appctx *appctx)
(appctx->cli_level & ~kw->level & (ACCESS_MASTER_ONLY|ACCESS_MASTER)) == (ACCESS_MASTER_ONLY|ACCESS_MASTER))) { (appctx->cli_level & ~kw->level & (ACCESS_MASTER_ONLY|ACCESS_MASTER)) == (ACCESS_MASTER_ONLY|ACCESS_MASTER))) {
/* keyword not found in this mode */ /* keyword not found in this mode */
cli_gen_usage_msg(appctx, args); cli_gen_usage_msg(appctx, args);
return 0; goto end;
} }
/* don't handle expert mode commands if not in this mode. */ /* don't handle expert mode commands if not in this mode. */
if (kw->level & ~appctx->cli_level & ACCESS_EXPERT) { if (kw->level & ~appctx->cli_level & ACCESS_EXPERT) {
cli_err(appctx, "This command is restricted to expert mode only.\n"); cli_err(appctx, "This command is restricted to expert mode only.\n");
return 0; goto end;
} }
if (kw->level & ~appctx->cli_level & ACCESS_EXPERIMENTAL) { if (kw->level & ~appctx->cli_level & ACCESS_EXPERIMENTAL) {
cli_err(appctx, "This command is restricted to experimental mode only.\n"); cli_err(appctx, "This command is restricted to experimental mode only.\n");
return 0; goto end;
} }
if (kw->level == ACCESS_EXPERT) if (kw->level == ACCESS_EXPERT)
@ -841,19 +842,29 @@ static int cli_parse_request(struct appctx *appctx)
appctx->io_handler = kw->io_handler; appctx->io_handler = kw->io_handler;
appctx->io_release = kw->io_release; appctx->io_release = kw->io_release;
if (kw->parse && kw->parse(args, payload, appctx, kw->private) != 0) if (kw->parse && kw->parse(args, payload, appctx, kw->private) != 0) {
ret = 1;
goto fail; goto fail;
}
/* kw->parse could set its own io_handler or io_release handler */ /* kw->parse could set its own io_handler or io_release handler */
if (!appctx->io_handler) if (!appctx->io_handler) {
ret = 1;
goto fail; goto fail;
}
appctx->st0 = CLI_ST_CALLBACK; appctx->st0 = CLI_ST_CALLBACK;
return 1; ret = 1;
goto end;
fail: fail:
appctx->io_handler = NULL; appctx->io_handler = NULL;
appctx->io_release = NULL; appctx->io_release = NULL;
return 1;
end:
/* Skip the command */
b_del(appctx->chunk, end - orig + 1);
return ret;
} }
/* prepends then outputs the argument msg with a syslog-type severity depending on severity_output value */ /* prepends then outputs the argument msg with a syslog-type severity depending on severity_output value */
@ -906,8 +917,10 @@ int cli_init(struct appctx *appctx)
appctx->cli_severity_output = bind_conf->severity_output; appctx->cli_severity_output = bind_conf->severity_output;
applet_reset_svcctx(appctx); applet_reset_svcctx(appctx);
appctx->st0 = CLI_ST_GETREQ; appctx->st0 = CLI_ST_PARSE_CMDLINE;
appctx->cli_level = bind_conf->level; appctx->cli_level = bind_conf->level;
appctx->cli_payload = NULL;
appctx->chunk = NULL;
/* Wakeup the applet ASAP. */ /* Wakeup the applet ASAP. */
applet_need_more_data(appctx); applet_need_more_data(appctx);
@ -915,91 +928,70 @@ int cli_init(struct appctx *appctx)
} }
size_t cli_snd_buf(struct appctx *appctx, struct buffer *buf, size_t count, unsigned flags) int cli_parse_cmdline(struct appctx *appctx)
{ {
char *str; char *str;
size_t len, ret = 0; size_t len;
int lf = 0; int ret = 0;
if (appctx->st0 == CLI_ST_INIT) if (!b_data(&appctx->inbuf))
cli_init(appctx); goto end;
else if (appctx->st0 == CLI_ST_END) {
/* drop all data on END state */ /* Allocate a chunk to process the command line */
ret = count; if (!appctx->chunk) {
b_del(buf, ret); appctx->chunk = alloc_trash_chunk();
if (!appctx->chunk) {
cli_err(appctx, "Failed to alloc a buffer to process the command line.\n");
applet_set_error(appctx);
b_reset(&appctx->inbuf);
goto end; goto end;
} }
else if (appctx->st0 != CLI_ST_GETREQ) }
goto end;
if (b_space_wraps(&appctx->inbuf))
b_slow_realign(&appctx->inbuf, trash.area, b_data(&appctx->inbuf));
while (1) { while (1) {
/* payload doesn't take escapes nor does it end on semi-colons, /* payload doesn't take escapes nor does it end on semi-colons,
* so we use the regular getline. Normal mode however must stop * so we use the regular getline. Normal mode however must stop
* on LFs and semi-colons that are not prefixed by a backslash. * on LFs and semi-colons that are not prefixed by a backslash.
* Note we reserve one byte at the end to insert a trailing nul * Note we reserve one byte at the end to insert a trailing nul
* byte. * byte.
*/ */
str = b_tail(&appctx->inbuf); str = b_tail(appctx->chunk);
if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)) if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD))
len = b_getdelim(buf, ret, count, str, b_room(&appctx->inbuf) - 1, "\n;", '\\'); len = b_getdelim(&appctx->inbuf, 0, b_data(&appctx->inbuf), str, b_room(appctx->chunk), "\n;", '\\');
else else
len = b_getline(buf, ret, count, str, b_room(&appctx->inbuf) - 1); len = b_getline(&appctx->inbuf, 0, b_data(&appctx->inbuf), str, b_room(appctx->chunk) - 1);
if (!len) { if (!len) {
if (!b_room(buf) || (count > b_room(&appctx->inbuf) - 1)) { if (!b_room(appctx->chunk) || (b_data(&appctx->inbuf) > b_room(appctx->chunk) - 1)) {
cli_err(appctx, "The command is too big for the buffer size. Please change tune.bufsize in the configuration to use a bigger command.\n"); cli_err(appctx, "The command line is too big for the buffer size. Please change tune.bufsize in the configuration to use a bigger command.\n");
applet_set_error(appctx);
b_reset(&appctx->inbuf);
}
else if (flags & CO_SFL_LAST_DATA) {
applet_set_eos(appctx);
applet_set_error(appctx); applet_set_error(appctx);
b_reset(&appctx->inbuf); b_reset(&appctx->inbuf);
} }
break; break;
} }
ret += len; b_del(&appctx->inbuf, len);
count -= len; b_add(appctx->chunk, len);
if (str[len-1] == '\n') if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)) {
lf = 1;
len--;
if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
str[len+1] = '\0';
b_add(&appctx->inbuf, len+1);
}
else {
/* Remove the trailing \r, if any and add a null byte at the
* end. For normal mode, the trailing \n is removed, but we
* conserve \r\n or \n sequences for payload mode.
*/
if (len && str[len-1] == '\r')
len--;
str[len] = '\0';
b_add(&appctx->inbuf, len);
}
if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
/* look for a pattern */
if (len == strlen(appctx->cli_payload_pat)) {
/* here use 'len' because str still contains the \n */
if (strncmp(str, appctx->cli_payload_pat, len) == 0) {
/* remove the last two \n */
b_sub(&appctx->inbuf, strlen(appctx->cli_payload_pat) + 2);
*b_tail(&appctx->inbuf) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf)
appctx->st1 |= APPCTX_CLI_ST1_LASTCMD;
}
}
}
else {
char *last_arg; char *last_arg;
char sep = str[len -1];
/* For the command line, change the separator (\n or \;) into a \0 */
str[--len] = '\0';
if (sep == ';') {
/* It is not the end of the command line, loop to get the next command */
continue;
}
/* The end of the command line was reached. Change the trailing \r, if any,
* by a null byte. For the command line, the trailing \r and \n are removed,
* but we conserve them for payload mode.
*/
if (str[len-1] == '\r')
str[--len] = '\0';
/* /*
* Look for the "payload start" pattern at the end of a * Look for the "payload start" pattern at the end of a
@ -1012,7 +1004,7 @@ size_t cli_snd_buf(struct appctx *appctx, struct buffer *buf, size_t count, unsi
*/ */
/* look for the first space starting by the end of the line */ /* look for the first space starting by the end of the line */
for (last_arg = b_tail(&appctx->inbuf); last_arg != b_head(&appctx->inbuf); last_arg--) { for (last_arg = str+len-1; last_arg != str; last_arg--) {
if (*last_arg == ' ' || *last_arg == '\t') { if (*last_arg == ' ' || *last_arg == '\t') {
last_arg++; last_arg++;
break; break;
@ -1020,33 +1012,59 @@ size_t cli_snd_buf(struct appctx *appctx, struct buffer *buf, size_t count, unsi
} }
if (strncmp(last_arg, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) { if (strncmp(last_arg, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) {
ssize_t pat_len = strlen(last_arg + strlen(PAYLOAD_PATTERN)); ssize_t pat_len = strlen(last_arg) - strlen(PAYLOAD_PATTERN);
/* A customized pattern can't be more than 7 characters /* A customized pattern can't be more than 7 characters
* if it's more, don't make it a payload * if it's more, don't make it a payload
*/ */
if (pat_len < sizeof(appctx->cli_payload_pat)) { if (pat_len < sizeof(appctx->cli_payload_pat)) {
appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
/* copy the customized pattern, don't store the << */ /* copy the customized pattern, don't store the << */
strncpy(appctx->cli_payload_pat, last_arg + strlen(PAYLOAD_PATTERN), sizeof(appctx->cli_payload_pat)-1); strncpy(appctx->cli_payload_pat, last_arg + strlen(PAYLOAD_PATTERN), sizeof(appctx->cli_payload_pat)-1);
appctx->cli_payload_pat[sizeof(appctx->cli_payload_pat)-1] = '\0'; appctx->cli_payload_pat[sizeof(appctx->cli_payload_pat)-1] = '\0';
b_add(&appctx->inbuf, 1); // keep the trailing \0 after the pattern
/* The last command finishes before the payload pattern.
* Dont' strip trailing spaces to be sure to detect when
* the payload should be used.
*/
*last_arg = '\0';
/* The payload will start on the next character in the buffer */
appctx->cli_payload = b_tail(appctx->chunk);
appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
}
} }
} }
else { else {
if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf) /* Add a \0 just after the \n to mark the end of this payload line
appctx->st1 |= APPCTX_CLI_ST1_LASTCMD; * to ease pattern detection. But it is not part of the buffer.
* (note: the space for this character was reserved when b_getline() was called)
*/
str[len] = '\0';
/* look for a pattern at the end of the payload
* (take care to exclue last character because it is a \n)
*/
if (len-1 == strlen(appctx->cli_payload_pat)) {
if (strncmp(str, appctx->cli_payload_pat, len-1) == 0) {
/* end of payload was reached, rewind before the previous \n and replace it by a \0 */
b_sub(appctx->chunk, strlen(appctx->cli_payload_pat) + 2);
*b_tail(appctx->chunk) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
}
} }
} }
if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) || (appctx->st1 & APPCTX_CLI_ST1_PROMPT)) { if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)) {
appctx->st0 = CLI_ST_PARSEREQ; appctx->st0 = CLI_ST_PROCESS_CMDLINE;
break; break;
} }
} }
b_del(buf, ret); if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
appctx->st0 = CLI_ST_PROMPT;
end: end:
if (appctx->st0 != CLI_ST_PARSE_CMDLINE)
ret = 1;
return ret; return ret;
} }
@ -1081,19 +1099,11 @@ void cli_io_handler(struct appctx *appctx)
applet_set_eos(appctx); applet_set_eos(appctx);
break; break;
} }
else if (appctx->st0 == CLI_ST_GETREQ) { else if (appctx->st0 == CLI_ST_PARSE_CMDLINE) {
/* Now we close the output if we're not in interactive if (cli_parse_cmdline(appctx) == 0)
* mode and the request buffer is empty. This still
* allows pipelined requests to be sent in
* non-interactive mode.
*/
if (se_fl_test(appctx->sedesc, SE_FL_SHW)) {
appctx->st0 = CLI_ST_END;
continue;
}
break; break;
} }
else if (appctx->st0 == CLI_ST_PARSEREQ) { else if (appctx->st0 == CLI_ST_PROCESS_CMDLINE) {
/* ensure we have some output room left in the event we /* ensure we have some output room left in the event we
* would want to return some info right after parsing. * would want to return some info right after parsing.
*/ */
@ -1105,10 +1115,7 @@ void cli_io_handler(struct appctx *appctx)
appctx->t->expire = TICK_ETERNITY; appctx->t->expire = TICK_ETERNITY;
appctx->st0 = CLI_ST_PROMPT; appctx->st0 = CLI_ST_PROMPT;
if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)) { cli_process_cmdline(appctx);
cli_parse_request(appctx);
b_reset(&appctx->inbuf);
}
} }
else { /* output functions */ else { /* output functions */
struct cli_print_ctx *ctx; struct cli_print_ctx *ctx;
@ -1196,12 +1203,15 @@ void cli_io_handler(struct appctx *appctx)
const char *prompt = ""; const char *prompt = "";
if (appctx->st1 & APPCTX_CLI_ST1_PROMPT) { if (appctx->st1 & APPCTX_CLI_ST1_PROMPT) {
/* if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
* when entering a payload with interactive mode, change the prompt /* when entering a payload with interactive mode, change the prompt
* to emphasize that more data can still be sent * to emphasize that more data can still be sent */
*/
if (b_data(&appctx->inbuf) && appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
prompt = "+ "; prompt = "+ ";
}
else if (b_data(appctx->chunk) && !(appctx->st1 & APPCTX_CLI_ST1_LASTCMD)) {
/* we are executing pipelined commands, don't display the prompt */
prompt = "\n";
}
else if (appctx->st1 & APPCTX_CLI_ST1_TIMED) { else if (appctx->st1 & APPCTX_CLI_ST1_TIMED) {
uint up = ns_to_sec(now_ns - start_time_ns); uint up = ns_to_sec(now_ns - start_time_ns);
snprintf(prompt_buf, sizeof(prompt_buf), snprintf(prompt_buf, sizeof(prompt_buf),
@ -1219,35 +1229,41 @@ void cli_io_handler(struct appctx *appctx)
if (applet_putstr(appctx, prompt) != -1) { if (applet_putstr(appctx, prompt) != -1) {
applet_reset_svcctx(appctx); applet_reset_svcctx(appctx);
appctx->st0 = CLI_ST_GETREQ; appctx->st0 = (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) ? CLI_ST_PARSE_CMDLINE : CLI_ST_PROCESS_CMDLINE;
} }
} }
/* If the output functions are still there, it means they require more room. */ /* If the output functions are still there, it means they require more room. */
if (appctx->st0 >= CLI_ST_OUTPUT) { if (appctx->st0 >= CLI_ST_OUTPUT) {
if (appctx->st0 != CLI_ST_CALLBACK)
applet_wont_consume(appctx); applet_wont_consume(appctx);
break; break;
} }
/* Now we close the output if we're not in interactive /* reactivate the \n at the end of the response for the next command */
* mode and the request buffer is empty. This still appctx->st1 &= ~APPCTX_CLI_ST1_NOLF;
* allows pipelined requests to be sent in
* non-interactive mode. /* switch state back to PARSE_CMDLINE to read next requests in interactove mode, otherwise close */
*/ if (appctx->st1 & APPCTX_CLI_ST1_LASTCMD) {
if ((appctx->st1 & (APPCTX_CLI_ST1_PROMPT|APPCTX_CLI_ST1_PAYLOAD|APPCTX_CLI_ST1_LASTCMD)) == APPCTX_CLI_ST1_LASTCMD) { applet_reset_svcctx(appctx);
free_trash_chunk(appctx->chunk);
appctx->cli_payload = NULL;
appctx->chunk = NULL;
appctx->st1 &= ~APPCTX_CLI_ST1_LASTCMD;
if (appctx->st1 & APPCTX_CLI_ST1_PROMPT) {
appctx->st0 = CLI_ST_PARSE_CMDLINE;
applet_will_consume(appctx);
applet_expect_data(appctx);
}
else {
applet_set_eoi(appctx); applet_set_eoi(appctx);
appctx->st0 = CLI_ST_END; appctx->st0 = CLI_ST_END;
continue; continue;
} }
}
/* switch state back to GETREQ to read next requests */ if ((b_data(&appctx->inbuf) && appctx->st0 == CLI_ST_PARSE_CMDLINE) || appctx->st0 == CLI_ST_PROCESS_CMDLINE)
applet_reset_svcctx(appctx); appctx_wakeup(appctx);
appctx->st0 = CLI_ST_GETREQ;
applet_will_consume(appctx);
applet_expect_data(appctx);
/* reactivate the \n at the end of the response for the next command */
appctx->st1 &= ~APPCTX_CLI_ST1_NOLF;
/* this forces us to yield between pipelined commands and /* this forces us to yield between pipelined commands and
* avoid extremely long latencies (e.g. "del map" etc). In * avoid extremely long latencies (e.g. "del map" etc). In
@ -3765,7 +3781,7 @@ static struct applet cli_applet = {
.name = "<CLI>", /* used for logging */ .name = "<CLI>", /* used for logging */
.fct = cli_io_handler, .fct = cli_io_handler,
.rcv_buf = appctx_raw_rcv_buf, .rcv_buf = appctx_raw_rcv_buf,
.snd_buf = cli_snd_buf, .snd_buf = appctx_raw_snd_buf,
.release = cli_release_handler, .release = cli_release_handler,
}; };
@ -3775,7 +3791,7 @@ static struct applet mcli_applet = {
.name = "<MCLI>", /* used for logging */ .name = "<MCLI>", /* used for logging */
.fct = cli_io_handler, .fct = cli_io_handler,
.rcv_buf = appctx_raw_rcv_buf, .rcv_buf = appctx_raw_rcv_buf,
.snd_buf = cli_snd_buf, .snd_buf = appctx_raw_snd_buf,
.release = cli_release_handler, .release = cli_release_handler,
}; };

View File

@ -11895,11 +11895,9 @@ static int hlua_cli_io_handler_fct(struct appctx *appctx)
{ {
struct hlua_cli_ctx *ctx = appctx->svcctx; struct hlua_cli_ctx *ctx = appctx->svcctx;
struct hlua *hlua; struct hlua *hlua;
struct stconn *sc;
struct hlua_function *fcn; struct hlua_function *fcn;
hlua = ctx->hlua; hlua = ctx->hlua;
sc = appctx_sc(appctx);
fcn = ctx->fcn; fcn = ctx->fcn;
/* Execute the function. */ /* Execute the function. */
@ -11913,10 +11911,13 @@ static int hlua_cli_io_handler_fct(struct appctx *appctx)
case HLUA_E_AGAIN: case HLUA_E_AGAIN:
/* We want write. */ /* We want write. */
if (HLUA_IS_WAKERESWR(hlua)) if (HLUA_IS_WAKERESWR(hlua))
sc_need_room(sc, -1); applet_have_more_data(appctx);
/* Set the timeout. */ /* Set the timeout. */
if (hlua->wake_time != TICK_ETERNITY) if (hlua->wake_time != TICK_ETERNITY)
task_schedule(hlua->task, hlua->wake_time); task_schedule(hlua->task, hlua->wake_time);
applet_will_consume(appctx);
applet_expect_data(appctx);
return 0; return 0;
/* finished with error. */ /* finished with error. */

View File

@ -696,7 +696,6 @@ int ring_dispatch_messages(struct ring *ring, void *ctx, size_t *ofs_ptr, size_t
int cli_io_handler_show_ring(struct appctx *appctx) int cli_io_handler_show_ring(struct appctx *appctx)
{ {
struct show_ring_ctx *ctx = appctx->svcctx; struct show_ring_ctx *ctx = appctx->svcctx;
struct stconn *sc = appctx_sc(appctx);
struct ring *ring = ctx->ring; struct ring *ring = ctx->ring;
size_t last_ofs; size_t last_ofs;
size_t ofs; size_t ofs;
@ -711,7 +710,7 @@ int cli_io_handler_show_ring(struct appctx *appctx)
/* we've drained everything and are configured to wait for more /* we've drained everything and are configured to wait for more
* data or an event (keypress, close) * data or an event (keypress, close)
*/ */
if (!sc_oc(sc)->output && !(sc->flags & SC_FL_SHUT_DONE)) { if (!b_data(&appctx->inbuf)) {
/* let's be woken up once new data arrive */ /* let's be woken up once new data arrive */
MT_LIST_APPEND(&ring->waiters, &appctx->wait_entry); MT_LIST_APPEND(&ring->waiters, &appctx->wait_entry);
ofs = ring_tail(ring); ofs = ring_tail(ring);
@ -726,9 +725,11 @@ int cli_io_handler_show_ring(struct appctx *appctx)
ret = 0; ret = 0;
} }
/* always drain all the request */ /* always drain all the request */
co_skip(sc_oc(sc), sc_oc(sc)->output); b_reset(&appctx->inbuf);
applet_fl_clr(appctx, APPCTX_FL_INBLK_FULL);
} }
applet_will_consume(appctx);
applet_expect_no_data(appctx); applet_expect_no_data(appctx);
return ret; return ret;
} }