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
options_parse(int argc,char * argv[],const char * options)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
options_clean()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
run_command(int argc,char ** argv,cmd_pos_t * pos)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
get_param(char ** to,const char * from)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
line2array(const char * line)218 line2array(const char *line)
219 {
220 const char *cur;
221 char *param;
222 int len;
223
224 for (cur = line; len = get_param(¶m, 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
my_argv_clean()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
CPL_MATCH_FN(command_complete)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
interactive_interp()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
source_interp(const char * name)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
engine_init(int comc,cmd_ops_t * comv,int argc,char ** argv,int * is_batch_mode)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
engine_fini()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
run_engine(int argc,char ** argv)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