Revision 238 (by dpavlin, 2004/03/08 17:46:16) tagging openisis 0.9.0
/*
	openisis - an open implementation of the CDS/ISIS database
	Version 0.8.x (patchlevel see file Version)
	Copyright (C) 2001-2003 by Erik Grziwotz, erik@openisis.org

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library 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
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	see README for more information
EOH */

/*
	$Id: lcli.c,v 1.13 2003/05/15 19:17:17 mawag Exp $
	OpenIsis client side of communication
*/

#ifndef WIN32
#include <sys/types.h>
#include <sys/socket.h> /* socket etc */
#include <netinet/in.h> /* IPPROTO_TCP, htons */
#include <netdb.h> /* gethostbyname */
#endif

#include <errno.h>
#include <memory.h>
#include <unistd.h>

#include "openisis.h"
#include "loi.h"
#include "lio.h"
#include "lcli.h"

/*
gcc -D_MAIN_ -I. -Lsto lcli.c -lopenisis && ./a.out 111 `find .`
gcc -D_MAIN_ -DWIN32 -I. lcli.c win/libopenisis.a /mingw/lib/libwsock32.a
*/

#define LF 10 /* LineFeed a.k.a. newline - '\n' isn't really well defined */
#define TAB 9 /* horizontal, that is */
#define VT 11 /* vertical, used as newline replacement */

#define STAT_CONT  0x01  /* not at beginning of line */
#define STAT_BIN   0x10  /* binary mode */

/* plain protocol
*  @return done or -err
*/
int cliPlain (Ios *s, int *stat, Rec **rec) {
	Field *f;
	int l = s->b.fill - s->b.done;
	unsigned char *b = s->b.c + s->b.done;
	unsigned char *end = b+l, *v, *p;
	if ( ! l ) { /* EOF: done */
		if ( ! (STAT_CONT & *stat) ) /* ok */
			return 1;
		/* last field wasn't closed by LF */
		return sMsg (ERR_INVAL, "cliPlain(%d): no EOL", LIO_FD & s->file);
	}
	if ( STAT_CONT & *stat )
		RSPACE( *rec, l, !0 );
	/* add text lines */
	while ( b<end ) {
		int conti = 0;
		switch ( STAT_CONT & *stat ) {
		case 0: /* at beginning of line -- start new field */
			if ( LF == *b ) /* empty line */
				return 1;
			if ( TAB != *b || ! *rec || ! (*rec)->len ) {
				RADD( *rec, 0,0,end-b, !0 );
			}
			else { /* binary mode continuation line */
				conti = 1;
				if ( !(STAT_BIN & *stat)) {
					sMsg( LOG_INFO, "cliPlain(%d): detected binary mode",
						LIO_FD & s->file);
					*stat |= STAT_BIN;
				}
				RSPACE( *rec, end-b, !0 );
			}
			if ( ! *rec )
				return -ERR_NOMEM;
			*stat |= STAT_CONT;
		case STAT_CONT: /* add to last field */
			f = (*rec)->field + (*rec)->len-1;
			v = (unsigned char*)f->val;
			p = v + f->len;
			if ( conti ) {
				*p++ = LF;
				b++;
			}
			if ( STAT_BIN & *stat ) {
				for ( ; b<end && LF != (*p = *b++); p++ )
					;
			} else {
				for ( ; b<end && LF != (*p = *b++); p++ )
					if ( VT == *p ) /* convert VTABs */
						*p = LF; /* back to newlines */
			}
			(*rec)->used += (p - v) - f->len;
			f->len = p - v;
			if ( LF == b[-1] ) {
				int ret = a2il( f->val, f->len, &f->tag );
				if ( ret ) {
					if ( ret < f->len && TAB == v[ret] )
						ret++;
					if ( ret < f->len )
						memmove( v, v+ret, f->len - ret );
					f->len -= ret;
				}
				*stat &= ~STAT_CONT;
			}
			sMsg (LOG_VERBOSE, "cliPlain(%d): fld[%2d] %3d = '%.*s'",
				LIO_FD & s->file, (*rec)->len-1, f->tag, f->len, f->val);
		}
	}
	return 0;
}

