You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/utils/kamcmd/kamcmd.c

2245 lines
51 KiB

/*
* Copyright (C) 2006 iptelorg GmbH
*
* This file is part of kamcmd, a free cli tool for Kamailio SIP server.
*
* kamailio is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*
* For a license to use the kamailio software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* kamailio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* send commands using binrpc
*
*/
#include <stdlib.h> /* exit, abort */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h> /* isprint */
#include <sys/socket.h>
#include <sys/un.h> /* unix sock*/
#include <netinet/in.h> /* udp sock */
#include <sys/uio.h> /* writev */
#include <netdb.h> /* gethostbyname */
#include <fcntl.h>
#include <time.h> /* time */
#include <sys/time.h>
#ifdef USE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#define USE_CFG_VARS /* cfg group and vars completion */
#define USE_COUNTERS /* counters/statistics completion */
#endif
#include "parse_listen_id.h"
#include "license.h"
#include "../../src/modules/ctl/ctl_defaults.h" /* default socket & port */
#include "../../src/modules/ctl/binrpc.h"
#include "../../src/modules/ctl/binrpc.c" /* ugly hack */
#ifndef NAME
#define NAME "kamcmd"
#endif
#ifndef VERSION
#define VERSION "1.5"
#endif
#define IOVEC_CNT 20
#define MAX_LINE_SIZE 16 * 1024 /* for non readline mode */
#define MAX_REPLY_SIZE 128 * 1024
#define MAX_BODY_SIZE 128 * 1024
#define MAX_BINRPC_ARGS 256
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 104
#endif
static char version[] = NAME " " VERSION;
#ifdef VERSION_NODATE
static char compiled[] = "";
#else
#ifdef VERSION_DATE
static char compiled[] = VERSION_DATE;
#else
static char compiled[] = __TIME__ " " __DATE__;
#endif
#endif
static char help_msg[] = "\
Usage: " NAME " [options][-s address] [ cmd ]\n\
Options:\n\
-s address unix socket name or host name to send the commands on\n\
-R name force reply socket name, for the unix datagram socket mode\n\
-D dir create the reply socket in the directory <dir> if no reply \n\
socket is forced (-R) and a unix datagram socket is selected\n\
as the transport\n\
-f format print the result using format. Format is a string containing\n\
%v at the places where values read from the reply should be\n\
substituted. To print '%v', escape it using '%': %%v.\n\
-v Verbose \n\
-V Version number\n\
-h This help message\n\
address:\n\
[proto:]name[:port] where proto is one of tcp, udp, unixs or unixd\n\
e.g.: tcp:localhost:2049 , unixs:/tmp/kamailio_ctl\n\
cmd:\n\
method [arg1 [arg2...]]\n\
arg:\n\
string or number; to force a number to be interpreted as string \n\
prefix it by \"s:\", e.g. s:1\n\
Examples:\n\
" NAME " -s unixs:/tmp/" NAME "_ctl system.listMethods\n\
" NAME " -f \"pid: %v desc: %v\\n\" -s udp:localhost:2047 core.ps \n\
" NAME " ps # uses default ctl socket \n\
" NAME " # enters interactive mode on the default socket \n\
" NAME " -s tcp:localhost # interactive mode, default port \n\
";
int verbose = 0;
char *reply_socket = 0; /* unix datagram reply socket name */
char *sock_dir = 0; /* same as above, but only the directory */
char *unix_socket = 0;
struct sockaddr_un mysun;
int quit; /* used only in interactive mode */
struct binrpc_val *rpc_array;
int rpc_no = 0;
#ifdef USE_CFG_VARS
struct binrpc_val *cfg_vars_array;
int cfg_vars_no;
struct cfg_var_grp
{
struct cfg_var_grp *next;
str grp_name; /**< group name */
str *var_names; /**< str array, null terminated */
int var_no;
};
struct cfg_var_grp *cfg_grp_lst; /** cfg groups list, allong with var names*/
struct cfg_var_grp *crt_cfg_grp;
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
struct binrpc_val *cnt_grps_array; /* response array */
int cnt_grps_no; /* number of response records */
struct cnt_var_grp
{
struct cnt_var_grp *next;
str grp_name;
str *var_names; /**< str array (null terminated strings)*/
int var_no;
struct binrpc_val *cnt_vars_array; /* var_name will point here */
int cnt_vars_no; /* cnt_vars_array size (no. of response records) */
};
struct cnt_var_grp *cnt_grp_lst; /* counters groups list, allong with vars */
struct cnt_var_grp *crt_cnt_grp;
#endif /* USE_COUNTERS */
#define IOV_SET(vect, str) \
do { \
(vect).iov_base = (str); \
(vect).iov_len = strlen((str)); \
} while(0)
#define INT2STR_MAX_LEN (19 + 1 + 1) /* 2^64~= 16*10^18 => 19+1 digits + \0 */
/* returns a pointer to a static buffer containing l in asciiz & sets len */
static inline char *int2str(unsigned int l, int *len)
{
static char r[INT2STR_MAX_LEN];
int i;
i = INT2STR_MAX_LEN - 2;
r[INT2STR_MAX_LEN - 1] = 0; /* null terminate */
do {
r[i] = l % 10 + '0';
i--;
l /= 10;
} while(l && (i >= 0));
if(l && (i < 0)) {
fprintf(stderr, "BUG: int2str: overflow\n");
}
if(len)
*len = (INT2STR_MAX_LEN - 2) - i;
return &r[i + 1];
}
static char *trim_ws(char *l)
{
char *ret;
for(; *l && ((*l == ' ') || (*l == '\t') || (*l == '\n') || (*l == '\r'));
l++)
;
ret = l;
if(*ret == 0)
return ret;
for(l = l + strlen(l) - 1;
(l > ret)
&& ((*l == ' ') || (*l == '\t') || (*l == '\n') || (*l == '\r'));
l--)
;
*(l + 1) = 0;
return ret;
}
int gen_cookie()
{
return rand();
}
struct binrpc_cmd
{
char *method;
int argc;
struct binrpc_val argv[MAX_BINRPC_ARGS];
};
struct cmd_alias
{
char *name;
char *method;
char *format; /* reply print format */
};
struct kamcmd_builtin
{
char *name;
int (*f)(int, struct binrpc_cmd *);
char *doc;
};
static int kamcmd_help(int s, struct binrpc_cmd *cmd);
static int kamcmd_ver(int s, struct binrpc_cmd *cmd);
static int kamcmd_quit(int s, struct binrpc_cmd *cmd);
static int kamcmd_warranty(int s, struct binrpc_cmd *cmd);
static struct cmd_alias cmd_aliases[] = {{"ps", "core.ps", "%v\t%v\n"},
{"psx", "core.psx", 0}, {"list", "system.listMethods", 0},
{"ls", "system.listMethods", 0}, {"ver", "core.version", 0},
{"version", "core.version", 0},
{"who", "ctl.who", "[%v] %v: %v %v -> %v %v\n"},
{"listen", "ctl.listen", "[%v] %v: %v %v\n"},
{"dns_mem_info", "dns.mem_info", "%v / %v\n"},
{"dns_debug", "dns.debug",
"%v (%v): size=%v ref=%v expire=%vs last=%vs ago f=%v\n"},
{"dns_debug_all", "dns.debug_all",
"%v (%v) [%v]: size=%v ref=%v expire=%vs last=%vs ago f=%v\n"
"\t\t%v:%v expire=%vs f=%v\n"},
{"dst_blocklist_mem_info", "dst_blocklist.mem_info", "%v / %v\n"},
{"dst_blocklist_debug", "dst_blocklist.debug",
"%v:%v:%v expire:%v flags: %v\n"},
{0, 0, 0}};
static struct kamcmd_builtin builtins[] = {{"?", kamcmd_help, "help"},
{"help", kamcmd_help, "displays help for a command"},
{"version", kamcmd_ver, "displays " NAME "version"},
{"quit", kamcmd_quit, "exits " NAME},
{"exit", kamcmd_quit, "exits " NAME},
{"warranty", kamcmd_warranty, "displays " NAME "'s warranty info"},
{"license", kamcmd_warranty, "displays " NAME "'s license"}, {0, 0}};
#ifdef USE_READLINE
enum complete_states
{
COMPLETE_INIT,
COMPLETE_CMD_NAME,
#ifdef USE_CFG_VARS
COMPLETE_CFG_GRP,
COMPLETE_CFG_VAR,
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
COMPLETE_CNT_GRP,
COMPLETE_CNT_VAR,
#endif /* USE_COUNTERS */
COMPLETE_NOTHING
};
/* instead of rl_attempted_completion_over which is not present in
some readline emulations, use attempted_completion_state */
static enum complete_states attempted_completion_state;
static int crt_param_no;
/* commands for which we complete the params to other method names */
char *complete_params_methods[] = {
"?", "h", "help", "system.methodSignature", "system.methodHelp", 0};
#ifdef USE_CFG_VARS
/* commands for which we complete the first param with a cfg var grp*/
char *complete_params_cfg_var[] = {"cfg.get", "cfg.help", "cfg.set_delayed_int",
"cfg.set_delayed_string", "cfg.set_now_int", "cfg.set_now_string", 0};
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
/* commands for which we complete the first param with a counter group */
char *complete_param1_counter_grp[] = {"cnt.get", "cnt.get_raw",
"cnt.grp_get_all", "cnt.reset", "cnt.var_list", "cnt.help", 0};
/* commands for which we completed the 2nd param with a counter name */
char *complete_param2_counter_name[] = {
"cnt.get", "cnt.get_raw", "cnt.reset", "cnt.help", 0};
#endif /* USE_COUNTERS */
#endif /* USE_READLINE */
static int parse_arg(struct binrpc_val *v, char *arg)
{
int i;
double f;
char *tmp;
int len;
i = strtol(arg, &tmp, 10);
if((tmp == 0) || (*tmp)) {
f = strtod(arg, &tmp);
if((tmp == 0) || (*tmp)) {
/* not an int or a float => string */
len = strlen(arg);
if((len >= 2) && (arg[0] == 's') && (arg[1] == ':')) {
tmp = &arg[2];
len -= 2;
} else {
tmp = arg;
}
v->type = BINRPC_T_STR;
v->u.strval.s = tmp;
v->u.strval.len = len;
} else { /* float */
v->type = BINRPC_T_DOUBLE;
v->u.fval = f;
}
} else { /* int */
v->type = BINRPC_T_INT;
v->u.intval = i;
}
return 0;
}
static int parse_cmd(struct binrpc_cmd *cmd, char **argv, int count)
{
int r;
cmd->method = argv[0];
if((count - 1) > MAX_BINRPC_ARGS) {
fprintf(stderr, "ERROR: too many args %d, only %d allowed\n", count - 1,
MAX_BINRPC_ARGS);
return -1;
}
for(r = 1; r < count; r++) {
if(parse_arg(&cmd->argv[r - 1], argv[r]) < 0)
return -1;
}
cmd->argc = r - 1;
return 0;
}
void print_binrpc_val(struct binrpc_val *v, int ident)
{
int r;
if((v->type == BINRPC_T_STRUCT) && !v->u.end)
ident--; /* fix to have struct beg. idented differently */
for(r = 0; r < ident; r++)
putchar(' ');
if(v->name.s) {
printf("%.*s: ", v->name.len, v->name.s);
}
switch(v->type) {
case BINRPC_T_INT:
printf("%d", v->u.intval);
break;
case BINRPC_T_STR:
case BINRPC_T_BYTES:
printf("%.*s", v->u.strval.len, v->u.strval.s);
break;
case BINRPC_T_ARRAY:
printf("%c", (v->u.end) ? ']' : '[');
break;
case BINRPC_T_STRUCT:
printf("%c", (v->u.end) ? '}' : '{');
break;
case BINRPC_T_DOUBLE:
if(v->u.fval == (double)((long long int)v->u.fval)) {
printf("%lld", (long long int)v->u.fval);
} else {
printf("%f", v->u.fval);
}
break;
default:
printf("ERROR: unknown type %d\n", v->type);
};
}
/* opens, and connects on a STREAM unix socket
* returns socket fd or -1 on error */
int connect_unix_sock(char *name, int type)
{
struct sockaddr_un ifsun;
int s;
int len;
int ret;
int retries;
retries = 0;
s = -1;
memset(&ifsun, 0, sizeof(struct sockaddr_un));
len = strlen(name);
if(len > UNIX_PATH_MAX) {
fprintf(stderr,
"ERROR: connect_unix_sock: name too long "
"(%d > %d): %s\n",
len, UNIX_PATH_MAX, name);
goto error;
}
ifsun.sun_family = AF_UNIX;
memcpy(ifsun.sun_path, name, len);
#ifdef HAVE_SOCKADDR_SA_LEN
ifsun.sun_len = len;
#endif
s = socket(PF_UNIX, type, 0);
if(s == -1) {
fprintf(stderr,
"ERROR: connect_unix_sock: cannot create unix socket"
" %s: %s [%d]\n",
name, strerror(errno), errno);
goto error;
}
if(type == SOCK_DGRAM) {
/* we must bind so that we can receive replies */
if(reply_socket == 0) {
if(sock_dir == 0)
sock_dir = "/tmp";
retry:
ret = snprintf(mysun.sun_path, UNIX_PATH_MAX, "%s/" NAME "_%d",
sock_dir, rand());
if((ret < 0) || (ret >= UNIX_PATH_MAX)) {
fprintf(stderr, "ERROR: buffer overflow while trying to"
"generate unix datagram socket name");
goto error;
}
} else {
if(strlen(reply_socket) > UNIX_PATH_MAX) {
fprintf(stderr,
"ERROR: buffer overflow while trying to"
"use the provided unix datagram socket name (%s)",
reply_socket);
goto error;
}
strcpy(mysun.sun_path, reply_socket);
}
mysun.sun_family = AF_UNIX;
if(bind(s, (struct sockaddr *)&mysun, sizeof(mysun)) == -1) {
if(errno == EADDRINUSE && (reply_socket == 0) && (retries < 10)) {
retries++;
/* try another one */
goto retry;
}
fprintf(stderr,
"ERROR: could not bind the unix socket to"
" %s: %s (%d)\n",
mysun.sun_path, strerror(errno), errno);
goto error;
}
unix_socket = mysun.sun_path;
}
if(connect(s, (struct sockaddr *)&ifsun, sizeof(ifsun)) == -1) {
fprintf(stderr, "ERROR: connect_unix_sock: connect(%s): %s [%d]\n",
name, strerror(errno), errno);
goto error;
}
return s;
error:
if(s != -1)
close(s);
return -1;
}
int connect_tcpudp_socket(char *address, int port, int type)
{
struct sockaddr_in addr;
struct hostent *he;
int sock;
sock = -1;
/* resolve destination */
he = gethostbyname(address);
if(he == 0) {
fprintf(stderr, "ERROR: could not resolve %s\n", address);
goto error;
}
/* open socket*/
addr.sin_family = he->h_addrtype;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr.s_addr, he->h_addr_list[0], he->h_length);
sock = socket(he->h_addrtype, type, 0);
if(sock == -1) {
fprintf(stderr, "ERROR: socket: %s\n", strerror(errno));
goto error;
}
if(connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr)) != 0) {
fprintf(stderr, "ERROR: connect: %s\n", strerror(errno));
goto error;
}
return sock;
error:
if(sock != -1)
close(sock);
return -1;
}
static void hexdump(unsigned char *buf, int len, int ascii)
{
int r, i;
/* dump it in hex */
for(r = 0; r < len; r++) {
if((r) && ((r % 16) == 0)) {
if(ascii) {
putchar(' ');
for(i = r - 16; i < r; i++) {
if(isprint(buf[i]))
putchar(buf[i]);
else
putchar('.');
}
}
putchar('\n');
}
printf("%02x ", buf[r]);
};
if(ascii) {
for(i = r; i % 16; i++)
printf(" ");
putchar(' ');
for(i = 16 * (r / 16); i < r; i++) {
if(isprint(buf[i]))
putchar(buf[i]);
else
putchar('.');
}
}
putchar('\n');
}
/* returns: -1 on error, number of bytes written on success */
static int send_binrpc_cmd(int s, struct binrpc_cmd *cmd, int cookie)
{
struct iovec v[IOVEC_CNT];
int r;
unsigned char msg_body[MAX_BODY_SIZE];
unsigned char msg_hdr[BINRPC_MAX_HDR_SIZE];
struct binrpc_pkt body;
int ret;
int n;
ret = binrpc_init_pkt(&body, msg_body, MAX_BODY_SIZE);
if(ret < 0)
goto binrpc_err;
ret = binrpc_addstr(&body, cmd->method, strlen(cmd->method));
if(ret < 0)
goto binrpc_err;
for(r = 0; r < cmd->argc; r++) {
switch(cmd->argv[r].type) {
case BINRPC_T_STR:
ret = binrpc_addstr(&body, cmd->argv[r].u.strval.s,
cmd->argv[r].u.strval.len);
break;
case BINRPC_T_INT:
ret = binrpc_addint(&body, cmd->argv[r].u.intval);
break;
case BINRPC_T_DOUBLE:
ret = binrpc_adddouble(&body, cmd->argv[r].u.fval);
break;
default:
fprintf(stderr, "ERROR: unsupported type %d\n",
cmd->argv[r].type);
}
if(ret < 0)
goto binrpc_err;
}
ret = binrpc_build_hdr(BINRPC_REQ, binrpc_pkt_len(&body), cookie, msg_hdr,
BINRPC_MAX_HDR_SIZE);
if(ret < 0)
goto binrpc_err;
v[0].iov_base = msg_hdr;
v[0].iov_len = ret;
v[1].iov_base = msg_body;
v[1].iov_len = binrpc_pkt_len(&body);
write_again:
if((n = writev(s, v, 2)) < 0) {
if(errno == EINTR)
goto write_again;
goto error_send;
}
return n;
error_send:
return -1;
binrpc_err:
return -2;
}
static int binrpc_errno = 0;
/* reads the whole reply
* returns < 0 on error, reply size on success + initializes in_pkt
* if ret==-2 (parse error), sets binrpc_errno to the binrpc error
* error returns: -1 - read error (check errno)
* -2 - binrpc parse error (chekc binrpc_errno)
* -3 - cookie error (the cookied doesn't match)
* -4 - message too big */
static int get_reply(int s, unsigned char *reply_buf, int max_reply_size,
int cookie, struct binrpc_parse_ctx *in_pkt, unsigned char **body)
{
unsigned char *crt;
unsigned char *hdr_end;
unsigned char *msg_end;
int n;
int ret;
hdr_end = crt = reply_buf;
msg_end = reply_buf + max_reply_size;
binrpc_errno = 0;
do {
n = read(s, crt, (int)(msg_end - crt));
if(n <= 0) {
if(errno == EINTR)
continue;
goto error_read;
}
if(verbose >= 3) {
/* dump it in hex */
printf("received %d bytes in reply (@offset %d):\n", n,
(int)(crt - reply_buf));
hexdump(crt, n, 1);
}
crt += n;
/* parse header if not parsed yet */
if(hdr_end == reply_buf) {
hdr_end = binrpc_parse_init(in_pkt, reply_buf, n, &ret);
if(ret < 0) {
if(ret == E_BINRPC_MORE_DATA)
continue;
goto error_parse;
}
if(verbose > 1) {
printf("new packet: type %02x, len %d, cookie %02x\n",
in_pkt->type, in_pkt->tlen, in_pkt->cookie);
}
if(in_pkt->cookie != cookie) {
fprintf(stderr,
"bad reply, cookie doesn't match: sent %02x "
"and received %02x\n",
cookie, in_pkt->cookie);
goto error;
}
msg_end = hdr_end + in_pkt->tlen;
if((int)(msg_end - reply_buf) > max_reply_size) {
/* reading the rest from the socket */
struct timeval timeout_save;
unsigned sizeoft = sizeof(timeout_save);
if(getsockopt(
s, SOL_SOCKET, SO_RCVTIMEO, &timeout_save, &sizeoft)
== 0) {
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(timeout))
== 0) {
while(read(s, reply_buf, max_reply_size) > 0)
;
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO,
(char *)&timeout_save, sizeof(timeout_save));
}
}
goto error_toolong;
}
}
} while(crt < msg_end);
*body = hdr_end;
return (int)(msg_end - reply_buf);
error_read:
return -1;
error_parse:
binrpc_errno = ret;
return -2;
error:
return -3;
error_toolong:
return -4;
}
/* returns a malloced copy of str, with all the escapes ('\') resolved */
static char *str_escape(char *str)
{
char *n;
char *ret;
ret = n = malloc(strlen(str) + 1);
if(n == 0)
goto end;
for(; *str; str++) {
*n = *str;
if(*str == '\\') {
switch(*(str + 1)) {
case 'n':
*n = '\n';
str++;
break;
case 'r':
*n = '\r';
str++;
break;
case 't':
*n = '\t';
str++;
break;
case '\\':
str++;
break;
}
}
n++;
}
*n = *str; /* terminating 0 */
end:
return ret;
}
/* parses strings like "bla bla %v 10%% %v\n test=%v",
* and stops at each %v, returning a pointer after the %v, setting *size
* to the string length (not including %v) and *type to the corresponding
* BINRPC type (for now only BINRPC_T_ALL).
* To escape a '%', use "%%", and check for type==-1 (which means skip a call
* again parse_fmt).
* Usage:
* n="test: %v,%v,%v\n";
* while(*n){
* s=n;
* n=parse_fmt(n, &type, &size);
* printf("%.*s", size, s);
* if (type==-1)
* continue;
* else
* printf("now we should get & print an object of type %d\n", type)
* }
*/
static char *parse_fmt(char *fmt, int *type, int *size)
{
char *s;
s = fmt;
do {
for(; *fmt && *fmt != '%'; fmt++)
;
if(*fmt == '%') {
switch(*(fmt + 1)) {
case 'v':
*type = BINRPC_T_ALL;
*size = (int)(fmt - s);
return (fmt + 2);
break;
case '%':
/* escaped % */
*size = (int)(fmt - s) + 1;
*type = -1; /* skip */
return (fmt + 2);
break;
}
}
} while(*fmt);
*type = -1; /* no value */
*size = (fmt - s);
return fmt;
}
static int print_body(struct binrpc_parse_ctx *in_pkt, unsigned char *body,
int size, char *fmt)
{
unsigned char *p;
unsigned char *end;
struct binrpc_val val;
int ret;
int rec;
char *f;
char *s;
int f_size;
int fmt_has_values;
p = body;
end = p + size;
rec = 0;
f = fmt;
fmt_has_values = 0;
/* read body */
while(p < end) {
if(f) {
do {
if(*f == 0)
f = fmt; /* reset */
s = f;
f = parse_fmt(f, &val.type, &f_size);
printf("%.*s", f_size, s);
if(val.type != -1) {
fmt_has_values = 1;
goto read_value;
}
} while(*f || fmt_has_values);
val.type = BINRPC_T_ALL;
} else {
val.type = BINRPC_T_ALL;
}
read_value:
val.name.s = 0;
val.name.len = 0;
p = binrpc_read_record(in_pkt, p, end, &val, 1, &ret);
if(ret < 0) {
if(fmt)
putchar('\n');
/*if (ret==E_BINRPC_MORE_DATA)
goto error_read_again;*/
if(ret == E_BINRPC_EOP) {
printf("end of message detected\n");
break;
}
fprintf(stderr,
"ERROR:: while parsing the record %d,"
" @%d: %02x : %s\n",
rec, in_pkt->offset, *p, binrpc_error(ret));
goto error;
}
rec++;
if(fmt) {
print_binrpc_val(&val, 0);
} else {
print_binrpc_val(&val, in_pkt->in_struct + in_pkt->in_array);
putchar('\n');
}
}
if(fmt && *f) {
/* print the rest, with empty values */
while(*f) {
s = f;
f = parse_fmt(f, &val.type, &f_size);
printf("%.*s", f_size, s);
}
}
return 0;
error:
return -1;
/*error_read_again:
fprintf(stderr, "ERROR: more data needed\n");
return -2;
*/
}
static int print_fault(
struct binrpc_parse_ctx *in_pkt, unsigned char *body, int size)
{
printf("error: ");
return print_body(in_pkt, body, size, "%v - %v\n");
}
static int run_binrpc_cmd(int s, struct binrpc_cmd *cmd, char *fmt)
{
int cookie;
unsigned char reply_buf[MAX_REPLY_SIZE];
unsigned char *msg_body;
struct binrpc_parse_ctx in_pkt;
int ret;
cookie = gen_cookie();
if((ret = send_binrpc_cmd(s, cmd, cookie)) < 0) {
if(ret == -1)
goto error_send;
else
goto binrpc_err;
}
/* read reply */
memset(&in_pkt, 0, sizeof(in_pkt));
if((ret = get_reply(
s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))
< 0) {
switch(ret) {
case -1:
goto error_read;
case -2:
goto error_parse;
case -3:
goto error_cookie;
case -4:
goto error_toobig;
}
goto error;
}
switch(in_pkt.type) {
case BINRPC_FAULT:
if(print_fault(&in_pkt, msg_body, in_pkt.tlen) < 0) {
goto error;
}
break;
case BINRPC_REPL:
if(print_body(&in_pkt, msg_body, in_pkt.tlen, fmt) < 0) {
goto error;
}
break;
default:
fprintf(stderr, "ERROR: not a reply\n");
goto error;
}
if(verbose)
printf(".\n");
/* normal exit */
return 0;
binrpc_err:
fprintf(stderr, "ERROR while building the packet: %s\n", binrpc_error(ret));
goto error;
error_parse:
fprintf(stderr, "ERROR while parsing the reply: %s\n",
binrpc_error(binrpc_errno));
goto error;
error_cookie:
fprintf(stderr, "ERROR: cookie does not match\n");
goto error;
error_toobig:
fprintf(stderr, "ERROR: reply too big\n");
goto error;
error_send:
fprintf(stderr, "ERROR: send packet failed: %s (%d)\n", strerror(errno),
errno);
goto error;
error_read:
fprintf(stderr, "ERROR: read reply failed: %s (%d)\n", strerror(errno),
errno);
goto error;
error:
return -1;
}
static int parse_line(struct binrpc_cmd *cmd, char *line)
{
char *p;
int count;
cmd->method = strtok(line, " \t");
if(cmd->method == 0)
goto error_no_method;
count = 0;
for(p = strtok(0, " \t"); p; p = strtok(0, " \t")) {
if(count >= MAX_BINRPC_ARGS)
goto error_too_many;
if(parse_arg(&cmd->argv[count], p) < 0) {
goto error_arg;
}
count++;
}
cmd->argc = count;
return 0;
error_no_method:
printf("ERROR: no method name\n");
return -1;
error_too_many:
printf("ERROR: too many arguments (%d), no more than %d allowed\n", count,
MAX_BINRPC_ARGS);
return -1;
error_arg:
printf("ERROR: bad argument %d: %s\n", count + 1, p);
return -1;
}
/* resolves builtin aliases */
static void fix_cmd(struct binrpc_cmd *cmd, char **format)
{
int r;
for(r = 0; cmd_aliases[r].name; r++) {
if(strcmp(cmd_aliases[r].name, cmd->method) == 0) {
cmd->method = cmd_aliases[r].method;
if(*format == 0)
*format = cmd_aliases[r].format;
break;
}
}
}
/* intercept builtin commands, returns 1 if intercepted, 0 if not, <0 on error
*/
static int run_builtins(int s, struct binrpc_cmd *cmd)
{
int r;
int ret;
for(r = 0; builtins[r].name; r++) {
if(strcmp(builtins[r].name, cmd->method) == 0) {
ret = builtins[r].f(s, cmd);
return (ret < 0) ? ret : 1;
}
}
return 0;
}
/* runs command from cmd */
inline static int run_cmd(int s, struct binrpc_cmd *cmd, char *format)
{
int ret;
char *fmt;
fmt = format;
fix_cmd(cmd, &fmt);
if(!(ret = run_builtins(s, cmd))) {
ret = run_binrpc_cmd(s, cmd, fmt);
}
return (ret > 0) ? 0 : ret;
}
/* runs a command represented in line */
inline static int run_line(int s, char *l, char *format)
{
struct binrpc_cmd cmd;
int ret;
if((ret = parse_line(&cmd, l)) == 0) {
return run_cmd(s, &cmd, format);
}
return ret;
}
static void free_rpc_array(struct binrpc_val *a, int size)
{
int r;
for(r = 0; r < size; r++) {
if(a[r].name.s)
free(a[r].name.s);
if((a[r].type == BINRPC_T_STR || a[r].type == BINRPC_T_BYTES)
&& a[r].u.strval.s) {
free(a[r].u.strval.s);
}
}
free(a);
}
/* parse the body into a malloc allocated, binrpc_val array */
static struct binrpc_val *parse_reply_body(int *records,
struct binrpc_parse_ctx *in_pkt, unsigned char *body, int size)
{
struct binrpc_val *a;
struct binrpc_val *t;
unsigned char *p;
unsigned char *end;
struct binrpc_val val;
int ret;
int rec;
rec = 0;
if(*records == 0) {
*records = 100; /* start with a reasonable size */
};
a = malloc(*records * sizeof(struct binrpc_val));
if(a == 0)
goto error_mem;
p = body;
end = p + size;
/* read body */
while(p < end) {
val.type = BINRPC_T_ALL;
val.name.s = 0;
val.name.len = 0;
p = binrpc_read_record(in_pkt, p, end, &val, 1, &ret);
if(ret < 0) {
if(ret == E_BINRPC_EOP) {
printf("end of message detected\n");
break;
}
fprintf(stderr,
"ERROR: while parsing the record %d,"
" @%d: %02x : %s\n",
rec, in_pkt->offset, *p, binrpc_error(ret));
goto error;
}
if(rec >= *records) {
t = realloc(a, *records * sizeof(struct binrpc_val) * 2);
if(t == 0)
goto error_mem;
a = t;
*records *= 2;
}
a[rec] = val;
if(val.name.s) {
if((a[rec].name.s = malloc(val.name.len + 1)) == 0)
goto error_mem;
memcpy(a[rec].name.s, val.name.s, val.name.len);
a[rec].name.s[val.name.len + 1] = 0; /* 0-term */
}
if(val.u.strval.s) {
if(val.type == BINRPC_T_STR) {
if((a[rec].u.strval.s = malloc(val.u.strval.len + 1)) == 0)
goto error_mem;
memcpy(a[rec].u.strval.s, val.u.strval.s, val.u.strval.len);
a[rec].u.strval.s[val.u.strval.len] = 0; /* 0-term */
} else if(val.type == BINRPC_T_BYTES) {
if((a[rec].u.strval.s = malloc(val.u.strval.len)) == 0)
goto error_mem;
memcpy(a[rec].u.strval.s, val.u.strval.s, val.u.strval.len);
}
}
rec++;
}
if(rec && (rec < *records)) {
a = realloc(a, rec * sizeof(struct binrpc_val));
}
*records = rec;
return a;
error_mem:
fprintf(stderr, "ERROR: parse_reply_body: out of memory\n");
error:
if(a) {
free_rpc_array(a, rec);
}
*records = 0;
return 0;
}
static int get_kamcmd_list(int s)
{
struct binrpc_cmd cmd;
int cookie;
unsigned char reply_buf[MAX_REPLY_SIZE];
unsigned char *msg_body;
struct binrpc_parse_ctx in_pkt;
int ret;
cmd.method = "system.listMethods";
cmd.argc = 0;
cookie = gen_cookie();
if((ret = send_binrpc_cmd(s, &cmd, cookie)) < 0) {
if(ret == -1)
goto error_send;
else
goto binrpc_err;
}
/* read reply */
memset(&in_pkt, 0, sizeof(in_pkt));
if((ret = get_reply(
s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))
< 0) {
goto error;
}
switch(in_pkt.type) {
case BINRPC_FAULT:
if(print_fault(&in_pkt, msg_body, in_pkt.tlen) < 0) {
goto error;
}
break;
case BINRPC_REPL:
rpc_no = 100; /* default cmd list */
if((rpc_array = parse_reply_body(
&rpc_no, &in_pkt, msg_body, in_pkt.tlen))
== 0)
goto error;
break;
default:
fprintf(stderr, "ERROR: not a reply\n");
goto error;
}
return 0;
binrpc_err:
error_send:
error:
return -1;
}
#if defined(USE_CFG_VARS) || defined(USE_COUNTERS)
/** check if cmd is a rpc command.
* Quick check (using the internal rpc_array) if cmd is a valid rpc command.
* @param cmd - null terminated ascii string
* @return 1 on success, 0 on failure.
*/
static int is_rpc_cmd(char *cmd)
{
int r;
int cmd_len;
cmd_len = strlen(cmd);
for(r = 0; r < rpc_no; r++) {
if((rpc_array[r].type == BINRPC_T_STR)
&& (rpc_array[r].u.strval.len == cmd_len)
&& (strncmp(cmd, rpc_array[r].u.strval.s, cmd_len) == 0))
return 1;
}
return 0;
}
#endif /* USE_CFG_VARS */
#ifdef USE_CFG_VARS
/* retrieve the cfg vars and group list */
static int get_cfgvars_list(int s)
{
struct binrpc_cmd cmd;
int cookie;
unsigned char reply_buf[MAX_REPLY_SIZE];
unsigned char *msg_body;
struct binrpc_parse_ctx in_pkt;
struct cfg_var_grp *grp;
struct cfg_var_grp *last_grp;
char *p;
char *end;
str grp_name;
str var_name;
int r;
int ret;
cmd.method = "cfg.list";
cmd.argc = 0;
if(!is_rpc_cmd(cmd.method))
goto error;
cookie = gen_cookie();
if((ret = send_binrpc_cmd(s, &cmd, cookie)) < 0) {
if(ret == -1)
goto error_send;
else
goto binrpc_err;
}
/* read reply */
memset(&in_pkt, 0, sizeof(in_pkt));
if((ret = get_reply(
s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))
< 0) {
goto error;
}
switch(in_pkt.type) {
case BINRPC_FAULT:
if(print_fault(&in_pkt, msg_body, in_pkt.tlen) < 0) {
goto error;
}
break;
case BINRPC_REPL:
cfg_vars_no = 100; /* default cmd list */
if((cfg_vars_array = parse_reply_body(
&cfg_vars_no, &in_pkt, msg_body, in_pkt.tlen))
== 0)
goto error;
break;
default:
fprintf(stderr, "ERROR: not a reply\n");
goto error;
}
/* get the config groups */
last_grp = 0;
for(r = 0; r < cfg_vars_no; r++) {
grp_name.s = 0;
grp_name.len = 0;
if(cfg_vars_array[r].type != BINRPC_T_STR)
continue;
grp_name.s = cfg_vars_array[r].u.strval.s;
end = cfg_vars_array[r].u.strval.len + grp_name.s;
/* parse <grp>: <var_name>*/
for(p = grp_name.s; p < end; p++) {
if(*p == ':') {
grp_name.len = (int)(long)(p - grp_name.s);
break;
}
}
for(grp = cfg_grp_lst; grp; grp = grp->next) {
if(grp->grp_name.len == grp_name.len
&& memcmp(grp->grp_name.s, grp_name.s, grp_name.len) == 0) {
break; /* found */
}
}
if(grp == 0) {
/* not found => create a new one */
grp = malloc(sizeof(*grp));
if(grp == 0)
goto error_mem;
memset(grp, 0, sizeof(*grp));
grp->grp_name = grp_name;
if(last_grp) {
last_grp->next = grp;
last_grp = grp;
} else {
cfg_grp_lst = grp;
last_grp = cfg_grp_lst;
}
}
grp->var_no++;
}
/* alloc the var arrays per group */
for(grp = cfg_grp_lst; grp; grp = grp->next) {
grp->var_names = malloc(sizeof(str) * grp->var_no);
if(grp->var_names == 0)
goto error_mem;
memset(grp->var_names, 0, sizeof(str) * grp->var_no);
grp->var_no = 0;
}
/* reparse to get the var names per group */
for(r = 0; r < cfg_vars_no; r++) {
grp_name.s = 0;
grp_name.len = 0;
var_name.s = 0;
var_name.len = 0;
if(cfg_vars_array[r].type != BINRPC_T_STR)
continue;
grp_name.s = cfg_vars_array[r].u.strval.s;
end = cfg_vars_array[r].u.strval.len + grp_name.s;
/* parse <grp>: <var_name>*/
for(p = grp_name.s; p < end; p++) {
if(*p == ':') {
grp_name.len = (int)(long)(p - grp_name.s);
p++;
for(; p < end && *p == ' '; p++)
;
var_name.s = p;
var_name.len = (int)(long)(end - p);
if(var_name.len == 0)
break;
for(grp = cfg_grp_lst; grp; grp = grp->next) {
if(grp->grp_name.len == grp_name.len
&& memcmp(grp->grp_name.s, grp_name.s, grp_name.len)
== 0) {
/* add var */
grp->var_names[grp->var_no] = var_name;
grp->var_no++;
}
}
break;
}
}
}
return 0;
binrpc_err:
error_send:
error:
error_mem:
return -1;
}
void free_cfg_grp_lst()
{
struct cfg_var_grp *grp;
struct cfg_var_grp *last;
grp = cfg_grp_lst;
while(grp) {
last = grp;
grp = grp->next;
if(last->var_names)
free(last->var_names);
free(last);
}
cfg_grp_lst = 0;
}
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
/* retrieve the counters names and group list */
static int get_counters_list(int s)
{
struct binrpc_cmd cmd;
int cookie;
unsigned char reply_buf[MAX_REPLY_SIZE];
unsigned char *msg_body;
struct binrpc_parse_ctx in_pkt;
struct cnt_var_grp *grp;
struct cnt_var_grp *last_grp;
str grp_name;
str var_name;
int r;
int ret;
cmd.method = "cnt.grps_list";
cmd.argc = 0;
if(!is_rpc_cmd(cmd.method))
goto error;
cookie = gen_cookie();
if((ret = send_binrpc_cmd(s, &cmd, cookie)) < 0) {
if(ret == -1)
goto error_send;
else
goto binrpc_err;
}
/* read reply */
memset(&in_pkt, 0, sizeof(in_pkt));
if((ret = get_reply(
s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))
< 0) {
goto error;
}
switch(in_pkt.type) {
case BINRPC_FAULT:
if(print_fault(&in_pkt, msg_body, in_pkt.tlen) < 0) {
goto error;
}
break;
case BINRPC_REPL:
cnt_grps_no = 20; /* default counter list */
if((cnt_grps_array = parse_reply_body(
&cnt_grps_no, &in_pkt, msg_body, in_pkt.tlen))
== 0)
goto error;
break;
default:
fprintf(stderr, "ERROR: not a reply\n");
goto error;
}
/* get the config groups */
last_grp = 0;
for(r = 0; r < cnt_grps_no; r++) {
grp_name.s = 0;
grp_name.len = 0;
if(cnt_grps_array[r].type != BINRPC_T_STR)
continue;
grp_name = cnt_grps_array[r].u.strval;
/* check for duplicates */
for(grp = cnt_grp_lst; grp; grp = grp->next) {
if(grp->grp_name.len == grp_name.len
&& memcmp(grp->grp_name.s, grp_name.s, grp_name.len) == 0) {
break; /* found */
}
}
if(grp == 0) {
/* not found => create a new one */
grp = malloc(sizeof(*grp));
if(grp == 0)
goto error_mem;
memset(grp, 0, sizeof(*grp));
grp->grp_name = grp_name;
if(last_grp) {
last_grp->next = grp;
last_grp = grp;
} else {
cnt_grp_lst = grp;
last_grp = cnt_grp_lst;
}
}
}
/* gets vars per group */
for(grp = cnt_grp_lst; grp; grp = grp->next) {
cmd.method = "cnt.var_list";
cmd.argv[0].type = BINRPC_T_STR;
cmd.argv[0].u.strval = grp->grp_name;
cmd.argc = 1;
if(!is_rpc_cmd(cmd.method))
goto error;
cookie = gen_cookie();
if((ret = send_binrpc_cmd(s, &cmd, cookie)) < 0) {
if(ret == -1)
goto error_send;
else
goto binrpc_err;
}
/* read reply */
memset(&in_pkt, 0, sizeof(in_pkt));
if((ret = get_reply(
s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))
< 0) {
goto error;
}
switch(in_pkt.type) {
case BINRPC_FAULT:
if(print_fault(&in_pkt, msg_body, in_pkt.tlen) < 0) {
goto error;
}
break;
case BINRPC_REPL:
grp->cnt_vars_no = 100; /* default counter list */
if((grp->cnt_vars_array = parse_reply_body(
&grp->cnt_vars_no, &in_pkt, msg_body, in_pkt.tlen))
== 0)
goto error;
break;
default:
fprintf(stderr, "ERROR: not a reply\n");
goto error;
}
grp->var_no = 0;
grp->var_names = malloc(sizeof(str) * grp->cnt_vars_no);
if(grp->var_names == 0)
goto error_mem;
memset(grp->var_names, 0, sizeof(str) * grp->cnt_vars_no);
for(r = 0; r < grp->cnt_vars_no; r++) {
if(grp->cnt_vars_array[r].type != BINRPC_T_STR)
continue;
var_name = grp->cnt_vars_array[r].u.strval;
grp->var_names[grp->var_no] = var_name;
grp->var_no++;
}
}
return 0;
binrpc_err:
error_send:
error:
error_mem:
return -1;
}
void free_cnt_grp_lst()
{
struct cnt_var_grp *grp;
struct cnt_var_grp *last;
grp = cnt_grp_lst;
while(grp) {
last = grp;
grp = grp->next;
if(last->cnt_vars_array)
free_rpc_array(last->cnt_vars_array, last->cnt_vars_no);
free(last);
}
cnt_grp_lst = 0;
}
#endif /* USE_COUNTERS */
static void print_formatting(char *prefix, char *format, char *suffix)
{
if(format) {
printf("%s", prefix);
for(; *format; format++) {
switch(*format) {
case '\t':
printf("\\t");
break;
case '\n':
printf("\\n");
break;
case '\r':
printf("\\r");
break;
default:
putchar(*format);
}
}
printf("%s", suffix);
}
}
static int kamcmd_help(int s, struct binrpc_cmd *cmd)
{
int r;
if(cmd->argc && (cmd->argv[0].type == BINRPC_T_STR)) {
/* if it has args, try command help */
for(r = 0; cmd_aliases[r].name; r++) {
if(strcmp(cmd->argv[0].u.strval.s, cmd_aliases[r].name) == 0) {
printf("%s is an alias for %s", cmd->argv[0].u.strval.s,
cmd_aliases[r].method);
print_formatting(" with reply formatting: \"",
cmd_aliases[r].format, "\"");
putchar('\n');
return 0;
}
}
for(r = 0; builtins[r].name; r++) {
if(strcmp(cmd->argv[0].u.strval.s, builtins[r].name) == 0) {
printf("builtin command: %s\n",
builtins[r].doc ? builtins[r].doc : "undocumented");
return 0;
}
}
cmd->method = "system.methodHelp";
if(run_binrpc_cmd(s, cmd, 0) < 0) {
printf("error: no such command %s\n", cmd->argv[0].u.strval.s);
}
return 0;
}
if(rpc_no == 0) {
if(get_kamcmd_list(s) < 0)
goto error;
}
for(r = 0; r < rpc_no; r++) {
if(rpc_array[r].type == BINRPC_T_STR) {
printf("%s\n", rpc_array[r].u.strval.s);
}
}
for(r = 0; cmd_aliases[r].name; r++) {
printf("alias: %s\n", cmd_aliases[r].name);
}
for(r = 0; builtins[r].name; r++) {
printf("builtin: %s\n", builtins[r].name);
}
return 0;
error:
return -1;
}
static int kamcmd_ver(int s, struct binrpc_cmd *cmd)
{
printf("%s\n", version);
printf("%s compiled on %s \n", __FILE__, compiled);
#ifdef USE_READLINE
printf("interactive mode command completion support\n");
#endif
return 0;
}
static int kamcmd_quit(int s, struct binrpc_cmd *cmd)
{
quit = 1;
return 0;
}
static int kamcmd_warranty(int s, struct binrpc_cmd *cmd)
{
printf("%s %s\n", NAME, VERSION);
printf("%s\n", COPYRIGHT);
printf("\n%s\n", LICENSE);
return 0;
}
#ifdef USE_READLINE
/* readline command generator */
static char *kamcmd_generator(const char *text, int state)
{
static int idx;
static int list; /* aliases, builtins, rpc_array */
static int len;
char *name;
#ifdef USE_CFG_VARS
static struct cfg_var_grp *grp;
#endif
#ifdef USE_COUNTERS
static struct cnt_var_grp *cnt_grp;
#endif
switch(attempted_completion_state) {
case COMPLETE_INIT:
case COMPLETE_NOTHING:
return 0;
case COMPLETE_CMD_NAME:
if(state == 0) {
/* init */
idx = list = 0;
len = strlen(text);
}
/* return next partial match */
switch(list) {
case 0: /* aliases*/
while((name = cmd_aliases[idx].name)) {
idx++;
if(strncmp(name, text, len) == 0)
return strdup(name);
}
list++;
idx = 0;
/* no break */
case 1: /* builtins */
while((name = builtins[idx].name)) {
idx++;
if(strncmp(name, text, len) == 0)
return strdup(name);
}
list++;
idx = 0;
/* no break */
case 2: /* rpc_array */
while(idx < rpc_no) {
if(rpc_array[idx].type == BINRPC_T_STR) {
name = rpc_array[idx].u.strval.s;
idx++;
if(strncmp(name, text, len) == 0)
return strdup(name);
} else {
idx++;
}
}
}
break;
#ifdef USE_CFG_VARS
case COMPLETE_CFG_GRP:
if(state == 0) {
/* init */
len = strlen(text);
grp = cfg_grp_lst;
} else {
grp = grp->next;
}
for(; grp; grp = grp->next) {
if(len <= grp->grp_name.len
&& memcmp(text, grp->grp_name.s, len) == 0) {
/* zero-term copy of the grp name */
name = malloc(grp->grp_name.len + 1);
if(name) {
memcpy(name, grp->grp_name.s, grp->grp_name.len);
name[grp->grp_name.len] = 0;
}
return name;
}
}
break;
case COMPLETE_CFG_VAR:
if(state == 0) {
/* init */
len = strlen(text);
idx = 0;
}
while(idx < crt_cfg_grp->var_no) {
if(len <= crt_cfg_grp->var_names[idx].len
&& memcmp(text, crt_cfg_grp->var_names[idx].s, len)
== 0) {
/* zero-term copy of the var name */
name = malloc(crt_cfg_grp->var_names[idx].len + 1);
if(name) {
memcpy(name, crt_cfg_grp->var_names[idx].s,
crt_cfg_grp->var_names[idx].len);
name[crt_cfg_grp->var_names[idx].len] = 0;
}
idx++;
return name;
}
idx++;
}
break;
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
case COMPLETE_CNT_GRP:
if(state == 0) {
/* init */
len = strlen(text);
cnt_grp = cnt_grp_lst;
} else {
cnt_grp = cnt_grp->next;
}
for(; cnt_grp; cnt_grp = cnt_grp->next) {
if(len <= cnt_grp->grp_name.len
&& memcmp(text, cnt_grp->grp_name.s, len) == 0) {
/* zero-term copy of the cnt_grp name */
name = malloc(cnt_grp->grp_name.len + 1);
if(name) {
memcpy(name, cnt_grp->grp_name.s,
cnt_grp->grp_name.len);
name[cnt_grp->grp_name.len] = 0;
}
return name;
}
}
break;
case COMPLETE_CNT_VAR:
if(state == 0) {
/* init */
len = strlen(text);
idx = 0;
}
while(idx < crt_cnt_grp->var_no) {
if(len <= crt_cnt_grp->var_names[idx].len
&& memcmp(text, crt_cnt_grp->var_names[idx].s, len)
== 0) {
/* zero-term copy of the var name */
name = malloc(crt_cnt_grp->var_names[idx].len + 1);
if(name) {
memcpy(name, crt_cnt_grp->var_names[idx].s,
crt_cnt_grp->var_names[idx].len);
name[crt_cnt_grp->var_names[idx].len] = 0;
}
idx++;
return name;
}
idx++;
}
break;
#endif /* USE_COUNTERS */
}
/* no matches */
return 0;
}
char **kamcmd_completion(const char *text, int start, int end)
{
int i, j;
int cmd_start, cmd_end, cmd_len;
int whitespace;
#ifdef USE_CFG_VARS
struct cfg_var_grp *grp;
static int grp_start;
int grp_len;
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
struct cnt_var_grp *cnt_grp;
static int cnt_grp_start;
int cnt_grp_len;
#endif /* USE_COUNTERS */
crt_param_no = 0;
/* skip over whitespace at the beginning */
for(j = 0; (j < start)
&& (rl_line_buffer[j] == ' ' || rl_line_buffer[j] == '\t');
j++)
;
cmd_start = j;
if(start == cmd_start) {
/* complete cmd name at beginning */
attempted_completion_state = COMPLETE_CMD_NAME;
#ifdef USE_CFG_VARS
grp_start = 0;
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
cnt_grp_start = 0;
#endif /* USE_COUNTERS */
} else { /* or if this is a command for which we complete the parameters */
/* find first whitespace after command name*/
for(; (j < start) && (rl_line_buffer[j] != ' ')
&& (rl_line_buffer[j] != '\t');
j++)
;
cmd_end = j;
cmd_len = cmd_end - cmd_start;
/* count params before the current one */
whitespace = 1;
for(; j < start; j++) {
if(rl_line_buffer[j] != ' ' && rl_line_buffer[j] != '\t') {
if(whitespace)
crt_param_no++;
whitespace = 0;
} else
whitespace = 1;
}
crt_param_no++;
if(crt_param_no == 1) {
for(i = 0; complete_params_methods[i]; i++) {
if((cmd_len == strlen(complete_params_methods[i]))
&& (strncmp(&rl_line_buffer[cmd_start],
complete_params_methods[i], cmd_len)
== 0)) {
attempted_completion_state = COMPLETE_CMD_NAME;
goto end;
}
}
#ifdef USE_CFG_VARS
/* try complete_param*_cfg_grp */
for(i = 0; complete_params_cfg_var[i]; i++) {
if((cmd_len == strlen(complete_params_cfg_var[i]))
&& (strncmp(&rl_line_buffer[cmd_start],
complete_params_cfg_var[i], cmd_len)
== 0)) {
attempted_completion_state = COMPLETE_CFG_GRP;
grp_start = start;
goto end;
}
}
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
/* try complete_param*_cfg_grp */
for(i = 0; complete_param1_counter_grp[i]; i++) {
if((cmd_len == strlen(complete_param1_counter_grp[i]))
&& (strncmp(&rl_line_buffer[cmd_start],
complete_param1_counter_grp[i], cmd_len)
== 0)) {
attempted_completion_state = COMPLETE_CNT_GRP;
cnt_grp_start = start;
goto end;
}
}
#endif /* USE_COUNTERS */
} else if(crt_param_no == 2) {
#ifdef USE_CFG_VARS
/* see if we complete cfg. var names for this command */
for(i = 0; complete_params_cfg_var[i]; i++) {
if((cmd_len == strlen(complete_params_cfg_var[i]))
&& (strncmp(&rl_line_buffer[cmd_start],
complete_params_cfg_var[i], cmd_len)
== 0)) {
/* get the group name: */
/* find grp_start */
for(j = cmd_end; (j < start)
&& ((rl_line_buffer[j] == ' ')
|| (rl_line_buffer[j] == '\t'));
j++)
;
grp_start = j;
/* find group end / grp_len*/
for(j = grp_start; (j < start) && (rl_line_buffer[j] != ' ')
&& (rl_line_buffer[j] != '\t');
j++)
;
grp_len = j - grp_start;
for(grp = cfg_grp_lst; grp; grp = grp->next) {
if(grp_len == grp->grp_name.len
&& memcmp(&rl_line_buffer[grp_start],
grp->grp_name.s, grp_len)
== 0) {
attempted_completion_state = COMPLETE_CFG_VAR;
crt_cfg_grp = grp;
goto end;
}
}
}
}
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
/* see if we complete counter names for this command */
for(i = 0; complete_param2_counter_name[i]; i++) {
if((cmd_len == strlen(complete_param2_counter_name[i]))
&& (strncmp(&rl_line_buffer[cmd_start],
complete_param2_counter_name[i], cmd_len)
== 0)) {
/* get the group name: */
/* find grp_start */
for(j = cmd_end; (j < start)
&& ((rl_line_buffer[j] == ' ')
|| (rl_line_buffer[j] == '\t'));
j++)
;
cnt_grp_start = j;
/* find group end / cnt_grp_len*/
for(j = cnt_grp_start;
(j < start) && (rl_line_buffer[j] != ' ')
&& (rl_line_buffer[j] != '\t');
j++)
;
cnt_grp_len = j - cnt_grp_start;
for(cnt_grp = cnt_grp_lst; cnt_grp;
cnt_grp = cnt_grp->next) {
if(cnt_grp_len == cnt_grp->grp_name.len
&& memcmp(&rl_line_buffer[cnt_grp_start],
cnt_grp->grp_name.s, cnt_grp_len)
== 0) {
attempted_completion_state = COMPLETE_CNT_VAR;
crt_cnt_grp = cnt_grp;
goto end;
}
}
}
}
#endif /* COUNTERS */
}
attempted_completion_state = COMPLETE_NOTHING;
}
end:
return 0; /* let readline call kamcmd_generator */
}
#endif /* USE_READLINE */
/* on exit cleanup */
static void cleanup()
{
if(unix_socket) {
if(unlink(unix_socket) < 0) {
fprintf(stderr, "ERROR: failed to delete %s: %s\n", unix_socket,
strerror(errno));
}
}
}
int main(int argc, char **argv)
{
int c;
char *sock_name;
int sock_type;
int s;
struct binrpc_cmd cmd;
struct id_list *sock_id;
char *format;
char *line;
char *l;
quit = 0;
format = 0;
line = 0;
s = -1;
sock_name = 0;
sock_type = UNIXS_SOCK;
opterr = 0;
while((c = getopt(argc, argv, "UVhs:D:R:vf:")) != -1) {
switch(c) {
case 'V':
printf("version: %s\n", version);
printf("%s compiled on %s \n", __FILE__, compiled);
exit(0);
break;
case 'h':
printf("version: %s\n", version);
printf("%s", help_msg);
exit(0);
break;
case 's':
sock_name = optarg;
break;
case 'R':
reply_socket = optarg;
break;
case 'D':
sock_dir = optarg;
break;
case 'U':
sock_type = UDP_SOCK;
break;
case 'v':
verbose++;
break;
case 'f':
format = str_escape(optarg);
if(format == 0) {
fprintf(stderr, "ERROR: memory allocation failure\n");
goto error;
}
break;
case '?':
if(isprint(optopt))
fprintf(stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf(stderr, "Unknown option character `\\x%x'.\n",
optopt);
goto error;
case ':':
fprintf(stderr, "Option `-%c' requires an argument.\n", optopt);
goto error;
default:
abort();
}
}
if(sock_name == 0) {
sock_name = DEFAULT_CTL_SOCKET;
}
/* init the random number generator */
srand(getpid() + time(0)); /* we don't need very strong random numbers */
if(sock_name == 0) {
fprintf(stderr, "ERROR: no server socket address specified\n");
goto error;
}
sock_id = parse_listen_id(sock_name, strlen(sock_name), sock_type);
if(sock_id == 0) {
fprintf(stderr, "ERROR: error parsing server socket address %s\n",
sock_name);
goto error;
}
switch(sock_id->proto) {
case UDP_SOCK:
case TCP_SOCK:
if(sock_id->port == 0) {
sock_id->port = DEFAULT_CTL_PORT;
/*
fprintf(stderr, "ERROR: no port specified: %s:<port>\n",
sock_name);
goto error;
*/
}
if((s = connect_tcpudp_socket(sock_id->name, sock_id->port,
(sock_id->proto == UDP_SOCK) ? SOCK_DGRAM
: SOCK_STREAM))
< 0) {
goto error;
}
break;
case UNIXS_SOCK:
case UNIXD_SOCK:
if((s = connect_unix_sock(sock_id->name,
(sock_id->proto == UNIXD_SOCK) ? SOCK_DGRAM
: SOCK_STREAM))
< 0)
goto error;
break;
case UNKNOWN_SOCK:
fprintf(stderr, "ERROR: Bad socket type for %s\n", sock_name);
goto error;
}
free(sock_id->buf); /* not needed anymore */
free(sock_id); /* not needed anymore */
sock_id = 0;
if(optind >= argc) {
/*fprintf(stderr, "ERROR: no command specified\n");
goto error; */
} else {
if(parse_cmd(&cmd, &argv[optind], argc - optind) < 0)
goto error;
if(run_cmd(s, &cmd, format) < 0)
goto error;
goto end;
}
/* interactive mode */
if(get_kamcmd_list(s) == 0) {
#ifdef USE_CFG_VARS
get_cfgvars_list(s);
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
get_counters_list(s);
#endif /* USE_COUNTERS */
}
/* banners */
printf("%s %s\n", NAME, VERSION);
printf("%s\n", COPYRIGHT);
printf("%s\n", DISCLAIMER);
#ifdef USE_READLINE
/* initialize readline */
/* allow conditional parsing of the ~/.inputrc file*/
rl_readline_name = NAME;
rl_completion_entry_function = kamcmd_generator;
rl_attempted_completion_function = kamcmd_completion;
while(!quit) {
line = readline(NAME "> ");
if(line == 0) /* EOF */
break;
l = trim_ws(line); /* trim whitespace */
if(*l) {
add_history(l);
run_line(s, l, format);
}
free(line);
line = 0;
}
#else
line = malloc(MAX_LINE_SIZE);
if(line == 0) {
fprintf(stderr, "memory allocation error\n");
goto error;
}
printf(NAME "> ");
fflush(stdout); /* prompt */
while(!quit && fgets(line, MAX_LINE_SIZE, stdin)) {
l = trim_ws(line);
if(*l) {
run_line(s, l, format);
}
printf(NAME "> ");
fflush(stdout); /* prompt */
};
free(line);
line = 0;
#endif /* USE_READLINE */
end:
/* normal exit */
if(line)
free(line);
if(format)
free(format);
if(rpc_array)
free_rpc_array(rpc_array, rpc_no);
#ifdef USE_CFG_VARS
if(cfg_grp_lst)
free_cfg_grp_lst();
if(cfg_vars_array) {
free_rpc_array(cfg_vars_array, cfg_vars_no);
cfg_vars_array = 0;
cfg_vars_no = 0;
}
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
if(cnt_grp_lst)
free_cnt_grp_lst();
if(cnt_grps_array) {
free_rpc_array(cnt_grps_array, cnt_grps_no);
cnt_grps_array = 0;
cnt_grps_no = 0;
}
#endif /* USE_COUNTERS */
cleanup();
exit(0);
error:
if(line)
free(line);
if(format)
free(format);
if(rpc_array)
free_rpc_array(rpc_array, rpc_no);
#ifdef USE_CFG_VARS
if(cfg_grp_lst)
free_cfg_grp_lst();
if(cfg_vars_array) {
free_rpc_array(cfg_vars_array, cfg_vars_no);
cfg_vars_array = 0;
cfg_vars_no = 0;
}
#endif /* USE_CFG_VARS */
#ifdef USE_COUNTERS
if(cnt_grp_lst)
free_cnt_grp_lst();
if(cnt_grps_array) {
free_rpc_array(cnt_grps_array, cnt_grps_no);
cnt_grps_array = 0;
cnt_grps_no = 0;
}
#endif /* USE_COUNTERS */
cleanup();
exit(-1);
}