cmdline (merge-intel): fix compilation warnings
[libcmdline.git] / src / lib / cmdline_parse.c
1 /*
2  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
3  * All rights reserved.
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <inttypes.h>
31 #include <ctype.h>
32
33 #include <netinet/in.h>
34
35 #include "cmdline_parse.h"
36 #include "cmdline.h"
37
38 //#define CMDLINE_DEBUG
39 //#define debug_printf printf
40 #define debug_printf(args...) do {} while(0)
41
42 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
43  * own. */
44 static int
45 isblank2(char c)
46 {
47         if (c == ' ' ||
48             c == '\t' )
49                 return 1;
50         return 0;
51 }
52
53 static int
54 isendofline(char c)
55 {
56         if (c == '\n' ||
57             c == '\r' )
58                 return 1;
59         return 0;
60 }
61
62 static int
63 iscomment(char c)
64 {
65         if (c == '#')
66                 return 1;
67         return 0;
68 }
69
70 int
71 cmdline_isendoftoken(char c)
72 {
73         if (!c || iscomment(c) || isblank2(c) || isendofline(c))
74                 return 1;
75         return 0;
76 }
77
78 static unsigned int
79 nb_common_chars(const char * s1, const char * s2)
80 {
81         unsigned int i=0;
82
83         while (*s1==*s2 && *s1 && *s2) {
84                 s1++;
85                 s2++;
86                 i++;
87         }
88         return i;
89 }
90
91 /**
92  * try to match the buffer with an instruction (only the first
93  * nb_match_token tokens if != 0). Return 0 if we match all the
94  * tokens, else the number of matched tokens, else -1.
95  */
96 static int
97 match_inst(cmdline_parse_inst_t *inst, const char *buf, unsigned int nb_match_token,
98            void * result_buf)
99 {
100         unsigned int token_num=0;
101         cmdline_parse_token_hdr_t * token_p;
102         unsigned int i=0;
103         int n = 0;
104         struct cmdline_token_hdr token_hdr;
105
106         token_p = inst->tokens[token_num];
107         if (token_p)
108                 memcpy(&token_hdr, token_p, sizeof(token_hdr));
109
110         /* check if we match all tokens of inst */
111         while (token_p && (!nb_match_token || i<nb_match_token)) {
112                 debug_printf("TK\n");
113                 /* skip spaces */
114                 while (isblank2(*buf)) {
115                         buf++;
116                 }
117
118                 /* end of buf */
119                 if ( isendofline(*buf) || iscomment(*buf) )
120                         break;
121
122                 n = token_hdr.ops->parse(token_p, buf, (result_buf ? result_buf+token_hdr.offset : NULL));
123                 if ( n < 0 )
124                         break;
125                 debug_printf("TK parsed (len=%d)\n", n);
126                 i++;
127                 buf += n;
128
129                 token_num ++;
130                 token_p = inst->tokens[token_num];
131                 if (token_p)
132                         memcpy(&token_hdr, token_p, sizeof(token_hdr));
133         }
134
135         /* does not match */
136         if (i==0)
137                 return -1;
138
139         /* in case we want to match a specific num of token */
140         if (nb_match_token) {
141                 if (i == nb_match_token) {
142                         return 0;
143                 }
144                 return i;
145         }
146
147         /* we don't match all the tokens */
148         if (token_p) {
149                 return i;
150         }
151
152         /* are there are some tokens more */
153         while (isblank2(*buf)) {
154                 buf++;
155         }
156
157         /* end of buf */
158         if ( isendofline(*buf) || iscomment(*buf) )
159                 return 0;
160
161         /* garbage after inst */
162         return i;
163 }
164
165
166 int
167 cmdline_parse(struct cmdline *cl, const char * buf)
168 {
169         unsigned int inst_num=0;
170         cmdline_parse_inst_t *inst;
171         const char *curbuf;
172         char result_buf[BUFSIZ]; /* XXX align, size zé in broblém */
173         void (*f)(void *, struct cmdline *, void *) = NULL;
174         void *data = NULL;
175         int comment = 0;
176         int linelen = 0;
177         int parse_it = 0;
178         int err = CMDLINE_PARSE_NOMATCH;
179         int tok;
180         cmdline_parse_ctx_t *ctx = cl->ctx;
181 #ifdef CMDLINE_DEBUG
182         char debug_buf[BUFSIZ];
183 #endif
184
185         /*
186          * - look if the buffer contains at least one line
187          * - look if line contains only spaces or comments
188          * - count line length
189          */
190         curbuf = buf;
191         while (! isendofline(*curbuf)) {
192                 if ( *curbuf == '\0' ) {
193                         debug_printf("Incomplete buf (len=%d)\n", linelen);
194                         return 0;
195                 }
196                 if ( iscomment(*curbuf) ) {
197                         comment = 1;
198                 }
199                 if ( ! isblank2(*curbuf) && ! comment) {
200                         parse_it = 1;
201                 }
202                 curbuf++;
203                 linelen++;
204         }
205
206         /* skip all endofline chars */
207         while (isendofline(buf[linelen])) {
208                 linelen++;
209         }
210
211         /* empty line */
212         if ( parse_it == 0 ) {
213                 debug_printf("Empty line (len=%d)\n", linelen);
214                 return linelen;
215         }
216
217 #ifdef CMDLINE_DEBUG
218         snprintf(debug_buf, (linelen>64 ? 64 : linelen), "%s", buf);
219         debug_printf("Parse line : len=%d, <%s>\n", linelen, debug_buf);
220 #endif
221
222         /* parse it !! */
223         inst = ctx[inst_num];
224         while (inst) {
225                 debug_printf("INST %d\n", inst_num);
226
227                 /* fully parsed */
228                 tok = match_inst(inst, buf, 0, result_buf);
229
230                 if (tok > 0) /* we matched at least one token */
231                         err = CMDLINE_PARSE_BAD_ARGS;
232
233                 else if (!tok) {
234                         debug_printf("INST fully parsed\n");
235                         /* skip spaces */
236                         while (isblank2(*curbuf)) {
237                                 curbuf++;
238                         }
239
240                         /* if end of buf -> there is no garbage after inst */
241                         if (isendofline(*curbuf) || iscomment(*curbuf)) {
242                                 if (!f) {
243                                         memcpy(&f, &inst->f, sizeof(f));
244                                         memcpy(&data, &inst->data, sizeof(data));
245                                 }
246                                 else {
247                                         /* more than 1 inst matches */
248                                         err = CMDLINE_PARSE_AMBIGUOUS;
249                                         f=NULL;
250                                         debug_printf("Ambiguous cmd\n");
251                                         break;
252                                 }
253                         }
254                 }
255
256                 inst_num ++;
257                 inst = ctx[inst_num];
258         }
259
260         /* call func */
261         if (f) {
262                 f(result_buf, cl, data);
263         }
264
265         /* no match */
266         else {
267                 debug_printf("No match err=%d\n", err);
268                 return err;
269         }
270
271         return linelen;
272 }
273
274 int
275 cmdline_complete(struct cmdline *cl, const char *buf, int *state,
276                  char *dst, unsigned int size)
277 {
278         const char *incomplete_token = buf;
279         unsigned int inst_num = 0;
280         cmdline_parse_inst_t *inst;
281         cmdline_parse_token_hdr_t *token_p;
282         struct cmdline_token_hdr token_hdr;
283         char tmpbuf[64], completion_buf[64];
284         unsigned int incomplete_token_len;
285         int completion_len = -1;
286         int nb_token = 0;
287         unsigned int i, n;
288         int l;
289         unsigned int nb_completable;
290         unsigned int nb_non_completable;
291         int local_state = 0;
292         char *help_str;
293         cmdline_parse_ctx_t *ctx = cl->ctx;
294
295         debug_printf("%s called\n", __FUNCTION__);
296         memset(&token_hdr, 0, sizeof(token_hdr));
297
298         /* count the number of complete token to parse */
299         for (i=0 ; buf[i] ; i++) {
300                 if (!isblank2(buf[i]) && isblank2(buf[i+1]))
301                         nb_token++;
302                 if (isblank2(buf[i]) && !isblank2(buf[i+1]))
303                         incomplete_token = buf+i+1;
304         }
305         incomplete_token_len = strlen(incomplete_token);
306
307         /* first call -> do a first pass */
308         if (*state <= 0) {
309                 debug_printf("try complete <%s>\n", buf);
310                 debug_printf("there is %d complete tokens, <%s> is incomplete\n", nb_token, incomplete_token);
311
312                 nb_completable = 0;
313                 nb_non_completable = 0;
314
315                 inst = ctx[inst_num];
316                 while (inst) {
317                         /* parse the first tokens of the inst */
318                         if (nb_token && match_inst(inst, buf, nb_token, NULL))
319                                 goto next;
320
321                         debug_printf("instruction match \n");
322                         token_p = inst->tokens[nb_token];
323                         if (token_p)
324                                 memcpy(&token_hdr, token_p, sizeof(token_hdr));
325
326                         /* non completable */
327                         if (!token_p ||
328                             !token_hdr.ops->complete_get_nb ||
329                             !token_hdr.ops->complete_get_elt ||
330                             (n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
331                                 nb_non_completable++;
332                                 goto next;
333                         }
334
335                         debug_printf("%d choices for this token\n", n);
336                         for (i=0 ; i<n ; i++) {
337                                 if (token_hdr.ops->complete_get_elt(token_p, i, tmpbuf, sizeof(tmpbuf)) < 0)
338                                         continue;
339                                 strcat(tmpbuf, " "); /* we have at least room for one char */
340                                 debug_printf("   choice <%s>\n", tmpbuf);
341                                 /* does the completion match the beginning of the word ? */
342                                 if (!strncmp(incomplete_token, tmpbuf, incomplete_token_len)) {
343                                         if (completion_len == -1) {
344                                                 strcpy(completion_buf, tmpbuf+incomplete_token_len);
345                                                 completion_len = strlen(tmpbuf+incomplete_token_len);
346
347                                         }
348                                         else {
349                                                 completion_len = nb_common_chars(completion_buf,
350                                                                                  tmpbuf+incomplete_token_len);
351                                                 completion_buf[completion_len] = 0;
352                                         }
353                                         nb_completable++;
354                                 }
355                         }
356                 next:
357                         inst_num ++;
358                         inst = ctx[inst_num];
359                 }
360
361                 debug_printf("total choices %d for this completion\n", nb_completable);
362
363                 /* no possible completion */
364                 if (nb_completable == 0 && nb_non_completable == 0)
365                         return 0;
366
367                 /* if multichoice is not required */
368                 if (*state == 0 && incomplete_token_len > 0) {
369                         /* one or several choices starting with the
370                            same chars */
371                         if (completion_len > 0) {
372                                 if ((unsigned)(completion_len + 1) > size)
373                                         return 0;
374
375                                 strcpy(dst, completion_buf);
376                                 return 2;
377                         }
378                 }
379         }
380
381         /* init state correctly */
382         if (*state == -1)
383                 *state = 0;
384
385         debug_printf("Multiple choice STATE=%d\n", *state);
386
387         inst_num = 0;
388         inst = ctx[inst_num];
389         while (inst) {
390                 /* we need to redo it */
391                 inst = ctx[inst_num];
392
393                 if (nb_token && match_inst(inst, buf, nb_token, NULL))
394                         goto next2;
395
396                 token_p = inst->tokens[nb_token];
397                 if (token_p)
398                         memcpy(&token_hdr, token_p, sizeof(token_hdr));
399
400                 /* one choice for this token */
401                 if (!token_p ||
402                     !token_hdr.ops->complete_get_nb ||
403                     !token_hdr.ops->complete_get_elt ||
404                     (n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
405                         if (local_state < *state) {
406                                 local_state++;
407                                 goto next2;
408                         }
409                         (*state)++;
410                         if (token_p && token_hdr.ops->get_help) {
411                                 token_hdr.ops->get_help(token_p, tmpbuf, sizeof(tmpbuf));
412                                 help_str = inst->help_str;
413                                 if (help_str)
414                                         snprintf(dst, size, "[%s]: %s", tmpbuf, help_str);
415                                 else
416                                         snprintf(dst, size, "[%s]: No help", tmpbuf);
417                         }
418                         else {
419                                 snprintf(dst, size, "[RETURN]");
420                         }
421                         return 1;
422                 }
423
424                 /* several choices */
425                 for (i=0 ; i<n ; i++) {
426                         if (token_hdr.ops->complete_get_elt(token_p, i, tmpbuf, sizeof(tmpbuf)) < 0)
427                                 continue;
428                         strcat(tmpbuf, " "); /* we have at least room for one char */
429                         debug_printf("   choice <%s>\n", tmpbuf);
430                         /* does the completion match the beginning of the word ? */
431                         if (!strncmp(incomplete_token, tmpbuf, incomplete_token_len)) {
432                                 if (local_state < *state) {
433                                         local_state++;
434                                         continue;
435                                 }
436                                 (*state)++;
437                                 l=snprintf(dst, size, "%s", tmpbuf);
438                                 if (l>=0 && token_hdr.ops->get_help) {
439                                         token_hdr.ops->get_help(token_p, tmpbuf, sizeof(tmpbuf));
440                                         help_str = inst->help_str;
441                                         if (help_str)
442                                                 snprintf(dst+l, size-l, "[%s]: %s", tmpbuf, help_str);
443                                         else
444                                                 snprintf(dst+l, size-l, "[%s]: No help", tmpbuf);
445                                 }
446
447                                 return 1;
448                         }
449                 }
450         next2:
451                 inst_num ++;
452                 inst = ctx[inst_num];
453         }
454         return 0;
455 }
456