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 75 options_parse(int argc, char *argv[], const char *options) 76 { 77 char 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 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 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 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 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 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 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 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 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 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 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 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