Home | History | Annotate | Download | only in idmap
      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