浏览代码

- 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 年之前
父节点
当前提交
45b6a746bc
共有 8 个文件被更改,包括 1946 次插入0 次删除
  1. 34 0
      NEWS
  2. 59 0
      utils/sercmd/EXAMPLES
  3. 22 0
      utils/sercmd/Makefile
  4. 4 0
      utils/sercmd/TODO
  5. 34 0
      utils/sercmd/license.h
  6. 267 0
      utils/sercmd/parse_listen_id.c
  7. 59 0
      utils/sercmd/parse_listen_id.h
  8. 1467 0
      utils/sercmd/sercmd.c

+ 34 - 0
NEWS

@@ -10,6 +10,14 @@ new archs:
 
 
 
 
 modules:
 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
  - dispatcher - added hashing after request uri and to uri
               - added a new flag parameter which can be used (for now) to
               - added a new flag parameter which can be used (for now) to
                 select only the username or the username, host and port when
                 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
               Vias a.s.o) and not on the original message
  
  
 core:
 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
  - 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.
    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].
    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
    tcp_max_connections = no. - maximum number of tcp connections (if the number
       is exceeded no new tcp connections will be accepted). Default: 2048.
       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)
 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);
+}