Rec* cliRead (CliChnl *ch) {
	Ios  str;
	Rec *rec;
	int  rt, stat;

	if (!ch || 0 > ch->sd) {
		return 0;
	}
	memset (&str, 0, sizeof (Ios));
	str.file = ch->sd | LIO_IN;
	rec = 0;
	stat = 0;
	ch->err = errno = 0;

	while (1) {
		str.b.done = str.b.fill;
		rt = ioStdio (&str, LIO_SFILL);
		if (0 > rt) {
			ch->err = errno;
			if (rec) {
				mFree (rec);
			}
			if (!(LIO_IN & str.file)) {
				ch->sd = -1;
			}
			return 0;
		}
		rt = cliPlain (&str, &stat, &rec);
		if (rt) {
			if (0 > rt) {
				if (rec) {
					mFree (rec);
				}
				return 0;
			}
			log_rec (LOG_VERBOSE, rec, "cliRead(%d): ", 0);
			sMsg (LOG_INFO, "cliRead(%d): read #flds %d",
				ch->sd, (rec ? rec->len : -1));
			return rec;
		}
	}
}

int cliWrite (CliChnl *ch, Rec *rec) {
	char  buf [4096];
	char *b;
	int   fd, len, sl, rl, rt;
	if (!ch || 0 > ch->sd) {
		return -ERR_BADF;
	}
	ch->err = 0;
	if (!rec || !rec->len) {
		return 0;
	}
	len = sizeof (buf);
	b = rSerA (rec, buf, &len);
	if (!b) {
		return sMsg (ERR_NOMEM, "cliWrite(%d): write (%d)", ch->sd, rec->used);
	}
	fd = ch->sd | LIO_OUT;
	for (sl = rt = errno = 0, rl = len; rl; sl += rt, rl -= rt) {
		rt = lio_write (&fd, b + sl, (unsigned)rl);
		ch->err = errno;
		if (0 > rt) {
			if (!(LIO_OUT & fd)) {
				ch->sd = -1;
			}
			break;
		}
	}
	if (b != buf) {
		mFree (b);
	}
	if (0 > rt) {
		return sMsg (ERR_IO, "cliWrite(%d): write (%d) = %d(%d)",
			len, rt, ch->err);
	}
	sMsg (LOG_INFO, "cliWrite(%d): wrote %d,%d", ch->sd, rec->len, len);
	return 0;
}

int cliConnect (CliChnl *ch, const char *hname, int port) {
#ifdef WIN32
	return sMsg (ERR_TRASH, "cliConnect: operation not supported");
#else
	struct sockaddr_in  addr;
	struct hostent     *hostp;
	int                 rt;
	if (!ch) {
		return sMsg (ERR_IDIOT, "cliConnect: null channel");
	}
	memset (ch, 0, sizeof (CliChnl));
	rt = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (0 > rt) {
		ch->err = errno;
		return sMsg (ERR_IO, "cliConnect: cannot create socket: %d", errno);
	}
	ch->sd = rt;
	memset (&addr, 0, sizeof (addr));
	if (hname) {
		hostp = gethostbyname (hname);
		if (! hostp) {
			ch->err = errno;
			return sMsg (ERR_INVAL, "cliConnect: unknown host <%s>: %d",
				hname, errno);
		}
		addr.sin_family = hostp->h_addrtype; /* already network-byte-order */
		addr.sin_addr.s_addr = *(long*)(*(hostp->h_addr_list)); /* dto */
	}
	else {
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
	}
	addr.sin_port = htons (port);
	rt = connect (ch->sd, &addr, sizeof (addr));
	if (0 > rt) {
		ch->err = errno;
		return sMsg (ERR_IO, "cliConnect: cannot connect to %s:%d: %d",
			(hname ? hname : "localhost"), port, errno);
	}
	return 0;
#endif
}

