2
0
Эх сурвалжийг харах

- new command line tool which communicates with the ctl module
(using the new binary protocol)
- supports various connection methods: udp, tcp, unix sockets (datagram
and stream)
- can do basic reply formatting (see -f )
- supports both command line and interactive modes
- if compiled with libreadline supports command completion in the
interactive mode
- depends only on libreadline/libedit if compiled with command completion
support
Example:
sercmd -s unixd:/tmp/unix_dgram -f 'pid:%v desc:"%v"\n' core.ps
sercmd # enters interactive mode, uses default unix:/tmp/ser_ctl socket
sercmd ps
sercmd who
(see also sercmd -h and utils/sercmd/EXAMPLES)

Andrei Pelinescu-Onciul 19 жил өмнө
parent
commit
45b6a746bc

+ 34 - 0
NEWS

@@ -10,6 +10,14 @@ new archs:
 
 
 modules:
+ - ctl        - new  fifo/unixsocket/xmlrpc like module, using a space 
+                efficient binary encoding for the requests.
+                It supports multiple clients on tcp, udp, unix stream or
+                unix datagram modes. By default (no modparams) it opens
+                one unix stream control socket in /tmp/ser_ctl.
+                It also includes extended fifo support: multiple fifos,
+                fifo over tcp, udp and unix sockets (see ctl/ctl.cfg).
+                Use utils/sercmd/sercmd to send commands to it.
  - dispatcher - added hashing after request uri and to uri
               - added a new flag parameter which can be used (for now) to
                 select only the username or the username, host and port when
@@ -41,6 +49,24 @@ modules:
               Vias a.s.o) and not on the original message
  
 core:
