c0568338b5288447fdc69b000687c087aea16a96
[libcmdline.git] / src / lib / cmdline_parse.c
1 /*-
2  * Copyright (c) <2010>, Intel Corporation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * - Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in
14  *   the documentation and/or other materials provided with the
15  *   distribution.
16  *
17  * - Neither the name of Intel Corporation nor the names of its
18  *   contributors may be used to endorse or promote products derived
19  *   from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
32  * OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34
35 /*
36  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
37  * All rights reserved.
38  * Redistribution and use in source and binary forms, with or without
39  * modification, are permitted provided that the following conditions are met:
40  *
41  *     * Redistributions of source code must retain the above copyright
42  *       notice, this list of conditions and the following disclaimer.
43  *     * Redistributions in binary form must reproduce the above copyright
44  *       notice, this list of conditions and the following disclaimer in the
45  *       documentation and/or other materials provided with the distribution.
46  *     * Neither the name of the University of California, Berkeley nor the
47  *       names of its contributors may be used to endorse or promote products
48  *       derived from this software without specific prior written permission.
49  *
50  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
51  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
52  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
54  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
55  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
56  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
57  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
58  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
59  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
60  */
61
62 #include <stdio.h>
63 #include <string.h>
64 #include <inttypes.h>
65 #include <ctype.h>
66 #include <termios.h>
67 #include <errno.h>
68 #include <stdarg.h>
69
70 #include <netinet/in.h>
71
72 #include "cmdline_parse.h"
73 #include "cmdline.h"
74
75 //#define debug_printf printf
76 #define debug_printf(args...) do {} while(0)
77
78 /* used internally for cmdline_help() and cmdline_complete() */
79 struct cmdline_preparse {
80         int nb_valid_tok; /* number of valid tokens in the buffer */
81         void *opaque;     /* pointer to opaque data */
82         char comp_tok_buf[CMDLINE_MAX_TOKEN_SIZE]; /* token to complete */
83         int comp_tok_len; /* length of the token to complete */
84         int comp_tok_offset; /* offset of token to complete in the line buf */
85 };
86
87 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
88  * own. */
89 static int
90 isblank2(char c)
91 {
92         if (c == ' ' ||
93             c == '\t' )
94                 return 1;
95         return 0;
96 }
97
98 static int
99 isendofline(char c)
100 {
101         if (c == '\n' ||
102             c == '\r' )
103                 return 1;
104         return 0;
105 }
106
107 static int
108 iscomment(char c)
109 {
110         if (c == '#')
111                 return 1;
112         return 0;
113 }
114
115 int
116 cmdline_isendoftoken(char c)
117 {
118         if (!c || iscomment(c) || isblank2(c) || isendofline(c))
119                 return 1;
120         return 0;
121 }
122
123 static unsigned int
124 nb_common_chars(const char * s1, const char * s2)
125 {
126         unsigned int i=0;
127
128         while (*s1==*s2 && *s1 && *s2) {
129                 s1++;
130                 s2++;
131                 i++;
132         }
133         return i;
134 }
135
136 /* quote a string and escape original quotes */
137 int cmdline_quote_token(char *dst, unsigned dstlen, const char *src)
138 {
139         unsigned s = 0, d = 0;
140
141         /* the 2 quotes + '\0' */
142         if (dstlen < 3)
143                 return -EMSGSIZE;
144
145         dst[d++] = '"';
146         while (src[s] != '\0') {
147                 if (d >= (dstlen-2))
148                         return -EMSGSIZE;
149
150                 if (src[s] == '"')
151                         dst[d++] = '\\';
152                 if (src[s] == '\\' && src[s+1] == '"')
153                         dst[d++] = '\\';
154
155                 dst[d++] = src[s++];
156         }
157
158         if (d >= (dstlen-2))
159                 return -EMSGSIZE;
160         dst[d++] = '"';
161         dst[d++] = '\0';
162         return s;
163 }
164
165 /* Remove quote and stop when we reach the end of token. Return the
166  * number of "eaten" bytes from the source buffer, or a negative value
167  * on error */
168 int cmdline_get_token(char *dst, unsigned dstlen, const char *src)
169 {
170         unsigned s = 0, d = 0;
171         int quoted = 0;
172
173         /* skip spaces */
174         while (isblank2(src[s]))
175                 s++;
176
177         /* empty token */
178         if (cmdline_isendoftoken(src[s]))
179                 return -EINVAL;
180
181         /* copy token and remove quotes */
182         while (src[s] != '\0') {
183                 if (d >= dstlen)
184                         return -EMSGSIZE;
185
186                 if (cmdline_isendoftoken(src[s]) && quoted == 0)
187                         break;
188
189                 if (src[s] == '\\' && src[s+1] == '"') {
190                         dst[d++] = '"';
191                         s += 2;
192                         continue;
193                 }
194                 if (src[s] == '\\' && src[s+1] == '\\') {
195                         dst[d++] = '\\';
196                         s += 2;
197                         continue;
198                 }
199                 if (src[s] == '"') {
200                         s++;
201                         quoted = !quoted;
202                         continue;
203                 }
204                 dst[d++] = src[s++];
205         }
206
207         /* not enough room in dst buffer */
208         if (d >= (dstlen-1))
209                 return -EMSGSIZE;
210
211         /* end of string during quote */
212         if (quoted)
213                 return -EINVAL;
214
215         dst[d++] = '\0';
216         return  s;
217 }
218
219 /* return the nth token from src and copy it in dst. Return the offset
220  * of the token in src, or a negative value on error. Note: the index
221  * of the first token is 0. */
222 static int cmdline_get_nth_token(char *dst, unsigned dstlen, int n,
223                                  const char *src)
224 {
225         int ret = 0, offset = 0;
226
227         do {
228                 offset += ret;
229
230                 /* skip spaces */
231                 while (isblank2(src[offset]))
232                         offset++;
233
234                 /* get the token starting at offset */
235                 ret = cmdline_get_token(dst, dstlen, src + offset);
236                 if (ret < 0)
237                         return ret;
238
239         } while (n--);
240
241         return offset;
242 }
243
244 /*
245  * try to match the buffer with an instruction (only the first
246  * nb_match_token tokens if != 0). Return 0 if we match all the
247  * tokens, else the number of matched tokens, else -1.
248
249 retval à vérifier.
250
251  */
252 static int
253 match_inst(cmdline_parse_inst_t *inst, const char *linebuf,
254            unsigned int nb_match_token, void *resbuf, unsigned resbuf_size)
255 {
256         unsigned int token_num = 0;
257         cmdline_parse_token_hdr_t *token;
258         int n = 0, res;
259         char token_str[CMDLINE_MAX_TOKEN_SIZE];
260
261         token = inst->tokens[token_num];
262
263         /* check if we match all tokens of inst */
264         while (token) {
265
266                 /* we matched enough tokens, return success */
267                 if (nb_match_token != 0 && token_num >= nb_match_token)
268                         return 0;
269
270                 debug_printf("TK\n");
271
272                 /* copy token and remove quotes */
273                 n = cmdline_get_token(token_str, sizeof(token_str), linebuf);
274                 if (n < 0)
275                         break;
276
277                 /* parse this token */
278                 if (resbuf == NULL)
279                         res = token->ops->parse(token, token_str, NULL, 0);
280                 else {
281                         unsigned rb_sz;
282                         void *rb = (char *)resbuf + token->offset;
283
284                         /* not enough room to store result */
285                         if (token->offset > resbuf_size)
286                                 return -ENOBUFS;
287
288                         rb_sz = resbuf_size - token->offset;
289                         res = token->ops->parse(token, token_str, rb, rb_sz);
290                 }
291
292                 /* does not match this token */
293                 if (res < 0)
294                         break;
295
296                 debug_printf("TK parsed (len=%d)\n", n);
297                 linebuf += n;
298                 token_num ++;
299                 token = inst->tokens[token_num];
300         }
301
302         /* does not match */
303         if (token_num == 0)
304                 return -ENOENT;
305
306         /* we don't match all the tokens */
307         if (token)
308                 return token_num;
309
310         /* are there are some tokens more */
311         while (isblank2(*linebuf))
312                 linebuf++;
313
314         /* end of buf, we match all inst  */
315         if (isendofline(*linebuf) || iscomment(*linebuf))
316                 return 0;
317
318         /* garbage after inst */
319         return token_num;
320 }
321
322
323 /* Check if a line buffer is valid and can be parsed or completed. The
324  * parsing stops when \n or \0 is reached. The also function checks
325  * that tokens are correctly quoted. The number of tokens in the
326  * buffer is returned. */
327 static int cmdline_validate_linebuf(const char *linebuf)
328 {
329         int quoted = 0, comment = 0;
330         int i = 0, nbtok = 0, token = 0;
331
332         while (linebuf[i] != '\0') {
333                 if (isendofline(linebuf[i]) && quoted == 0)
334                         break;
335                 if (comment == 1) {
336                         i++;
337                         continue;
338                 }
339                 if (iscomment(linebuf[i]) && quoted == 0) {
340                         comment = 1;
341                         i ++;
342                         continue;
343                 }
344
345                 /* end of token */
346                 if (isblank2(linebuf[i]) && quoted == 0)
347                         token = 0;
348                 /* new token */
349                 if (!isblank2(linebuf[i]) && token == 0) {
350                         token = 1;
351                         nbtok++;
352                 }
353
354                 if (linebuf[i] == '\\' && linebuf[i+1] == '"') {
355                         i += 2;
356                         continue;
357                 }
358                 if (linebuf[i] == '\\' && linebuf[i+1] == '\\') {
359                         i += 2;
360                         continue;
361                 }
362                 if (linebuf[i] == '"') {
363                         i++;
364                         quoted = !quoted;
365                         continue;
366                 }
367                 i++;
368         }
369         if (quoted)
370                 return CMDLINE_PARSE_UNTERMINATED_QUOTE;
371         return nbtok;
372 }
373
374 /* Try to parse a buffer according to the specified context. The
375  * argument linebuf must end with \n or \0. */
376 int
377 cmdline_parse(cmdline_parse_ctx_t *ctx, const char *linebuf, void *opaque)
378 {
379         cmdline_parse_inst_t **pinst;
380         cmdline_parse_inst_t *inst;
381         char result_buf[CMDLINE_MAX_DSTBUF_SIZE];
382         void (*f)(void *, struct cmdline *, void *) = NULL;
383         void *data = NULL;
384         int ret;
385
386         ret = cmdline_validate_linebuf(linebuf);
387         if (ret < 0)
388                 return ret;
389         if (ret == 0)
390                 return CMDLINE_PARSE_EMPTY;
391
392
393         /* parse it !! */
394         for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) {
395                 inst = *pinst;
396                 debug_printf("INST\n");
397
398                 /* fully parsed */
399                 ret = match_inst(inst, linebuf, 0, result_buf, sizeof(result_buf));
400
401                 if (ret != 0)
402                         continue;
403
404                 debug_printf("INST fully parsed\n");
405
406                 /* if end of buf -> there is no garbage after inst */
407                 if (f != NULL) {
408                         /* more than 1 inst matches */
409                         debug_printf("Ambiguous cmd\n");
410                         return CMDLINE_PARSE_AMBIGUOUS;
411                 }
412                 f = inst->f;
413                 data = inst->data;
414         }
415
416         /* call func */
417         if (f == NULL)
418                 return CMDLINE_PARSE_NOMATCH;
419
420         f(result_buf, opaque, data);
421         return CMDLINE_PARSE_SUCCESS;
422 }
423
424 /* called by cmdline_help() and cmdline_complete() to preparse the
425  * line buffer (the operations done are common to these functions) */
426 static int cmdline_preparse(struct cmdline_preparse *preparse, const char *buf)
427 {
428         int ret, len, nb_tok;
429
430         /* count the number of tokens in the line buffer */
431         ret = cmdline_validate_linebuf(buf);
432         if (ret < 0)
433                 return ret;
434         nb_tok = ret;
435
436         /* if last token is not complete, decrement nb_valid_tok */
437         len = strlen(buf);
438         if (nb_tok == 0 || isblank2(buf[len - 1])) {
439                 preparse->nb_valid_tok = nb_tok;
440                 preparse->comp_tok_offset = len;
441                 preparse->comp_tok_buf[0] = '\0';
442                 preparse->comp_tok_len = 0;
443         }
444         else {
445                 preparse->nb_valid_tok = nb_tok - 1;
446
447                 /* get the incomplete token (can be empty) and return its
448                  * offset in the buffer */
449                 preparse->comp_tok_offset =
450                         cmdline_get_nth_token(preparse->comp_tok_buf,
451                                               sizeof(preparse->comp_tok_buf),
452                                               preparse->nb_valid_tok,
453                                               buf);
454                 preparse->comp_tok_len = strlen(preparse->comp_tok_buf);
455         }
456
457         return 0;
458 }
459
460 /* Display a contextual help using the write_buf() function pointer
461  * given as parameter (called with its opaque pointer). The contextual
462  * help depends on the buffer given. */
463 int cmdline_help(cmdline_parse_ctx_t *ctx, const char *buf,
464                  cmdline_write_t *write_buf, void *opaque)
465 {
466         cmdline_parse_token_hdr_t *token;
467         cmdline_parse_inst_t **pinst;
468         cmdline_parse_inst_t *inst;
469         struct cmdline_preparse preparse;
470         char helpbuf[CMDLINE_MAX_DSTBUF_SIZE];
471         char tmpbuf[CMDLINE_MAX_DSTBUF_SIZE];
472         char *help_str;
473         int n, iterate;
474
475         cmdline_preparse(&preparse, buf);
476
477         debug_printf("display contextual help\n");
478
479         for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) {
480                 inst = *pinst;
481
482                 /* match the beginning of the command */
483                 if (preparse.nb_valid_tok != 0 &&
484                     match_inst(inst, buf, preparse.nb_valid_tok,
485                                NULL, 0) != 0)
486                         continue;
487
488                 token = inst->tokens[preparse.nb_valid_tok];
489
490                 /* end of inst */
491                 if (token == NULL) {
492                         n = snprintf(helpbuf, sizeof(helpbuf), "[RETURN]\n");
493                         if (n > 0)
494                                 write_buf(opaque, helpbuf, n);
495                         continue;
496                 }
497
498                 /* token matches, but no completion */
499                 if (token->ops->complete_start == NULL ||
500                     token->ops->complete_iterate == NULL)
501                         iterate = 0;
502                 else
503                         iterate = 1;
504
505                 /* store the incomplete token in tmpbuf */
506                 n = preparse.comp_tok_len + 1;
507                 if (n > sizeof(tmpbuf))
508                         n = sizeof(tmpbuf);
509                 snprintf(tmpbuf, n, "%s", preparse.comp_tok_buf);
510
511                 if (iterate == 1 &&
512                     token->ops->complete_start(token, tmpbuf,
513                                                &preparse.opaque) < 0) {
514                         /* cancel iteration, complete_start() returned
515                          * a negative value, meaning no completion  */
516                         iterate = 0;
517                         if (token->ops->complete_end != NULL)
518                                 token->ops->complete_end(token,
519                                                          &preparse.opaque);
520                 }
521
522                 /* get token dynamic help string */
523                 if ((token->ops->help == NULL) ||
524                     (token->ops->help(token, tmpbuf, sizeof(tmpbuf)) < 0))
525                         snprintf(tmpbuf, sizeof(tmpbuf), "unknown");
526
527                 /* get instruction static help string */
528                 help_str = inst->help_str;
529                 if (help_str == NULL)
530                         help_str = "No help";
531
532                 /* send it to callback function */
533                 n = snprintf(helpbuf, sizeof(helpbuf),
534                              "[%s]: %s\n", tmpbuf, help_str);
535                 if (n >= 0 && n < sizeof(helpbuf))
536                         write_buf(opaque, helpbuf, n);
537
538                 if (iterate == 0)
539                         continue;
540
541                 /* iterate over all possible completion for this inst */
542                 while (token->ops->complete_iterate(token,
543                                                     &preparse.opaque,
544                                                     tmpbuf,
545                                                     sizeof(tmpbuf)) == 0) {
546
547
548                         debug_printf("   choice <%s>\n", tmpbuf);
549
550                         /* does the completion match the beginning of
551                          * the word ? */
552                         if (strncmp(preparse.comp_tok_buf, tmpbuf,
553                                     preparse.comp_tok_len))
554                                 continue;
555
556                         /* get the token and add it in help buffer */
557                         n = snprintf(helpbuf, sizeof(helpbuf), "  %s\n", tmpbuf);
558                         if (n >= 0 && n < sizeof(helpbuf))
559                                 write_buf(opaque, helpbuf, n);
560                 }
561
562                 /* no more completion, go to next inst */
563                 if ( token->ops->complete_end != NULL)
564                         token->ops->complete_end(token, &preparse.opaque);
565         }
566
567         return 0;
568 }
569
570 /* try to complete the buffer given as a parameter */
571 int
572 cmdline_complete(cmdline_parse_ctx_t *ctx, const char *buf,
573                  char *dst, unsigned int dstsize)
574 {
575         cmdline_parse_token_hdr_t *token;
576         cmdline_parse_inst_t **pinst;
577         cmdline_parse_inst_t *inst;
578         struct cmdline_preparse preparse;
579         int nb_match = 0;
580         int nb_completion = 0;
581         int completion_len = CMDLINE_MAX_TOKEN_SIZE;
582         char completion_buf[CMDLINE_MAX_TOKEN_SIZE];
583         char tmpbuf[CMDLINE_MAX_TOKEN_SIZE];
584         int ret, n;
585
586         debug_printf("%s called\n", __FUNCTION__);
587
588         /* fill the preparse structure that contains infos that will
589          * help us to complete the buffer */
590         ret = cmdline_preparse(&preparse, buf);
591         if (ret < 0)
592                 return CMDLINE_COMPLETE_NONE;
593
594         /* try to complete !! */
595         for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) {
596                 inst = *pinst;
597
598                 debug_printf("INST\n");
599
600                 /* try to match the first tokens */
601                 if (preparse.nb_valid_tok != 0 &&
602                     match_inst(inst, buf, preparse.nb_valid_tok,
603                                NULL, 0) != 0)
604                         continue;
605
606                 nb_match ++;
607                 token = inst->tokens[preparse.nb_valid_tok];
608
609                 /* non completable */
610                 if (token == NULL ||
611                     token->ops->complete_start == NULL ||
612                     token->ops->complete_iterate == NULL)
613                         continue;
614
615                 /* store the incomplete token in tmpbuf */
616                 n = preparse.comp_tok_len + 1;
617                 if (n > sizeof(tmpbuf))
618                         n = sizeof(tmpbuf);
619                 snprintf(tmpbuf, n, "%s", preparse.comp_tok_buf);
620
621                 /* non completable */
622                 if (token->ops->complete_start(token, tmpbuf,
623                                                &preparse.opaque) < 0) {
624                         if (token->ops->complete_end != NULL)
625                                 token->ops->complete_end(token,
626                                                          &preparse.opaque);
627                         continue;
628                 }
629
630                 /* all possible completion for this token */
631                 while (1) {
632
633                         ret = token->ops->complete_iterate(token,
634                                                            &preparse.opaque,
635                                                            tmpbuf,
636                                                            sizeof(tmpbuf)-1);
637
638                         if (ret < 0)
639                                 break;
640
641                         debug_printf("Completion %s\n", tmpbuf);
642
643                         /* we kept at least the room for one char */
644                         /* XXX to be updated for non-final completion */
645                         if (ret == 0)
646                                 strcat(tmpbuf, " ");
647
648                         debug_printf("   choice <%s>\n", tmpbuf);
649
650                         /* does the completion match the beginning of
651                          * the word ? */
652                         if (strncmp(preparse.comp_tok_buf, tmpbuf,
653                                     preparse.comp_tok_len))
654                                 continue;
655
656                         /* first one, save the buffer */
657                         if (nb_completion == 0) {
658                                 completion_len = snprintf(completion_buf,
659                                                         sizeof(completion_buf),
660                                                         "%s", tmpbuf);
661                         }
662                         else {
663                                 n = nb_common_chars(completion_buf, tmpbuf);
664                                 if (n < completion_len)
665                                         completion_len = n;
666                         }
667                         nb_completion ++;
668
669                         /* we cannot add any char, just display help */
670                         if (completion_len == preparse.comp_tok_len)
671                                 break;
672                 }
673                 if (token->ops->complete_end != NULL)
674                         token->ops->complete_end(token, &preparse.opaque);
675
676                 if (completion_len == preparse.comp_tok_len)
677                         break;
678         }
679
680         debug_printf("nb_completion=%d, completion_len=%d\n",
681                      nb_completion, completion_len);
682
683         /* one choice, append chars and return */
684         if (nb_completion == 1) {
685                 snprintf(dst, dstsize, "%s",
686                          completion_buf + preparse.comp_tok_len);
687                 return CMDLINE_COMPLETE_APPEND;
688         }
689
690         /* many choices, but starting with same chars: append chars
691          * and return */
692         if (nb_completion != 0 && completion_len > preparse.comp_tok_len) {
693                 if (completion_len >= dstsize)
694                         completion_len = dstsize - 1;
695                 strncpy(dst, completion_buf + preparse.comp_tok_len,
696                         completion_len);
697                 dst[completion_len] = '\0';
698                 return CMDLINE_COMPLETE_APPEND;
699         }
700
701         /* no match, nothing to do */
702         if (nb_match == 0 || nb_completion == 0)
703                 return CMDLINE_COMPLETE_NONE;
704
705         return CMDLINE_COMPLETE_MANY;
706 }
707