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