void cliClose (CliChnl *ch) {
	if (ch && 0 <= ch->sd) {
		close (ch->sd);
		ch->sd = -1;
	}
}

/* -----------------------------------------------------------------------
 * test
 */
#ifdef _MAIN_

#include <stdio.h> /* printf */
#include <stdlib.h> /* atoi */
#include <string.h> /* strerror */
#include "luti.h" /* log_rec */

void cmp (Rec *in, Rec *out) {
	Field *F1, *F2;
	int l1 = in->len;
	int l2 = out->len;
	int j, k;
	for (j = k = 0, F1 = in->field, F2 = out->field; l2 > k; ++k, ++F2) {
		if (0 > F2->tag) {
			continue;
		}
		if (l1 <= j) {
			printf ("ERR too many answers %d %d\n", j, k);
			log_rec (0, in, "ERR IN ", 0);
			log_rec (0, out, "ERR OUT ", 0);
			exit (1);
		}
		if (F1->tag != F2->tag) {
			printf ("ERR tag %d mismatch %d %d\n", j, F1->tag, F2->tag);
			log_rec (0, in, "ERR IN ", 0);
			log_rec (0, out, "ERR OUT ", 0);
			exit (1);
		}
		if (F1->len != F2->len) {
			printf ("ERR len %d mismatch %d %d\n", j, F1->tag, F2->tag);
			log_rec (0, in, "ERR IN ", 0);
			log_rec (0, out, "ERR OUT ", 0);
			exit (1);
		}
		if (memcmp (F1->val, F2->val, F1->len)) {
			printf ("ERR val  %d mismatch\n", j);
			log_rec (0, in, "ERR IN ", 0);
			log_rec (0, out, "ERR OUT ", 0);
			exit (1);
		}
		++j;
		++F1;
	}
}

int loop (CliChnl *ch, Rec *demo, int fail) {
	Rec *rsp;
	int rt;
	rt = cliWrite (ch, demo);
	if (0 > rt) {
		printf ("%s write %d %s\n", (fail ? "ERR":"WARN"),
			ch->err, strerror (ch->err));
		if (!fail) {
			return -1;
		}
		exit (1);
	}
	rsp = cliRead (ch);
	if (! rsp) {
		printf ("%s read %d %s\n", (fail ? "ERR":"WARN"),
			ch->err, strerror (ch->err));
		if (!fail) {
			return -1;
		}
		exit (1);
	}
	cmp (demo, rsp);
	mFree (rsp);
	return 0;
}

int main (int argc, char **argv) {
	CliChnl ch;
	char *hname = 0;
	int port = 0;
	Rec *demo = 0;
	int  num = 0;
	int  j;

	for (j = 1; argc > j; ++j) {
		if (*argv[j] == '-') {
			if (argv[j][1] == 'h') {
				hname = argv[j] + 2;
				continue;
			}
			if (argv[j][1] == 'p') {
				port = atoi (argv[j] + 2);
				continue;
			}
		}
		num = atoi (argv[j]);
		break;
	}

	if (0 >= num) {
		num = 1;
	}

	for (j = argc; 0 <= --j;  ) {
		RADDS (demo, j, argv[j], !0);
	}

	if (0 >= port) {
		port = 8080;
	}
	if (cliConnect (&ch, hname, port)) {
		printf ("ERR connect %d %s\n", ch.err, strerror (ch.err));
		exit (1);
	}

	j = num;
	while (1) {
		if (loop (&ch, demo, 0)) {
			sleep (15);
			if (cliConnect (&ch, hname, port)) {
				printf ("ERR reconnect %d %s\n", ch.err, strerror (ch.err));
				exit (1);
			}
			loop (&ch, demo, !0);
		}
		if (! --j) {
			break;
		}
		if (! (j % 10)) {
			printf ("loop %d ...\n", j);
		}
	}

	cliClose (&ch);
	printf ("ok.\n");
	return 0;
}

#endif /* _MAIN_ */