/ src / hal / utils / halcmd.c
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   */