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(¶m, 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