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