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