1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1982-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
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 
28 static const char mkservice_usage[] =
29 "[-?\n@(#)$Id: mkservice (AT&T Research) 2001-06-13 $\n]"
30 USAGE_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 
69 static const char eloop_usage[] =
70 "[-?\n@(#)$Id: eloop (AT&T Research) 2001-06-13 $\n]"
71 USAGE_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 
105 static const char*	disctab[] =
106 {
107 	"accept",
108 	"action",
109 	"close",
110 	0
111 };
112 
113 typedef struct Service_s Service_t;
114 
115 struct 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 
128 static short		*file_list;
129 static Sfio_t		**poll_list;
130 static Service_t	**service_list;
131 static int		npoll;
132 static int		nready;
133 static int		ready;
134 static int		(*covered_fdnotify)(int, int);
135 
fdclose(Service_t * sp,register int fd)136 static 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 
fdnotify(int fd1,int fd2)155 static 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 
process_stream(Sfio_t * iop)183 static 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 
waitnotify(int fd,long timeout,int rw)211 static 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 
service_init(void)263 static 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 
service_add(Service_t * sp)273 void 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 
Accept(register Service_t * sp,int accept_fd)282 static 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 
Action(Service_t * sp,int fd,int close)310 static 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 
Error(Service_t * sp,int level,const char * arg,...)333 static 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 
setdisc(Namval_t * np,const char * event,Namval_t * action,Namfun_t * fp)346 static 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 
putval(Namval_t * np,const char * val,int flag,Namfun_t * fp)375 static 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 
398 static const Namdisc_t servdisc =
399 {
400 	sizeof(Service_t),
401 	putval,
402 	0,
403 	0,
404 	setdisc
405 };
406 
b_mkservice(int argc,char ** argv,Shbltin_t * context)407 int	b_mkservice(int argc, char** argv, Shbltin_t *context)
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(context);
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 = context;
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 
b_eloop(int argc,char ** argv,Shbltin_t * context)461 int	b_eloop(int argc, char** argv, Shbltin_t *context)
462 {
463 	register long	timeout = -1;
464 	NOT_USED(argc);
465 	NOT_USED(context);
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