xref: /onnv-gate/usr/src/cmd/filebench/common/auto_comp.c (revision 6750:bd49b8b8d6f3)
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