1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2010 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                  David Korn <dgk@research.att.com>                   *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21/*
22 * mkservice varname pathname
23 * eloop [-t timeout]
24 * Written by David Korn
25 * AT&T Labs
26 */
27
28static const char mkservice_usage[] =
29"[-?\n@(#)$Id: mkservice (AT&T Research) 2001-06-13 $\n]"
30USAGE_LICENSE
31"[+NAME? mkservice - create a shell server ]"
32"[+DESCRIPTION?\bmkservice\b creates a tcp or udp server that is "
33	"implemented by shell functions.]"
34"[+?The \aservice_path\a must be of the form \b/dev/tcp/localhost/\b\aportno\a "
35	"or \b/dev/udp/localhost/\b\aportno\a depending on whether the "
36	"\btcp\b or \budp\b protocol is used.  \aportno\a is the port "
37	"number that the service will use.]"
38"[+?The shell variable \avarname\a is associated with the service.  This "
39	"variable can have subvariables that keeps the state of all "
40	"active connections.  The functions \avarname\a\b.accept\b, "
41	"\avarname\a\b.action\b and \avarname\a\b.close\b implement the "
42	"service as follows:]{"
43	"[+accept?This function is invoked when a client tries to connect "
44		"to the service.  It is called with an argument which "
45		"is the file descriptor number associated with the "
46		"accepted connection.  If the function returns a non-zero "
47		"value, this connection will be closed.]"
48	"[+action?This function is invoked when there is data waiting "
49		"to be read from one of the active connections.  It is "
50		"called with the file descriptor number that has data "
51		"to be read.  If the function returns a non-zero "
52		"value, this connection will be closed.]"
53	"[+close?This function is invoked when the connection is closed.]"
54	"}"
55"[+?If \avarname\a is unset, then all active connection, and the service "
56	"itself will be closed.]"
57""
58"\n"
59"\nvarname service_path\n"
60"\n"
61"[+EXIT STATUS?]{"
62        "[+0?Success.]"
63        "[+>0?An error occurred.]"
64"}"
65"[+SEE ALSO?\beloop\b(1)]"
66;
67
68
69static const char eloop_usage[] =
70"[-?\n@(#)$Id: eloop (AT&T Research) 2001-06-13 $\n]"
71USAGE_LICENSE
72"[+NAME? eloop - process event loop]"
73"[+DESCRIPTION?\beloop\b causes the shell to block waiting for events "
74	"to process.  By default, \beloop\b does not return.]"
75"[t]#[timeout?\atimeout\a is the number of milliseconds to wait "
76	"without receiving any events to process.]"
77"\n"
78"\n\n"
79"\n"
80"[+EXIT STATUS?If no timeout is specified, \beloop\b will not return "
81	"unless interrupted.  Otherwise]{"
82        "[+0?The specified timeout interval occurred.]"
83        "[+>0?An error occurred.]"
84"}"
85"[+SEE ALSO?\bmkservice\b(1)]"
86;
87
88
89#include	"defs.h"
90
91#include	<cmd.h>
92#include	<error.h>
93#include	<nval.h>
94#include	<sys/socket.h>
95#include 	<netinet/in.h>
96
97#define ACCEPT	0
98#define ACTION	1
99#define CLOSE	2
100
101#ifndef O_SERVICE
102#   define O_SERVICE	O_NOCTTY
103#endif
104
105static const char*	disctab[] =
106{
107	"accept",
108	"action",
109	"close",
110	0
111};
112
113typedef struct Service_s Service_t;
114
115struct Service_s
116{
117	Namfun_t	fun;
118	short		fd;
119	int		refcount;
120	int		(*acceptf)(Service_t*,int);
121	int		(*actionf)(Service_t*,int,int);
122	int		(*errorf)(Service_t*,int,const char*, ...);
123	void		*context;
124	Namval_t*	node;
125	Namval_t*	disc[elementsof(disctab)-1];
126};
127
128static short		*file_list;
129static Sfio_t		**poll_list;
130static Service_t	**service_list;
131static int		npoll;
132static int		nready;
133static int		ready;
134static int		(*covered_fdnotify)(int, int);
135
136static int fdclose(Service_t *sp, register int fd)
137{
138	register int i;
139	service_list[fd] = 0;
140	if(sp->fd==fd)
141		sp->fd = -1;
142	for(i=0; i < npoll; i++)
143	{
144		if(file_list[i]==fd)
145		{
146			file_list[i] = file_list[npoll--];
147			if(sp->actionf)
148				(*sp->actionf)(sp, fd, 1);
149			return(1);
150		}
151	}
152	return(0);
153}
154
155static int fdnotify(int fd1, int fd2)
156{
157	Service_t *sp;
158	if (covered_fdnotify)
159		(*covered_fdnotify)(fd1, fd2);
160	if(fd2!=SH_FDCLOSE)
161	{
162		register int i;
163		service_list[fd2] = service_list[fd1];
164		service_list[fd1] = 0;
165		for(i=0; i < npoll; i++)
166		{
167			if(file_list[i]==fd1)
168			{
169				file_list[i] = fd2;
170				return(0);
171			}
172		}
173	}
174	else if(sp = service_list[fd1])
175	{
176		fdclose(sp,fd1);
177		if(--sp->refcount==0)
178			nv_unset(sp->node);
179	}
180	return(0);
181}
182
183static void process_stream(Sfio_t* iop)
184{
185	int r=0, fd = sffileno(iop);
186	Service_t * sp = service_list[fd];
187	if(fd==sp->fd)	/* connection socket */
188	{
189		struct sockaddr addr;
190		socklen_t addrlen = sizeof(addr);
191		fd = accept(fd, &addr, &addrlen);
192		service_list[fd] = sp;
193		sp->refcount++;
194		file_list[npoll++] = fd;
195		if(fd>=0)
196		{
197			if(sp->acceptf)
198				r = (*sp->acceptf)(sp,fd);
199		}
200	}
201	else if(sp->actionf)
202	{
203		service_list[fd] = 0;
204		r = (*sp->actionf)(sp, fd, 0);
205		service_list[fd] = sp;
206		if(r<0)
207			close(fd);
208	}
209}
210
211static int waitnotify(int fd, long timeout, int rw)
212{
213	Sfio_t *special=0, **pstream;
214	register int	i;
215
216	if (fd >= 0)
217		special = sh_fd2sfio(fd);
218	while(1)
219	{
220		pstream = poll_list;
221		while(ready < nready)
222			process_stream(pstream[ready++]);
223		if(special)
224			*pstream++ = special;
225		for(i=0; i < npoll; i++)
226		{
227			if(service_list[file_list[i]])
228				*pstream++ = sh_fd2sfio(file_list[i]);
229		}
230#if 1
231		for(i=0; i < pstream-poll_list; i++)
232			sfset(poll_list[i],SF_WRITE,0);
233#endif
234		nready = ready = 0;
235		errno = 0;
236#ifdef DEBUG
237		sfprintf(sfstderr,"before poll npoll=%d",pstream-poll_list);
238		for(i=0; i < pstream-poll_list; i++)
239			sfprintf(sfstderr," %d",sffileno(poll_list[i]));
240		sfputc(sfstderr,'\n');
241#endif
242		nready  = sfpoll(poll_list,pstream-poll_list,timeout);
243#ifdef DEBUG
244		sfprintf(sfstderr,"after poll nready=%d",nready);
245		for(i=0; i < nready; i++)
246			sfprintf(sfstderr," %d",sffileno(poll_list[i]));
247		sfputc(sfstderr,'\n');
248#endif
249#if 1
250		for(i=0; i < pstream-poll_list; i++)
251			sfset(poll_list[i],SF_WRITE,1);
252#endif
253		if(nready<=0)
254			return(errno? -1: 0);
255		if(special && poll_list[0]==special)
256		{
257			ready = 1;
258			return(fd);
259		}
260	}
261}
262
263static int service_init(void)
264{
265	file_list =  newof(NULL,short,n,0);
266	poll_list =  newof(NULL,Sfio_t*,n,0);
267	service_list =  newof(NULL,Service_t*,n,0);
268	covered_fdnotify = sh_fdnotify(fdnotify);
269	sh_waitnotify(waitnotify);
270	return(1);
271}
272
273void service_add(Service_t *sp)
274{
275	static int init;
276	if (!init)
277		init = service_init();
278	service_list[sp->fd] = sp;
279	file_list[npoll++] = sp->fd;
280}
281
282static int Accept(register Service_t *sp, int accept_fd)
283{
284	register Namval_t*	nq = sp->disc[ACCEPT];
285	int			fd;
286
287	fd = fcntl(accept_fd, F_DUPFD, 10);
288	if (fd >= 0)
289	{
290		close(accept_fd);
291		if (nq)
292		{
293			char*	av[3];
294			char	buff[20];
295
296			av[1] = buff;
297			av[2] = 0;
298			sfsprintf(buff, sizeof(buff), "%d", fd);
299			if (sh_fun(nq, sp->node, av))
300			{
301				close(fd);
302				return -1;
303			}
304		}
305	}
306	sfsync(NiL);
307	return fd;
308}
309
310static int Action(Service_t *sp, int fd, int close)
311{
312	register Namval_t*	nq;
313	int			r=0;
314
315	if(close)
316		nq = sp->disc[CLOSE];
317	else
318		nq = sp->disc[ACTION];
319	if (nq)
320	{
321		char*	av[3];
322		char	buff[20];
323
324		av[1] = buff;
325		av[2] = 0;
326		sfsprintf(buff, sizeof(buff), "%d", fd);
327		r=sh_fun(nq, sp->node, av);
328	}
329	sfsync(NiL);
330	return r > 0 ? -1 : 1;
331}
332
333static int Error(Service_t *sp, int level, const char* arg, ...)
334{
335	va_list			ap;
336
337	va_start(ap, arg);
338	if(sp->node)
339		nv_unset(sp->node);
340	free((void*)sp);
341        errorv(NiL, ERROR_exit(1), ap);
342        va_end(ap);
343	return 0;
344}
345
346static char* setdisc(Namval_t* np, const char* event, Namval_t* action, Namfun_t* fp)
347{
348	register Service_t*	sp = (Service_t*)fp;
349	register const char*	cp;
350	register int		i;
351	register int		n = strlen(event) - 1;
352	register Namval_t*	nq;
353
354	for (i = 0; cp = disctab[i]; i++)
355	{
356		if (memcmp(event, cp, n))
357			continue;
358		if (action == np)
359			action = sp->disc[i];
360		else
361		{
362			if (nq = sp->disc[i])
363				free((void*)nq);
364			if (action)
365				sp->disc[i] = action;
366			else
367				sp->disc[i] = 0;
368		}
369		return action ? (char*)action : "";
370	}
371	/* try the next level */
372	return nv_setdisc(np, event, action, fp);
373}
374
375static void putval(Namval_t* np, const char* val, int flag, Namfun_t* fp)
376{
377	register Service_t* sp = (Service_t*)fp;
378	if (!val)
379		fp = nv_stack(np, NiL);
380	nv_putv(np, val, flag, fp);
381	if (!val)
382	{
383		register int i;
384		for(i=0; i< sh.lim.open_max; i++)
385		{
386			if(service_list[i]==sp)
387			{
388				close(i);
389				if(--sp->refcount<=0)
390					break;
391			}
392		}
393		free((void*)fp);
394		return;
395	}
396}
397
398static const Namdisc_t servdisc =
399{
400	sizeof(Service_t),
401	putval,
402	0,
403	0,
404	setdisc
405};
406
407int	b_mkservice(int argc, char** argv, void* extra)
408{
409	register char*		var;
410	register char*		path;
411	register Namval_t*	np;
412	register Service_t*	sp;
413	register int		fd;
414
415	NOT_USED(argc);
416	NOT_USED(extra);
417	for (;;)
418	{
419		switch (optget(argv, mkservice_usage))
420		{
421		case 0:
422			break;
423		case ':':
424			error(2, opt_info.arg);
425			continue;
426		case '?':
427			error(ERROR_usage(2), opt_info.arg);
428			continue;
429		}
430		break;
431	}
432	argv += opt_info.index;
433	if (error_info.errors || !(var = *argv++) || !(path = *argv++) || *argv)
434		error(ERROR_usage(2), optusage(NiL));
435	if (!(sp = newof(0, Service_t, 1, 0)))
436		error(ERROR_exit(1), "out of space");
437	sp->acceptf = Accept;
438	sp->actionf = Action;
439	sp->errorf = Error;
440	sp->refcount = 1;
441	sp->context = extra;
442	sp->node = 0;
443	sp->fun.disc = &servdisc;
444	if((fd = sh_open(path, O_SERVICE|O_RDWR))<=0)
445	{
446		free((void*)sp);
447		error(ERROR_exit(1), "%s: cannot start service", path);
448	}
449	if((sp->fd = fcntl(fd, F_DUPFD, 10))>=10)
450		close(fd);
451	else
452		sp->fd = fd;
453	np = nv_open(var,sh.var_tree,NV_ARRAY|NV_VARNAME|NV_NOASSIGN);
454	sp->node = np;
455	nv_putval(np, path, 0);
456	nv_stack(np, (Namfun_t*)sp);
457	service_add(sp);
458	return(0);
459}
460
461int	b_eloop(int argc, char** argv, void* extra)
462{
463	register long	timeout = -1;
464	NOT_USED(argc);
465	NOT_USED(extra);
466	for (;;)
467	{
468		switch (optget(argv, eloop_usage))
469		{
470		case 0:
471			break;
472		case 't':
473			timeout = opt_info.num;
474			continue;
475		case ':':
476			error(2, opt_info.arg);
477			continue;
478		case '?':
479			error(ERROR_usage(2), opt_info.arg);
480			continue;
481		}
482		break;
483	}
484	argv += opt_info.index;
485	if (error_info.errors  || *argv)
486		error(ERROR_usage(2), optusage(NiL));
487	while(1)
488	{
489		if(waitnotify(-1, timeout, 0)==0)
490			break;
491		sfprintf(sfstderr,"interrupted\n");
492	}
493	return(errno != 0);
494}
495