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