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