summaryrefslogtreecommitdiffstats
path: root/src/cli.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-03 05:11:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-03 05:11:10 +0000
commitcff6d757e3ba609c08ef2aaa00f07e53551e5bf6 (patch)
tree08c4fc3255483ad397d712edb4214ded49149fd9 /src/cli.c
parentAdding upstream version 2.9.7. (diff)
downloadhaproxy-upstream/3.0.0.tar.xz
haproxy-upstream/3.0.0.zip
Adding upstream version 3.0.0.upstream/3.0.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cli.c')
-rw-r--r--src/cli.c565
1 files changed, 376 insertions, 189 deletions
diff --git a/src/cli.c b/src/cli.c
index 51f3d77..9470d12 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -455,7 +455,7 @@ static struct proxy *cli_alloc_fe(const char *name, const char *file, int line)
init_new_proxy(fe);
fe->next = proxies_list;
proxies_list = fe;
- fe->last_change = ns_to_sec(now_ns);
+ fe->fe_counters.last_change = ns_to_sec(now_ns);
fe->id = strdup("GLOBAL");
fe->cap = PR_CAP_FE|PR_CAP_INT;
fe->maxconn = 10; /* default to 10 concurrent connections */
@@ -742,9 +742,8 @@ static int cli_parse_request(struct appctx *appctx)
int i = 0;
struct cli_kw *kw;
- p = appctx->chunk->area;
- end = p + appctx->chunk->data;
-
+ p = b_head(&appctx->inbuf);
+ end = b_tail(&appctx->inbuf);
/*
* Get pointers on words.
* One extra slot is reserved to store a pointer on a null byte.
@@ -806,29 +805,13 @@ static int cli_parse_request(struct appctx *appctx)
i++;
}
/* fill unused slots */
- p = appctx->chunk->area + appctx->chunk->data;
+ p = b_tail(&appctx->inbuf);
for (; i < MAX_CLI_ARGS + 1; i++)
args[i] = p;
if (!**args)
return 0;
- if (appctx->st1 & APPCTX_CLI_ST1_SHUT_EXPECTED) {
- /* The previous command line was finished by a \n in non-interactive mode.
- * It should not be followed by another command line. In non-interactive mode,
- * only one line should be processed. Because of a bug, it is not respected.
- * So emit a warning, only once in the process life, to warn users their script
- * must be updated.
- */
- appctx->st1 &= ~APPCTX_CLI_ST1_SHUT_EXPECTED;
- if (ONLY_ONCE()) {
- ha_warning("Commands sent to the CLI were chained using a new line character while in non-interactive mode."
- " This is not reliable, not officially supported and will not be supported anymore in future versions. "
- "Please use ';' to delimit commands instead.");
- }
- }
-
-
kw = cli_find_kw(args);
if (!kw ||
(kw->level & ~appctx->cli_level & ACCESS_MASTER_ONLY) ||
@@ -916,6 +899,151 @@ static int cli_output_msg(struct appctx *appctx, const char *msg, int severity,
return applet_putchk(appctx, tmp);
}
+int cli_init(struct appctx *appctx)
+{
+ struct stconn *sc = appctx_sc(appctx);
+ struct bind_conf *bind_conf = strm_li(__sc_strm(sc))->bind_conf;
+
+ appctx->cli_severity_output = bind_conf->severity_output;
+ applet_reset_svcctx(appctx);
+ appctx->st0 = CLI_ST_GETREQ;
+ appctx->cli_level = bind_conf->level;
+
+ /* Wakeup the applet ASAP. */
+ applet_need_more_data(appctx);
+ return 0;
+
+}
+
+size_t cli_snd_buf(struct appctx *appctx, struct buffer *buf, size_t count, unsigned flags)
+{
+ char *str;
+ size_t len, ret = 0;
+ int lf = 0;
+
+ if (appctx->st0 == CLI_ST_INIT)
+ cli_init(appctx);
+ 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) {
+ /* payload doesn't take escapes nor does it end on semi-colons,
+ * so we use the regular getline. Normal mode however must stop
+ * 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
+ * byte.
+ */
+ str = b_tail(&appctx->inbuf);
+ if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD))
+ len = b_getdelim(buf, ret, count, str, b_room(&appctx->inbuf) - 1, "\n;", '\\');
+ else
+ len = b_getline(buf, ret, count, str, b_room(&appctx->inbuf) - 1);
+
+ if (!len) {
+ if (!b_room(buf) || (count > b_room(&appctx->inbuf) - 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");
+ applet_set_error(appctx);
+ b_reset(&appctx->inbuf);
+ }
+ else if (flags & CO_SFL_LAST_DATA) {
+ applet_set_eos(appctx);
+ applet_set_error(appctx);
+ b_reset(&appctx->inbuf);
+ }
+ break;
+ }
+
+ ret += len;
+ count -= len;
+
+ if (str[len-1] == '\n')
+ lf = 1;
+
+ /* 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 if for payload mode.
+ */
+ len--;
+ if (len && str[len-1] == '\r')
+ len--;
+ if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
+ str[len+1] = '\0';
+ b_add(&appctx->inbuf, len+1);
+ }
+ else {
+ 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;
+
+ /*
+ * Look for the "payload start" pattern at the end of a
+ * line Its location is not remembered here, this is
+ * just to switch to a gathering mode.
+ *
+ * The pattern must start by << followed by 0 to 7
+ * characters, and finished by the end of the command
+ * (\n or ;).
+ */
+
+ /* 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--) {
+ if (*last_arg == ' ' || *last_arg == '\t') {
+ last_arg++;
+ break;
+ }
+ }
+
+ if (strncmp(last_arg, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) {
+ ssize_t pat_len = strlen(last_arg + strlen(PAYLOAD_PATTERN));
+
+ /* A customized pattern can't be more than 7 characters
+ * if it's more, don't make it a payload
+ */
+ if (pat_len < sizeof(appctx->cli_payload_pat)) {
+ appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
+ /* copy the customized pattern, don't store the << */
+ 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';
+ b_add(&appctx->inbuf, 1); // keep the trailing \0 after the pattern
+ }
+ }
+ else {
+ if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf)
+ appctx->st1 |= APPCTX_CLI_ST1_LASTCMD;
+ }
+ }
+
+ if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) || (appctx->st1 & APPCTX_CLI_ST1_PROMPT)) {
+ appctx->st0 = CLI_ST_PARSEREQ;
+ break;
+ }
+ }
+ b_del(buf, ret);
+
+ end:
+ return ret;
+}
+
/* This I/O handler runs as an applet embedded in a stream connector. It is
* used to processes I/O from/to the stats unix socket. The system relies on a
* state machine handling requests and various responses. We read a request,
@@ -926,181 +1054,62 @@ static int cli_output_msg(struct appctx *appctx, const char *msg, int severity,
*/
static void cli_io_handler(struct appctx *appctx)
{
- struct stconn *sc = appctx_sc(appctx);
- struct channel *req = sc_oc(sc);
- struct channel *res = sc_ic(sc);
- struct bind_conf *bind_conf = strm_li(__sc_strm(sc))->bind_conf;
- int reql;
- int len;
- int lf = 0;
+ if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC|APPCTX_FL_OUTBLK_FULL))
+ goto out;
- if (unlikely(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) {
- co_skip(sc_oc(sc), co_data(sc_oc(sc)));
+ if (!appctx_get_buf(appctx, &appctx->outbuf)) {
goto out;
}
- /* Check if the input buffer is available. */
- if (!b_size(&res->buf)) {
- sc_need_room(sc, 0);
+ if (unlikely(applet_fl_test(appctx, APPCTX_FL_EOS|APPCTX_FL_ERROR))) {
+ appctx->st0 = CLI_ST_END;
goto out;
}
while (1) {
if (appctx->st0 == CLI_ST_INIT) {
/* reset severity to default at init */
- appctx->cli_severity_output = bind_conf->severity_output;
- applet_reset_svcctx(appctx);
- appctx->st0 = CLI_ST_GETREQ;
- appctx->cli_level = bind_conf->level;
+ cli_init(appctx);
+ break;
}
else if (appctx->st0 == CLI_ST_END) {
- se_fl_set(appctx->sedesc, SE_FL_EOS);
- free_trash_chunk(appctx->chunk);
- appctx->chunk = NULL;
+ applet_set_eos(appctx);
break;
}
else if (appctx->st0 == CLI_ST_GETREQ) {
- char *str;
-
- /* use a trash chunk to store received data */
- if (!appctx->chunk) {
- appctx->chunk = alloc_trash_chunk();
- if (!appctx->chunk) {
- se_fl_set(appctx->sedesc, SE_FL_ERROR);
- appctx->st0 = CLI_ST_END;
- continue;
- }
- }
-
- str = appctx->chunk->area + appctx->chunk->data;
-
- /* ensure we have some output room left in the event we
- * would want to return some info right after parsing.
- */
- if (buffer_almost_full(sc_ib(sc))) {
- sc_need_room(sc, b_size(&res->buf) / 2);
- break;
- }
-
- /* payload doesn't take escapes nor does it end on semi-colons, so
- * we use the regular getline. Normal mode however must stop on
- * LFs and semi-colons that are not prefixed by a backslash. Note
- * that we reserve one byte at the end to insert a trailing nul byte.
+ /* Now we close the output if we're not in interactive
+ * mode and the request buffer is empty. This still
+ * allows pipelined requests to be sent in
+ * non-interactive mode.
*/
-
- if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
- reql = co_getline(sc_oc(sc), str,
- appctx->chunk->size - appctx->chunk->data - 1);
- else
- reql = co_getdelim(sc_oc(sc), str,
- appctx->chunk->size - appctx->chunk->data - 1,
- "\n;", '\\');
-
- if (reql <= 0) { /* closed or EOL not found */
- if (reql == 0)
- break;
- se_fl_set(appctx->sedesc, SE_FL_ERROR);
+ if (se_fl_test(appctx->sedesc, SE_FL_SHW)) {
appctx->st0 = CLI_ST_END;
continue;
}
-
- if (str[reql-1] == '\n')
- lf = 1;
-
- /* now it is time to check that we have a full line,
- * remove the trailing \n and possibly \r, then cut the
- * line.
+ break;
+ }
+ else if (appctx->st0 == CLI_ST_PARSEREQ) {
+ /* ensure we have some output room left in the event we
+ * would want to return some info right after parsing.
*/
- len = reql - 1;
- if (str[len] != '\n' && str[len] != ';') {
- se_fl_set(appctx->sedesc, SE_FL_ERROR);
- appctx->st0 = CLI_ST_END;
- continue;
- }
-
- if (len && str[len-1] == '\r')
- len--;
-
- str[len] = '\0';
- appctx->chunk->data += len;
-
- if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
- appctx->chunk->area[appctx->chunk->data] = '\n';
- appctx->chunk->area[appctx->chunk->data + 1] = 0;
- appctx->chunk->data++;
+ if (buffer_almost_full(&appctx->outbuf)) {
+ applet_fl_set(appctx, APPCTX_FL_OUTBLK_FULL);
+ break;
}
+ appctx->t->expire = TICK_ETERNITY;
appctx->st0 = CLI_ST_PROMPT;
- 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 */
- appctx->chunk->data -= strlen(appctx->cli_payload_pat) + 2;
- appctx->chunk->area[appctx->chunk->data] = 0;
- cli_parse_request(appctx);
- chunk_reset(appctx->chunk);
- /* NB: cli_sock_parse_request() may have put
- * another CLI_ST_O_* into appctx->st0.
- */
-
- appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
- if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf)
- appctx->st1 |= APPCTX_CLI_ST1_SHUT_EXPECTED;
- }
- }
- }
- else {
- char *last_arg;
- /*
- * Look for the "payload start" pattern at the end of a line
- * Its location is not remembered here, this is just to switch
- * to a gathering mode.
- * The pattern must start by << followed by 0
- * to 7 characters, and finished by the end of
- * the command (\n or ;).
- */
- /* look for the first space starting by the end of the line */
- for (last_arg = appctx->chunk->area + appctx->chunk->data; last_arg != appctx->chunk->area; last_arg--) {
- if (*last_arg == ' ' || *last_arg == '\t') {
- last_arg++;
- break;
- }
- }
- if (strncmp(last_arg, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) {
- ssize_t pat_len = strlen(last_arg + strlen(PAYLOAD_PATTERN));
-
- /* A customized pattern can't be more than 7 characters
- * if it's more, don't make it a payload
- */
- if (pat_len < sizeof(appctx->cli_payload_pat)) {
- appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
- /* copy the customized pattern, don't store the << */
- 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->chunk->data++; // keep the trailing \0 after the pattern
- }
- }
- else {
- /* no payload, the command is complete: parse the request */
- cli_parse_request(appctx);
- chunk_reset(appctx->chunk);
- if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf)
- appctx->st1 |= APPCTX_CLI_ST1_SHUT_EXPECTED;
- }
+ if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)) {
+ cli_parse_request(appctx);
+ b_reset(&appctx->inbuf);
}
-
- /* re-adjust req buffer */
- co_skip(sc_oc(sc), reql);
- sc_opposite(sc)->flags |= SC_FL_RCV_ONCE; /* we plan to read small requests */
}
else { /* output functions */
struct cli_print_ctx *ctx;
const char *msg;
int sev;
-
+ cli_output:
switch (appctx->st0) {
case CLI_ST_PROMPT:
break;
@@ -1146,17 +1155,28 @@ static void cli_io_handler(struct appctx *appctx)
appctx->st0 == CLI_ST_PRINT_UMSGERR) {
usermsgs_clr(NULL);
}
+ appctx->t->expire = TICK_ETERNITY;
appctx->st0 = CLI_ST_PROMPT;
}
+ if (applet_fl_test(appctx, APPCTX_FL_ERR_PENDING)) {
+ appctx->st0 = CLI_ST_END;
+ continue;
+ }
+
break;
case CLI_ST_CALLBACK: /* use custom pointer */
if (appctx->io_handler)
if (appctx->io_handler(appctx)) {
+ appctx->t->expire = TICK_ETERNITY;
appctx->st0 = CLI_ST_PROMPT;
if (appctx->io_release) {
appctx->io_release(appctx);
appctx->io_release = NULL;
+ /* some release handlers might have
+ * pending output to print.
+ */
+ continue;
}
}
break;
@@ -1175,7 +1195,7 @@ static void cli_io_handler(struct appctx *appctx)
* when entering a payload with interactive mode, change the prompt
* to emphasize that more data can still be sent
*/
- if (appctx->chunk->data && appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
+ if (b_data(&appctx->inbuf) && appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
prompt = "+ ";
else if (appctx->st1 & APPCTX_CLI_ST1_TIMED) {
uint up = ns_to_sec(now_ns - start_time_ns);
@@ -1209,8 +1229,8 @@ static void cli_io_handler(struct appctx *appctx)
* allows pipelined requests to be sent in
* non-interactive mode.
*/
- if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && !co_data(req) && (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD))) {
- se_fl_set(appctx->sedesc, SE_FL_EOI);
+ if ((appctx->st1 & (APPCTX_CLI_ST1_PROMPT|APPCTX_CLI_ST1_PAYLOAD|APPCTX_CLI_ST1_LASTCMD)) == APPCTX_CLI_ST1_LASTCMD) {
+ applet_set_eoi(appctx);
appctx->st0 = CLI_ST_END;
continue;
}
@@ -1230,14 +1250,16 @@ static void cli_io_handler(struct appctx *appctx)
* refills the buffer with new bytes in non-interactive
* mode, avoiding to close on apparently empty commands.
*/
- if (co_data(sc_oc(sc))) {
- appctx_wakeup(appctx);
- goto out;
- }
+ break;
}
}
out:
+ if (appctx->st0 == CLI_ST_END) {
+ /* eat the whole request */
+ b_reset(&appctx->inbuf);
+ applet_fl_clr(appctx, APPCTX_FL_INBLK_FULL);
+ }
return;
}
@@ -1247,9 +1269,6 @@ static void cli_io_handler(struct appctx *appctx)
*/
static void cli_release_handler(struct appctx *appctx)
{
- free_trash_chunk(appctx->chunk);
- appctx->chunk = NULL;
-
if (appctx->io_release) {
appctx->io_release(appctx);
appctx->io_release = NULL;
@@ -1272,13 +1291,8 @@ static void cli_release_handler(struct appctx *appctx)
static int cli_io_handler_show_env(struct appctx *appctx)
{
struct show_env_ctx *ctx = appctx->svcctx;
- struct stconn *sc = appctx_sc(appctx);
char **var = ctx->var;
- /* FIXME: Don't watch the other side !*/
- if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE))
- return 1;
-
chunk_reset(&trash);
/* we have two inner loops here, one for the proxy, the other one for
@@ -1308,16 +1322,11 @@ static int cli_io_handler_show_env(struct appctx *appctx)
*/
static int cli_io_handler_show_fd(struct appctx *appctx)
{
- struct stconn *sc = appctx_sc(appctx);
struct show_fd_ctx *fdctx = appctx->svcctx;
uint match = fdctx->show_mask;
int fd = fdctx->fd;
int ret = 1;
- /* FIXME: Don't watch the other side !*/
- if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE))
- goto end;
-
chunk_reset(&trash);
/* isolate the threads once per round. We're limited to a buffer worth
@@ -2010,6 +2019,174 @@ static int cli_parse_set_ratelimit(char **args, char *payload, struct appctx *ap
return 1;
}
+/* Parse a "wait <time>" command.
+ * It uses a "cli_wait_ctx" struct for its context.
+ * Returns 0 if the server deletion has been successfully scheduled, 1 on failure.
+ */
+static int cli_parse_wait(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct cli_wait_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ uint wait_ms;
+ const char *err;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[1])
+ return cli_err(appctx, "Expects a duration in milliseconds.\n");
+
+ err = parse_time_err(args[1], &wait_ms, TIME_UNIT_MS);
+ if (err || wait_ms < 1) {
+ /* in case -h is passed as the first option, continue to the next test */
+ if (strcmp(args[1], "-h") == 0)
+ args--;
+ else
+ return cli_err(appctx, "Invalid duration.\n");
+ }
+
+ if (strcmp(args[2], "srv-removable") == 0) {
+ struct ist be_name, sv_name;
+
+ if (!*args[3])
+ return cli_err(appctx, "Missing server name (<backend>/<server>).\n");
+
+ sv_name = ist(args[3]);
+ be_name = istsplit(&sv_name, '/');
+ if (!istlen(sv_name))
+ return cli_err(appctx, "Require 'backend/server'.\n");
+
+ be_name = istdup(be_name);
+ sv_name = istdup(sv_name);
+ if (!isttest(be_name) || !isttest(sv_name)) {
+ free(istptr(be_name));
+ free(istptr(sv_name));
+ return cli_err(appctx, "Out of memory trying to clone the server name.\n");
+ }
+
+ ctx->args[0] = ist0(be_name);
+ ctx->args[1] = ist0(sv_name);
+ ctx->cond = CLI_WAIT_COND_SRV_UNUSED;
+ }
+ else if (*args[2]) {
+ /* show the command's help either upon request (-h) or error */
+ err = "Usage: wait {-h|<duration>} [condition [args...]]\n"
+ " - '-h' displays this help\n"
+ " - <duration> is the maximum wait time, optionally suffixed by the unit among\n"
+ " 'us', 'ms', 's', 'm', 'h', and 'd'. ; the default unit is milliseconds.\n"
+ " - <condition> indicates what to wait for, no longer than the specified\n"
+ " duration. Supported conditions are:\n"
+ " - <none> : by default, just sleep for the specified duration.\n"
+ " - srv-removable <px>/<sv> : wait for this server to become removable.\n"
+ "";
+
+ if (strcmp(args[2], "-h") == 0)
+ return cli_msg(appctx, LOG_INFO, err);
+ else
+ return cli_err(appctx, err);
+ }
+
+ ctx->start = now_ms;
+ ctx->deadline = tick_add(now_ms, wait_ms);
+
+ /* proceed with the I/O handler */
+ return 0;
+}
+
+/* Execute a "wait" condition. The delay is exponentially incremented between
+ * now_ms and ctx->deadline in powers of 1.5 and with a bound set to 10% of the
+ * programmed wait time, so that in a few wakeups we can later check a condition
+ * with reasonable accuracy. Shutdowns and other errors are handled as well and
+ * terminate the operation, but not new inputs so that it remains possible to
+ * chain other commands after it. Returns 0 if not finished, 1 if finished.
+ */
+static int cli_io_handler_wait(struct appctx *appctx)
+{
+ struct cli_wait_ctx *ctx = appctx->svcctx;
+ uint total, elapsed, left, wait;
+ int ret;
+
+ /* note: upon first invocation, the timeout is not set */
+ if (tick_isset(appctx->t->expire) &&
+ !tick_is_expired(appctx->t->expire, now_ms))
+ goto wait;
+
+ /* here we should evaluate our waiting conditions, if any */
+
+ if (ctx->cond == CLI_WAIT_COND_SRV_UNUSED) {
+ /* check if the server in args[0]/args[1] can be released now */
+ thread_isolate();
+ ret = srv_check_for_deletion(ctx->args[0], ctx->args[1], NULL, NULL, NULL);
+ thread_release();
+
+ if (ret < 0) {
+ /* unrecoverable failure */
+ ctx->error = CLI_WAIT_ERR_FAIL;
+ return 1;
+ } else if (ret > 0) {
+ /* immediate success */
+ ctx->error = CLI_WAIT_ERR_DONE;
+ return 1;
+ }
+ /* let's check the timer */
+ }
+
+ /* and here we recalculate the new wait time or abort */
+ left = tick_remain(now_ms, ctx->deadline);
+ if (!left) {
+ /* let the release handler know we've expired. When there is no
+ * wait condition, it's a simple sleep so we declare we're done.
+ */
+ if (ctx->cond == CLI_WAIT_COND_NONE)
+ ctx->error = CLI_WAIT_ERR_DONE;
+ else
+ ctx->error = CLI_WAIT_ERR_EXP;
+ return 1;
+ }
+
+ total = tick_remain(ctx->start, ctx->deadline);
+ elapsed = total - left;
+ wait = elapsed / 2 + 1;
+ if (wait > left)
+ wait = left;
+ else if (wait > total / 10)
+ wait = total / 10;
+
+ appctx->t->expire = tick_add(now_ms, wait);
+
+ wait:
+ /* Stop waiting upon close/abort/error */
+ if (unlikely(se_fl_test(appctx->sedesc, SE_FL_SHW)) && !b_data(&appctx->inbuf)) {
+ ctx->error = CLI_WAIT_ERR_INTR;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* release structs allocated by "delete server" */
+static void cli_release_wait(struct appctx *appctx)
+{
+ struct cli_wait_ctx *ctx = appctx->svcctx;
+ const char *msg;
+ int i;
+
+ switch (ctx->error) {
+ case CLI_WAIT_ERR_EXP: msg = "Wait delay expired.\n"; break;
+ case CLI_WAIT_ERR_INTR: msg = "Interrupted.\n"; break;
+ case CLI_WAIT_ERR_FAIL: msg = ctx->msg ? ctx->msg : "Failed.\n"; break;
+ default: msg = "Done.\n"; break;
+ }
+
+ for (i = 0; i < sizeof(ctx->args) / sizeof(ctx->args[0]); i++)
+ ha_free(&ctx->args[i]);
+
+ if (ctx->error == CLI_WAIT_ERR_DONE)
+ cli_msg(appctx, LOG_INFO, msg);
+ else
+ cli_err(appctx, msg);
+}
+
/* parse the "expose-fd" argument on the bind lines */
static int bind_parse_expose_fd(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
{
@@ -2471,8 +2648,13 @@ static int pcli_prefix_to_pid(const char *prefix)
return -1;
}
-/* Return::
- * >= 0 : number of words to escape
+/*
+ * pcli_find_and_exec_kw() parses a command for the master CLI. It looks for a
+ * prefix or a command that is handled directly by the proxy and never sent to
+ * a worker.
+ *
+ * Return:
+ * >= 0 : number of words that were parsed and need to be skipped
* = -1 : error
*/
int pcli_find_and_exec_kw(struct stream *s, char **args, int argl, char **errmsg, int *next_pid)
@@ -2959,7 +3141,7 @@ int pcli_wait_for_response(struct stream *s, struct channel *rep, int an_bit)
pendconn_free(s);
/* let's do a final log if we need it */
- if (!LIST_ISEMPTY(&fe->logformat) && s->logs.logwait &&
+ if (!lf_expr_isempty(&fe->logformat) && s->logs.logwait &&
!(s->flags & SF_MONITOR) &&
(!(fe->options & PR_O_NULLNOLOG) || s->req.total)) {
s->do_log(s);
@@ -3366,6 +3548,8 @@ static struct applet cli_applet = {
.obj_type = OBJ_TYPE_APPLET,
.name = "<CLI>", /* used for logging */
.fct = cli_io_handler,
+ .rcv_buf = appctx_raw_rcv_buf,
+ .snd_buf = cli_snd_buf,
.release = cli_release_handler,
};
@@ -3374,6 +3558,8 @@ static struct applet mcli_applet = {
.obj_type = OBJ_TYPE_APPLET,
.name = "<MCLI>", /* used for logging */
.fct = cli_io_handler,
+ .rcv_buf = appctx_raw_rcv_buf,
+ .snd_buf = cli_snd_buf,
.release = cli_release_handler,
};
@@ -3401,6 +3587,7 @@ static struct cli_kw_list cli_kws = {{ },{
{ { "show", "version", NULL }, "show version : show version of the current process", cli_parse_show_version, NULL, NULL, NULL, ACCESS_MASTER },
{ { "operator", NULL }, "operator : lower the level of the current CLI session to operator", cli_parse_set_lvl, NULL, NULL, NULL, ACCESS_MASTER},
{ { "user", NULL }, "user : lower the level of the current CLI session to user", cli_parse_set_lvl, NULL, NULL, NULL, ACCESS_MASTER},
+ { { "wait", NULL }, "wait {-h|<delay_ms>} cond [args...] : wait the specified delay or condition (-h to see list)", cli_parse_wait, cli_io_handler_wait, cli_release_wait, NULL },
{{},}
}};