+ - added named routes: names can be used instead of numbers in all the
+   route commads or route declarations. route(number) is equivalent to
+   route("number").
+   Example:
+     route("test");
+      route["test"]{
+           ...
+     }
+ - added named flags, declared at the beginning of the config file with:
+     flags  flag1_name[:position],  flag2_name ...
+   Example:
+       flags test, a:1, b:2 ;
+       route{
+              setflag(test);
+              if (isflagset(a)){ # equiv. to isflagset(1)
+                ....
+              }
+              resetflag(b);  # equiv. to resetflag(2) 
  - added return [val] which returns from a route. if no value is specified, or
    a route reaches its end without executing a return statement, it returns 1.
    If return is used in the top level route is equivalent with exit [val].
@@ -123,6 +149,14 @@ new config variables:
    tcp_max_connections = no. - maximum number of tcp connections (if the number
       is exceeded no new tcp connections will be accepted). Default: 2048.
 
+tools:
+  utils/sercmd - command line serctl like tool for interrogating ser ctl
+                 module (uses the binrpc encoding). Supports various
+                 connection methods (udp, tcp, unix stream & datagram sockets),
+                 reply formating (see -f, e.g. sercmd -f "pid:%v %v\n" core.ps)
+                 , interactive mode, command line completion (if compiled with 
+                 libreadline) a.s.o.
+
 
 0.9.4 fixes/improvements (0.9.4 is a bug fix release for 0.9.3)
  

+ 59 - 0
utils/sercmd/EXAMPLES

@@ -0,0 +1,59 @@
+# $id$
+
+sercmd usage examples
+
+
+help:
+	sercmd  unixd:/tmp/unix_dgram -h
+
+use an udp ser control socket:
+ ser config:
+  loadmodule "modules/ctl/ctl.so"
+  modparam("ctl", "binrpc", "udp:localhost:2046")
+  modparam("ctl", "binrpc", "tcp:localhost:2046")
+  modparam("ctl", "binrpc", "unixs:/tmp/unix_stream")
+  modparam("ctl", "binrpc", "unixd:/tmp/unix_dgram")
+
+ sercmd:
+	sercmd -s udp:localhost:2046 core.version
+
+use a tcp socket:
+	sercmd -s tcp:localhost:2046 core.version
+
+use a stream unix socket:
+	sercmd -s unixs:/tmp/unix_stream core.version
+
+use a datagram unix socket:
+	sercmd -s unixd:/tmp/unix_dgram core.version
+
+
+
+list available commands on ser side:
+	sercmd -s unixd:/tmp/unix_drgam ls
+
+list all available commands (including sercmd builtins or aliases):
+	sercmd -s unixd:/tmp/unix_dgram ?
+or
+	sercmd -s unixd:/tmp/unix_dgram help
+
+get help on one command:
+	sercmd -s unixd:/tmp/unix_dgram help core.ps
+
+list ser processes:
+	sercmd -s unixd:/tmp/unix_dgram ps
+
+send an rpc command to ser:
+	sercmd -s unixd:/tmp/unix_dgram core.shmmem
+
+format the output:
+	sercmd -s unixd:/tmp/unix_dgram -f 'pid:%v desc:"%v"\n' core.ps
+	(note: you could use just ps instead of core.ps)
+	
+format the output as csv:
+	sercmd -s unixd:/tmp/unix_dgram -f '%v,' core.ps
+
+enter interactive mode:
+	sercmd -s unixd:/tmp/unix_dgram
+	(note: type help,or ? to see the command list, tab expansion should also
+	 work)
+

+ 22 - 0
utils/sercmd/Makefile

@@ -0,0 +1,22 @@
+# $Id$
+
+include ../../Makefile.defs
+
+auto_gen=
+NAME=sercmd
+RELEASE=0.1
+use_readline=1
+DEFS:= -DNAME='"$(NAME)"' -DVERSION='"$(RELEASE)"' \
+		$(filter -D%HAVE -DARCH% -DOS% -D__CPU% -D__OS%, $(DEFS))
+LIBS:=$(filter-out -lfl  -ldl -lpthread -lssl -lcrypto, $(LIBS))
+
+ifeq ($(use_readline),1)
+	DEFS+=-DUSE_READLINE
+	LIBS+=-lreadline
+endif
+
+include ../../Makefile.sources
+include ../../Makefile.rules
+
+
+modules:

+ 4 - 0
utils/sercmd/TODO

@@ -0,0 +1,4 @@
+
+- help for internal commands & aliases
+- send timeout
+- reconnect?

+ 34 - 0
utils/sercmd/license.h

@@ -0,0 +1,34 @@
+/*
+ * $Id$
+ *
+ * sercmd GPL license, standard disclaimer and copyright
+ */
+
+#ifndef __license_h_
+#define __license_h_
+
+#define COPYRIGHT  "Copyright 2006 iptelorg GmbH"
+#define DISCLAIMER \
+"This is free software with ABSOLUTELY NO WARRANTY.\n\
+For details type `warranty'."
+
+
+#define LICENSE \
+"    This program is free software; you can redistribute it and/or modify\n\
+    it under the terms of the GNU General Public License as published by\n\
+    the Free Software Foundation; either version 2 of the License , or\n\
+    (at your option) any later version.\n\
+\n\
+    This program is distributed in the hope that it will be useful,\n\
+    but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
+    GNU General Public License for more details.\n\
+\n\
+    You should have received a copy of the GNU General Public License\n\
+    along with this program. If not, write to\n\
+\n\
+       The Free Software Foundation, Inc.\n\
+       51 Franklin Street, Fifth Floor,\n\
+       Boston, MA 02110-1301, USA."
+
+#endif

+ 267 - 0
utils/sercmd/parse_listen_id.c

@@ -0,0 +1,267 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser 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 ser 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:
+ *    [email protected]
+ *
+ * ser 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-02-20  created by andrei
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h> /* malloc */
+#include <string.h>
+#include <netdb.h> /* getservbyname*/
+
+#include "parse_listen_id.h"
+
+/* ser source compat. defines*/
+#define pkg_malloc malloc
+#define pkg_free   free
+
+#ifdef __SUNPRO_C
+#define DBG(...)
+#define LOG(lev, ...) fprintf(stderr,  __VA_ARGS__)
+#else
+#define DBG(fmt, args...)
+#define LOG(lev, fmt, args...) fprintf(stderr, fmt, ## args)
+#endif
+
+
+
+
+
+/* converts a str to an u. short, returns the u. short and sets *err on
+ * error and if err!=null
+  */
+static inline unsigned short str2s(const char* s, unsigned int len,
+									int *err)
+{
+	unsigned short ret;
+	int i;
+	unsigned char *limit;
+	unsigned char *init;
+	unsigned char* str;
+
+	/*init*/
+	str=(unsigned char*)s;
+	ret=i=0;
+	limit=str+len;
+	init=str;
+
+	for(;str<limit ;str++){
+		if ( (*str <= '9' ) && (*str >= '0') ){
+				ret=ret*10+*str-'0';
+				i++;
+				if (i>5) goto error_digits;
+		}else{
+				/* error unknown char */
+				goto error_char;
+		}
+	}
+	if (err) *err=0;
+	return ret;
+
+error_digits:
+	DBG("str2s: ERROR: too many letters in [%.*s]\n", (int)len, init);
+	if (err) *err=1;
+	return 0;
+error_char:
+	DBG("str2s: ERROR: unexpected char %c in %.*s\n", *str, (int)len, init);
+	if (err) *err=1;
+	return 0;
+}
+
+
+
+/* parse proto:address:port   or proto:address */
+/* returns struct id_list on success (pkg_malloc'ed), 0 on error
+ * WARNING: it will add \0 in the string*/
+/* parses:
+ *     tcp|udp|unix:host_name:port
+ *     tcp|udp|unix:host_name
+ *     host_name:port
+ *     host_name
+ * 
+ *
+ *     where host_name=string, ipv4 address, [ipv6 address],
+ *         unix socket path (starts with '/')
+ */
+struct id_list* parse_listen_id(char* l, int len, enum socket_protos def)
+{
+	char* p;
+	enum socket_protos proto;
+	char* name;
+	char* port_str;
+	int port;
+	int err;
+	struct servent* se;
+	char* s;
+	struct id_list* id;
+	
+	s=pkg_malloc((len+1)*sizeof(char));
+	if (s==0){
+		LOG(L_ERR, "ERROR:parse_listen_id: out of memory\n");
+		goto error;
+	}
+	memcpy(s, l, len);
+	s[len]=0; /* null terminate */
+	
+	/* duplicate */
+	proto=UNKNOWN_SOCK;
+	port=0;
+	name=0;
+	port_str=0;
+	p=s;
+	
+	if ((*p)=='[') goto ipv6;
+	/* find proto or name */
+	for (; *p; p++){
+		if (*p==':'){
+			*p=0;
+			if (strcasecmp("tcp", s)==0){
+				proto=TCP_SOCK;
+				goto find_host;
+			}else if (strcasecmp("udp", s)==0){
+				proto=UDP_SOCK;
+				goto find_host;
+			}else if ((strcasecmp("unix", s)==0)||(strcasecmp("unixd", s)==0)){
+				proto=UNIXS_SOCK;
+				goto find_host;
+			}else if (strcasecmp("unixs", s)==0){
+				proto=UNIXS_SOCK;
+				goto find_host;
+#ifdef USE_FIFO
+			}else if (strcasecmp("fifo", s)==0){
+				proto=FIFO_SOCK;
+				goto find_host;
+#endif
+			}else{
+				proto=UNKNOWN_SOCK;
+				/* this might be the host */
+				name=s;
+				goto find_port;
+			}
+		}
+	}
+	name=s;
+	goto end; /* only name found */
+find_host:
+	p++;
+	if (*p=='[') goto ipv6;
+	name=p;
+	for (; *p; p++){
+		if ((*p)==':'){
+			*p=0;
+			goto find_port;
+		}
+	}
+	goto end; /* nothing after name */
+ipv6:
+	name=p;
+	p++;
+	for(;*p;p++){
+		if(*p==']'){
+			if(*(p+1)==':'){
+				p++; *p=0;
+				goto find_port;
+			}else if (*(p+1)==0) goto end;
+		}else{
+			goto error;
+		}
+	}
+	
+find_port:
+	p++;
+	port_str=(*p)?p:0;
+	
+end:
+	/* fix all the stuff */
+	if (name==0) goto error;
+	if (proto==UNKNOWN_SOCK){
+		/* try to guess */
+		if (port_str){
+			switch(def){
+				case TCP_SOCK:
+				case UDP_SOCK:
+					proto=def;
+					break;
+				default:
+					proto=TCP_SOCK;
+					DBG("guess:%s is a tcp socket\n", name);
+			}
+		}else if (name && strchr(name, '/')){
+			switch(def){
+				case TCP_SOCK:
+				case UDP_SOCK:
+					DBG("guess:%s is a unix socket\n", name);
+					proto=UNIXD_SOCK;
+					break;
+				default:
+					/* def is filename based => use default */
+					proto=def;
+			}
+		}else{
+			/* using default */
+			proto=def;
+		}
+	}
+	if (port_str){
+		port=str2s(port_str, strlen(port_str), &err);
+		if (err){
+			/* try getservbyname */
+			se=getservbyname(port_str, 
+					(proto==TCP_SOCK)?"tcp":(proto==UDP_SOCK)?"udp":0);
+			if (se) port=ntohs(se->s_port);
+			else goto error;
+		}
+	}else{
+		/* no port, check if the hostname is a port 
+		 * (e.g. tcp:3012 == tcp:*:3012 */
+		if (proto==TCP_SOCK|| proto==UDP_SOCK){
+			port=str2s(name, strlen(name), &err);
+			if (err){
+				port=0;
+			}else{
+				name="*"; /* inaddr any  */
+			}
+		}
+	}
+	id=pkg_malloc(sizeof(struct id_list));
+	if (id==0){
+		LOG(L_ERR, "ERROR:parse_listen_id: out of memory\n");
+		goto error;
+	}
+	id->name=name;
+	id->proto=proto;
+	id->data_proto=P_BINRPC;
+	id->port=port;
+	id->buf=s;
+	id->next=0;
+	return id;
+error:
+	if (s) pkg_free(s);
+	return 0;
+}

+ 59 - 0
utils/sercmd/parse_listen_id.h

@@ -0,0 +1,59 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser 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 ser 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:
+ *    [email protected]
+ *
+ * ser 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-02-20  created by andrei
+ */
+
+
+#ifndef parse_listen_id_h
+#define parse_listen_id_h
+
+enum payload_proto	{ P_BINRPC , P_FIFO };
+
+enum socket_protos	{	UNKNOWN_SOCK=0, UDP_SOCK, TCP_SOCK, 
+						UNIXS_SOCK, UNIXD_SOCK
+#ifdef USE_FIFO
+							, FIFO_SOCK
+#endif
+};
+
+
+
+struct id_list{
+	char* name;
+	enum socket_protos proto;
+	enum payload_proto data_proto;
+	int port;
+	char* buf; /* name points somewhere here */
+	struct id_list* next;
+};
+
+
+struct id_list* parse_listen_id(char* l, int len, enum socket_protos def);
+
+#endif

+ 1467 - 0
utils/sercmd/sercmd.c

@@ -0,0 +1,1467 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser 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 ser 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:
+ *    [email protected]
+ *
+ * ser 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * send commands using binrpc
+ *
+ * History:
+ * --------
+ *  2006-02-14  created by andrei
+ */
+
+
+#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 <time.h> /* time */
+
+#ifdef USE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#include "parse_listen_id.h"
+#include "license.h"
+
+#include "../../modules/ctl/ctl_defaults.h" /* default socket & port */
+#include "../../modules/ctl/binrpc.h"
+#include "../../modules/ctl/binrpc.c" /* ugly hack */
+
+
+#ifndef NAME
+#define NAME    "sercmd"
+#endif
+#ifndef VERSION
+#define VERSION "0.1"
+#endif
+
+#define IOVEC_CNT 20
+#define BUF_SIZE  65535
+#define MAX_LINE_SIZE 1024 /* for non readline mode */
+#define MAX_REPLY_SIZE  4096
+#define MAX_BODY_SIZE   4096
+#define MAX_BINRPC_ARGS  128
+
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+static char id[]="$Id$";
+static char version[]= NAME " " VERSION;
+static char compiled[]= __TIME__ " " __DATE__;
+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:2048 , unixs:/tmp/ser_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\
+Example:\n\
+        " NAME " -s unixs:/tmp/ser_unix 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;
+
+
+
+
+#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 sercmd_builtin{
+	char* name;
+	int (*f)(int, struct binrpc_cmd*);
+	char* doc;
+};
+
+
+static int sercmd_help(int s, struct binrpc_cmd* cmd);
+static int sercmd_ver(int s, struct binrpc_cmd* cmd);
+static int sercmd_quit(int s, struct binrpc_cmd* cmd);
+static int sercmd_warranty(int s, struct binrpc_cmd* cmd);
+
+
+static struct cmd_alias cmd_aliases[]={
+	{	"ps",			"core.ps",				"%v\t%v\n"	},
+	{	"list",			"system.listMethods",	0			},
+	{	"ls",			"system.listMethods",	0			},
+	{	"server",		"core.version",			0			},
+	{	"serversion",	"core.version",			0			},
+	{	"who",			"ctl.who",				"[%v] %v: %v %v -> %v %v\n"},
+	{	"listen",		"ctl.listen",			"[%v] %v: %v %v\n"},
+	{0,0,0}
+};
+
+
+static struct sercmd_builtin builtins[]={
+	{	"?",		sercmd_help, "help"},
+	{	"help",		sercmd_help, "displays help for a command"},
+	{	"version",	sercmd_ver,  "displays " NAME "version"},
+	{	"quit",		sercmd_quit, "exits " NAME },
+	{	"exit",		sercmd_quit, "exits " NAME },
+	{	"warranty",		sercmd_warranty, "displays " NAME "'s warranty info"},
+	{	"license",		sercmd_warranty, "displays " NAME "'s license"},
+	{0,0}
+};
+
+
+
+#ifdef USE_READLINE
+/* commands for which we complete the params to other command names */
+char* complete_params[]={
+	"?",
+	"h",
+	"help",
+	"system.methodSignature",
+	"system.methodHelp",
+	0
+};
+#endif
+
+
+
+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 strut 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;
+		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;
+}
+
+
+
+/* reads the whole reply
+ * returns < 0 on error, reply size on success + initializes in_pkt */
+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;
+	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)
+				goto error_toolong;
+		}
+	}while(crt<msg_end);
+	
+	*body=hdr_end;
+	return (int)(msg_end-reply_buf);
+error_read:
+	return -1;
+error_parse:
+	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 an 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, &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:
+			case -3:
+			case -4:
+				goto error_parse;
+				goto error_parse;
+				goto error_parse;
+		}
+		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(ret));
+	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;
+}
+
+
+
+/* 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;
+
+	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;
+	rec=0;
+	
+	/* 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, &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;
+		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(a);
+	}
+	*records=0;
+	return 0;
+}
+
+
+
+static int get_sercmd_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=10; /* 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;
+}
+
+
+
+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 sercmd_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_sercmd_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 sercmd_ver(int s, struct binrpc_cmd* cmd)
+{
+	printf("%s\n", version);
+	printf("%s\n", id);
+	printf("%s compiled on %s \n", __FILE__, compiled);
+#ifdef USE_READLINE
+	printf("interactive mode command completion support\n");
+#endif
+	return 0;
+}
+
+
+
+static int sercmd_quit(int s, struct binrpc_cmd* cmd)
+{
+	quit=1;
+	return 0;
+}
+
+
+
+static int sercmd_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* sercmd_generator(const char* text, int state)
+{
+	static int idx;
+	static int list; /* aliases, builtins, rpc_array */
+	static int len;
+	char* 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++;
+				}
+			}
+	}
+	/* no matches */
+	return 0;
+}
+
+
+char** sercmd_completion(const char* text, int start, int end)
+{
+	int r;
+	int i;
+	
+	rl_attempted_completion_over=1;
+	/* complete only at beginning */
+	if (start==0){
+		rl_attempted_completion_over=0;
+	}else{ /* or if this is a command for which we complete the parameters */
+		/* find first whitespace */
+		for(r=0; (r<start) && (rl_line_buffer[r]!=' ') && 
+				(rl_line_buffer[r]!='\t'); r++);
+		for(i=0; complete_params[i]; i++){
+			if ((r==strlen(complete_params[i])) &&
+					(strncmp(rl_line_buffer, complete_params[i], r)==0)){
+					rl_attempted_completion_over=0;
+					break;
+			}
+		}
+	}
+	return 0; /* let readline call sercmd_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 port_no;
+	int sock_type;
+	int s;
+	struct binrpc_cmd cmd;
+	int rec;
+	struct id_list* sock_id;
+	char* format;
+	int interactive;
+	char* line;
+	char* l;
+
+	quit=0;
+	format=0;
+	line=0;
+	interactive=0;
+	rec=1;
+	s=-1;
+	sock_name=0;
+	port_no=0;
+	sock_type=UNIXS_SOCK;
+	opterr=0;
+	while((c=getopt(argc, argv, "Vhs:D:R:vf:"))!=-1){
+		switch(c){
+			case 'V':
+				printf("version: %s\n", version);
+				printf("%s\n", id);
+				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 ser 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 ser adress %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); /* not needed anymore */
+	sock_id=0;
+	
+	if (optind>=argc){
+			interactive=1;
+			/*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 */
+	get_sercmd_list(s);
+	/* 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=sercmd_generator;
+	rl_attempted_completion_function=sercmd_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);
+	cleanup();
+	exit(0);
+error:
+	if (line)
+		free(line);
+	if (format)
+		free(format);
+	if (rpc_array)
+		free(rpc_array);
+	cleanup();
+	exit(-1);
+}