xref: /dpdk/lib/cmdline/cmdline_parse.c (revision 0f1dc8cb671203d52488fd66936f2fe6dcca03cc)
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