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