9b3fbb051d
When parsing a CLI, all the CLI instances are checked
one by one. Even if an instance already matches the CLI,
the parsing will not stop for ambiguous check.
The problem is that the following check may change the
parsing result of the previous one even if the following
instance doesn't match.
Use a temporary validate for the parsing result when
trying to match an instance and only store the result
when it matches, so the previous result has no chance
to be changed.
Fixes: af75078fec
("first public release")
Cc: stable@dpdk.org
Signed-off-by: Wenzhuo Lu <wenzhuo.lu@intel.com>
Acked-by: Olivier Matz <olivier.matz@6wind.com>
623 lines
15 KiB
C
623 lines
15 KiB
C
/*-
|
|
* BSD LICENSE
|
|
*
|
|
* Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Intel Corporation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
|
|
* All rights reserved.
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the University of California, Berkeley nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include <ctype.h>
|
|
#include <termios.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <rte_string_fns.h>
|
|
|
|
#include "cmdline_rdline.h"
|
|
#include "cmdline_parse.h"
|
|
#include "cmdline.h"
|
|
|
|
#ifdef RTE_LIBRTE_CMDLINE_DEBUG
|
|
#define debug_printf printf
|
|
#else
|
|
#define debug_printf(args...) do {} while(0)
|
|
#endif
|
|
|
|
#define CMDLINE_BUFFER_SIZE 64
|
|
|
|
/* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
|
|
* own. */
|
|
static int
|
|
isblank2(char c)
|
|
{
|
|
if (c == ' ' ||
|
|
c == '\t' )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isendofline(char c)
|
|
{
|
|
if (c == '\n' ||
|
|
c == '\r' )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
iscomment(char c)
|
|
{
|
|
if (c == '#')
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cmdline_isendoftoken(char c)
|
|
{
|
|
if (!c || iscomment(c) || isblank2(c) || isendofline(c))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cmdline_isendofcommand(char c)
|
|
{
|
|
if (!c || iscomment(c) || isendofline(c))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int
|
|
nb_common_chars(const char * s1, const char * s2)
|
|
{
|
|
unsigned int i=0;
|
|
|
|
while (*s1==*s2 && *s1) {
|
|
s1++;
|
|
s2++;
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* try to match the buffer with an instruction (only the first
|
|
* nb_match_token tokens if != 0). Return 0 if we match all the
|
|
* tokens, else the number of matched tokens, else -1.
|
|
*/
|
|
static int
|
|
match_inst(cmdline_parse_inst_t *inst, const char *buf,
|
|
unsigned int nb_match_token, void *resbuf, unsigned resbuf_size,
|
|
cmdline_parse_token_hdr_t
|
|
*(*dyn_tokens)[CMDLINE_PARSE_DYNAMIC_TOKENS])
|
|
{
|
|
unsigned int token_num=0;
|
|
cmdline_parse_token_hdr_t * token_p;
|
|
unsigned int i=0;
|
|
int n = 0;
|
|
struct cmdline_token_hdr token_hdr;
|
|
|
|
token_p = inst->tokens[token_num];
|
|
if (!token_p && dyn_tokens && inst->f) {
|
|
if (!(*dyn_tokens)[0])
|
|
inst->f(&(*dyn_tokens)[0], NULL, dyn_tokens);
|
|
token_p = (*dyn_tokens)[0];
|
|
}
|
|
if (token_p)
|
|
memcpy(&token_hdr, token_p, sizeof(token_hdr));
|
|
|
|
/* check if we match all tokens of inst */
|
|
while (token_p && (!nb_match_token || i<nb_match_token)) {
|
|
debug_printf("TK\n");
|
|
/* skip spaces */
|
|
while (isblank2(*buf)) {
|
|
buf++;
|
|
}
|
|
|
|
/* end of buf */
|
|
if ( isendofline(*buf) || iscomment(*buf) )
|
|
break;
|
|
|
|
if (resbuf == NULL) {
|
|
n = token_hdr.ops->parse(token_p, buf, NULL, 0);
|
|
} else {
|
|
unsigned rb_sz;
|
|
|
|
if (token_hdr.offset > resbuf_size) {
|
|
printf("Parse error(%s:%d): Token offset(%u) "
|
|
"exceeds maximum size(%u)\n",
|
|
__FILE__, __LINE__,
|
|
token_hdr.offset, resbuf_size);
|
|
return -ENOBUFS;
|
|
}
|
|
rb_sz = resbuf_size - token_hdr.offset;
|
|
|
|
n = token_hdr.ops->parse(token_p, buf, (char *)resbuf +
|
|
token_hdr.offset, rb_sz);
|
|
}
|
|
|
|
if (n < 0)
|
|
break;
|
|
|
|
debug_printf("TK parsed (len=%d)\n", n);
|
|
i++;
|
|
buf += n;
|
|
|
|
token_num ++;
|
|
if (!inst->tokens[0]) {
|
|
if (token_num < (CMDLINE_PARSE_DYNAMIC_TOKENS - 1)) {
|
|
if (!(*dyn_tokens)[token_num])
|
|
inst->f(&(*dyn_tokens)[token_num],
|
|
NULL,
|
|
dyn_tokens);
|
|
token_p = (*dyn_tokens)[token_num];
|
|
} else
|
|
token_p = NULL;
|
|
} else
|
|
token_p = inst->tokens[token_num];
|
|
if (token_p)
|
|
memcpy(&token_hdr, token_p, sizeof(token_hdr));
|
|
}
|
|
|
|
/* does not match */
|
|
if (i==0)
|
|
return -1;
|
|
|
|
/* in case we want to match a specific num of token */
|
|
if (nb_match_token) {
|
|
if (i == nb_match_token) {
|
|
return 0;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* we don't match all the tokens */
|
|
if (token_p) {
|
|
return i;
|
|
}
|
|
|
|
/* are there are some tokens more */
|
|
while (isblank2(*buf)) {
|
|
buf++;
|
|
}
|
|
|
|
/* end of buf */
|
|
if ( isendofline(*buf) || iscomment(*buf) )
|
|
return 0;
|
|
|
|
/* garbage after inst */
|
|
return i;
|
|
}
|
|
|
|
|
|
int
|
|
cmdline_parse(struct cmdline *cl, const char * buf)
|
|
{
|
|
unsigned int inst_num=0;
|
|
cmdline_parse_inst_t *inst;
|
|
const char *curbuf;
|
|
union {
|
|
char buf[CMDLINE_PARSE_RESULT_BUFSIZE];
|
|
long double align; /* strong alignment constraint for buf */
|
|
} result, tmp_result;
|
|
cmdline_parse_token_hdr_t *dyn_tokens[CMDLINE_PARSE_DYNAMIC_TOKENS];
|
|
void (*f)(void *, struct cmdline *, void *) = NULL;
|
|
void *data = NULL;
|
|
int comment = 0;
|
|
int linelen = 0;
|
|
int parse_it = 0;
|
|
int err = CMDLINE_PARSE_NOMATCH;
|
|
int tok;
|
|
cmdline_parse_ctx_t *ctx;
|
|
#ifdef RTE_LIBRTE_CMDLINE_DEBUG
|
|
char debug_buf[BUFSIZ];
|
|
#endif
|
|
|
|
if (!cl || !buf)
|
|
return CMDLINE_PARSE_BAD_ARGS;
|
|
|
|
ctx = cl->ctx;
|
|
memset(&dyn_tokens, 0, sizeof(dyn_tokens));
|
|
|
|
/*
|
|
* - look if the buffer contains at least one line
|
|
* - look if line contains only spaces or comments
|
|
* - count line length
|
|
*/
|
|
curbuf = buf;
|
|
while (! isendofline(*curbuf)) {
|
|
if ( *curbuf == '\0' ) {
|
|
debug_printf("Incomplete buf (len=%d)\n", linelen);
|
|
return 0;
|
|
}
|
|
if ( iscomment(*curbuf) ) {
|
|
comment = 1;
|
|
}
|
|
if ( ! isblank2(*curbuf) && ! comment) {
|
|
parse_it = 1;
|
|
}
|
|
curbuf++;
|
|
linelen++;
|
|
}
|
|
|
|
/* skip all endofline chars */
|
|
while (isendofline(buf[linelen])) {
|
|
linelen++;
|
|
}
|
|
|
|
/* empty line */
|
|
if ( parse_it == 0 ) {
|
|
debug_printf("Empty line (len=%d)\n", linelen);
|
|
return linelen;
|
|
}
|
|
|
|
#ifdef RTE_LIBRTE_CMDLINE_DEBUG
|
|
snprintf(debug_buf, (linelen>64 ? 64 : linelen), "%s", buf);
|
|
debug_printf("Parse line : len=%d, <%s>\n", linelen, debug_buf);
|
|
#endif
|
|
|
|
/* parse it !! */
|
|
inst = ctx[inst_num];
|
|
while (inst) {
|
|
debug_printf("INST %d\n", inst_num);
|
|
|
|
/* fully parsed */
|
|
tok = match_inst(inst, buf, 0, tmp_result.buf,
|
|
sizeof(tmp_result.buf), &dyn_tokens);
|
|
|
|
if (tok > 0) /* we matched at least one token */
|
|
err = CMDLINE_PARSE_BAD_ARGS;
|
|
|
|
else if (!tok) {
|
|
debug_printf("INST fully parsed\n");
|
|
memcpy(&result, &tmp_result,
|
|
sizeof(result));
|
|
/* skip spaces */
|
|
while (isblank2(*curbuf)) {
|
|
curbuf++;
|
|
}
|
|
|
|
/* if end of buf -> there is no garbage after inst */
|
|
if (isendofline(*curbuf) || iscomment(*curbuf)) {
|
|
if (!f) {
|
|
memcpy(&f, &inst->f, sizeof(f));
|
|
memcpy(&data, &inst->data, sizeof(data));
|
|
}
|
|
else {
|
|
/* more than 1 inst matches */
|
|
err = CMDLINE_PARSE_AMBIGUOUS;
|
|
f=NULL;
|
|
debug_printf("Ambiguous cmd\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inst_num ++;
|
|
inst = ctx[inst_num];
|
|
}
|
|
|
|
/* call func */
|
|
if (f) {
|
|
f(result.buf, cl, data);
|
|
}
|
|
|
|
/* no match */
|
|
else {
|
|
debug_printf("No match err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
return linelen;
|
|
}
|
|
|
|
int
|
|
cmdline_complete(struct cmdline *cl, const char *buf, int *state,
|
|
char *dst, unsigned int size)
|
|
{
|
|
const char *partial_tok = buf;
|
|
unsigned int inst_num = 0;
|
|
cmdline_parse_inst_t *inst;
|
|
cmdline_parse_token_hdr_t *token_p;
|
|
struct cmdline_token_hdr token_hdr;
|
|
char tmpbuf[CMDLINE_BUFFER_SIZE], comp_buf[CMDLINE_BUFFER_SIZE];
|
|
cmdline_parse_token_hdr_t *dyn_tokens[CMDLINE_PARSE_DYNAMIC_TOKENS];
|
|
unsigned int partial_tok_len;
|
|
int comp_len = -1;
|
|
int tmp_len = -1;
|
|
int nb_token = 0;
|
|
unsigned int i, n;
|
|
int l;
|
|
unsigned int nb_completable;
|
|
unsigned int nb_non_completable;
|
|
int local_state = 0;
|
|
const char *help_str;
|
|
cmdline_parse_ctx_t *ctx;
|
|
|
|
if (!cl || !buf || !state || !dst)
|
|
return -1;
|
|
|
|
ctx = cl->ctx;
|
|
|
|
debug_printf("%s called\n", __func__);
|
|
memset(&token_hdr, 0, sizeof(token_hdr));
|
|
memset(&dyn_tokens, 0, sizeof(dyn_tokens));
|
|
|
|
/* count the number of complete token to parse */
|
|
for (i=0 ; buf[i] ; i++) {
|
|
if (!isblank2(buf[i]) && isblank2(buf[i+1]))
|
|
nb_token++;
|
|
if (isblank2(buf[i]) && !isblank2(buf[i+1]))
|
|
partial_tok = buf+i+1;
|
|
}
|
|
partial_tok_len = strnlen(partial_tok, RDLINE_BUF_SIZE);
|
|
|
|
/* first call -> do a first pass */
|
|
if (*state <= 0) {
|
|
debug_printf("try complete <%s>\n", buf);
|
|
debug_printf("there is %d complete tokens, <%s> is incomplete\n",
|
|
nb_token, partial_tok);
|
|
|
|
nb_completable = 0;
|
|
nb_non_completable = 0;
|
|
|
|
inst = ctx[inst_num];
|
|
while (inst) {
|
|
/* parse the first tokens of the inst */
|
|
if (nb_token &&
|
|
match_inst(inst, buf, nb_token, NULL, 0,
|
|
&dyn_tokens))
|
|
goto next;
|
|
|
|
debug_printf("instruction match\n");
|
|
if (!inst->tokens[0]) {
|
|
if (nb_token <
|
|
(CMDLINE_PARSE_DYNAMIC_TOKENS - 1)) {
|
|
if (!dyn_tokens[nb_token])
|
|
inst->f(&dyn_tokens[nb_token],
|
|
NULL,
|
|
&dyn_tokens);
|
|
token_p = dyn_tokens[nb_token];
|
|
} else
|
|
token_p = NULL;
|
|
} else
|
|
token_p = inst->tokens[nb_token];
|
|
if (token_p)
|
|
memcpy(&token_hdr, token_p, sizeof(token_hdr));
|
|
|
|
/* non completable */
|
|
if (!token_p ||
|
|
!token_hdr.ops->complete_get_nb ||
|
|
!token_hdr.ops->complete_get_elt ||
|
|
(n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
|
|
nb_non_completable++;
|
|
goto next;
|
|
}
|
|
|
|
debug_printf("%d choices for this token\n", n);
|
|
for (i=0 ; i<n ; i++) {
|
|
if (token_hdr.ops->complete_get_elt(token_p, i,
|
|
tmpbuf,
|
|
sizeof(tmpbuf)) < 0)
|
|
continue;
|
|
|
|
/* we have at least room for one char */
|
|
tmp_len = strnlen(tmpbuf, sizeof(tmpbuf));
|
|
if (tmp_len < CMDLINE_BUFFER_SIZE - 1) {
|
|
tmpbuf[tmp_len] = ' ';
|
|
tmpbuf[tmp_len+1] = 0;
|
|
}
|
|
|
|
debug_printf(" choice <%s>\n", tmpbuf);
|
|
|
|
/* does the completion match the
|
|
* beginning of the word ? */
|
|
if (!strncmp(partial_tok, tmpbuf,
|
|
partial_tok_len)) {
|
|
if (comp_len == -1) {
|
|
snprintf(comp_buf, sizeof(comp_buf),
|
|
"%s", tmpbuf + partial_tok_len);
|
|
comp_len =
|
|
strnlen(tmpbuf + partial_tok_len,
|
|
sizeof(tmpbuf) - partial_tok_len);
|
|
|
|
}
|
|
else {
|
|
comp_len =
|
|
nb_common_chars(comp_buf,
|
|
tmpbuf+partial_tok_len);
|
|
comp_buf[comp_len] = 0;
|
|
}
|
|
nb_completable++;
|
|
}
|
|
}
|
|
next:
|
|
debug_printf("next\n");
|
|
inst_num ++;
|
|
inst = ctx[inst_num];
|
|
}
|
|
|
|
debug_printf("total choices %d for this completion\n",
|
|
nb_completable);
|
|
|
|
/* no possible completion */
|
|
if (nb_completable == 0 && nb_non_completable == 0)
|
|
return 0;
|
|
|
|
/* if multichoice is not required */
|
|
if (*state == 0 && partial_tok_len > 0) {
|
|
/* one or several choices starting with the
|
|
same chars */
|
|
if (comp_len > 0) {
|
|
if ((unsigned)(comp_len + 1) > size)
|
|
return 0;
|
|
|
|
snprintf(dst, size, "%s", comp_buf);
|
|
dst[comp_len] = 0;
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* init state correctly */
|
|
if (*state == -1)
|
|
*state = 0;
|
|
|
|
debug_printf("Multiple choice STATE=%d\n", *state);
|
|
|
|
inst_num = 0;
|
|
inst = ctx[inst_num];
|
|
while (inst) {
|
|
/* we need to redo it */
|
|
inst = ctx[inst_num];
|
|
|
|
if (nb_token &&
|
|
match_inst(inst, buf, nb_token, NULL, 0, &dyn_tokens))
|
|
goto next2;
|
|
|
|
if (!inst->tokens[0]) {
|
|
if (nb_token < (CMDLINE_PARSE_DYNAMIC_TOKENS - 1)) {
|
|
if (!dyn_tokens[nb_token])
|
|
inst->f(&dyn_tokens[nb_token],
|
|
NULL,
|
|
&dyn_tokens);
|
|
token_p = dyn_tokens[nb_token];
|
|
} else
|
|
token_p = NULL;
|
|
} else
|
|
token_p = inst->tokens[nb_token];
|
|
if (token_p)
|
|
memcpy(&token_hdr, token_p, sizeof(token_hdr));
|
|
|
|
/* one choice for this token */
|
|
if (!token_p ||
|
|
!token_hdr.ops->complete_get_nb ||
|
|
!token_hdr.ops->complete_get_elt ||
|
|
(n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
|
|
if (local_state < *state) {
|
|
local_state++;
|
|
goto next2;
|
|
}
|
|
(*state)++;
|
|
if (token_p && token_hdr.ops->get_help) {
|
|
token_hdr.ops->get_help(token_p, tmpbuf,
|
|
sizeof(tmpbuf));
|
|
help_str = inst->help_str;
|
|
if (help_str)
|
|
snprintf(dst, size, "[%s]: %s", tmpbuf,
|
|
help_str);
|
|
else
|
|
snprintf(dst, size, "[%s]: No help",
|
|
tmpbuf);
|
|
}
|
|
else {
|
|
snprintf(dst, size, "[RETURN]");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* several choices */
|
|
for (i=0 ; i<n ; i++) {
|
|
if (token_hdr.ops->complete_get_elt(token_p, i, tmpbuf,
|
|
sizeof(tmpbuf)) < 0)
|
|
continue;
|
|
/* we have at least room for one char */
|
|
tmp_len = strnlen(tmpbuf, sizeof(tmpbuf));
|
|
if (tmp_len < CMDLINE_BUFFER_SIZE - 1) {
|
|
tmpbuf[tmp_len] = ' ';
|
|
tmpbuf[tmp_len + 1] = 0;
|
|
}
|
|
|
|
debug_printf(" choice <%s>\n", tmpbuf);
|
|
|
|
/* does the completion match the beginning of
|
|
* the word ? */
|
|
if (!strncmp(partial_tok, tmpbuf,
|
|
partial_tok_len)) {
|
|
if (local_state < *state) {
|
|
local_state++;
|
|
continue;
|
|
}
|
|
(*state)++;
|
|
l=snprintf(dst, size, "%s", tmpbuf);
|
|
if (l>=0 && token_hdr.ops->get_help) {
|
|
token_hdr.ops->get_help(token_p, tmpbuf,
|
|
sizeof(tmpbuf));
|
|
help_str = inst->help_str;
|
|
if (help_str)
|
|
snprintf(dst+l, size-l, "[%s]: %s",
|
|
tmpbuf, help_str);
|
|
else
|
|
snprintf(dst+l, size-l,
|
|
"[%s]: No help", tmpbuf);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
next2:
|
|
inst_num ++;
|
|
inst = ctx[inst_num];
|
|
}
|
|
return 0;
|
|
}
|