#include "config.h"
#ifndef NO_INI
#include "inifile.h"
FILE *halcmd_inifile = NULL;
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <fnmatch.h>
#include <search.h>
#include "rtapi.h"
#include "hal.h"
#include "../hal_priv.h"
#include "halcmd_commands.h"
int comp_id = -1;
int hal_flag = 0;
int halcmd_done = 0;
int scriptmode = 0;
int echo_mode = 0;
char comp_name[HAL_NAME_LEN+1];
static void quit(int);
int halcmd_startup(int quiet) {
int msg_lvl_save=rtapi_get_msg_level();
signal(SIGINT, quit);
signal(SIGTERM, quit);
signal(SIGPIPE, SIG_IGN);
snprintf(comp_name, sizeof(comp_name), "halcmd%d", getpid());
hal_flag = 1;
if (quiet) rtapi_set_msg_level(RTAPI_MSG_NONE);
comp_id = hal_init(comp_name);
if (quiet) rtapi_set_msg_level(msg_lvl_save);
hal_flag = 0;
if (comp_id < 0) {
if (!quiet) {
fprintf(stderr, "halcmd: hal_init() failed: %d\n", comp_id );
fprintf(stderr, "NOTE: 'rtapi' kernel module must be loaded\n" );
}
return -EINVAL;
}
hal_ready(comp_id);
return 0;
}
void halcmd_shutdown(void) {
hal_flag = 1;
hal_exit(comp_id);
}
#define FUNCT(x) ((halcmd_func_t)x)
struct halcmd_command halcmd_commands[] = {
{"addf", FUNCT(do_addf_cmd), A_TWO | A_PLUS },
{"alias", FUNCT(do_alias_cmd), A_THREE },
{"delf", FUNCT(do_delf_cmd), A_TWO | A_OPTIONAL },
{"delsig", FUNCT(do_delsig_cmd), A_ONE },
{"echo", FUNCT(do_echo_cmd), A_ZERO },
{"getp", FUNCT(do_getp_cmd), A_ONE },
{"gets", FUNCT(do_gets_cmd), A_ONE },
{"ptype", FUNCT(do_ptype_cmd), A_ONE },
{"stype", FUNCT(do_stype_cmd), A_ONE },
{"help", FUNCT(do_help_cmd), A_ONE | A_OPTIONAL },
{"linkpp", FUNCT(do_linkpp_cmd), A_TWO | A_REMOVE_ARROWS },
{"linkps", FUNCT(do_linkps_cmd), A_TWO | A_REMOVE_ARROWS },
{"linksp", FUNCT(do_linksp_cmd), A_TWO | A_REMOVE_ARROWS },
{"list", FUNCT(do_list_cmd), A_ONE | A_PLUS },
{"loadrt", FUNCT(do_loadrt_cmd), A_ONE | A_PLUS },
{"loadusr", FUNCT(do_loadusr_cmd), A_PLUS | A_TILDE },
{"lock", FUNCT(do_lock_cmd), A_ONE | A_OPTIONAL },
{"net", FUNCT(do_net_cmd), A_ONE | A_PLUS | A_REMOVE_ARROWS },
{"newsig", FUNCT(do_newsig_cmd), A_TWO },
{"save", FUNCT(do_save_cmd), A_TWO | A_OPTIONAL | A_TILDE },
{"setexact_for_test_suite_only", FUNCT(do_setexact_cmd), A_ZERO },
{"setp", FUNCT(do_setp_cmd), A_TWO },
{"sets", FUNCT(do_sets_cmd), A_TWO },
{"show", FUNCT(do_show_cmd), A_ONE | A_OPTIONAL | A_PLUS},
{"source", FUNCT(do_source_cmd), A_ONE | A_TILDE },
{"start", FUNCT(do_start_cmd), A_ZERO},
{"status", FUNCT(do_status_cmd), A_ONE | A_OPTIONAL },
{"stop", FUNCT(do_stop_cmd), A_ZERO},
{"unalias", FUNCT(do_unalias_cmd), A_TWO },
{"unecho", FUNCT(do_unecho_cmd), A_ZERO },
{"unlinkp", FUNCT(do_unlinkp_cmd), A_ONE },
{"unload", FUNCT(do_unload_cmd), A_ONE },
{"unloadrt", FUNCT(do_unloadrt_cmd), A_ONE },
{"unloadusr", FUNCT(do_unloadusr_cmd), A_ONE },
{"unlock", FUNCT(do_unlock_cmd), A_ONE | A_OPTIONAL },
{"waitusr", FUNCT(do_waitusr_cmd), A_ONE },
};
int halcmd_ncommands = (sizeof(halcmd_commands) / sizeof(halcmd_commands[0]));
static int sort_command(const void *a, const void *b) {
const struct halcmd_command *ca = a, *cb = b;
return strcmp(ca->name, cb->name);
}
static int compare_command(const void *namep, const void *commandp) {
const char *name = namep;
const struct halcmd_command *command = commandp;
return strcmp(name, command->name);
}
pid_t hal_systemv_nowait(char *const argv[]) {
pid_t pid;
int n;
hal_exit(comp_id);
comp_id = 0;
pid = fork();
if ( pid < 0 ) {
halcmd_error("fork() failed\n");
comp_id = hal_init(comp_name);
if (comp_id < 0) {
fprintf(stderr, "halcmd: hal_init() failed after fork: %d\n",
comp_id );
exit(-1);
}
hal_ready(comp_id);
return -1;
}
if ( pid == 0 ) {
for(n=0; argv[n] != NULL; n++) {
rtapi_print_msg(RTAPI_MSG_DBG, "%s ", argv[n] );
}
if (n == 0) {
halcmd_error("hal_systemv_nowait: empty argv array passed in\n");
exit(1);
}
rtapi_print_msg(RTAPI_MSG_DBG, "\n" );
execvp(argv[0], argv);
halcmd_error("execv(%s): %s\n", argv[0], strerror(errno) );
exit(1);
}
comp_id = hal_init(comp_name);
return pid;
}
int hal_systemv(char *const argv[]) {
pid_t pid;
int status;
int retval;
pid = hal_systemv_nowait(argv);
retval = waitpid ( pid, &status, 0 );
if (comp_id < 0) {
fprintf(stderr, "halcmd: hal_init() failed after systemv: %d\n", comp_id );
exit(-1);
}
hal_ready(comp_id);
if ( retval < 0 ) {
halcmd_error("waitpid(%d) failed: %s\n", pid, strerror(errno) );
return -1;
}
if ( WIFEXITED(status) == 0 ) {
halcmd_error("child did not exit normally\n");
return -1;
}
retval = WEXITSTATUS(status);
if ( retval != 0 ) {
halcmd_error("exit value: %d\n", retval );
return -1;
}
return 0;
}
static void quit(int sig)
{
if ( hal_flag ) {
halcmd_done = 1;
} else {
if ( comp_id > 0 ) {
hal_exit(comp_id);
}
_exit(1);
}
}
static int count_args(char **argv) {
int i = 0;
while(argv[i] && argv[i][0]) i++;
return i;
}
#define ARG(i) (argc > i ? argv[i] : 0)
#define REST(i) (argc > i ? argv + i : argv + argc)
static int parse_cmd1(char **argv) {
struct halcmd_command *command = bsearch(argv[0],
halcmd_commands, halcmd_ncommands,
sizeof(struct halcmd_command), compare_command);
int argc = count_args(argv);
if(argc == 0)
return 0;
if(!command) {
if(argc == 3 && !strcmp(argv[1], "=")) {
return do_setp_cmd(argv[0], argv[2]);
} else {
halcmd_error("Unknown command '%s'\n", argv[0]);
return -EINVAL;
}
} else {
int result = -EINVAL;
int is_optional = command->type & A_OPTIONAL,
is_plus = command->type & A_PLUS,
nargs = command->type & 0xff,
posargs;
if(command->type & A_REMOVE_ARROWS) {
int s, d;
for(s=d=0; argv[s] && argv[s][0]; s++) {
if(!strcmp(argv[s], "<=") ||
!strcmp(argv[s], "=>") ||
!strcmp(argv[s], "<=>")) {
continue;
} else {
argv[d++] = argv[s];
}
}
argv[d] = 0;
argc = d;
}
posargs = argc - 1;
if(posargs < nargs && !is_optional) {
halcmd_error("%s requires %s%d arguments, %d given\n",
command->name, is_plus ? "at least " : "", nargs, posargs);
return -EINVAL;
}
if(posargs > nargs && !is_plus) {
halcmd_error("%s requires %s%d arguments, %d given\n",
command->name, is_optional ? "at most " : "", nargs, posargs);
return -EINVAL;
}
#ifndef NO_INI
if(command->type & A_TILDE)
{
int i;
for(i=0; i<argc; i++)
{
char *buf = malloc(LINELEN);
TildeExpansion(argv[i], buf, LINELEN);
argv[i] = buf;
}
}
#endif
if(!strcmp(command->name, "echo")) {echo_mode = 1;}
if(!strcmp(command->name, "unecho")) {echo_mode = 0;}
switch(nargs | is_plus) {
case A_ZERO: {
result = command->func();
break;
}
case A_PLUS: {
int(*f)(char **args) = (int(*)(char**))command->func;
result = f(REST(1));
break;
}
case A_ONE: {
int(*f)(char *arg) = (int(*)(char*))command->func;
result = f(ARG(1));
break;
}
case A_ONE | A_PLUS: {
int(*f)(char *arg, char **rest) =
(int(*)(char*,char**))command->func;
result = f(ARG(1), REST(2));
break;
}
case A_TWO: {
int(*f)(char *arg, char *arg2) =
(int(*)(char*,char*))command->func;
result = f(ARG(1), ARG(2));
break;
}
case A_TWO | A_PLUS: {
int(*f)(char *arg, char *arg2, char **rest) =
(int(*)(char*,char*,char**))command->func;
result = f(ARG(1), ARG(2), REST(3));
break;
}
case A_THREE: {
int(*f)(char *arg, char *arg2, char *arg3) =
(int(*)(char*,char*,char*))command->func;
result = f(ARG(1), ARG(2), ARG(3));
break;
}
case A_THREE | A_PLUS: {
int(*f)(char *arg, char *arg2, char *arg3, char **rest) =
(int(*)(char*,char*,char*,char**))command->func;
result = f(ARG(1), ARG(2), ARG(3), REST(4));
break;
}
default:
halcmd_error("BUG: unchandled case: command=%s type=0x%x",
command->name, command->type);
result = -EINVAL;
}
#ifndef NO_TILDE
if(command->type & A_TILDE)
{
int i;
for(i=0; i<argc; i++)
{
free(argv[i]);
}
}
#endif
return result;
}
}
int halcmd_parse_cmd(char *tokens[])
{
int retval;
static int first_time = 1;
if(first_time) {
qsort(halcmd_commands, halcmd_ncommands,
sizeof(struct halcmd_command), sort_command);
first_time = 0;
}
hal_flag = 1;
retval = parse_cmd1(tokens);
hal_flag = 0;
return retval;
}
static int tokenize(char *cmd_buf, char **tokens)
{
enum { BETWEEN_TOKENS,
IN_TOKEN,
SINGLE_QUOTE,
DOUBLE_QUOTE,
END_OF_LINE } state;
char *cp1;
int m;
m = 0;
cp1 = cmd_buf;
state = BETWEEN_TOKENS;
while ( m < MAX_TOK ) {
if(*cp1 == '\r')
{
char nextc = *(cp1+1);
if(nextc == '\n' || nextc == '\0')
{
static int warned=0;
if(!warned)
halcmd_warning("File contains DOS-style line endings.\n");
warned = 1;
}
else
{
halcmd_error("File contains embedded carriage returns.\n");
return -1;
}
}
switch ( state ) {
case BETWEEN_TOKENS:
if ( *cp1 == '\0' ) {
state = END_OF_LINE;
} else if ( isspace(*cp1) ) {
cp1++;
} else if ( *cp1 == '\'' ) {
tokens[m] = cp1++;
state = SINGLE_QUOTE;
} else if ( *cp1 == '\"' ) {
tokens[m] = cp1++;
state = DOUBLE_QUOTE;
} else {
tokens[m] = cp1++;
state = IN_TOKEN;
}
break;
case IN_TOKEN:
if ( *cp1 == '\0' ) {
m++;
state = END_OF_LINE;
} else if ( *cp1 == '\'' ) {
cp1++;
state = SINGLE_QUOTE;
} else if ( *cp1 == '\"' ) {
cp1++;
state = DOUBLE_QUOTE;
} else if ( isspace(*cp1) ) {
*cp1++ = '\0';
m++;
state = BETWEEN_TOKENS;
} else {
cp1++;
}
break;
case SINGLE_QUOTE:
if ( *cp1 == '\0' ) {
m++;
state = END_OF_LINE;
} else if ( *cp1 == '\'' ) {
cp1++;
state = IN_TOKEN;
} else {
cp1++;
}
break;
case DOUBLE_QUOTE:
if ( *cp1 == '\0' ) {
m++;
state = END_OF_LINE;
} else if ( *cp1 == '\"' ) {
cp1++;
state = IN_TOKEN;
} else {
cp1++;
}
break;
case END_OF_LINE:
tokens[m++] = cp1;
break;
default:
state = BETWEEN_TOKENS;
}
}
if ( state != END_OF_LINE ) {
halcmd_error("too many tokens on line\n");
return -1;
}
return 0;
}
static int strip_comments ( char *buf )
{
enum { NORMAL,
SINGLE_QUOTE,
DOUBLE_QUOTE
} state;
char *cp1;
cp1 = buf;
state = NORMAL;
while ( 1 ) {
switch ( state ) {
case NORMAL:
if (( *cp1 == '#' ) || ( *cp1 == '\n' ) || ( *cp1 == '\0' )) {
*cp1 = '\0';
return 0;
} else if ( *cp1 == '\'' ) {
cp1++;
state = SINGLE_QUOTE;
} else if ( *cp1 == '\"' ) {
cp1++;
state = DOUBLE_QUOTE;
} else {
cp1++;
}
break;
case SINGLE_QUOTE:
if (( *cp1 == '\n' ) || ( *cp1 == '\0' )) {
*cp1 = '\0';
return -1;
} else if ( *cp1 == '\'' ) {
cp1++;
state = NORMAL;
} else {
cp1++;
}
break;
case DOUBLE_QUOTE:
if (( *cp1 == '\n' ) || ( *cp1 == '\0' )) {
*cp1 = '\0';
return -1;
} else if ( *cp1 == '\"' ) {
cp1++;
state = NORMAL;
} else {
cp1++;
}
break;
default:
state = NORMAL;
}
}
}
static int strlimcpy(char **dest, char *src, int srclen, int *destspace) {
if (*destspace < srclen) {
return -1;
} else {
strncpy(*dest, src, srclen);
(*dest)[srclen] = '\0';
srclen = strlen(*dest);
*destspace -= srclen;
*dest += srclen;
}
return 0;
}
static int replace_vars(char *source_str, char *dest_str, int max_chars, char **detail)
{
int retval = 0, loopcount=0;
int next_delim, remaining, buf_space;
char *replacement, sec[128], var[128];
static char info[256];
char *sp=source_str, *dp=dest_str, *secP, *varP;
const char
* words = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_";
dest_str[max_chars-1] = '\0';
*dest_str='\0';
buf_space = max_chars-1;
while ((remaining = strlen(sp)) > 0) {
loopcount++;
next_delim=strcspn(sp, "$[");
if (strlimcpy(&dp, sp, next_delim, &buf_space) < 0)
return -6;
sp += next_delim;
if (next_delim < remaining) {
switch (*sp++) {
case '$':
varP = sp;
if (*sp=='(') {
varP=++sp;
next_delim=strcspn(varP, ")");
if (next_delim >= strlen(varP))
return -1;
sp++;
} else next_delim = strspn(varP, words);
if (next_delim == 0)
return -2;
if (next_delim > 127)
return -7;
strncpy(var, varP, next_delim);
var[next_delim]='\0';
replacement = getenv(var);
if (replacement == NULL)
{
snprintf(info, sizeof(info), "%s", var);
*detail = info;
return -4;
}
if (strlimcpy(&dp, replacement, strlen(replacement), &buf_space) <0)
return -6;
sp += next_delim;
break;
case '[':
secP = sp;
next_delim = strcspn(secP, "]");
if (next_delim >= strlen(secP))
return -3;
if (next_delim > 127)
return -7;
strncpy(sec, secP, next_delim);
sec[next_delim]='\0';
sp += next_delim+1;
varP = sp;
if (*sp=='(') {
varP=++sp;
next_delim=strcspn(varP, ")");
if (next_delim > strlen(varP))
return -1;
sp++;
} else next_delim = strspn(varP, words);
if (next_delim == 0)
return -2;
if (next_delim > 127)
return -7;
strncpy(var, varP, next_delim);
var[next_delim]='\0';
if ( strlen(sec) > 0 ) {
replacement = (char *) iniFind(halcmd_inifile, var, sec);
} else {
replacement = (char *) iniFind(halcmd_inifile, var, NULL);
}
if (replacement==NULL) {
*detail = info;
snprintf(info, sizeof(info), "[%s]%s", sec, var);
return -5;
}
if (strlimcpy(&dp, replacement, strlen(replacement), &buf_space) < 0)
return -6;
sp += next_delim;
break;
}
}
}
return retval;
}
static const char *replace_errors[] = {
"Missing close parenthesis.\n",
"Empty variable name.\n",
"Missing close square bracket.\n",
"Environment variable '%s' not found.\n",
"Ini variable '%s' not found.\n",
"Line too long.\n",
"Variable name too long.\n",
};
int halcmd_preprocess_line ( char *line, char **tokens )
{
int retval;
char *detail = NULL;
static char cmd_buf[2*MAX_CMD_LEN];
retval = strip_comments(line);
if (retval != 0) {
halcmd_error("unterminated quoted string\n");
return -1;
}
retval = replace_vars(line, cmd_buf, sizeof(cmd_buf)-2, &detail);
if (retval != 0) {
if ((retval < 0) && (retval >= -7)) {
if(detail) {
halcmd_error(replace_errors[(-retval) -1], detail);
} else {
halcmd_error("%s", replace_errors[(-retval) -1]);
}
} else {
halcmd_error("unknown variable replacement error\n");
}
return -2;
}
retval = tokenize(cmd_buf, tokens);
if (retval != 0) {
return -3;
}
tokens[MAX_TOK] = "";
return 0;
}
int halcmd_parse_line(char *line) {
char *tokens[MAX_TOK+1];
int result = halcmd_preprocess_line(line, tokens);
if(result < 0) return result;
return halcmd_parse_cmd(tokens);
}
static int linenumber=0;
static char *filename=NULL;
void halcmd_set_filename(const char *new_filename) {
if(filename) free(filename);
filename = strdup(new_filename);
}
const char *halcmd_get_filename(void) { return filename; }
void halcmd_set_linenumber(int new_linenumber) { linenumber = new_linenumber; }
int halcmd_get_linenumber(void) { return linenumber; }