mirror of https://github.com/sipwise/kamailio.git
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.
2245 lines
51 KiB
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);
|
|
}
|