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