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