halcmd.c
1 /** This file, 'halcmd.c', is a HAL component that provides a simple 2 command line interface to the hal. It is a user space component. 3 For detailed instructions, see "man halcmd". 4 */ 5 6 /** Copyright (C) 2003 John Kasunich 7 <jmkasunich AT users DOT sourceforge DOT net> 8 9 Other contributers: 10 Martin Kuhnle 11 <mkuhnle AT users DOT sourceforge DOT net> 12 Alex Joni 13 <alex_joni AT users DOT sourceforge DOT net> 14 Benn Lipkowitz 15 <fenn AT users DOT sourceforge DOT net> 16 Stephen Wille Padnos 17 <swpadnos AT users DOT sourceforge DOT net> 18 */ 19 20 /** This program is free software; you can redistribute it and/or 21 modify it under the terms of version 2 of the GNU General 22 Public License as published by the Free Software Foundation. 23 This library is distributed in the hope that it will be useful, 24 but WITHOUT ANY WARRANTY; without even the implied warranty of 25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 GNU General Public License for more details. 27 28 You should have received a copy of the GNU General Public 29 License along with this library; if not, write to the Free Software 30 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 31 32 THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR 33 ANY HARM OR LOSS RESULTING FROM ITS USE. IT IS _EXTREMELY_ UNWISE 34 TO RELY ON SOFTWARE ALONE FOR SAFETY. Any machinery capable of 35 harming persons must have provisions for completely removing power 36 from all motors, etc, before persons enter any danger area. All 37 machinery must be designed to comply with local and national safety 38 codes, and the authors of this software can not, and do not, take 39 any responsibility for such compliance. 40 41 This code was written as part of the EMC HAL project. For more 42 information, go to www.linuxcnc.org. 43 */ 44 45 #include "config.h" 46 47 #ifndef NO_INI 48 #include "inifile.h" /* iniFind() from libnml */ 49 FILE *halcmd_inifile = NULL; 50 #endif 51 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <ctype.h> 55 #include <string.h> 56 #include <sys/stat.h> 57 #include <sys/wait.h> 58 #include <unistd.h> 59 #include <fcntl.h> 60 #include <signal.h> 61 #include <errno.h> 62 #include <time.h> 63 #include <fnmatch.h> 64 #include <search.h> 65 66 #include "rtapi.h" /* RTAPI realtime OS API */ 67 #include "hal.h" /* HAL public API decls */ 68 #include "../hal_priv.h" /* private HAL decls */ 69 #include "halcmd_commands.h" 70 71 /*********************************************************************** 72 * LOCAL FUNCTION DECLARATIONS * 73 ************************************************************************/ 74 75 /* These functions are used internally by this file. The code is at 76 the end of the file. */ 77 78 /*********************************************************************** 79 * GLOBAL VARIABLES * 80 ************************************************************************/ 81 82 int comp_id = -1; /* -1 means hal_init() not called yet */ 83 int hal_flag = 0; /* used to indicate that halcmd might have the 84 hal mutex, so the sig handler can't just 85 exit, instead it must set 'done' */ 86 int halcmd_done = 0; /* used to break out of processing loop */ 87 int scriptmode = 0; /* used to make output "script friendly" (suppress headers) */ 88 int echo_mode = 0; 89 char comp_name[HAL_NAME_LEN+1]; /* name for this instance of halcmd */ 90 91 static void quit(int); 92 93 int halcmd_startup(int quiet) { 94 int msg_lvl_save=rtapi_get_msg_level(); 95 /* register signal handlers - if the process is killed 96 we need to call hal_exit() to free the shared memory */ 97 signal(SIGINT, quit); 98 signal(SIGTERM, quit); 99 signal(SIGPIPE, SIG_IGN); 100 /* at this point all options are parsed, connect to HAL */ 101 /* create a unique module name, to allow for multiple halcmd's */ 102 snprintf(comp_name, sizeof(comp_name), "halcmd%d", getpid()); 103 /* tell the signal handler that we might have the mutex */ 104 hal_flag = 1; 105 if (quiet) rtapi_set_msg_level(RTAPI_MSG_NONE); 106 /* connect to the HAL */ 107 comp_id = hal_init(comp_name); 108 if (quiet) rtapi_set_msg_level(msg_lvl_save); 109 /* done with mutex */ 110 hal_flag = 0; 111 /* check result */ 112 if (comp_id < 0) { 113 if (!quiet) { 114 fprintf(stderr, "halcmd: hal_init() failed: %d\n", comp_id ); 115 fprintf(stderr, "NOTE: 'rtapi' kernel module must be loaded\n" ); 116 } 117 return -EINVAL; 118 } 119 hal_ready(comp_id); 120 return 0; 121 } 122 123 void halcmd_shutdown(void) { 124 /* tell the signal handler we might have the mutex */ 125 hal_flag = 1; 126 hal_exit(comp_id); 127 } 128 129 /* parse_cmd() decides which command has been invoked, gathers any 130 arguments that are needed, and calls a function to execute the 131 command. 132 */ 133 134 #define FUNCT(x) ((halcmd_func_t)x) 135 136 struct halcmd_command halcmd_commands[] = { 137 {"addf", FUNCT(do_addf_cmd), A_TWO | A_PLUS }, 138 {"alias", FUNCT(do_alias_cmd), A_THREE }, 139 {"delf", FUNCT(do_delf_cmd), A_TWO | A_OPTIONAL }, 140 {"delsig", FUNCT(do_delsig_cmd), A_ONE }, 141 {"echo", FUNCT(do_echo_cmd), A_ZERO }, 142 {"getp", FUNCT(do_getp_cmd), A_ONE }, 143 {"gets", FUNCT(do_gets_cmd), A_ONE }, 144 {"ptype", FUNCT(do_ptype_cmd), A_ONE }, 145 {"stype", FUNCT(do_stype_cmd), A_ONE }, 146 {"help", FUNCT(do_help_cmd), A_ONE | A_OPTIONAL }, 147 {"linkpp", FUNCT(do_linkpp_cmd), A_TWO | A_REMOVE_ARROWS }, 148 {"linkps", FUNCT(do_linkps_cmd), A_TWO | A_REMOVE_ARROWS }, 149 {"linksp", FUNCT(do_linksp_cmd), A_TWO | A_REMOVE_ARROWS }, 150 {"list", FUNCT(do_list_cmd), A_ONE | A_PLUS }, 151 {"loadrt", FUNCT(do_loadrt_cmd), A_ONE | A_PLUS }, 152 {"loadusr", FUNCT(do_loadusr_cmd), A_PLUS | A_TILDE }, 153 {"lock", FUNCT(do_lock_cmd), A_ONE | A_OPTIONAL }, 154 {"net", FUNCT(do_net_cmd), A_ONE | A_PLUS | A_REMOVE_ARROWS }, 155 {"newsig", FUNCT(do_newsig_cmd), A_TWO }, 156 {"save", FUNCT(do_save_cmd), A_TWO | A_OPTIONAL | A_TILDE }, 157 {"setexact_for_test_suite_only", FUNCT(do_setexact_cmd), A_ZERO }, 158 {"setp", FUNCT(do_setp_cmd), A_TWO }, 159 {"sets", FUNCT(do_sets_cmd), A_TWO }, 160 {"show", FUNCT(do_show_cmd), A_ONE | A_OPTIONAL | A_PLUS}, 161 {"source", FUNCT(do_source_cmd), A_ONE | A_TILDE }, 162 {"start", FUNCT(do_start_cmd), A_ZERO}, 163 {"status", FUNCT(do_status_cmd), A_ONE | A_OPTIONAL }, 164 {"stop", FUNCT(do_stop_cmd), A_ZERO}, 165 {"unalias", FUNCT(do_unalias_cmd), A_TWO }, 166 {"unecho", FUNCT(do_unecho_cmd), A_ZERO }, 167 {"unlinkp", FUNCT(do_unlinkp_cmd), A_ONE }, 168 {"unload", FUNCT(do_unload_cmd), A_ONE }, 169 {"unloadrt", FUNCT(do_unloadrt_cmd), A_ONE }, 170 {"unloadusr", FUNCT(do_unloadusr_cmd), A_ONE }, 171 {"unlock", FUNCT(do_unlock_cmd), A_ONE | A_OPTIONAL }, 172 {"waitusr", FUNCT(do_waitusr_cmd), A_ONE }, 173 }; 174 int halcmd_ncommands = (sizeof(halcmd_commands) / sizeof(halcmd_commands[0])); 175 176 static int sort_command(const void *a, const void *b) { 177 const struct halcmd_command *ca = a, *cb = b; 178 return strcmp(ca->name, cb->name); 179 } 180 181 static int compare_command(const void *namep, const void *commandp) { 182 const char *name = namep; 183 const struct halcmd_command *command = commandp; 184 return strcmp(name, command->name); 185 } 186 187 188 pid_t hal_systemv_nowait(char *const argv[]) { 189 pid_t pid; 190 int n; 191 192 /* now we need to fork, and then exec .... */ 193 /* disconnect from the HAL shmem area before forking */ 194 hal_exit(comp_id); 195 comp_id = 0; 196 /* now the fork() */ 197 pid = fork(); 198 if ( pid < 0 ) { 199 /* fork failed */ 200 halcmd_error("fork() failed\n"); 201 /* reconnect to the HAL shmem area */ 202 comp_id = hal_init(comp_name); 203 if (comp_id < 0) { 204 fprintf(stderr, "halcmd: hal_init() failed after fork: %d\n", 205 comp_id ); 206 exit(-1); 207 } 208 hal_ready(comp_id); 209 return -1; 210 } 211 if ( pid == 0 ) { 212 /* child process */ 213 /* print debugging info if "very verbose" (-V) */ 214 for(n=0; argv[n] != NULL; n++) { 215 rtapi_print_msg(RTAPI_MSG_DBG, "%s ", argv[n] ); 216 } 217 if (n == 0) { 218 halcmd_error("hal_systemv_nowait: empty argv array passed in\n"); 219 exit(1); 220 } 221 rtapi_print_msg(RTAPI_MSG_DBG, "\n" ); 222 /* call execv() to invoke command */ 223 execvp(argv[0], argv); 224 /* should never get here */ 225 halcmd_error("execv(%s): %s\n", argv[0], strerror(errno) ); 226 exit(1); 227 } 228 /* parent process */ 229 /* reconnect to the HAL shmem area */ 230 comp_id = hal_init(comp_name); 231 232 return pid; 233 } 234 235 int hal_systemv(char *const argv[]) { 236 pid_t pid; 237 int status; 238 int retval; 239 240 /* do the fork */ 241 pid = hal_systemv_nowait(argv); 242 /* this is the parent process, wait for child to end */ 243 retval = waitpid ( pid, &status, 0 ); 244 if (comp_id < 0) { 245 fprintf(stderr, "halcmd: hal_init() failed after systemv: %d\n", comp_id ); 246 exit(-1); 247 } 248 hal_ready(comp_id); 249 /* check result of waitpid() */ 250 if ( retval < 0 ) { 251 halcmd_error("waitpid(%d) failed: %s\n", pid, strerror(errno) ); 252 return -1; 253 } 254 if ( WIFEXITED(status) == 0 ) { 255 halcmd_error("child did not exit normally\n"); 256 return -1; 257 } 258 retval = WEXITSTATUS(status); 259 if ( retval != 0 ) { 260 halcmd_error("exit value: %d\n", retval ); 261 return -1; 262 } 263 return 0; 264 } 265 266 267 /*********************************************************************** 268 * MAIN PROGRAM * 269 ************************************************************************/ 270 271 /* signal handler */ 272 static void quit(int sig) 273 { 274 if ( hal_flag ) { 275 /* this process might have the hal mutex, so just set the 276 'done' flag and return, exit after mutex work finishes */ 277 halcmd_done = 1; 278 } else { 279 /* don't have to worry about the mutex, but if we just 280 return, we might return into the fgets() and wait 281 all day instead of exiting. So we exit from here. */ 282 if ( comp_id > 0 ) { 283 hal_exit(comp_id); 284 } 285 _exit(1); 286 } 287 } 288 /* main() is responsible for parsing command line options, and then 289 parsing either a single command from the command line or a series 290 of commands from a file or standard input. It breaks the command[s] 291 into tokens, and passes them to parse_cmd() which does the actual 292 work for each command. 293 */ 294 295 static int count_args(char **argv) { 296 int i = 0; 297 while(argv[i] && argv[i][0]) i++; 298 return i; 299 } 300 301 #define ARG(i) (argc > i ? argv[i] : 0) 302 #define REST(i) (argc > i ? argv + i : argv + argc) 303 304 static int parse_cmd1(char **argv) { 305 struct halcmd_command *command = bsearch(argv[0], 306 halcmd_commands, halcmd_ncommands, 307 sizeof(struct halcmd_command), compare_command); 308 int argc = count_args(argv); 309 310 if(argc == 0) 311 return 0; 312 313 if(!command) { 314 // special case: pin/param = newvalue 315 if(argc == 3 && !strcmp(argv[1], "=")) { 316 return do_setp_cmd(argv[0], argv[2]); 317 } else { 318 halcmd_error("Unknown command '%s'\n", argv[0]); 319 return -EINVAL; 320 } 321 } else { 322 int result = -EINVAL; 323 int is_optional = command->type & A_OPTIONAL, 324 is_plus = command->type & A_PLUS, 325 nargs = command->type & 0xff, 326 posargs; 327 328 if(command->type & A_REMOVE_ARROWS) { 329 int s, d; 330 for(s=d=0; argv[s] && argv[s][0]; s++) { 331 if(!strcmp(argv[s], "<=") || 332 !strcmp(argv[s], "=>") || 333 !strcmp(argv[s], "<=>")) { 334 continue; 335 } else { 336 argv[d++] = argv[s]; 337 } 338 } 339 argv[d] = 0; 340 argc = d; 341 } 342 343 posargs = argc - 1; 344 if(posargs < nargs && !is_optional) { 345 halcmd_error("%s requires %s%d arguments, %d given\n", 346 command->name, is_plus ? "at least " : "", nargs, posargs); 347 return -EINVAL; 348 } 349 350 if(posargs > nargs && !is_plus) { 351 halcmd_error("%s requires %s%d arguments, %d given\n", 352 command->name, is_optional ? "at most " : "", nargs, posargs); 353 return -EINVAL; 354 } 355 356 #ifndef NO_INI 357 if(command->type & A_TILDE) 358 { 359 int i; 360 for(i=0; i<argc; i++) 361 { 362 char *buf = malloc(LINELEN); 363 TildeExpansion(argv[i], buf, LINELEN); 364 argv[i] = buf; 365 } 366 } 367 #endif 368 if(!strcmp(command->name, "echo")) {echo_mode = 1;} 369 if(!strcmp(command->name, "unecho")) {echo_mode = 0;} 370 switch(nargs | is_plus) { 371 case A_ZERO: { 372 result = command->func(); 373 break; 374 } 375 376 case A_PLUS: { 377 int(*f)(char **args) = (int(*)(char**))command->func; 378 result = f(REST(1)); 379 break; 380 } 381 382 case A_ONE: { 383 int(*f)(char *arg) = (int(*)(char*))command->func; 384 result = f(ARG(1)); 385 break; 386 } 387 388 case A_ONE | A_PLUS: { 389 int(*f)(char *arg, char **rest) = 390 (int(*)(char*,char**))command->func; 391 result = f(ARG(1), REST(2)); 392 break; 393 } 394 395 case A_TWO: { 396 int(*f)(char *arg, char *arg2) = 397 (int(*)(char*,char*))command->func; 398 result = f(ARG(1), ARG(2)); 399 break; 400 } 401 402 case A_TWO | A_PLUS: { 403 int(*f)(char *arg, char *arg2, char **rest) = 404 (int(*)(char*,char*,char**))command->func; 405 result = f(ARG(1), ARG(2), REST(3)); 406 break; 407 } 408 409 case A_THREE: { 410 int(*f)(char *arg, char *arg2, char *arg3) = 411 (int(*)(char*,char*,char*))command->func; 412 result = f(ARG(1), ARG(2), ARG(3)); 413 break; 414 } 415 416 case A_THREE | A_PLUS: { 417 int(*f)(char *arg, char *arg2, char *arg3, char **rest) = 418 (int(*)(char*,char*,char*,char**))command->func; 419 result = f(ARG(1), ARG(2), ARG(3), REST(4)); 420 break; 421 } 422 423 default: 424 halcmd_error("BUG: unchandled case: command=%s type=0x%x", 425 command->name, command->type); 426 result = -EINVAL; 427 } 428 429 #ifndef NO_TILDE 430 if(command->type & A_TILDE) 431 { 432 int i; 433 for(i=0; i<argc; i++) 434 { 435 free(argv[i]); 436 } 437 } 438 #endif 439 440 return result; 441 } 442 } 443 444 int halcmd_parse_cmd(char *tokens[]) 445 { 446 int retval; 447 static int first_time = 1; 448 449 if(first_time) { 450 /* ensure that commands is sorted when it is searched later */ 451 qsort(halcmd_commands, halcmd_ncommands, 452 sizeof(struct halcmd_command), sort_command); 453 first_time = 0; 454 } 455 456 hal_flag = 1; 457 retval = parse_cmd1(tokens); 458 hal_flag = 0; 459 return retval; 460 } 461 462 /* tokenize() sets an array of pointers to each non-whitespace 463 token in the input line. It expects that variable substitution 464 and comment removal have already been done, and that any 465 trailing newline has been removed. 466 */ 467 static int tokenize(char *cmd_buf, char **tokens) 468 { 469 enum { BETWEEN_TOKENS, 470 IN_TOKEN, 471 SINGLE_QUOTE, 472 DOUBLE_QUOTE, 473 END_OF_LINE } state; 474 char *cp1; 475 int m; 476 /* convert a line of text into individual tokens */ 477 m = 0; 478 cp1 = cmd_buf; 479 state = BETWEEN_TOKENS; 480 while ( m < MAX_TOK ) { 481 if(*cp1 == '\r') 482 { 483 char nextc = *(cp1+1); 484 if(nextc == '\n' || nextc == '\0') 485 { 486 static int warned=0; 487 if(!warned) 488 halcmd_warning("File contains DOS-style line endings.\n"); 489 warned = 1; 490 } 491 else 492 { 493 halcmd_error("File contains embedded carriage returns.\n"); 494 return -1; 495 } 496 } 497 switch ( state ) { 498 case BETWEEN_TOKENS: 499 if ( *cp1 == '\0' ) { 500 /* end of the line */ 501 state = END_OF_LINE; 502 } else if ( isspace(*cp1) ) { 503 /* whitespace, skip it */ 504 cp1++; 505 } else if ( *cp1 == '\'' ) { 506 /* start of a quoted string and a new token */ 507 tokens[m] = cp1++; 508 state = SINGLE_QUOTE; 509 } else if ( *cp1 == '\"' ) { 510 /* start of a quoted string and a new token */ 511 tokens[m] = cp1++; 512 state = DOUBLE_QUOTE; 513 } else { 514 /* first char of a token */ 515 tokens[m] = cp1++; 516 state = IN_TOKEN; 517 } 518 break; 519 case IN_TOKEN: 520 if ( *cp1 == '\0' ) { 521 /* end of the line */ 522 m++; 523 state = END_OF_LINE; 524 } else if ( *cp1 == '\'' ) { 525 /* start of a quoted string */ 526 cp1++; 527 state = SINGLE_QUOTE; 528 } else if ( *cp1 == '\"' ) { 529 /* start of a quoted string */ 530 cp1++; 531 state = DOUBLE_QUOTE; 532 } else if ( isspace(*cp1) ) { 533 /* end of the current token */ 534 *cp1++ = '\0'; 535 m++; 536 state = BETWEEN_TOKENS; 537 } else { 538 /* ordinary character */ 539 cp1++; 540 } 541 break; 542 case SINGLE_QUOTE: 543 if ( *cp1 == '\0' ) { 544 /* end of the line */ 545 m++; 546 state = END_OF_LINE; 547 } else if ( *cp1 == '\'' ) { 548 /* end of quoted string */ 549 cp1++; 550 state = IN_TOKEN; 551 } else { 552 /* ordinary character */ 553 cp1++; 554 } 555 break; 556 case DOUBLE_QUOTE: 557 if ( *cp1 == '\0' ) { 558 /* end of the line */ 559 m++; 560 state = END_OF_LINE; 561 } else if ( *cp1 == '\"' ) { 562 /* end of quoted string */ 563 cp1++; 564 state = IN_TOKEN; 565 } else { 566 /* ordinary character, copy to buffer */ 567 cp1++; 568 } 569 break; 570 case END_OF_LINE: 571 tokens[m++] = cp1; 572 break; 573 default: 574 /* should never get here */ 575 state = BETWEEN_TOKENS; 576 } 577 } 578 if ( state != END_OF_LINE ) { 579 halcmd_error("too many tokens on line\n"); 580 return -1; 581 } 582 return 0; 583 } 584 585 /* strip_comments() removes any comment in the string. It also 586 removes any trailing newline. Single- or double-quoted strings 587 are respected - a '#' inside a quoted string does not indicate 588 a comment. 589 */ 590 static int strip_comments ( char *buf ) 591 { 592 enum { NORMAL, 593 SINGLE_QUOTE, 594 DOUBLE_QUOTE 595 } state; 596 char *cp1; 597 598 cp1 = buf; 599 state = NORMAL; 600 while ( 1 ) { 601 switch ( state ) { 602 case NORMAL: 603 if (( *cp1 == '#' ) || ( *cp1 == '\n' ) || ( *cp1 == '\0' )) { 604 /* end of the line */ 605 *cp1 = '\0'; 606 return 0; 607 } else if ( *cp1 == '\'' ) { 608 /* start of a quoted string */ 609 cp1++; 610 state = SINGLE_QUOTE; 611 } else if ( *cp1 == '\"' ) { 612 /* start of a quoted string */ 613 cp1++; 614 state = DOUBLE_QUOTE; 615 } else { 616 /* normal character */ 617 cp1++; 618 } 619 break; 620 case SINGLE_QUOTE: 621 if (( *cp1 == '\n' ) || ( *cp1 == '\0' )) { 622 /* end of the line, unterminated quoted string */ 623 *cp1 = '\0'; 624 return -1; 625 } else if ( *cp1 == '\'' ) { 626 /* end of quoted string */ 627 cp1++; 628 state = NORMAL; 629 } else { 630 /* ordinary character */ 631 cp1++; 632 } 633 break; 634 case DOUBLE_QUOTE: 635 if (( *cp1 == '\n' ) || ( *cp1 == '\0' )) { 636 /* end of the line, unterminated quoted string */ 637 *cp1 = '\0'; 638 return -1; 639 } else if ( *cp1 == '\"' ) { 640 /* end of quoted string */ 641 cp1++; 642 state = NORMAL; 643 } else { 644 /* ordinary character */ 645 cp1++; 646 } 647 break; 648 default: 649 /* should never get here */ 650 state = NORMAL; 651 } 652 } 653 } 654 655 /* strlimcpy: 656 a wrapper for strncpy which has two improvements: 657 1) takes two variables for limits, and uses the lower of the two for the limit 658 2) subtracts the number of characters copied from the second limit 659 660 This allows one to keep track of remaining buffer size and use the correct limits 661 simply 662 663 Parameters are: 664 pointer to destination string pointer 665 source string pointer 666 source length to copy 667 pointer to remaining destination space 668 669 return value: 670 -1 if the copy was limited by destination space remaining 671 0 otherwise 672 Side Effects: 673 the value of *destspace will be reduced by the copied length 674 The value of *dest will will be advanced by the copied length 675 676 */ 677 static int strlimcpy(char **dest, char *src, int srclen, int *destspace) { 678 if (*destspace < srclen) { 679 return -1; 680 } else { 681 strncpy(*dest, src, srclen); 682 (*dest)[srclen] = '\0'; 683 srclen = strlen(*dest); /* use the actual number of bytes copied */ 684 *destspace -= srclen; 685 *dest += srclen; 686 } 687 return 0; 688 } 689 690 /* replace_vars: 691 replaces environment and ini var references in source_str. 692 This routine does string replacement only 693 return value is 0 on success (ie, no variable lookups failed) 694 The source string is not modified 695 696 In case of an error, dest_str will contain everything that was 697 successfully replaced, but will not contain anything past the 698 var that caused the error. 699 700 environment vars are in the following formats: 701 $envvar<whitespace> 702 $(envvar)<any char> 703 704 ini vars are in the following formats: 705 [SECTION]VAR<whitespace> 706 [SECTION](VAR)<any char> 707 708 return values: 709 0 success 710 -1 missing close parenthesis 711 -2 null variable name (either environment or ini varname) 712 -3 missing close square bracket 713 -4 environment variable not found 714 -5 ini variable not found 715 -6 replacement would overflow output buffer 716 -7 var name exceeds limit 717 */ 718 static int replace_vars(char *source_str, char *dest_str, int max_chars, char **detail) 719 { 720 int retval = 0, loopcount=0; 721 int next_delim, remaining, buf_space; 722 char *replacement, sec[128], var[128]; 723 static char info[256]; 724 char *sp=source_str, *dp=dest_str, *secP, *varP; 725 const char 726 * words = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"; 727 728 dest_str[max_chars-1] = '\0'; /* make sure there's a terminator */ 729 *dest_str='\0'; /* return null string if input is null string */ 730 buf_space = max_chars-1; /* leave space for terminating null */ 731 while ((remaining = strlen(sp)) > 0) { 732 loopcount++; 733 next_delim=strcspn(sp, "$["); 734 if (strlimcpy(&dp, sp, next_delim, &buf_space) < 0) 735 return -6; 736 sp += next_delim; 737 if (next_delim < remaining) { /* a new delimiter was found */ 738 switch (*sp++) { /* this is the found delimiter, since sp was incremented */ 739 case '$': 740 varP = sp; 741 if (*sp=='(') { /* look for a parenthesized var */ 742 varP=++sp; 743 next_delim=strcspn(varP, ")"); 744 if (next_delim >= strlen(varP)) /* error - no matching parens */ 745 return -1; 746 sp++; 747 } else next_delim = strspn(varP, words); 748 if (next_delim == 0) /* null var name */ 749 return -2; 750 if (next_delim > 127) /* var name too long */ 751 return -7; 752 strncpy(var, varP, next_delim); 753 var[next_delim]='\0'; 754 replacement = getenv(var); 755 if (replacement == NULL) 756 { 757 snprintf(info, sizeof(info), "%s", var); 758 *detail = info; 759 return -4; 760 } 761 if (strlimcpy(&dp, replacement, strlen(replacement), &buf_space) <0) 762 return -6; 763 sp += next_delim; 764 break; 765 case '[': 766 secP = sp; 767 next_delim = strcspn(secP, "]"); 768 if (next_delim >= strlen(secP)) /* error - no matching square bracket */ 769 return -3; 770 if (next_delim > 127) /* section name too long */ 771 return -7; 772 strncpy(sec, secP, next_delim); 773 sec[next_delim]='\0'; 774 sp += next_delim+1; 775 varP = sp; /* should point past the ']' now */ 776 if (*sp=='(') { /* look for a parenthesized var */ 777 varP=++sp; 778 next_delim=strcspn(varP, ")"); 779 if (next_delim > strlen(varP)) /* error - no matching parens */ 780 return -1; 781 sp++; 782 } else next_delim = strspn(varP, words); 783 if (next_delim == 0) 784 return -2; 785 if (next_delim > 127) /* var name too long */ 786 return -7; 787 strncpy(var, varP, next_delim); 788 var[next_delim]='\0'; 789 if ( strlen(sec) > 0 ) { 790 /* get value from ini file */ 791 /* cast to char ptr, we are discarding the 'const' */ 792 replacement = (char *) iniFind(halcmd_inifile, var, sec); 793 } else { 794 /* no section specified */ 795 replacement = (char *) iniFind(halcmd_inifile, var, NULL); 796 } 797 if (replacement==NULL) { 798 *detail = info; 799 snprintf(info, sizeof(info), "[%s]%s", sec, var); 800 return -5; 801 } 802 if (strlimcpy(&dp, replacement, strlen(replacement), &buf_space) < 0) 803 return -6; 804 sp += next_delim; 805 break; 806 } 807 } 808 } 809 return retval; 810 } 811 812 813 static const char *replace_errors[] = { 814 "Missing close parenthesis.\n", 815 "Empty variable name.\n", 816 "Missing close square bracket.\n", 817 "Environment variable '%s' not found.\n", 818 "Ini variable '%s' not found.\n", 819 "Line too long.\n", 820 "Variable name too long.\n", 821 }; 822 823 824 int halcmd_preprocess_line ( char *line, char **tokens ) 825 { 826 int retval; 827 char *detail = NULL; 828 static char cmd_buf[2*MAX_CMD_LEN]; 829 830 /* strip comments and trailing newline (if any) */ 831 retval = strip_comments(line); 832 if (retval != 0) { 833 halcmd_error("unterminated quoted string\n"); 834 return -1; 835 } 836 /* copy to cmd_buf while doing variable replacements */ 837 retval = replace_vars(line, cmd_buf, sizeof(cmd_buf)-2, &detail); 838 if (retval != 0) { 839 if ((retval < 0) && (retval >= -7)) { /* print better replacement errors */ 840 if(detail) { 841 halcmd_error(replace_errors[(-retval) -1], detail); 842 } else { 843 halcmd_error("%s", replace_errors[(-retval) -1]); 844 } 845 } else { 846 halcmd_error("unknown variable replacement error\n"); 847 } 848 return -2; 849 } 850 /* split cmd_buff into tokens */ 851 retval = tokenize(cmd_buf, tokens); 852 if (retval != 0) { 853 return -3; 854 } 855 /* tokens[] contains MAX_TOK+1 elements so there is always 856 at least one empty one at the end... make it empty now */ 857 tokens[MAX_TOK] = ""; 858 return 0; 859 } 860 861 int halcmd_parse_line(char *line) { 862 char *tokens[MAX_TOK+1]; 863 int result = halcmd_preprocess_line(line, tokens); 864 if(result < 0) return result; 865 return halcmd_parse_cmd(tokens); 866 } 867 868 static int linenumber=0; 869 static char *filename=NULL; 870 871 void halcmd_set_filename(const char *new_filename) { 872 if(filename) free(filename); 873 filename = strdup(new_filename); 874 } 875 const char *halcmd_get_filename(void) { return filename; } 876 877 void halcmd_set_linenumber(int new_linenumber) { linenumber = new_linenumber; } 878 int halcmd_get_linenumber(void) { return linenumber; } 879 880 /* vim:sts=4:sw=4:et 881 */