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