/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1990-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Labs Research
 *
 * css - multiplex multiple clients on one filter server
 */

static const char usage[] =
"[-?\n@(#)$Id: css (AT&T Labs Research) 1998-05-01 $\n]"
USAGE_LICENSE
"[+NAME?css - multiplex multiple clients on one connect stream server]"
"[+DESCRIPTION?\bcss\b multiplexes multiple clients on one filter server."
"	A filter server is a process that reads lines from the standard input"
"	and writes result lines to the standard output. \aconnect-stream\a"
"	is the connect stream path by which the filter service will be known.]"

"[t:timeout?The service will exit after a \atime\a period of client"
"	inactivity.]:[time]"

"\n"
"\nconnect-stream command [ arg ... ]\n"
"\n"

"[+PROTOCOL?A filter service must follow a simple line oriented protocol. All"
"	client lines are split into arguments and a number is inserted in the"
"	second argument position. This number, followed by a space, must be"
"	placed at the beginning of each line written by the filter server for"
"	the given client request.]"

"[+SEE ALSO?\bcoshell\b(1), \bcs\b(1), \bss\b(1), \bcs\b(3)]"
;

#include <css.h>
#include <ctype.h>
#include <error.h>
#include <proc.h>

typedef struct
{
	Csid_t		id;
	int		service;
} Connection_t;

typedef struct
{
	Cssdisc_t	disc;
	Proc_t*		proc;
	Sfio_t*		tmp;
} State_t;

static char	buf[8 * 1024];

static int
acceptf(Css_t* css, Cssfd_t* fp, Csid_t* ip, char** av, Cssdisc_t* disc)
{
	register Connection_t*	con;

	NoP(av);
	if (!(con = newof(0, Connection_t, 1, 0)))
		return -1;
	fp->data = con;
	con->id = *ip;
	return fp->fd;
}

static int
actionf(register Css_t* css, register Cssfd_t* fp, Cssdisc_t* disc)
{
	register State_t*	state = (State_t*)disc;
	register Connection_t*	con;
	register char*		s;
	register char*		t;
	int			n;
	int			i;

	switch (fp->status)
	{
	case CS_POLL_CLOSE:
		if (con = (Connection_t*)fp->data)
		{
			if (con->service)
				error(ERROR_SYSTEM|3, "service termination exit");
			free(con);
		}
		return 0;
	case CS_POLL_READ:
		con = (Connection_t*)fp->data;
		if ((n = csread(css->state, fp->fd, buf, sizeof(buf) - 1, CS_LINE)) <= 0)
		{
			if (con->service)
				error(ERROR_SYSTEM|3, "service termination exit");
			return -1;
		}
		buf[n] = 0;
		for (s = buf; isspace(*s); s++);
		if (con->service)
		{
			for (i = 0; isdigit(*s); i = i * 10 + *s++ - '0');
			for (; isspace(*s); s++);
			if (*s && cssfd(css, i, 0))
			{
				n -= s - buf;
				if (cswrite(css->state, i, s, n) != n)
					cssfd(css, i, CS_POLL_CLOSE);
			}
		}
		else if (*s == '!')
		{
			if ((n -= ++s - buf) > 0 && cswrite(css->state, state->proc->wfd, s, n) != n)
				return -1;
		}
		else
		{
			for (t = s; *t && !isspace(*t); t++);
			for (; isspace(*t); t++);
			if (*s == 'Q' && !*t)
			{
				if (con->id.uid == geteuid())
					error(3, "service quit exit");
			}
			else
			{
				n = sfprintf(state->tmp, "%-.*s%d %s", t - s, s, fp->fd, t);
				if (cswrite(css->state, state->proc->wfd, sfstruse(state->tmp), n) != n)
					return -1;
			}
		}
		return 1;
	}
	return 0;
}

static int
exceptf(Css_t* css, unsigned long op, unsigned long arg, Cssdisc_t* disc)
{
	switch (op)
	{
	case CSS_INTERRUPT:
		error(ERROR_SYSTEM|3, "%s: interrupt exit", fmtsignal(arg));
		return 0;
	case CSS_DORMANT:
		error(2, "service dormant exit");
		exit(0);
	}
	error(ERROR_SYSTEM|3, "poll error op=0x%08x arg=0x%08x", op, arg);
	return -1;
}


int
main(int argc, char** argv)
{
	Css_t*		css;
	Cssfd_t*	fp;
	Connection_t*	con;
	char*		e;
	State_t		state;

	NoP(argc);
	error_info.id = "css";
	memset(&state, 0, sizeof(state));
	state.disc.version = CSS_VERSION;
	state.disc.flags = CSS_DAEMON|CSS_ERROR|CSS_INTERRUPT;
	state.disc.acceptf = acceptf;
	state.disc.actionf = actionf;
	state.disc.errorf = errorf;
	state.disc.exceptf = exceptf;
	for (;;)
	{
		switch (optget(argv, usage))
		{
		case 't':
			state.disc.timeout = strelapsed(opt_info.arg, &e, 1);
			if (*e)
				error(3, "%s: invalid timeout value", opt_info.arg);
			state.disc.flags |= CSS_DORMANT;
			continue;
		case '?':
			error(ERROR_USAGE|4, "%s", opt_info.arg);
			continue;
		case ':':
			error(2, "%s", opt_info.arg);
			continue;
		}
		break;
	}
	argv += opt_info.index;
	if (!argv[0] || !argv[1])
		error(ERROR_USAGE|4, "%s", optusage(NiL));
	if (!(state.tmp = sfstropen()))
		error(ERROR_SYSTEM|3, "out of space [tmp stream]");
	if (!(state.proc = procopen(argv[1], argv + 1, NiL, NiL, PROC_READ|PROC_WRITE)))
		error(ERROR_SYSTEM|3, "%s: cannot execute", argv[1]);
	if (!(css = cssopen(argv[0], &state.disc)))
		return 1;
	if (!(fp = cssfd(css, state.proc->rfd, CS_POLL_READ)))
		error(ERROR_SYSTEM|3, "%s: cannot poll output", argv[1]);
	if (!(con = newof(0, Connection_t, 1, 0)))
		error(ERROR_SYSTEM|3, "out of space");
	fp->data = con;
	con->service = 1;
	csspoll(CS_NEVER, 0);
	return 1;
}
