1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright(c) 2010-2014 Intel Corporation.
3 * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
4 * All rights reserved.
5 */
6
7 #include <stdio.h>
8 #include <errno.h>
9 #include <string.h>
10 #include <stdbool.h>
11
12 #include <rte_string_fns.h>
13
14 #include "cmdline_private.h"
15
16 #ifdef RTE_LIBRTE_CMDLINE_DEBUG
17 #define debug_printf printf
18 #else
19 #define debug_printf(...) do {} while (0)
20 #endif
21
22 #define CMDLINE_BUFFER_SIZE 64
23
24 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
25 * own. */
26 static int
isblank2(char c)27 isblank2(char c)
28 {
29 if (c == ' ' ||
30 c == '\t' )
31 return 1;
32 return 0;
33 }
34
35 static int
isendofline(char c)36 isendofline(char c)
37 {
38 if (c == '\n' ||
39 c == '\r' )
40 return 1;
41 return 0;
42 }
43
44 static int
iscomment(char c)45 iscomment(char c)
46 {
47 if (c == '#')
48 return 1;
49 return 0;
50 }
51
52 int
cmdline_isendoftoken(char c)53 cmdline_isendoftoken(char c)
54 {
55 if (!c || iscomment(c) || isblank2(c) || isendofline(c))
56 return 1;
57 return 0;
58 }
59
60 int
cmdline_isendofcommand(char c)61 cmdline_isendofcommand(char c)
62 {
63 if (!c || iscomment(c) || isendofline(c))
64 return 1;
65 return 0;
66 }
67
68 static unsigned int
nb_common_chars(const char * s1,const char * s2)69 nb_common_chars(const char * s1, const char * s2)
70 {
71 unsigned int i=0;
72
73 while (*s1==*s2 && *s1) {
74 s1++;
75 s2++;
76 i++;
77 }
78 return i;
79 }
80
81 /** Retrieve either static or dynamic token at a given index. */
82 static cmdline_parse_token_hdr_t *
get_token(cmdline_parse_inst_t * inst,unsigned int index)83 get_token(cmdline_parse_inst_t *inst, unsigned int index)
84 {
85 cmdline_parse_token_hdr_t *token_p;
86
87 /* check presence of static tokens first */
88 if (inst->tokens[0] || !inst->f)
89 return inst->tokens[index];
90 /* generate dynamic token */
91 token_p = NULL;
92 inst->f(&token_p, NULL, &inst->tokens[index]);
93 return token_p;
94 }
95
96 /**
97 * try to match the buffer with an instruction (only the first
98 * nb_match_token tokens if != 0). Return 0 if we match all the
99 * tokens, else the number of matched tokens, else -1.
100 */
101 static int
match_inst(cmdline_parse_inst_t * inst,const char * buf,unsigned int nb_match_token,void * resbuf,unsigned resbuf_size)102 match_inst(cmdline_parse_inst_t *inst, const char *buf,
103 unsigned int nb_match_token, void *resbuf, unsigned resbuf_size)
104 {
105 cmdline_parse_token_hdr_t *token_p = NULL;
106 unsigned int i=0;
107 int n = 0;
108 struct cmdline_token_hdr token_hdr;
109
110 if (resbuf != NULL)
111 memset(resbuf, 0, resbuf_size);
112 /* check if we match all tokens of inst */
113 while (!nb_match_token || i < nb_match_token) {
114 token_p = get_token(inst, i);
115 if (!token_p)
116 break;
117 memcpy(&token_hdr, token_p, sizeof(token_hdr));
118
119 debug_printf("TK\n");
120 /* skip spaces */
121 while (isblank2(*buf)) {
122 buf++;
123 }
124
125 /* end of buf */
126 if ( isendofline(*buf) || iscomment(*buf) )
127 break;
128
129 if (resbuf == NULL) {
130 n = token_hdr.ops->parse(token_p, buf, NULL, 0);
131 } else {
132 unsigned rb_sz;
133
134 if (token_hdr.offset > resbuf_size) {
135 printf("Parse error(%s:%d): Token offset(%u) "
136 "exceeds maximum size(%u)\n",
137 __FILE__, __LINE__,
138 token_hdr.offset, resbuf_size);
139 return -ENOBUFS;
140 }
141 rb_sz = resbuf_size - token_hdr.offset;
142
143 n = token_hdr.ops->parse(token_p, buf, (char *)resbuf +
144 token_hdr.offset, rb_sz);
145 }
146
147 if (n < 0)
148 break;
149
150 debug_printf("TK parsed (len=%d)\n", n);
151 i++;
152 buf += n;
153 }
154
155 /* does not match */
156 if (i==0)
157 return -1;
158
159 /* in case we want to match a specific num of token */
160 if (nb_match_token) {
161 if (i == nb_match_token) {
162 return 0;
163 }
164 return i;
165 }
166
167 /* we don't match all the tokens */
168 if (token_p) {
169 return i;
170 }
171
172 /* are there are some tokens more */
173 while (isblank2(*buf)) {
174 buf++;
175 }
176
177 /* end of buf */
178 if ( isendofline(*buf) || iscomment(*buf) )
179 return 0;
180
181 /* garbage after inst */
182 return i;
183 }
184
185
186 static inline int
__cmdline_parse(struct cmdline * cl,const char * buf,bool call_fn)187 __cmdline_parse(struct cmdline *cl, const char *buf, bool call_fn)
188 {
189 unsigned int inst_num=0;
190 cmdline_parse_inst_t *inst;
191 const char *curbuf;
192 union {
193 char buf[CMDLINE_PARSE_RESULT_BUFSIZE];
194 long double align; /* strong alignment constraint for buf */
195 } result, tmp_result;
196 void (*f)(void *, struct cmdline *, void *) = NULL;
197 void *data = NULL;
198 int comment = 0;
199 int linelen = 0;
200 int parse_it = 0;
201 int err = CMDLINE_PARSE_NOMATCH;
202 int tok;
203 cmdline_parse_ctx_t *ctx;
204 char *result_buf = result.buf;
205
206 if (!cl || !buf)
207 return CMDLINE_PARSE_BAD_ARGS;
208
209 ctx = cl->ctx;
210
211 /*
212 * - look if the buffer contains at least one line
213 * - look if line contains only spaces or comments
214 * - count line length
215 */
216 curbuf = buf;
217 while (! isendofline(*curbuf)) {
218 if ( *curbuf == '\0' ) {
219 debug_printf("Incomplete buf (len=%d)\n", linelen);
220 return 0;
221 }
222 if ( iscomment(*curbuf) ) {
223 comment = 1;
224 }
225 if ( ! isblank2(*curbuf) && ! comment) {
226 parse_it = 1;
227 }
228 curbuf++;
229 linelen++;
230 }
231
232 /* skip all endofline chars */
233 while (isendofline(buf[linelen])) {
234 linelen++;
235 }
236
237 /* empty line */
238 if ( parse_it == 0 ) {
239 debug_printf("Empty line (len=%d)\n", linelen);
240 return linelen;
241 }
242
243 debug_printf("Parse line : len=%d, <%.*s>\n",
244 linelen, linelen > 64 ? 64 : linelen, buf);
245
246 /* parse it !! */
247 inst = ctx[inst_num];
248 while (inst) {
249 debug_printf("INST %d\n", inst_num);
250
251 /* fully parsed */
252 tok = match_inst(inst, buf, 0, result_buf,
253 CMDLINE_PARSE_RESULT_BUFSIZE);
254
255 if (tok > 0) /* we matched at least one token */
256 err = CMDLINE_PARSE_BAD_ARGS;
257
258 else if (!tok) {
259 debug_printf("INST fully parsed\n");
260 /* skip spaces */
261 while (isblank2(*curbuf)) {
262 curbuf++;
263 }
264
265 /* if end of buf -> there is no garbage after inst */
266 if (isendofline(*curbuf) || iscomment(*curbuf)) {
267 if (!f) {
268 memcpy(&f, &inst->f, sizeof(f));
269 memcpy(&data, &inst->data, sizeof(data));
270 result_buf = tmp_result.buf;
271 }
272 else {
273 /* more than 1 inst matches */
274 err = CMDLINE_PARSE_AMBIGUOUS;
275 f=NULL;
276 debug_printf("Ambiguous cmd\n");
277 break;
278 }
279 }
280 }
281
282 inst_num ++;
283 inst = ctx[inst_num];
284 }
285
286 /* no match */
287 if (f == NULL) {
288 debug_printf("No match err=%d\n", err);
289 return err;
290 }
291
292 /* call func if requested */
293 if (call_fn)
294 f(result.buf, cl, data);
295
296 return linelen;
297 }
298
299 int
cmdline_parse(struct cmdline * cl,const char * buf)300 cmdline_parse(struct cmdline *cl, const char *buf)
301 {
302 return __cmdline_parse(cl, buf, true);
303 }
304
305 int
cmdline_parse_check(struct cmdline * cl,const char * buf)306 cmdline_parse_check(struct cmdline *cl, const char *buf)
307 {
308 return __cmdline_parse(cl, buf, false);
309 }
310
311 int
cmdline_complete(struct cmdline * cl,const char * buf,int * state,char * dst,unsigned int size)312 cmdline_complete(struct cmdline *cl, const char *buf, int *state,
313 char *dst, unsigned int size)
314 {
315 const char *partial_tok = buf;
316 unsigned int inst_num = 0;
317 cmdline_parse_inst_t *inst;
318 cmdline_parse_token_hdr_t *token_p;
319 struct cmdline_token_hdr token_hdr;
320 char tmpbuf[CMDLINE_BUFFER_SIZE], comp_buf[CMDLINE_BUFFER_SIZE];
321 unsigned int partial_tok_len;
322 int comp_len = -1;
323 int tmp_len = -1;
324 int nb_token = 0;
325 unsigned int i, n;
326 int l;
327 unsigned int nb_completable;
328 unsigned int nb_non_completable;
329 int local_state = 0;
330 const char *help_str;
331 cmdline_parse_ctx_t *ctx;
332
333 if (!cl || !buf || !state || !dst)
334 return -1;
335
336 ctx = cl->ctx;
337
338 debug_printf("%s called\n", __func__);
339 memset(&token_hdr, 0, sizeof(token_hdr));
340
341 /* count the number of complete token to parse */
342 for (i=0 ; buf[i] ; i++) {
343 if (!isblank2(buf[i]) && isblank2(buf[i+1]))
344 nb_token++;
345 if (isblank2(buf[i]) && !isblank2(buf[i+1]))
346 partial_tok = buf+i+1;
347 }
348 partial_tok_len = strnlen(partial_tok, RDLINE_BUF_SIZE);
349
350 /* first call -> do a first pass */
351 if (*state <= 0) {
352 debug_printf("try complete <%s>\n", buf);
353 debug_printf("there is %d complete tokens, <%s> is incomplete\n",
354 nb_token, partial_tok);
355
356 nb_completable = 0;
357 nb_non_completable = 0;
358
359 inst = ctx[inst_num];
360 while (inst) {
361 /* parse the first tokens of the inst */
362 if (nb_token &&
363 match_inst(inst, buf, nb_token, NULL, 0))
364 goto next;
365
366 debug_printf("instruction match\n");
367 token_p = get_token(inst, nb_token);
368 if (token_p)
369 memcpy(&token_hdr, token_p, sizeof(token_hdr));
370
371 /* non completable */
372 if (!token_p ||
373 !token_hdr.ops->complete_get_nb ||
374 !token_hdr.ops->complete_get_elt ||
375 (n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
376 nb_non_completable++;
377 goto next;
378 }
379
380 debug_printf("%d choices for this token\n", n);
381 for (i=0 ; i<n ; i++) {
382 if (token_hdr.ops->complete_get_elt(token_p, i,
383 tmpbuf,
384 sizeof(tmpbuf)) < 0)
385 continue;
386
387 /* we have at least room for one char */
388 tmp_len = strnlen(tmpbuf, sizeof(tmpbuf));
389 if (tmp_len < CMDLINE_BUFFER_SIZE - 1) {
390 tmpbuf[tmp_len] = ' ';
391 tmpbuf[tmp_len+1] = 0;
392 }
393
394 debug_printf(" choice <%s>\n", tmpbuf);
395
396 /* does the completion match the
397 * beginning of the word ? */
398 if (!strncmp(partial_tok, tmpbuf,
399 partial_tok_len)) {
400 if (comp_len == -1) {
401 strlcpy(comp_buf,
402 tmpbuf + partial_tok_len,
403 sizeof(comp_buf));
404 comp_len =
405 strnlen(tmpbuf + partial_tok_len,
406 sizeof(tmpbuf) - partial_tok_len);
407
408 }
409 else {
410 comp_len =
411 nb_common_chars(comp_buf,
412 tmpbuf+partial_tok_len);
413 comp_buf[comp_len] = 0;
414 }
415 nb_completable++;
416 }
417 }
418 next:
419 debug_printf("next\n");
420 inst_num ++;
421 inst = ctx[inst_num];
422 }
423
424 debug_printf("total choices %d for this completion\n",
425 nb_completable);
426
427 /* no possible completion */
428 if (nb_completable == 0 && nb_non_completable == 0)
429 return 0;
430
431 /* if multichoice is not required */
432 if (*state == 0 && partial_tok_len > 0) {
433 /* one or several choices starting with the
434 same chars */
435 if (comp_len > 0) {
436 if ((unsigned)(comp_len + 1) > size)
437 return 0;
438
439 strlcpy(dst, comp_buf, size);
440 dst[comp_len] = 0;
441 return 2;
442 }
443 }
444 }
445
446 /* init state correctly */
447 if (*state == -1)
448 *state = 0;
449
450 debug_printf("Multiple choice STATE=%d\n", *state);
451
452 inst_num = 0;
453 inst = ctx[inst_num];
454 while (inst) {
455 /* we need to redo it */
456 inst = ctx[inst_num];
457
458 if (nb_token &&
459 match_inst(inst, buf, nb_token, NULL, 0))
460 goto next2;
461
462 token_p = get_token(inst, nb_token);
463 if (token_p)
464 memcpy(&token_hdr, token_p, sizeof(token_hdr));
465
466 /* one choice for this token */
467 if (!token_p ||
468 !token_hdr.ops->complete_get_nb ||
469 !token_hdr.ops->complete_get_elt ||
470 (n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
471 if (local_state < *state) {
472 local_state++;
473 goto next2;
474 }
475 (*state)++;
476 if (token_p && token_hdr.ops->get_help) {
477 token_hdr.ops->get_help(token_p, tmpbuf,
478 sizeof(tmpbuf));
479 help_str = inst->help_str;
480 if (help_str)
481 snprintf(dst, size, "[%s]: %s", tmpbuf,
482 help_str);
483 else
484 snprintf(dst, size, "[%s]: No help",
485 tmpbuf);
486 }
487 else {
488 snprintf(dst, size, "[RETURN]");
489 }
490 return 1;
491 }
492
493 /* several choices */
494 for (i=0 ; i<n ; i++) {
495 if (token_hdr.ops->complete_get_elt(token_p, i, tmpbuf,
496 sizeof(tmpbuf)) < 0)
497 continue;
498 /* we have at least room for one char */
499 tmp_len = strnlen(tmpbuf, sizeof(tmpbuf));
500 if (tmp_len < CMDLINE_BUFFER_SIZE - 1) {
501 tmpbuf[tmp_len] = ' ';
502 tmpbuf[tmp_len + 1] = 0;
503 }
504
505 debug_printf(" choice <%s>\n", tmpbuf);
506
507 /* does the completion match the beginning of
508 * the word ? */
509 if (!strncmp(partial_tok, tmpbuf,
510 partial_tok_len)) {
511 if (local_state < *state) {
512 local_state++;
513 continue;
514 }
515 (*state)++;
516 l=strlcpy(dst, tmpbuf, size);
517 if (l>=0 && token_hdr.ops->get_help) {
518 token_hdr.ops->get_help(token_p, tmpbuf,
519 sizeof(tmpbuf));
520 help_str = inst->help_str;
521 if (help_str)
522 snprintf(dst+l, size-l, "[%s]: %s",
523 tmpbuf, help_str);
524 else
525 snprintf(dst+l, size-l,
526 "[%s]: No help", tmpbuf);
527 }
528
529 return 1;
530 }
531 }
532 next2:
533 inst_num ++;
534 inst = ctx[inst_num];
535 }
536 return 0;
537 }
538