2 openisis - an open implementation of the CDS/ISIS database
3 Version 0.8.x (patchlevel see file Version)
4 Copyright (C) 2001-2003 by Erik Grziwotz, erik@openisis.org
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 see README for more information
24 $Id: lsv.c,v 1.14 2003/06/11 14:53:26 kripke Exp $
28 #include <sys/select.h>
30 #include <sys/types.h>
31 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <string.h> /* memcpy et al */
44 static Con *con[FD_SETSIZE]; /* usually 1024, but up to 1<<16 */
46 static pthread_mutex_t lsv_mutex_init = PTHREAD_MUTEX_INITIALIZER;
47 static pthread_cond_t lsv_cond_init = PTHREAD_COND_INITIALIZER;
48 static pthread_key_t lsv_key_wrk;
50 static Wrk *lsv_wrk_single;
53 static siginfo_t lsv_sig;
55 static const char * pdg[] = {
61 static const char * stg[] = {
73 static void lsv_sighand ( int sig, siginfo_t *info, void *ucontext )
75 if ( SIGINT == sig || SIGTERM == sig )
80 lsv_sig.si_signo = sig;
87 static int lsv_enq ( Que *q, Con *c )
96 q->head = q->tail = c;
101 static void lsv_penq ( Srv *srv, Con *c )
105 c->grp ? &srv->pool :
112 lsv_enq( &work->que, c );
113 sMsg( LOG_DEBUG, "enq req %d from %s qlen %d",
114 LIO_FD&c->ios.file, c->nam, work->que.len );
121 pthread_cond_signal( &w->todo );
122 sMsg( LOG_DEBUG, "woke %d of %d", w->id, srv->plen );
128 static Con *lsv_deq ( Que *q )
145 return (Wrk*)pthread_getspecific( lsv_key_wrk );
147 return lsv_wrk_single;
153 in threaded mode, run forever, waiting for jobs.
154 non-threaded, do the jobs that are available and return
156 static void *lsv_wrk ( void *arg )
158 Wrk *self = (Wrk*)arg;
159 Srv *srv = self->srv;
163 self->id ? &srv->pool :
169 /* block signals, they should go to the receiver */
171 sigaddset( &blk, SIGHUP );
172 sigaddset( &blk, SIGINT );
173 sigaddset( &blk, SIGTERM );
174 sigaddset( &blk, SIGPIPE );
175 pthread_sigmask( SIG_BLOCK, &blk, 0 );
177 pthread_setspecific( lsv_key_wrk, arg );
179 srv->app( 0, LSV_APPINI );
181 lsv_wrk_single = self;
184 self->ses = sGet(); /* allocate session for thread */
189 /* ****************************************
190 critical section enter
193 pthread_mutex_lock( &srv->mut );
195 if ( job ) { /* from last turn */
196 sMsg( LOG_DEBUG, "thr %d enq job %s pdg %s",
197 self->id, job->nam, pdg[job->pdg] );
198 job->stg = LSV_STGRET;
199 lsv_enq( &srv->recv, job );
201 if ( job->pdg ) /* wakeup receiver */
202 write( srv->pip[1], &job->pdg, 1 );
204 if ( job->ses->que ) { /* immediatly reschedule queued job */
205 Con *q = job->ses->que;
206 job->ses->que = q->nxt;
207 sMsg( LOG_VERBOSE, "dequeing con %s for ses '%s'",
208 q->nam, job->ses->name );
213 while ( !(job = lsv_deq( &work->que ))
214 && !(LSV_SHUTDN & srv->flg)
219 self->nxt = work->wait;
220 work->wait = self; /* pls pls lemme wrk */
223 sMsg( LOG_DEBUG, "thr %d waiting", self->id );
224 /* wait releases the mutex */
225 pthread_cond_wait( &self->todo, &srv->mut );
230 job->stg = LSV_STGRUN;
232 pthread_mutex_unlock( &srv->mut );
234 /* ****************************************
235 critical section leave
238 /* end of monday morning meeting blah blah -- start working */
239 if ( ! job || (LSV_SHUTDN & srv->flg) )
245 job->ses = self->ses;
246 job->ses->io[1] = &job->ios;
248 CLRREC( job->ses->res );
249 sMsg( LOG_DEBUG, "thr %d run job %s ses '%s'.%d",
250 self->id, job->nam, job->ses->name, job->ses->accnt );
251 ret = srv->app( job, LSV_APPRUN );
252 sMsg( LOG_DEBUG, "thr %d got %d buf %d",
253 self->id, ret, LIO_SAVAIL( &job->ios ) );
256 lock only in order to create SMP memory barrier.
257 on a single processor system, the byte should be safely readable
258 by receiver without this lock.
259 TOD: check alternatives:
260 since receiver is checking the stage of active connections
261 only in rare cases, it might be more efficient to guard either
262 this set or the processing up to here with a per thread mutex.
265 pthread_mutex_lock( &srv->mut );
267 job->stg = LSV_STGDON;
270 pthread_mutex_unlock( &srv->mut );
273 ret = srv->prt( &job->ios, LIO_SCLOSE );
275 sMsg( LOG_DEBUG, "thr %d done job %s", self->id, job->nam );
276 if ( job->ses != self->ses )
277 sSet( self->ses ); /* reset session */
281 srv->app( 0, LSV_APPFIN );
287 static void *lsv_rcv ( void *arg )
289 Srv *srv = (Srv*)arg;
299 FD_SET( srv->lsn, &fd );
302 FD_SET( srv->pip[0], &fd );
303 if ( fdlen < srv->pip[0]+1 )
304 fdlen = srv->pip[0]+1;
308 struct sockaddr_in npeer; /* of new connection */
309 int nsock = -1; /* new socket */
314 /* select on read only; writing is done in workers and
315 exceptions (i.e. telnet OOB data) are not used
318 if ( srv->recv.len ) { /* instead of listening on pip */
319 memset( &ready, 0, sizeof(ready) );
323 if ( 0 > (sel = select( fdlen, &ready, 0, 0, 0 )) ) {
324 if ( EINTR == errno ) {
325 sMsg( LOG_DEBUG, "select woken by signal" );
327 ret = sMsg( LOG_SYSERR, "select" );
331 if ( lsv_sig.si_signo ) {
332 sMsg( LOG_DEBUG, "SIG%s (%d) from pid %d",
333 SIGHUP==lsv_sig.si_signo ? "HUP" :
334 SIGINT==lsv_sig.si_signo ? "INT" :
335 SIGTERM==lsv_sig.si_signo ? "TERM" :
336 SIGPIPE==lsv_sig.si_signo ? "PIPE" : "?",
337 lsv_sig.si_signo, lsv_sig.si_pid );
338 if ( 1 /*SIGHUP==lsv_sig.si_signo*/ ) {
339 for ( i=FD_SETSIZE; i--; )
343 "con %3d peer %s stg %s prt %d app %d ios %d %s (0x%x)",
344 i, c->nam, stg[c->stg], c->prt, c->app,
345 c->ios.pos + c->ios.b.done,
346 LIO_SISOPEN(&c->ios) ? "opn" : "clo", c->ios.file );
349 if ( lsv_term ) /* even if it wasn't the last signal delivered */
351 lsv_sig.si_signo = 0;
352 /* must NOT access the set after interrupt,
353 probably all fds are set
357 timeUpd( &srv->tim );
358 timeGtfm( srv->gtm, &srv->tim );
359 expire.millis = srv->tim.millis - srv->sto*LLL(1000);
362 pthread_mutex_lock( &srv->mut ); /* back in locked land */
366 /* -- we're the acceptor */
367 if ( FD_ISSET( srv->lsn, &ready ) ) { /* new connection */
368 unsigned /*socklen_t is broken*/ alen = sizeof(npeer);
369 nsock = accept( srv->lsn, (struct sockaddr*)&npeer, &alen );
371 ret = sMsg( LOG_SYSERR, "accept" );
374 if ( FD_SETSIZE <= nsock ) {
375 ret = sMsg( ERR_BADF,
376 "socket %d >= FD_SETSIZE %d", nsock, FD_SETSIZE );
379 if ( sizeof(npeer) != alen ) {
380 ret = sMsg( ERR_INVAL, "bad peer len %d", alen );
385 SO_SNDTIMEO fixed value on linux
386 SO_OOBINLINE ... hmm, don't care
390 if ( !(c = con[nsock]) ) {
391 c = con[nsock] = mAlloc( sizeof(*c) );
392 c->ios.file = nsock; /* preliminary -- further setup below */
396 case LSV_STGCON: /* still in control of receiver */
398 sMsg( ERR_IDIOT, "bad recv stage %d on new con", c->stg );
400 case LSV_STGNEW: /* clean idle socket -- pass to receiver */
401 lsv_enq( &srv->recv, c );
403 case LSV_STGSES: /* still in control of receiver */
405 sMsg( ERR_IDIOT, "bad worker stage %d on new con", c->stg );
406 case LSV_STGRUN: /* worker will queue it */
407 /* hmmm ... should have COM ... */
410 case LSV_STGRET: /* worker has queued it */
415 FD_CLR( srv->lsn, &ready ); /* skip in fd check */
416 } /* new connection */
419 if ( FD_ISSET( srv->pip[0], &ready ) ) { /* pending events */
420 char trash[LSV_NUMWRK];
421 read( srv->pip[0], trash, sizeof(trash) );
422 FD_CLR( srv->pip[0], &ready );
427 /* switching roles -- now we're the receiver */
428 while ( (c = lsv_deq( &srv->recv )) ) { /* care for ready connections */
429 int sock = LIO_FD & c->ios.file;
430 sMsg( LOG_DEBUG, "deq %d from %s pdg %s",
431 sock, c->nam, pdg[c->pdg] );
432 if ( c->pdg && c->stg )
434 if ( !(LIO_IN & c->ios.file) && (LIO_RDWR & c->ios.file) ) {
435 /* cleanup closed socks */
436 if ( c != con[sock] ) {
437 ret = sMsg( ERR_IDIOT,
438 "deq bad sock %d (0x%x)", sock, c->ios.file );
439 goto done; /* PANIC time */
441 sMsg( LOG_DEBUG, "clos%s sock %d",
442 (LIO_OUT & c->ios.file) ? "ing" : "ed", sock );
443 if ( (LIO_OUT & c->ios.file) )
444 lio_close( &c->ios.file, LIO_INOUT );
445 c->ios.file &= ~LIO_RDWR; /* clear also, so we're not closing again */
447 FD_CLR( sock, &ready ); /* probably an EOF to read */
448 if ( fdlen == sock+1 )
450 /* && !FD_ISSET( fdlen-1, &fd ) risk of muting tmp. disabled */
451 && (!con[fdlen-1] || !LIO_SISOPEN(&con[fdlen-1]->ios))
455 if ( LSV_PDGNEW == c->pdg ) { /* new connection */
456 struct sockaddr_in peer; /* of new connection */
457 if ( nsock == sock ) /* the one accepted just above */
460 unsigned alen = sizeof(peer);
461 if ( getpeername( sock, (struct sockaddr*)&peer, &alen )
462 || sizeof(peer) != alen
464 ret = sMsg( LOG_SYSERR, "getpeername alen %d", alen );
471 unsigned host = (unsigned)ntohl( peer.sin_addr.s_addr );
472 unsigned short port = (unsigned short)ntohs( peer.sin_port );
475 memset( c->nam, 0, sizeof(c->nam) );
476 p += u2a( p, 0xff & (host >> 24) ); *p++ = '.';
477 p += u2a( p, 0xff & (host >> 16) ); *p++ = '.';
478 p += u2a( p, 0xff & (host >> 8) ); *p++ = '.';
479 p += u2a( p, 0xff & (host ) ); *p++ = ':';
481 /* *p++ = '@'; p += u2a( p, sock ); */
483 c->con++; /* never mind overflow ... */
485 "connect %d on %d from %s", c->con+1, sock, c->nam );
486 LIO_SINIT( &c->ios, srv->prt, c->nam,
487 sock | LIO_INOUT|LIO_RDWR|LIO_SOCK );
488 LIO_BINIT( &c->flt );
490 if ( fdlen < sock+1 )
492 c->bin = 0; /* do not reset on every request */
494 if ( ! (LIO_IN & c->ios.file) )
498 if ( ! FD_ISSET( sock, &fd ) ) {
499 sMsg( LOG_DEBUG, "reenabling %s", c->nam );
501 if ( fdlen < sock+1 )
505 c->grp = c->pdg = c->prt = c->app = 0;
506 /* prepare for new request */
508 c->ios.b.done = c->ios.b.fill = 0;
512 /* this is done in the receiver rather than the acceptor
513 since in multi-receiver model, receiver owns the req
515 c->req = mAlloc( LSV_BUFSIZ );
516 OPENISIS_INITREC( c->req, LSV_BUFSIZ, 64 );
518 if ( !(LSV_CONSES & srv->flg) )
520 } /* prepare connections */
523 for ( i=fdlen; sel && i--; ) { /* read from the ready */
526 if ( ! FD_ISSET( i, &ready ) )
531 ret = sMsg( ERR_IDIOT, "ran on empty con %d", i );
534 if ( LSV_STGINP < c->stg ) {
535 sMsg( LOG_DEBUG, "con %d busy stage %d!", i, c->stg );
538 FD_CLR( i, &fd ); /* mute this */
542 got = lio_read( &c->ios.file, c->ios.b.c, sizeof(c->ios.b.c) );
544 if ( -ERR_EOF != got )
546 sMsg( LOG_DEBUG, "EOF on 0x%x line %d from %s",
547 c->ios.file, c->req->len, c->nam );
549 if ( ! c->req->len ) /* initial eof */
550 goto eof; /* close immediatly */
551 /* lio_close( &c->ios.file, LIO_IN ); */
552 c->ios.file &= ~LIO_IN; /* don't shut */
553 c->pdg = LSV_PDGEOF; /* so it will be dequed and closed */
555 got = 0; /* tell proto to close ... */
557 if ( ! c->req->len ) { /* stage should be CON */
558 RADDS( c->req, -LSV_PEER, c->nam, 1 );
559 sMsg( LOG_DEBUG, "req on %d from %s",
560 LIO_FD&c->ios.file, c->nam );
564 got = srv->prt( &c->ios, LIO_SPUSH );
565 sMsg( LOG_DEBUG, "got 0x%x from %s", got, c->nam );
566 if ( ! got && (LIO_IN & c->ios.file) ) /* ok: more next time */
568 if ( 0 > got ) /* nok */
570 got = c->srv->app( c, LSV_APPGOT );
571 if ( 0 > got ) /* nok */
573 if ( !(LSV_CONSES & srv->flg)
574 && c->req && (sid = rGet( c->req, -LSV_SID, 0 ))
576 c->ses = cSesByName( (char*)sid->val, sid->len, &srv->tim, &expire );
577 if ( ! c->ses ) { /* sad sad sad */
578 sMsg( LOG_ERROR, "out of sessions" );
579 goto err; /* drop connection */
582 && (! c->ses->prop || ! c->ses->prop->len) /* overflow paranoia */
584 got = c->srv->app( c, LSV_APPSES );
585 if ( 0 > got ) /* nok */
589 RADDS( c->req, -LSV_TIME, srv->gtm, 1 );
591 sMsg( LOG_DEBUG, "req %s for ses '%s' %s",
592 c->nam, c->ses->name, c->ses->cur ? "busy" : "idle" );
594 if ( c->ses->cur || c->ses->que ) { /* oh - busy !? */
595 Con **cpp = &c->ses->que;
597 for ( ; *cpp; cpp = &(*cpp)->nxt )
600 "too many con queing on ses '%s' - dropping %s",
601 c->ses->name, c->nam );
604 sMsg( LOG_VERBOSE, "queing con %s for ses '%s'",
605 c->nam, c->ses->name );
616 sMsg( ERR_BADF, "err 0x%x from %s", got, c->nam );
618 lio_close( &c->ios.file, LIO_INOUT );
621 } /* read from the ready */
624 srv->busy += srv->nwr - srv->plen;
626 srv->wlen += srv->pool.que.len;
629 "\n==== %s\nturn %d: %d jobs for %d idle workers %d pending",
630 srv->gtm, srv->turn, srv->pool.que.len, srv->plen, pending );
631 for ( i=srv->nwr; i--; )
632 if ( srv->wrk[i].cur )
633 sMsg( LOG_INFO, "thr %d serving %d from %s",
634 i, LIO_FD&srv->wrk[i].cur->ios.file, srv->wrk[i].cur->nam );
637 pthread_mutex_unlock( &srv->mut );
645 pthread_mutex_unlock( &srv->mut );
646 srv->flg |= LSV_SHUTDN;
647 for ( i=srv->nwr; i--; )
648 pthread_cond_signal( &srv->wrk[i].todo );
654 int svRun ( Srv *srv, const char *addr )
656 static const int yes = !0;
657 struct sigaction siga;
658 struct sockaddr_in sa; /* server address */
661 /* assume we're the main-thread
662 -- try to make sure we get the controlling session
668 srv->sto = 30*60; /* 30 minutes session timeout */
673 srv->nwr = LSV_NUMWRK/2; /* half the max */
674 else if ( LSV_NUMWRK < srv->nwr )
675 srv->nwr = LSV_NUMWRK; /* max */
677 srv->mut = lsv_mutex_init;
679 /* prepare signals */
680 memset( &siga, 0, sizeof(siga) );
681 siga.sa_sigaction = lsv_sighand;
682 sigemptyset( &siga.sa_mask );
683 sigaddset( &siga.sa_mask, SIGHUP );
684 sigaddset( &siga.sa_mask, SIGINT );
685 sigaddset( &siga.sa_mask, SIGTERM );
686 sigaddset( &siga.sa_mask, SIGPIPE );
687 siga.sa_flags = SA_SIGINFO;
688 sigaction( SIGHUP, &siga, 0 );
689 sigaction( SIGINT, &siga, 0 );
690 sigaction( SIGTERM, &siga, 0 );
691 sigaction( SIGPIPE, &siga, 0 );
694 srv->lsn = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP/*6*/ );
695 /* fcntl( srv->lsn, O_NONBLOCK ); not needed */
696 sa.sin_family = AF_INET;
697 sa.sin_port = htons( addr ? a2i( addr, -1 ) : 8080 );
699 #ifdef LSV_LOCALHOST_ONLY
700 htonl(INADDR_LOOPBACK);
704 if ( setsockopt( srv->lsn, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes) ) ) {
705 ret = sMsg( LOG_SYSERR, "setsockopt" );
708 if ( bind( srv->lsn, (struct sockaddr*)&sa, sizeof(sa) ) ) {
709 ret = sMsg( LOG_SYSERR, "bind on port %u", htons(sa.sin_port) );
712 if ( listen( srv->lsn, 64/*backlog*/ ) ) {
713 ret = sMsg( LOG_SYSERR, "listen" );
717 /* prepare pending event pipe */
718 if ( pipe( srv->pip ) ) {
719 ret = sMsg( LOG_SYSERR, "pipe" );
723 if ( pthread_key_create( &lsv_key_wrk, 0 ) ) {
724 ret = sMsg( LOG_SYSERR, "key_create" );
729 memset( srv->wrk, 0, sizeof(srv->wrk) );
730 for ( i=srv->nwr; i--; ) {
732 srv->wrk[i].srv = srv;
734 srv->wrk[i].ses = cSession(0);
735 memcpy( srv->wrk[i].ses->name, "wrk", 3 );
736 u2a( srv->wrk[i].ses->name+3, i );
738 srv->wrk[i].todo = lsv_cond_init;
740 pthread_create( &srv->wrk[i].thr, 0, lsv_wrk, srv->wrk+i );
746 /* start the server thread */
747 pthread_create( &srv->rcv, 0, lsv_rcv, srv );
748 /* run main worker */
749 srv->wrk[0].thr = pthread_self();
753 pthread_join( srv->rcv, 0 );
754 for ( i=srv->nwr; i--; )
755 pthread_join( srv->wrk[i].thr, 0 );
757 for ( i=srv->nwr; i--; )
758 sMsg( LOG_INFO, "worker %d had %d jobs %d waits",
759 i, srv->wrk[i].jobs, srv->wrk[i].waits );
761 "server had %d jobs on %d connections %d turns"
762 " avg queue len %.02f workers %.02f",
763 srv->jobs, srv->conn, srv->turn,
764 srv->wlen/srv->turn, srv->busy/srv->turn );
771 /** helper, probably should go to lio.
772 cat l bytes from c to buf b flushing as needed.
775 int lio_sout ( Ios *s, Buf *b, const char *c, unsigned l )
780 if ( LIO_BUFSIZ/2 < b->fill && b->done ) { /* purge */
781 if ( b->done < b->fill )
782 memmove( b->c, b->c + b->done, b->fill - b->done );
787 if ( c && l && b->fill < LIO_BUFSIZ ) {
788 unsigned cp = LIO_BUFSIZ - b->fill;
791 memcpy( b->c + b->fill, c, cp );
798 if ( b->fill > b->done ) {
799 int wr = lio_write( &s->file, b->c + b->done, b->fill - b->done );
805 if ( b->done > b->fill ) {
806 b->done = b->fill = 0;
810 if ( b->done >= b->fill ) {
812 b->done = b->fill = 0;
814 } while ( c ? l : (unsigned)(b->fill > b->done) );
820 #define LF 10 /* LineFeed a.k.a. newline - '\n' isn't really well defined */
821 #define TAB 9 /* horizontal, that is */
822 #define VT 11 /* vertical, used as newline replacement */
823 static const char lf = LF;
824 static const char tab = TAB;
825 static const char vt = VT;
827 /* the plain protocol */
828 int svPlain ( Ios *s, int op )
834 int l = s->b.fill - s->b.done;
835 unsigned char *b = s->b.c + s->b.done;
836 unsigned char *end = b+l, *v, *p;
837 if ( ! l ) { /* EOF: done */
838 if ( ! c->prt ) /* ok */
840 /* last field wasn't closed by LF */
841 return sMsg( ERR_INVAL, "no EOL from %s", c->nam );
844 RSPACE( c->req, l, !0 );
849 case 0: /* at beginning of line -- start new field */
850 if ( LF == *b ) /* empty line */
852 if ( TAB != *b || !c->req->len )
853 RADD( c->req, 0,0,end-b, !0 );
854 else { /* binary mode continuation line */
858 "detected binary mode on con %s", c->nam );
861 RSPACE( c->req, end-b, !0 );
866 case 1: /* add to last field */
867 f = c->req->field + c->req->len-1;
868 v = (unsigned char*)f->val;
875 for ( ; b<end && LF != (*p = *b++); p++ )
878 for ( ; b<end && LF != (*p = *b++); p++ )
879 if ( VT == *p ) /* convert VTABs */
880 *p = LF; /* back to newlines */
881 c->req->used += (p - v) - f->len;
884 int ret = a2il( f->val, f->len, &f->tag );
886 if ( ret < f->len && TAB == v[ret] )
889 memmove( v, v+ret, f->len - ret );
892 ret = c->srv->app( c, LSV_APPARG );
897 sMsg( LOG_INFO, "prs from %s: [%2d] %3d = '%.*s'",
898 c->nam, c->req->len-1, f->tag, f->len, f->val );
902 } /* case LIO_SPUSH */
904 if ( s->b.fill <= s->b.done )
907 if ( !(LIO_OUT & s->file) )
910 this switch is not protected, since receiver doesn't care
912 if ( c->stg < LSV_STGCOM )
914 if ( ! s->pos+s->b.done && c->ses->res ) {
916 int n = c->ses->res->len;
917 sMsg( LOG_INFO, "com %d fields to %s", c->ses->res->len, c->nam );
918 for ( f = c->ses->res->field; n--; f++ ) {
919 const char *v = f->val, *nl;
921 int nlen = i2a( num, f->tag );
923 lio_sout( s, &c->flt, num, nlen );
924 while ( vl && (nl = memchr(v,'\n',vl)) ) {
927 lio_sout( s, &c->flt, v, ll );
928 lio_sout( s, &c->flt, &tab, 1 );
930 lio_sout( s, &c->flt, v, ll-1 );
931 lio_sout( s, &c->flt, &vt, 1 );
937 lio_sout( s, &c->flt, v, vl );
938 lio_sout( s, &c->flt, &lf, 1 );
942 s->b.done = s->b.fill = 0;
945 lio_sout( s, &c->flt, 0, 0 );
946 lio_stdio( s, LIO_SFLUSH );
949 if ( LIO_SCLOSE == op )
950 lio_sout( s, &c->flt, &lf, 1 );
951 lio_sout( s, &c->flt, 0, 0 ); /* finally flush */
954 return lio_stream( s, op ); /* hmm -- SPURGE ? */
958 /* the echo application */
959 int svEcho ( Con *c, int task )
962 static Tm tm = { LLL(50) };
966 if ( task ) { /* preparing: we don't care */
967 if ( LSV_APPGOT == task )
968 c->grp = 1; /* don't require mainthead */
972 sMsg( LOG_INFO, "echo %d: %d fields to %s",
973 svCur()->id, c->req->len, c->nam );
977 for ( i=c->req->len, f = c->req->field; i--; f++ ) {
978 RADDF( c->ses->res, f, !0 );
980 plain protocol actually does not support plain output ...
981 sMsg( c->ses, 0, "%d = '%.*s'\n", f->tag, f->len, f->val );
988 int main ( int argc, const char **argv )
992 memset( &echo, 0, sizeof(echo) );
993 echo.prt = lsv_plain;
996 cLog( LOG_DEBUG, 0 );
997 return svRun( &echo, 1<argc ? argv[1] : 0 );