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