1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #pragma ident "%Z%%M% %I% %E% SMI"
27
28 #include <sys/types.h>
29 #include <dirent.h>
30 #include <strings.h>
31
32 #include "filebench.h"
33 #include "auto_comp.h"
34
35 #define VARNAME_MAXLEN 128
36 #define FILENAME_MAXLEN 128
37 #define MALLOC_STEP 64
38
39 #define CSUF_CMD " "
40 #define CSUF_ARG " "
41 #define CSUF_LVARNAME "="
42 #define CSUF_RVARNAME ","
43 #define CSUF_ATTRNAME "="
44
45 #define ATTR_LIST_SEP ','
46 #define ATTR_ASSIGN_OP '='
47 #define VAR_ASSIGN_OP '='
48 #define VAR_PREFIX '$'
49
50 typedef char ac_fname_t[FILENAME_MAXLEN];
51
52 typedef struct ac_fname_cache {
53 ac_fname_t *fnc_buf;
54 int fnc_bufsize;
55 time_t fnc_mtime;
56 } ac_fname_cache_t;
57
58 typedef enum ac_match_result {
59 MATCH_DONE,
60 MATCH_CONT
61 } ac_match_result_t;
62
63 /*
64 * We parse an user input line into multiple blank separated strings.
65 * The last string is always the one user wants to complete, the other
66 * preceding strings set up the context on how to complete the last one.
67 *
68 * ac_str_t repsents one such a string, which can be of the following
69 * types:
70 *
71 * STRTYPE_COMPLETE - the string is one of the preceding strings.
72 * STRTYPE_INCOMPLETE - the string is the one being completed, user
73 * has inputted at least one character for it.
74 * STRTYPE_NULL - the string is the one being completed, user
75 * has inputted nothing for it.
76 *
77 * ac_str_t structure has the following members:
78 *
79 * startp - the start position of the string in the user input buffer
80 * endp - the end position of the string in the user input buffer
81 * strtype - the type of the string. It can be of the following values:
82 * STRTYPE_COMPLETE, STRTYPE_INCOMPLETE, STRTYPE_NULL,
83 * and STRTYPE_INVALID.
84 */
85
86 typedef enum ac_strtype {
87 STRTYPE_COMPLETE,
88 STRTYPE_INCOMPLETE,
89 STRTYPE_NULL,
90 STRTYPE_INVALID
91 } ac_strtype_t;
92
93 typedef struct ac_str {
94 const char *startp;
95 const char *endp;
96 ac_strtype_t strtype;
97 } ac_str_t;
98
99 #define STR_NUM 3
100
101 typedef struct ac_inputline {
102 ac_str_t strs[STR_NUM];
103 } ac_inputline_t;
104
105 /*
106 * ac_iter represents a general interface to access a list of values for
107 * matching user input string. The structure has the following methods:
108 *
109 * bind - bind the iterator to a list, and save a pointer to user
110 * passed space in nlistpp.
111 * reset - reset internal index pointer to point to list head.
112 * get_nextstr - this is the method that does the real work. It
113 * walks through the list and returns string associated with
114 * the current item. It can also return some other thing the
115 * caller is interested via the user passed space pointed by
116 * nlistpp. In our case, that is a pointer to a list which
117 * contains all possible values for the next string in user
118 * input.
119 *
120 * It has the following data members:
121 *
122 * listp - a pointer to the list to be iterated through
123 * curp - index pointer to maintain position when iterating
124 * nlistpp - a pointer to user passed space for returning a list of
125 * values for the next string in user input
126 */
127
128 typedef struct ac_iter {
129 void *listp;
130 void *curp;
131 void *nlistpp;
132 void (*bind)(struct ac_iter *, void *, void *);
133 void (*reset)(struct ac_iter *);
134 const char *(*get_nextstr)(struct ac_iter *);
135 } ac_iter_t;
136
137 /*
138 * We consider a filebench command is composed of a sequence of tokens
139 * (ie., command name, argument name, attribute name, etc.). Many of
140 * these tokens have limited string values. These values, as well as
141 * their dependencies, are used to complete user input string.
142 *
143 * There are the following tokens:
144 *
145 * TOKTYPE_CMD - command name
146 * TOKTYPE_ARG - argument name
147 * TOKTYPE_ATTRNAME - attribute name
148 * TOKTYPE_ATTRVAL - attribute value
149 * TOKTYPE_LVARNAME - variable name, used on left side of assign
150 * operator
151 * TOKTYPE_RVARNAME - variable name, used on right side of assign
152 * operator
153 * TOKTYPE_VARVAL - variable value
154 * TOKTYPE_LOADFILE - load file name
155 * TOKTYPE_ATTRLIST - pseudo token type for attribute list
156 * TOKTYPE_VARLIST - pseudo token type for variable list
157 * TOKTYPE_NULL - pseudo token type for aborting auto-completion
158 *
159 * The reason why there are two different token types for variable name
160 * is because, depending on its position, there are different requirements
161 * on how to do completion and display matching results. See more details
162 * in lvarname_iter and rvarname_iter definition.
163 *
164 * Attribute list and variable list are not really a single token. Instead
165 * they contain multiple tokens, and thus have different requirements on
166 * how to complete them. TOKTYPE_ATTRLIST and TOKTYPE_VARLIST are
167 * introduced to to solve this issue. See more details below on
168 * get_curtok() function in ac_tokinfo_t structure.
169 *
170 * ac_tokval_t represents a string value a token can have. The structure
171 * also contains a pointer to a ac_tvlist_t structure, which represents
172 * all possible values for the next token in the same command.
173 *
174 * str - the token's string value
175 * nlistp - a list which contains string values for the token
176 * that follows
177 *
178 * ac_tvlist_t represents all possible values for a token. These values
179 * are stored in an ac_tokval_t array. The structure also has a member
180 * toktype, which is used to index an ac_tokinfo_t array to get the
181 * information on how to access and use the associated value list.
182 *
183 * vals - a list of string values for this token
184 * toktype - the token's type
185 *
186 * ac_tokinfo_t contains information on how to access and use the
187 * string values of a specific token. Among them, the most important
188 * thing is an iterator to access the value list. The reason to use
189 * iterator is to encapsulate list implementation details. That is
190 * necessary because some tokens have dynamic values(for example,
191 * argument of load command), which cannot be predefined using
192 * ac_tokval_t array.
193 *
194 * ac_tokinfo_t structure has the following members:
195 *
196 * toktype - token type
197 * iter - iterator to access the token's value list
198 * cont_suffix - continuation suffix for this token. See note 1
199 * below on what is continuation suffix.
200 * get_curtok - a function to parse a multi-token user string.
201 * It parse that string and returns the word being
202 * completed and its token type. See note 2 below.
203 *
204 * Notes:
205 *
206 * 1) Continuation suffix is a convenient feature provided by libtecla.
207 * A continuation suffix is a string which is automatically appended
208 * to a fully completed string. For example, if a command name is
209 * fully completed, a blank space will be appended to it. This is
210 * very convenient because it not only saves typing, but also gives
211 * user an indication to continue.
212 *
213 * 2) get_curtok() function is a trick to support user input strings
214 * which have multiple tokens. Take attribute list as an example,
215 * although we defined a token type TOKTYPE_ATTRLIST for it, it is
216 * not a token actually, instead it contains multiple tokens like
217 * attribute name, attribute value, etc., and attribute value can
218 * be either a literal string or a variable name prefixed with a
219 * '$' sign. For this reason, get_curtok() function is needed to
220 * parse that string to get the word being completed and its token
221 * type so that we can match the word against with the proper value
222 * list.
223 */
224
225 typedef enum ac_toktype {
226 TOKTYPE_CMD,
227 TOKTYPE_ARG,
228 TOKTYPE_ATTRNAME,
229 TOKTYPE_ATTRVAL,
230 TOKTYPE_LVARNAME,
231 TOKTYPE_RVARNAME,
232 TOKTYPE_VARVAL,
233 TOKTYPE_LOADFILE,
234 TOKTYPE_ATTRLIST,
235 TOKTYPE_VARLIST,
236 TOKTYPE_NULL
237 } ac_toktype_t;
238
239 typedef ac_toktype_t (*ac_get_curtok_func_t)(ac_str_t *);
240
241 typedef struct ac_tokinfo {
242 ac_toktype_t toktype;
243 ac_iter_t *iter;
244 char *cont_suffix;
245 ac_get_curtok_func_t get_curtok;
246 } ac_tokinfo_t;
247
248 typedef struct ac_tokval {
249 char *str;
250 struct ac_tvlist *nlistp;
251 } ac_tokval_t;
252
253 typedef struct ac_tvlist {
254 ac_tokval_t *vals;
255 ac_toktype_t toktype;
256 } ac_tvlist_t;
257
258 /*
259 * Variables and prototypes
260 */
261
262 static void common_bind(ac_iter_t *, void *, void *);
263 static void common_reset(ac_iter_t *);
264 static void varname_bind(ac_iter_t *, void *, void *);
265 static void loadfile_bind(ac_iter_t *, void *, void *);
266 static const char *get_next_tokval(ac_iter_t *);
267 static const char *get_next_lvarname(ac_iter_t *);
268 static const char *get_next_rvarname(ac_iter_t *);
269 static const char *get_next_loadfile(ac_iter_t *);
270 static ac_toktype_t parse_attr_list(ac_str_t *);
271 static ac_toktype_t parse_var_list(ac_str_t *);
272
273 static ac_iter_t tokval_iter = {
274 NULL,
275 NULL,
276 NULL,
277 common_bind,
278 common_reset,
279 get_next_tokval
280 };
281
282 static ac_iter_t lvarname_iter = {
283 NULL,
284 NULL,
285 NULL,
286 varname_bind,
287 common_reset,
288 get_next_lvarname
289 };
290
291 static ac_iter_t rvarname_iter = {
292 NULL,
293 NULL,
294 NULL,
295 varname_bind,
296 common_reset,
297 get_next_rvarname
298 };
299
300 static ac_iter_t loadfile_iter = {
301 NULL,
302 NULL,
303 NULL,
304 loadfile_bind,
305 common_reset,
306 get_next_loadfile
307 };
308
309 /*
310 * Note: We use toktype to index into this array, so for each toktype,
311 * there must be one element in the array, and in the same order
312 * as that toktype is defined in ac_toktype.
313 */
314 static ac_tokinfo_t token_info[] = {
315 { TOKTYPE_CMD, &tokval_iter, CSUF_CMD, NULL },
316 { TOKTYPE_ARG, &tokval_iter, CSUF_ARG, NULL },
317 { TOKTYPE_ATTRNAME, &tokval_iter, CSUF_ATTRNAME, NULL },
318 { TOKTYPE_ATTRVAL, NULL, NULL, NULL },
319 { TOKTYPE_LVARNAME, &lvarname_iter, CSUF_LVARNAME, NULL },
320 { TOKTYPE_RVARNAME, &rvarname_iter, CSUF_RVARNAME, NULL },
321 { TOKTYPE_VARVAL, NULL, NULL, NULL },
322 { TOKTYPE_LOADFILE, &loadfile_iter, CSUF_ARG, NULL },
323 { TOKTYPE_ATTRLIST, NULL, NULL, parse_attr_list },
324 { TOKTYPE_VARLIST, NULL, NULL, parse_var_list },
325 { TOKTYPE_NULL, NULL, NULL, NULL }
326 };
327
328 static ac_tokval_t event_attrnames[] = {
329 { "rate", NULL},
330 { NULL, NULL}
331 };
332
333 static ac_tvlist_t event_attrs = {
334 event_attrnames,
335 TOKTYPE_ATTRLIST
336 };
337
338 static ac_tokval_t file_attrnames[] = {
339 { "path", NULL },
340 { "reuse", NULL },
341 { "prealloc", NULL },
342 { "paralloc", NULL },
343 { NULL, NULL }
344 };
345
346 static ac_tvlist_t file_attrs = {
347 file_attrnames,
348 TOKTYPE_ATTRLIST
349 };
350
351 static ac_tokval_t fileset_attrnames[] = {
352 { "size", NULL },
353 { "path", NULL },
354 { "dirwidth", NULL },
355 { "prealloc", NULL },
356 { "filesizegamma", NULL },
357 { "dirgamma", NULL },
358 { "cached", NULL },
359 { "entries", NULL },
360 { NULL, NULL }
361 };
362
363 static ac_tvlist_t fileset_attrs = {
364 fileset_attrnames,
365 TOKTYPE_ATTRLIST
366 };
367
368 static ac_tokval_t process_attrnames[] = {
369 { "nice", NULL },
370 { "instances", NULL },
371 { NULL, NULL }
372 };
373
374 static ac_tvlist_t process_attrs = {
375 process_attrnames,
376 TOKTYPE_ATTRLIST
377 };
378
379 static ac_tokval_t create_argnames[] = {
380 { "file", NULL },
381 { "fileset", NULL },
382 { "process", NULL },
383 { NULL, NULL }
384 };
385
386 static ac_tvlist_t create_args = {
387 create_argnames,
388 TOKTYPE_ARG
389 };
390
391 static ac_tokval_t define_argnames[] = {
392 { "file", &file_attrs },
393 { "fileset", &fileset_attrs },
394 { "process", &process_attrs },
395 { NULL, NULL }
396 };
397
398 static ac_tvlist_t define_args = {
399 define_argnames,
400 TOKTYPE_ARG
401 };
402
403 static ac_tvlist_t load_args = {
404 NULL,
405 TOKTYPE_LOADFILE
406 };
407
408 static ac_tvlist_t set_args = {
409 NULL,
410 TOKTYPE_VARLIST
411 };
412
413 static ac_tokval_t shutdown_argnames[] = {
414 { "process", NULL },
415 { NULL, NULL }
416 };
417
418 static ac_tvlist_t shutdown_args = {
419 shutdown_argnames,
420 TOKTYPE_ARG
421 };
422
423 static ac_tokval_t stats_argnames[] = {
424 { "clear", NULL },
425 { "directory", NULL },
426 { "command", NULL },
427 { "dump", NULL },
428 { "xmldump", NULL },
429 { NULL, NULL }
430 };
431
432 static ac_tvlist_t stats_args = {
433 stats_argnames,
434 TOKTYPE_ARG
435 };
436
437 static ac_tokval_t fb_cmdnames[] = {
438 { "create", &create_args },
439 { "define", &define_args },
440 { "debug", NULL },
441 { "echo", NULL },
442 { "eventgen", &event_attrs },
443 { "foreach", NULL },
444 { "help", NULL },
445 { "list", NULL },
446 { "load", &load_args },
447 { "log", NULL },
448 { "quit", NULL },
449 { "run", NULL },
450 { "set", &set_args },
451 { "shutdown", &shutdown_args },
452 { "sleep", NULL },
453 { "stats", &stats_args },
454 { "system", NULL },
455 { "usage", NULL },
456 { "vars", NULL },
457 { "version", NULL },
458 { NULL, NULL },
459 };
460
461 static ac_tvlist_t fb_cmds = {
462 fb_cmdnames,
463 TOKTYPE_CMD
464 };
465
466 static ac_fname_cache_t loadnames = { NULL, 0, 0 };
467
468 static int search_loadfiles(ac_fname_cache_t *);
469 static void parse_user_input(const char *, int, ac_inputline_t *);
470 static int compare_string(ac_str_t *, const char *, boolean_t, const char **);
471 static ac_match_result_t match_string(WordCompletion *, const char *, int,
472 ac_str_t *, ac_iter_t *, const char *);
473
474 /*
475 * Bind the iterator to the passed list
476 */
477 static void
common_bind(ac_iter_t * iterp,void * listp,void * nlistpp)478 common_bind(ac_iter_t *iterp, void *listp, void *nlistpp)
479 {
480 iterp->listp = listp;
481 iterp->nlistpp = nlistpp;
482 }
483
484 /*
485 * Reset index pointer to point to list head
486 */
487 static void
common_reset(ac_iter_t * iterp)488 common_reset(ac_iter_t *iterp)
489 {
490 iterp->curp = iterp->listp;
491 }
492
493 /*
494 * Walk through an array of ac_tokval_t structures and return string
495 * of each item.
496 */
497 static const char *
get_next_tokval(ac_iter_t * iterp)498 get_next_tokval(ac_iter_t *iterp)
499 {
500 ac_tokval_t *listp = iterp->listp; /* list head */
501 ac_tokval_t *curp = iterp->curp; /* index pointer */
502 /* user passed variable for returning value list for next token */
503 ac_tvlist_t **nlistpp = iterp->nlistpp;
504 const char *p;
505
506 if (listp == NULL || curp == NULL)
507 return (NULL);
508
509 /* get the current item's string */
510 p = curp->str;
511
512 /*
513 * save the current item's address into a user passed variable
514 */
515 if (nlistpp != NULL)
516 *nlistpp = curp->nlistp;
517
518 /* advance the index pointer */
519 iterp->curp = ++curp;
520
521 return (p);
522 }
523
524 /*
525 * Bind the iterator to filebench_shm->shm_var_list
526 */
527 /* ARGSUSED */
528 static void
varname_bind(ac_iter_t * iterp,void * listp,void * nlistpp)529 varname_bind(ac_iter_t *iterp, void *listp, void * nlistpp)
530 {
531 iterp->listp = filebench_shm->shm_var_list;
532 iterp->nlistpp = nlistpp;
533 }
534
535 /*
536 * Walk through a linked list of var_t type structures and return name
537 * of each variable with a preceding '$' sign
538 */
539 static const char *
get_next_lvarname(ac_iter_t * iterp)540 get_next_lvarname(ac_iter_t *iterp)
541 {
542 static char buf[VARNAME_MAXLEN];
543
544 var_t *listp = iterp->listp; /* list head */
545 var_t *curp = iterp->curp; /* index pointer */
546 /* User passed variable for returning value list for next token */
547 ac_tvlist_t **nlistpp = iterp->nlistpp;
548 const char *p;
549
550 if (listp == NULL || curp == NULL)
551 return (NULL);
552
553 /* Get current variable's name, copy it to buf, with a '$' prefix */
554 p = curp->var_name;
555 (void) snprintf(buf, sizeof (buf), "$%s", p);
556
557 /* No information for the next input string */
558 if (nlistpp != NULL)
559 *nlistpp = NULL;
560
561 /* Advance the index pointer */
562 iterp->curp = curp->var_next;
563
564 return (buf);
565 }
566
567 /*
568 * Walk through a linked list of var_t type structures and return name
569 * of each variable
570 */
571 static const char *
get_next_rvarname(ac_iter_t * iterp)572 get_next_rvarname(ac_iter_t *iterp)
573 {
574 var_t *listp = iterp->listp; /* list head */
575 var_t *curp = iterp->curp; /* index pointer */
576 /* User passed variable for returning value list for next item */
577 ac_tvlist_t **nlistpp = iterp->nlistpp;
578 const char *p;
579
580 if (listp == NULL || curp == NULL)
581 return (NULL);
582
583 /* Get current variable's name */
584 p = curp->var_name;
585
586 /* No information for the next input string */
587 if (nlistpp != NULL)
588 *nlistpp = NULL;
589
590 /* Advance the index pointer */
591 iterp->curp = curp->var_next;
592
593 return (p);
594 }
595
596 /*
597 * Bind the iterator to loadnames.fnc_buf, which is an ac_fname_t array
598 * and contains up-to-date workload file names. The function calls
599 * search_loadfiles() to update the cache before the binding.
600 */
601 /* ARGSUSED */
602 static void
loadfile_bind(ac_iter_t * iterp,void * listp,void * nlistpp)603 loadfile_bind(ac_iter_t *iterp, void *listp, void * nlistpp)
604 {
605 /* Check loadfile name cache, update it if needed */
606 (void) search_loadfiles(&loadnames);
607
608 iterp->listp = loadnames.fnc_buf;
609 iterp->nlistpp = nlistpp;
610 }
611
612 /*
613 * Walk through a string(ac_fname_t, more exactly) array and return each
614 * string, until a NULL iterm is encountered.
615 */
616 static const char *
get_next_loadfile(ac_iter_t * iterp)617 get_next_loadfile(ac_iter_t *iterp)
618 {
619 ac_fname_t *listp = iterp->listp; /* list head */
620 ac_fname_t *curp = iterp->curp; /* index pointer */
621 /* User passed variable for returning value list for next item */
622 ac_tvlist_t **nlistpp = iterp->nlistpp;
623 const char *p;
624
625 if (listp == NULL || curp == NULL)
626 return (NULL);
627
628 /*
629 * Get current file name. If an NULL item is encountered, it means
630 * this is the end of the list. In that case, we need to set p to
631 * NULL to indicate to the caller that the end of the list is reached.
632 */
633 p = (char *)curp;
634 if (*p == NULL)
635 p = NULL;
636
637 /* No information for the next input string */
638 if (nlistpp != NULL)
639 *nlistpp = NULL;
640
641 /* Advance the index pointer */
642 iterp->curp = ++curp;
643
644 return (p);
645 }
646
647 /*
648 * Search for available workload files in workload direcotry and
649 * update workload name cache.
650 */
651 static int
search_loadfiles(ac_fname_cache_t * fnamecache)652 search_loadfiles(ac_fname_cache_t *fnamecache)
653 {
654 DIR *dirp;
655 struct dirent *fp;
656 struct stat dstat;
657 time_t mtime;
658 ac_fname_t *buf;
659 int bufsize = MALLOC_STEP;
660 int len, i;
661
662 if (stat(FILEBENCHDIR"/workloads", &dstat) != 0)
663 return (-1);
664 mtime = dstat.st_mtime;
665
666 /* Return if there is no change since last time */
667 if (mtime == fnamecache->fnc_mtime)
668 return (0);
669
670 /* Get loadfile names and cache it */
671 if ((buf = malloc(sizeof (ac_fname_t) * bufsize)) == NULL)
672 return (-1);
673 if ((dirp = opendir(FILEBENCHDIR"/workloads")) == NULL)
674 return (-1);
675 i = 0;
676 while ((fp = readdir(dirp)) != NULL) {
677 len = strlen(fp->d_name);
678 if (len <= 2 || (fp->d_name)[len - 2] != '.' ||
679 (fp->d_name)[len - 1] != 'f')
680 continue;
681
682 if (i == bufsize) {
683 bufsize += MALLOC_STEP;
684 if ((buf = realloc(buf, sizeof (ac_fname_t) *
685 bufsize)) == NULL)
686 return (-1);
687 }
688
689 (void) snprintf(buf[i], FILENAME_MAXLEN, "%s", fp->d_name);
690 if (len -2 <= FILENAME_MAXLEN - 1) {
691 /* Remove .f suffix in file name */
692 buf[i][len -2] = NULL;
693 }
694 i++;
695 }
696 /* Added a NULL iterm as the array's terminator */
697 buf[i][0] = NULL;
698
699 if (fnamecache->fnc_bufsize != 0)
700 free(fnamecache->fnc_buf);
701 fnamecache->fnc_buf = buf;
702 fnamecache->fnc_bufsize = bufsize;
703 fnamecache->fnc_mtime = mtime;
704
705 return (0);
706 }
707
708 /*
709 * Parse user input line into a list of blank separated strings, and
710 * save the result in the passed ac_inputline_t structure. line and word_end
711 * parameters are passed from libtecla library. line points to user input
712 * buffer, and word_end is the index of the last character of user input.
713 */
714 /* ARGSUSED */
715 static void
parse_user_input(const char * line,int word_end,ac_inputline_t * input)716 parse_user_input(const char *line, int word_end, ac_inputline_t *input)
717 {
718 const char *p = line;
719 int i;
720
721 /* Reset all fileds */
722 for (i = 0; i < STR_NUM; i++) {
723 input->strs[i].startp = NULL;
724 input->strs[i].endp = NULL;
725 input->strs[i].strtype = STRTYPE_INVALID;
726 }
727
728 /*
729 * Parse user input. We don't use word_end to do boundary checking,
730 * instead we take advantage of the fact that the passed line
731 * parameter is always terminated by '\0'.
732 */
733 for (i = 0; i < STR_NUM; i++) {
734 /* Skip leading blank spaces */
735 while (*p == ' ')
736 p++;
737
738 if (*p == NULL) {
739 /*
740 * User input nothing for the string being input
741 * before he pressed TAB. We use STR_NULL flag
742 * to indicate this so that match_str() will list
743 * all available candidates.
744 */
745 input->strs[i].startp = p;
746 input->strs[i].strtype = STRTYPE_NULL;
747 return;
748 }
749
750 /* Recoard the start and end of the string */
751 input->strs[i].startp = p;
752 while ((*p != ' ') && (*p != NULL))
753 p++;
754 input->strs[i].endp = p - 1;
755
756 if (*p == NULL) {
757 input->strs[i].strtype = STRTYPE_INCOMPLETE;
758 return;
759 } else {
760 /* The string is followed by a blank space */
761 input->strs[i].strtype = STRTYPE_COMPLETE;
762 }
763 }
764 }
765
766 /*
767 * Parse an input string which is an attribue list, get the current word
768 * user wants to complete, and return its token type.
769 *
770 * An atribute list has the following format:
771 *
772 * name1=val,name2=$var,...
773 *
774 * The function modifies the passed acstr string on success to point to
775 * the word being completed.
776 */
777 static ac_toktype_t
parse_attr_list(ac_str_t * acstr)778 parse_attr_list(ac_str_t *acstr)
779 {
780 const char *p;
781
782 if (acstr->strtype == STRTYPE_COMPLETE) {
783 /*
784 * User has input a complete string for attribute list
785 * return TOKTYPE_NULL to abort the matching.
786 */
787 return (TOKTYPE_ATTRLIST);
788 } else if (acstr->strtype == STRTYPE_NULL) {
789 /*
790 * User haven't input anything for the attribute list,
791 * he must be trying to list all attribute names.
792 */
793 return (TOKTYPE_ATTRNAME);
794 }
795
796 /*
797 * The string may contain multiple comma separated "name=value"
798 * items. Try to find the last one and move startp to point to it.
799 */
800 for (p = acstr->endp; p >= acstr->startp && *p != ATTR_LIST_SEP; p--) {}
801
802 if (p == acstr->endp) {
803 /*
804 * The last character of the string is ',', which means
805 * user is trying to list all attribute names.
806 */
807 acstr->startp = p + 1;
808 acstr->strtype = STRTYPE_NULL;
809 return (TOKTYPE_ATTRNAME);
810 } else if (p > acstr->startp) {
811 /*
812 * Found ',' between starp and endp, move startp pointer
813 * to point to the last item.
814 */
815 acstr->startp = p + 1;
816 }
817
818 /*
819 * Now startp points to the last "name=value" item. Search in
820 * the characters user has input for this item:
821 *
822 * a) if there isn't '=' character, user is inputting attribute name
823 * b) if there is a '=' character and it is followed by a '$',
824 * user is inputting variable name
825 * c) if there is a '=' character and it isn't followed by a '$',
826 * user is inputting a literal string as attribute value.
827 */
828 for (p = acstr->startp; p <= acstr->endp; p++) {
829 if (*p == ATTR_ASSIGN_OP) {
830 /* Found "=" operator in the string */
831 if (*(p + 1) == VAR_PREFIX) {
832 acstr->startp = p + 2;
833 if (*acstr->startp != NULL)
834 acstr->strtype = STRTYPE_INCOMPLETE;
835 else
836 acstr->strtype = STRTYPE_NULL;
837 return (TOKTYPE_RVARNAME);
838 } else {
839 return (TOKTYPE_ATTRVAL);
840 }
841 }
842 }
843
844 /* Didn't find '=' operator, the string must be an attribute name */
845 return (TOKTYPE_ATTRNAME);
846 }
847
848 /*
849 * Parse an input string which is a variable list, get the current word
850 * user wants to complete, and return its token type.
851 *
852 * A varaible list has the following format:
853 *
854 * $varname=value
855 *
856 * The function modifies the passed acstr string on success to point to
857 * the word being completed.
858 */
859 static ac_toktype_t
parse_var_list(ac_str_t * acstr)860 parse_var_list(ac_str_t *acstr)
861 {
862 const char *p;
863
864 if (acstr->strtype == STRTYPE_COMPLETE) {
865 /*
866 * User has input a complete string for var list
867 * return TOKTYPE_NULL to abort the matching.
868 */
869 return (TOKTYPE_NULL);
870 } else if (acstr->strtype == STRTYPE_NULL) {
871 /*
872 * User haven't input anything for the attribute list,
873 * he must be trying to list all available var names.
874 */
875 return (TOKTYPE_LVARNAME);
876 }
877
878 /*
879 * Search in what user has input:
880 *
881 * a) if there isn't a '=' character, user is inputting var name
882 * b) if there is a '=' character, user is inputting var value
883 */
884 for (p = acstr->startp; p <= acstr->endp; p++) {
885 if (*p == VAR_ASSIGN_OP)
886 return (TOKTYPE_VARVAL);
887 }
888
889 /* Didn't find '=' operator, user must be inputting an var name */
890 return (TOKTYPE_LVARNAME);
891 }
892
893 /*
894 * Compare two strings acstr and str. acstr is a string of ac_str_t type,
895 * str is a normal string. If issub is B_TRUE, the function checks if
896 * acstr is a sub-string of str, starting from index 0; otherwise it checks
897 * if acstr and str are exactly the same.
898 *
899 * The function returns 0 on success and -1 on failure. When it succeeds,
900 * it also set restp to point to the rest part of the normal string.
901 */
902 static int
compare_string(ac_str_t * acstr,const char * str,boolean_t issub,const char ** restp)903 compare_string(ac_str_t *acstr, const char *str, boolean_t issub,
904 const char **restp)
905 {
906 const char *p, *q;
907
908 for (p = acstr->startp, q = str; (p <= acstr->endp) && (*q != '\0');
909 p++, q++) {
910 if (*p != *q)
911 return (-1);
912 }
913
914 if (p == acstr->endp + 1) {
915 if (*q == '\0' || issub == B_TRUE) {
916 if (restp != NULL)
917 *restp = q;
918 return (0);
919 }
920 }
921
922 return (-1);
923 }
924
925 /*
926 * Use the passed iterp iterator to access a list of string values to
927 * look for those matches with acstr, an user input string to be completed.
928 *
929 * cpl, line, work_end, and cont_suffix are parameters needed by
930 * cpl_add_completion(), which adds matched entries to libtecla.
931 *
932 * Since user input line may have multiple strings, the function is
933 * expected to be called multiple times to match those strings one
934 * by one until the last one is reached.
935 *
936 * The multi-step matching process also means the function should provide
937 * a way to indicate to the caller whether to continue or abort the
938 * whole matching process. The function does that with the following
939 * return values:
940 *
941 * MATCH_DONE - the matching for the whole user input is done. This
942 * can mean either some items are found or none is found.
943 * In either case, the caller shouldn't continue to
944 * match the rest strings, either because there is
945 * no strings left, or because the matching for the
946 * current string failed so there is no need to check
947 * further.
948 * MATCH_CONT - the matching for the current string succeeds, but
949 * user needs to continue to match the rest strings.
950 */
951 static ac_match_result_t
match_string(WordCompletion * cpl,const char * line,int word_end,ac_str_t * acstr,ac_iter_t * iterp,const char * cont_suffix)952 match_string(WordCompletion *cpl, const char *line, int word_end,
953 ac_str_t *acstr, ac_iter_t *iterp, const char *cont_suffix)
954 {
955 const char *str, *restp;
956
957 iterp->reset(iterp);
958
959 if (acstr->strtype == STRTYPE_COMPLETE) {
960 while ((str = iterp->get_nextstr(iterp)) != NULL) {
961 if (!compare_string(acstr, str, B_FALSE, NULL)) {
962 /* Continue to check rest strings */
963 return (MATCH_CONT);
964 }
965 }
966 } else if (acstr->strtype == STRTYPE_NULL) {
967 /* User input nothing. List all available strings */
968 while ((str = iterp->get_nextstr(iterp)) != NULL) {
969 (void) cpl_add_completion(cpl, line,
970 acstr->startp - line, word_end, str,
971 NULL, cont_suffix);
972 }
973 } else if (acstr->strtype == STRTYPE_INCOMPLETE) {
974 while ((str = iterp->get_nextstr(iterp)) != NULL) {
975 if (!compare_string(acstr, str, B_TRUE, &restp)) {
976 /* It matches! Add it. */
977 (void) cpl_add_completion(cpl, line,
978 acstr->startp - line, word_end, restp,
979 NULL, cont_suffix);
980 }
981 }
982 }
983
984 return (MATCH_DONE);
985 }
986
987 /*
988 * This is the interface between filebench and libtecla for auto-
989 * completion. It is called by libtecla whenever user initiates a
990 * auto-completion request(ie., pressing TAB key).
991 *
992 * The function calls parse_user_input() to parse user input into
993 * multiple strings, then it calls match_string() to match each
994 * string in user input in sequence until either the last string
995 * is reached and completed or the the matching fails.
996 */
997 /* ARGSUSED */
CPL_MATCH_FN(command_complete)998 CPL_MATCH_FN(command_complete)
999 {
1000 ac_inputline_t inputline;
1001 ac_tvlist_t *clistp = &fb_cmds, *nlistp;
1002 ac_toktype_t toktype;
1003 ac_iter_t *iterp;
1004 char *cont_suffix;
1005 ac_get_curtok_func_t get_curtok;
1006 int i, ret;
1007
1008 /* Parse user input and save the result in inputline variable. */
1009 parse_user_input(line, word_end, &inputline);
1010
1011 /*
1012 * Match each string in user input against the proper token's
1013 * value list, and continue the loop until either the last string
1014 * is reached and completed or the matching aborts.
1015 */
1016 for (i = 0; i < STR_NUM &&
1017 inputline.strs[i].strtype != STRTYPE_INVALID && clistp != NULL;
1018 i++) {
1019 toktype = clistp->toktype;
1020
1021 /*
1022 * If the current stirng can contain multiple tokens, modify
1023 * the stirng to point to the word being input and return
1024 * its token type.
1025 */
1026 get_curtok = token_info[toktype].get_curtok;
1027 if (get_curtok != NULL)
1028 toktype = (*get_curtok)(&inputline.strs[i]);
1029
1030 iterp = token_info[toktype].iter;
1031 cont_suffix = token_info[toktype].cont_suffix;
1032 /* Return if there is no completion info for the token */
1033 if (iterp == NULL)
1034 break;
1035
1036 iterp->bind(iterp, clistp->vals, &nlistp);
1037 /* Match user string against the token's list */
1038 ret = match_string(cpl, line, word_end, &inputline.strs[i],
1039 iterp, cont_suffix);
1040 if (ret == MATCH_DONE)
1041 return (0);
1042 clistp = nlistp;
1043 }
1044
1045 return (0);
1046 }
1047