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 #include <stdio.h>
27 #include <stdlib.h>
28 #include <strings.h>
29 #include <locale.h>
30 #include <ctype.h>
31 #ifdef WITH_LIBTECLA
32 #include <libtecla.h>
33 #endif
34 #include "idmap_engine.h"
35 
36 /* The maximal line length. Longer lines may not be parsed OK. */
37 #define	MAX_CMD_LINE_SZ 1023
38 
39 #ifdef WITH_LIBTECLA
40 #define	MAX_HISTORY_LINES 1023
41 static GetLine * gl_h;
42 /* LINTED E_STATIC_UNUSED */
43 #endif
44 
45 /* Array for arguments of the actuall command */
46 static char ** my_argv;
47 /* Allocated size for my_argv */
48 static int my_argv_size = 16;
49 /* Actuall length of my_argv */
50 static int my_argc;
51 
52 /* Array for subcommands */
53 static cmd_ops_t *my_comv;
54 /* my_comc length */
55 static int my_comc;
56 
57 /* Input filename specified by the -f flag */
58 static char *my_filename;
59 
60 /*
61  * Batch mode means reading file, stdin or libtecla input. Shell input is
62  * a non-batch mode.
63  */
64 static int my_batch_mode;
65 
66 /* Array of all possible flags */
67 static flag_t flags[FLAG_ALPHABET_SIZE];
68 
69 /* getopt variables */
70 extern char *optarg;
71 extern int optind, optopt, opterr;
72 
73 /* Fill the flags array: */
74 static int
options_parse(int argc,char * argv[],const char * options)75 options_parse(int argc, char *argv[], const char *options)
76 {
77 	int c;
78 
79 	optind = 1;
80 
81 	while ((c = getopt(argc, argv, options)) != EOF) {
82 		switch (c) {
83 		case '?':
84 			return (-1);
85 		case ':':
86 	/* This is relevant only if options starts with ':': */
87 			(void) fprintf(stderr,
88 			    gettext("Option %s: missing parameter\n"),
89 			    argv[optind - 1]);
90 			return (-1);
91 		default:
92 			if (optarg == NULL)
93 				flags[c] = FLAG_SET;
94 			else
95 				flags[c] = optarg;
96 
97 		}
98 	}
99 	return (optind);
100 }
101 
102 /* Unset all flags */
103 static void
options_clean()104 options_clean()
105 {
106 	(void) memset(flags, 0, FLAG_ALPHABET_SIZE * sizeof (flag_t));
107 }
108 
109 /* determine which subcommand is argv[0] and execute its handler */
110 static int
run_command(int argc,char ** argv,cmd_pos_t * pos)111 run_command(int argc, char **argv, cmd_pos_t *pos)
112 {
113 	int i;
114 
115 	if (argc == 0) {
116 		if (my_batch_mode)
117 			return (0);
118 		return (-1);
119 	}
120 	for (i = 0; i < my_comc; i++) {
121 		int optind;
122 		int rc;
123 
124 		if (strcmp(my_comv[i].cmd, argv[0]) != 0)
125 			continue;
126 
127 		/* We found it. Now execute the handler. */
128 		options_clean();
129 		optind = options_parse(argc, argv, my_comv[i].options);
130 		if (optind < 0) {
131 			return (-1);
132 		}
133 
134 		rc = my_comv[i].p_do_func(flags,
135 		    argc - optind,
136 		    argv + optind,
137 		    pos);
138 
139 		return (rc);
140 	}
141 
142 	(void) fprintf(stderr, gettext("Unknown command %s\n"),
143 	    argv[0]);
144 
145 	return (-1);
146 
147 }
148 
149 /*
150  * Read another parameter from "from", up to a space char (unless it
151  * is quoted). Duplicate it to "to". Remove quotation, if any.
152  */
153 static int
get_param(char ** to,const char * from)154 get_param(char **to, const char *from)
155 {
156 	int to_i, from_i;
157 	char c;
158 	int last_slash = 0; 	/* Preceded by a slash? */
159 	int in_string = 0;	/* Inside quites? */
160 	int is_param = 0;
161 	size_t buf_size = 20;	/* initial length of the buffer. */
162 	char *buf = (char *)malloc(buf_size * sizeof (char));
163 
164 	from_i = 0;
165 	while (isspace(from[from_i]))
166 		from_i++;
167 
168 	for (to_i = 0; '\0' != from[from_i]; from_i++) {
169 		c = from[from_i];
170 
171 		if (to_i >= buf_size - 1) {
172 			buf_size *= 2;
173 			buf = (char *)realloc(buf, buf_size * sizeof (char));
174 		}
175 
176 		if (c == '"' && !last_slash) {
177 			in_string = !in_string;
178 			is_param = 1;
179 			continue;
180 
181 		} else if (c == '\\' && !last_slash) {
182 			last_slash = 1;
183 			continue;
184 
185 		} else if (!last_slash && !in_string && isspace(c)) {
186 			break;
187 		}
188 
189 		buf[to_i++] = from[from_i];
190 		last_slash = 0;
191 
192 	}
193 
194 	if (to_i == 0 && !is_param) {
195 		free(buf);
196 		*to = NULL;
197 		return (0);
198 	}
199 
200 	buf[to_i] = '\0';
201 	*to = buf;
202 
203 	if (in_string)
204 		return (-1);
205 
206 	return (from_i);
207 }
208 
209 /*
210  * Split a string to a parameter array and append it to the specified position
211  * of the array
212  */
213 static int
line2array(const char * line)214 line2array(const char *line)
215 {
216 	const char *cur;
217 	char *param;
218 	int len;
219 
220 	for (cur = line; len = get_param(&param, cur); cur += len) {
221 		if (my_argc >= my_argv_size) {
222 			my_argv_size *= 2;
223 			my_argv = (char **)realloc(my_argv,
224 			    my_argv_size * sizeof (char *));
225 		}
226 
227 		my_argv[my_argc] = param;
228 		++my_argc;
229 
230 		/* quotation not closed */
231 		if (len < 0)
232 			return (-1);
233 
234 	}
235 	return (0);
236 
237 }
238 
239 /* Clean all aruments from my_argv. Don't deallocate my_argv itself. */
240 static void
my_argv_clean()241 my_argv_clean()
242 {
243 	int i;
244 	for (i = 0; i < my_argc; i++) {
245 		free(my_argv[i]);
246 		my_argv[i] = NULL;
247 	}
248 	my_argc = 0;
249 }
250 
251 
252 #ifdef WITH_LIBTECLA
253 /* This is libtecla tab completion. */
254 static
CPL_MATCH_FN(command_complete)255 CPL_MATCH_FN(command_complete)
256 {
257 	/*
258 	 * WordCompletion *cpl; const char *line; int word_end are
259 	 * passed from the CPL_MATCH_FN macro.
260 	 */
261 	int i;
262 	char *prefix;
263 	int prefix_l;
264 
265 	/* We go on even if quotation is not closed */
266 	(void) line2array(line);
267 
268 
269 	/* Beginning of the line: */
270 	if (my_argc == 0) {
271 		for (i = 0; i < my_comc; i++)
272 			(void) cpl_add_completion(cpl, line, word_end,
273 			    word_end, my_comv[i].cmd, "", " ");
274 		goto cleanup;
275 	}
276 
277 	/* Is there something to complete? */
278 	if (isspace(line[word_end - 1]))
279 		goto cleanup;
280 
281 	prefix = my_argv[my_argc - 1];
282 	prefix_l = strlen(prefix);
283 
284 	/* Subcommand name: */
285 	if (my_argc == 1) {
286 		for (i = 0; i < my_comc; i++)
287 			if (strncmp(prefix, my_comv[i].cmd, prefix_l) == 0)
288 				(void) cpl_add_completion(cpl, line,
289 				    word_end - prefix_l,
290 				    word_end, my_comv[i].cmd + prefix_l,
291 				    "", " ");
292 		goto cleanup;
293 	}
294 
295 	/* Long options: */
296 	if (prefix[0] == '-' && prefix [1] == '-') {
297 		char *options2 = NULL;
298 		char *paren;
299 		char *thesis;
300 		int i;
301 
302 		for (i = 0; i < my_comc; i++)
303 			if (0 == strcmp(my_comv[i].cmd, my_argv[0])) {
304 				options2 = strdup(my_comv[i].options);
305 				break;
306 			}
307 
308 		/* No such subcommand, or not enough memory: */
309 		if (options2 == NULL)
310 			goto cleanup;
311 
312 		for (paren = strchr(options2, '(');
313 		    paren && ((thesis = strchr(paren + 1, ')')) != NULL);
314 		    paren = strchr(thesis + 1, '(')) {
315 		/* Short option or thesis must precede, so this is safe: */
316 			*(paren - 1) = '-';
317 			*paren = '-';
318 			*thesis = '\0';
319 			if (strncmp(paren - 1, prefix, prefix_l) == 0) {
320 				(void) cpl_add_completion(cpl, line,
321 				    word_end - prefix_l,
322 				    word_end, paren - 1 + prefix_l, "", " ");
323 			}
324 		}
325 		free(options2);
326 
327 		/* "--" is a valid completion */
328 		if (prefix_l == 2) {
329 			(void) cpl_add_completion(cpl, line,
330 			    word_end - 2,
331 			    word_end, "", "", " ");
332 		}
333 
334 	}
335 
336 cleanup:
337 	my_argv_clean();
338 	return (0);
339 }
340 
341 /* libtecla subshell: */
342 static int
interactive_interp()343 interactive_interp()
344 {
345 	int rc = 0;
346 	char *prompt;
347 	const char *line;
348 
349 	(void) sigset(SIGINT, SIG_IGN);
350 
351 	gl_h = new_GetLine(MAX_CMD_LINE_SZ, MAX_HISTORY_LINES);
352 
353 	if (gl_h == NULL) {
354 		(void) fprintf(stderr,
355 		    gettext("Error reading terminal: %s.\n"),
356 		    gl_error_message(gl_h, NULL, 0));
357 		return (-1);
358 	}
359 
360 	(void) gl_customize_completion(gl_h, NULL, command_complete);
361 
362 	for (;;) {
363 new_line:
364 		my_argv_clean();
365 		prompt = "> ";
366 continue_line:
367 		line = gl_get_line(gl_h, prompt, NULL, -1);
368 
369 		if (line == NULL) {
370 			switch (gl_return_status(gl_h)) {
371 			case GLR_SIGNAL:
372 				gl_abandon_line(gl_h);
373 				goto new_line;
374 
375 			case GLR_EOF:
376 				(void) line2array("exit");
377 				break;
378 
379 			case GLR_ERROR:
380 				(void) fprintf(stderr,
381 				    gettext("Error reading terminal: %s.\n"),
382 				    gl_error_message(gl_h, NULL, 0));
383 				rc = -1;
384 				goto end_of_input;
385 			default:
386 				(void) fprintf(stderr, "Internal error.\n");
387 				exit(1);
388 			}
389 		} else {
390 			if (line2array(line) < 0) {
391 				(void) fprintf(stderr,
392 				    gettext("Quotation not closed\n"));
393 				goto new_line;
394 			}
395 			if (my_argc == 0) {
396 				goto new_line;
397 			}
398 			if (strcmp(my_argv[my_argc-1], "\n") == 0) {
399 				my_argc--;
400 				free(my_argv[my_argc]);
401 				(void) strcpy(prompt, "> ");
402 				goto continue_line;
403 			}
404 		}
405 
406 		rc = run_command(my_argc, my_argv, NULL);
407 
408 		if (strcmp(my_argv[0], "exit") == 0 && rc == 0) {
409 			break;
410 		}
411 
412 	}
413 
414 end_of_input:
415 	gl_h = del_GetLine(gl_h);
416 	my_argv_clean();
417 	return (rc);
418 }
419 #endif
420 
421 /* Interpretation of a source file given by "name" */
422 static int
source_interp(const char * name)423 source_interp(const char *name)
424 {
425 	FILE *f;
426 	int is_stdin;
427 	int rc = -1;
428 	char line[MAX_CMD_LINE_SZ];
429 	cmd_pos_t pos;
430 
431 	if (name == NULL || strcmp("-", name) == 0) {
432 		f = stdin;
433 		is_stdin = 1;
434 	} else {
435 		is_stdin = 0;
436 		f = fopen(name, "r");
437 		if (f == NULL) {
438 			perror(name);
439 			return (-1);
440 		}
441 	}
442 
443 	pos.linenum = 0;
444 	pos.line = line;
445 
446 	while (fgets(line, MAX_CMD_LINE_SZ, f)) {
447 		pos.linenum ++;
448 
449 		if (line2array(line) < 0) {
450 			(void) fprintf(stderr,
451 			    gettext("Quotation not closed\n"));
452 			my_argv_clean();
453 			continue;
454 		}
455 
456 		/* We do not wan't "\n" as the last parameter */
457 		if (my_argc != 0 && strcmp(my_argv[my_argc-1], "\n") == 0) {
458 			my_argc--;
459 			free(my_argv[my_argc]);
460 			continue;
461 		}
462 
463 		if (my_argc != 0 && strcmp(my_argv[0], "exit") == 0) {
464 			rc = 0;
465 			my_argv_clean();
466 			break;
467 		}
468 
469 		rc = run_command(my_argc, my_argv, &pos);
470 		my_argv_clean();
471 	}
472 
473 	if (my_argc > 0) {
474 		(void) fprintf(stderr, gettext("Line continuation missing\n"));
475 		rc = 1;
476 		my_argv_clean();
477 	}
478 
479 	if (!is_stdin)
480 		(void) fclose(f);
481 
482 	return (rc);
483 }
484 
485 /*
486  * Initialize the engine.
487  * comc, comv is the array of subcommands and its length,
488  * argc, argv are arguments to main to be scanned for -f filename and
489  *    the length og the array,
490  * is_batch_mode passes to the caller the information if the
491  *    batch mode is on.
492  *
493  * Return values:
494  * 0: ... OK
495  * IDMAP_ENG_ERROR: error and message printed already
496  * IDMAP_ENG_ERROR_SILENT: error and message needs to be printed
497  *
498  */
499 
500 int
engine_init(int comc,cmd_ops_t * comv,int argc,char ** argv,int * is_batch_mode)501 engine_init(int comc, cmd_ops_t *comv, int argc, char **argv,
502     int *is_batch_mode)
503 {
504 	int c;
505 
506 	my_comc = comc;
507 	my_comv = comv;
508 
509 	my_argc = 0;
510 	my_argv = (char **)calloc(my_argv_size, sizeof (char *));
511 
512 	if (argc < 1) {
513 		my_filename = NULL;
514 		if (isatty(fileno(stdin))) {
515 #ifdef WITH_LIBTECLA
516 			my_batch_mode = 1;
517 #else
518 			my_batch_mode = 0;
519 			return (IDMAP_ENG_ERROR_SILENT);
520 #endif
521 		} else
522 			my_batch_mode = 1;
523 
524 		goto the_end;
525 	}
526 
527 	my_batch_mode = 0;
528 
529 	optind = 0;
530 	while ((c = getopt(argc, argv,
531 	    "f:(command-file)")) != EOF) {
532 		switch (c) {
533 		case '?':
534 			return (IDMAP_ENG_ERROR);
535 		case 'f':
536 			my_batch_mode = 1;
537 			my_filename = optarg;
538 			break;
539 		default:
540 			(void) fprintf(stderr, "Internal error.\n");
541 			exit(1);
542 		}
543 	}
544 
545 the_end:
546 
547 	if (is_batch_mode != NULL)
548 		*is_batch_mode = my_batch_mode;
549 	return (0);
550 }
551 
552 /* finitialize the engine */
553 int
engine_fini()554 engine_fini()
555 {
556 	my_argv_clean();
557 	free(my_argv);
558 	return (0);
559 }
560 
561 /*
562  * Interpret the subcommands defined by the arguments, unless
563  * my_batch_mode was set on in egnine_init.
564  */
565 int
run_engine(int argc,char ** argv)566 run_engine(int argc, char **argv)
567 {
568 	int rc = -1;
569 
570 	if (my_batch_mode) {
571 #ifdef WITH_LIBTECLA
572 		if (isatty(fileno(stdin)))
573 			rc = interactive_interp();
574 		else
575 #endif
576 			rc = source_interp(my_filename);
577 		goto cleanup;
578 	}
579 
580 	rc = run_command(argc, argv, NULL);
581 
582 cleanup:
583 	return (rc);
584 }
585