root / trunk / src / dot / proxy / event.c

Revision 7618, 20.2 kB (checked in by BradNeuberg, 22 months ago)

Local and remote SVN repositories somehow became out of sync and corrupted -- re-adding these in

Line 
1/*
2Copyright (c) 2003-2006 by Juliusz Chroboczek
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20THE SOFTWARE.
21*/
22
23#include "polipo.h"
24
25#ifdef HAVE_FORK
26static volatile sig_atomic_t exitFlag = 0;
27#else
28static int exitFlag = 0;
29#endif
30static int in_signalCondition = 0;
31
32static TimeEventHandlerPtr timeEventQueue;
33static TimeEventHandlerPtr timeEventQueueLast;
34
35struct timeval current_time;
36struct timeval null_time = {0,0};
37
38static int fdEventSize = 0;
39static int fdEventNum = 0;
40static struct pollfd *poll_fds = NULL;
41static FdEventHandlerPtr *fdEvents = NULL, *fdEventsLast = NULL;
42int diskIsClean = 1;
43
44static int fds_invalid = 0;
45
46static inline int
47timeval_cmp(struct timeval *t1, struct timeval *t2)
48{
49    if(t1->tv_sec < t2->tv_sec)
50        return -1;
51    else if(t1->tv_sec > t2->tv_sec)
52        return +1;
53    else if(t1->tv_usec < t2->tv_usec)
54        return -1;
55    else if(t1->tv_usec > t2->tv_usec)
56        return +1;
57    else
58        return 0;
59}
60
61static inline void
62timeval_minus(struct timeval *d,
63              const struct timeval *s1, const struct timeval *s2)
64{
65    if(s1->tv_usec > s2->tv_usec) {
66        d->tv_usec = s1->tv_usec - s2->tv_usec;
67        d->tv_sec = s1->tv_sec - s2->tv_sec;
68    } else {
69        d->tv_usec = s1->tv_usec + 1000000 - s2->tv_usec;
70        d->tv_sec = s1->tv_sec - s2->tv_sec - 1;
71    }
72}
73
74int
75timeval_minus_usec(const struct timeval *s1, const struct timeval *s2)
76{
77    return (s1->tv_sec - s2->tv_sec) * 1000000 + s1->tv_usec - s2->tv_usec;
78}
79
80#ifdef HAVE_FORK
81static void
82sigexit(int signo)
83{
84    if(signo == SIGUSR1)
85        exitFlag = 1;
86    else if(signo == SIGUSR2)
87        exitFlag = 2;
88    else
89        exitFlag = 3;
90}
91#endif
92
93void
94initEvents()
95{
96#ifdef HAVE_FORK
97    struct sigaction sa;
98    sigset_t ss;
99
100    sigemptyset(&ss);
101    sa.sa_handler = SIG_IGN;
102    sa.sa_mask = ss;
103    sa.sa_flags = 0;
104    sigaction(SIGPIPE, &sa, NULL);
105
106    sigemptyset(&ss);
107    sa.sa_handler = sigexit;
108    sa.sa_mask = ss;
109    sa.sa_flags = 0;
110    sigaction(SIGTERM, &sa, NULL);
111
112    sigemptyset(&ss);
113    sa.sa_handler = sigexit;
114    sa.sa_mask = ss;
115    sa.sa_flags = 0;
116    sigaction(SIGHUP, &sa, NULL);
117
118    sigemptyset(&ss);
119    sa.sa_handler = sigexit;
120    sa.sa_mask = ss;
121    sa.sa_flags = 0;
122    sigaction(SIGINT, &sa, NULL);
123
124    sigemptyset(&ss);
125    sa.sa_handler = sigexit;
126    sa.sa_mask = ss;
127    sa.sa_flags = 0;
128    sigaction(SIGUSR1, &sa, NULL);
129
130    sigemptyset(&ss);
131    sa.sa_handler = sigexit;
132    sa.sa_mask = ss;
133    sa.sa_flags = 0;
134    sigaction(SIGUSR2, &sa, NULL);
135#endif
136
137    timeEventQueue = NULL;
138    timeEventQueueLast = NULL;
139    fdEventSize = 0;
140    fdEventNum = 0;
141    poll_fds = NULL;
142    fdEvents = NULL;
143    fdEventsLast = NULL;
144}
145
146void
147uninitEvents(void)
148{
149#ifdef HAVE_FORK
150    struct sigaction sa;
151    sigset_t ss;
152
153    sigemptyset(&ss);
154    sa.sa_handler = SIG_DFL;
155    sa.sa_mask = ss;
156    sa.sa_flags = 0;
157    sigaction(SIGTERM, &sa, NULL);
158
159    sigemptyset(&ss);
160    sa.sa_handler = SIG_DFL;
161    sa.sa_mask = ss;
162    sa.sa_flags = 0;
163    sigaction(SIGHUP, &sa, NULL);
164
165    sigemptyset(&ss);
166    sa.sa_handler = SIG_DFL;
167    sa.sa_mask = ss;
168    sa.sa_flags = 0;
169    sigaction(SIGINT, &sa, NULL);
170
171    sigemptyset(&ss);
172    sa.sa_handler = SIG_DFL;
173    sa.sa_mask = ss;
174    sa.sa_flags = 0;
175    sigaction(SIGUSR1, &sa, NULL);
176
177    sigemptyset(&ss);
178    sa.sa_handler = SIG_DFL;
179    sa.sa_mask = ss;
180    sa.sa_flags = 0;
181    sigaction(SIGUSR2, &sa, NULL);
182#endif
183}
184
185#ifdef HAVE_FORK
186void
187interestingSignals(sigset_t *ss)
188{
189    sigemptyset(ss);
190    sigaddset(ss, SIGTERM);
191    sigaddset(ss, SIGHUP);
192    sigaddset(ss, SIGINT);
193    sigaddset(ss, SIGUSR1);
194    sigaddset(ss, SIGUSR2);
195}
196#endif
197
198void
199timeToSleep(struct timeval *time)
200{
201    if(!timeEventQueue) {
202        time->tv_sec = ~0L;
203        time->tv_usec = ~0L;
204    } else {
205        *time = timeEventQueue->time;
206    }
207}
208
209static TimeEventHandlerPtr
210enqueueTimeEvent(TimeEventHandlerPtr event)
211{
212    TimeEventHandlerPtr otherevent;
213
214    /* We try to optimise two cases -- the event happens very soon, or
215       it happens after most of the other events. */
216    if(timeEventQueue == NULL ||
217       timeval_cmp(&event->time, &timeEventQueue->time) < 0) {
218        /* It's the first event */
219        event->next = timeEventQueue;
220        event->previous = NULL;
221        if(timeEventQueue) {
222            timeEventQueue->previous = event;
223        } else {
224            timeEventQueueLast = event;
225        }
226        timeEventQueue = event;
227    } else if(timeval_cmp(&event->time, &timeEventQueueLast->time) >= 0) {
228        /* It's the last one */
229        event->next = NULL;
230        event->previous = timeEventQueueLast;
231        timeEventQueueLast->next = event;
232        timeEventQueueLast = event;
233    } else {
234        /* Walk from the end */
235        otherevent = timeEventQueueLast;
236        while(otherevent->previous &&
237              timeval_cmp(&event->time, &otherevent->previous->time) < 0) {
238            otherevent = otherevent->previous;
239        }
240        event->next = otherevent;
241        event->previous = otherevent->previous;
242        if(otherevent->previous) {
243            otherevent->previous->next = event;
244        } else {
245            timeEventQueue = event;
246        }
247        otherevent->previous = event;
248    }
249    return event;
250}
251
252TimeEventHandlerPtr
253scheduleTimeEvent(int seconds,
254                  int (*handler)(TimeEventHandlerPtr), int dsize, void *data)
255{
256    struct timeval when;
257    TimeEventHandlerPtr event;
258
259    if(seconds >= 0) {
260        when = current_time;
261        when.tv_sec += seconds;
262    } else {
263        when.tv_sec = 0;
264        when.tv_usec = 0;
265    }
266
267    event = malloc(sizeof(TimeEventHandlerRec) - 1 + dsize);
268    if(event == NULL) {
269        do_log(L_ERROR, "Couldn't allocate time event handler -- "
270               "discarding all objects.\n");
271        exitFlag = 2;
272        return NULL;
273    }
274
275    event->time = when;
276    event->handler = handler;
277    /* Let the compiler optimise the common case */
278    if(dsize == sizeof(void*))
279        memcpy(event->data, data, sizeof(void*));
280    else if(dsize > 0)
281        memcpy(event->data, data, dsize);
282
283    return enqueueTimeEvent(event);
284}
285
286void
287cancelTimeEvent(TimeEventHandlerPtr event)
288{
289    if(event == timeEventQueue)
290        timeEventQueue = event->next;
291    if(event == timeEventQueueLast)
292        timeEventQueueLast = event->previous;
293    if(event->next)
294        event->next->previous = event->previous;
295    if(event->previous)
296        event->previous->next = event->next;
297    free(event);
298}
299
300int
301allocateFdEventNum(int fd)
302{
303    int i;
304    if(fdEventNum < fdEventSize) {
305        i = fdEventNum;
306        fdEventNum++;
307    } else {
308        struct pollfd *new_poll_fds;
309        FdEventHandlerPtr *new_fdEvents, *new_fdEventsLast;
310        int new_size = 3 * fdEventSize / 2 + 1;
311
312        new_poll_fds = realloc(poll_fds, new_size * sizeof(struct pollfd));
313        if(!new_poll_fds)
314            return -1;
315        new_fdEvents = realloc(fdEvents, new_size * sizeof(FdEventHandlerPtr));
316        if(!new_fdEvents)
317            return -1;
318        new_fdEventsLast = realloc(fdEventsLast,
319                                   new_size * sizeof(FdEventHandlerPtr));
320        if(!new_fdEventsLast)
321            return -1;
322
323        poll_fds = new_poll_fds;
324        fdEvents = new_fdEvents;
325        fdEventsLast = new_fdEventsLast;
326        fdEventSize = new_size;
327        i = fdEventNum;
328        fdEventNum++;
329    }
330
331    poll_fds[i].fd = fd;
332    poll_fds[i].events = POLLERR | POLLHUP | POLLNVAL;
333    poll_fds[i].revents = 0;
334    fdEvents[i] = NULL;
335    fdEventsLast[i] = NULL;
336    fds_invalid = 1;
337    return i;
338}
339
340void
341deallocateFdEventNum(int i)
342{
343    if(i < fdEventNum - 1) {
344        memmove(&poll_fds[i], &poll_fds[i + 1],
345                (fdEventNum - i - 1) * sizeof(struct pollfd));
346        memmove(&fdEvents[i], &fdEvents[i + 1],
347                (fdEventNum - i - 1) * sizeof(FdEventHandlerPtr));
348        memmove(&fdEventsLast[i], &fdEventsLast[i + 1],
349                (fdEventNum - i - 1) * sizeof(FdEventHandlerPtr));
350    }
351    fdEventNum--;
352    fds_invalid = 1;
353}
354
355FdEventHandlerPtr
356makeFdEvent(int fd, int poll_events,
357            int (*handler)(int, FdEventHandlerPtr), int dsize, void *data)
358{
359    FdEventHandlerPtr event;
360
361    event = malloc(sizeof(FdEventHandlerRec) - 1 + dsize);
362    if(event == NULL) {
363        do_log(L_ERROR, "Couldn't allocate fd event handler -- "
364               "discarding all objects.\n");
365        exitFlag = 2;
366        return NULL;
367    }
368    event->fd = fd;
369    event->poll_events = poll_events;
370    event->handler = handler;
371    /* Let the compiler optimise the common cases */
372    if(dsize == sizeof(void*))
373        memcpy(event->data, data, sizeof(void*));
374    else if(dsize == sizeof(StreamRequestRec))
375        memcpy(event->data, data, sizeof(StreamRequestRec));
376    else if(dsize > 0)
377        memcpy(event->data, data, dsize);
378    return event;
379}
380
381FdEventHandlerPtr
382registerFdEventHelper(FdEventHandlerPtr event)
383{
384    int i;
385    int fd = event->fd;
386
387    for(i = 0; i < fdEventNum; i++)
388        if(poll_fds[i].fd == fd)
389            break;
390
391    if(i >= fdEventNum)
392        i = allocateFdEventNum(fd);
393    if(i < 0) {
394        free(event);
395        return NULL;
396    }
397
398    event->next = NULL;
399    event->previous = fdEventsLast[i];
400    if(fdEvents[i] == NULL) {
401        fdEvents[i] = event;
402    } else {
403        fdEventsLast[i]->next = event;
404    }
405    fdEventsLast[i] = event;
406    poll_fds[i].events |= event->poll_events;
407
408    return event;
409}
410
411FdEventHandlerPtr
412registerFdEvent(int fd, int poll_events,
413                int (*handler)(int, FdEventHandlerPtr), int dsize, void *data)
414{
415    FdEventHandlerPtr event;
416
417    event = makeFdEvent(fd, poll_events, handler, dsize, data);
418    if(event == NULL)
419        return NULL;
420
421    return registerFdEventHelper(event);
422}
423
424static int
425recomputePollEvents(FdEventHandlerPtr event)
426{
427    int pe = 0;
428    while(event) {
429        pe |= event->poll_events;
430        event = event->next;
431    }
432    return pe | POLLERR | POLLHUP | POLLNVAL;
433}
434
435static void
436unregisterFdEventI(FdEventHandlerPtr event, int i)
437{
438    assert(i < fdEventNum && poll_fds[i].fd == event->fd);
439
440    if(fdEvents[i] == event) {
441        assert(!event->previous);
442        fdEvents[i] = event->next;
443    } else {
444        event->previous->next = event->next;
445    }
446
447    if(fdEventsLast[i] == event) {
448        assert(!event->next);
449        fdEventsLast[i] = event->previous;
450    } else {
451        event->next->previous = event->previous;
452    }
453
454    free(event);
455
456    if(fdEvents[i] == NULL) {
457        deallocateFdEventNum(i);
458    } else {
459        poll_fds[i].events = recomputePollEvents(fdEvents[i]) |
460            POLLERR | POLLHUP | POLLNVAL;
461    }
462}
463
464void 
465unregisterFdEvent(FdEventHandlerPtr event)
466{
467    int i;
468
469    for(i = 0; i < fdEventNum; i++) {
470        if(poll_fds[i].fd == event->fd) {
471            unregisterFdEventI(event, i);
472            return;
473        }
474    }
475    abort();
476}
477
478void
479runTimeEventQueue()
480{
481    TimeEventHandlerPtr event;
482    int done;
483
484    while(timeEventQueue &&
485          timeval_cmp(&timeEventQueue->time, &current_time) <= 0) {
486        event = timeEventQueue;
487        timeEventQueue = event->next;
488        if(timeEventQueue)
489            timeEventQueue->previous = NULL;
490        else
491            timeEventQueueLast = NULL;
492        done = event->handler(event);
493        assert(done);
494        free(event);
495    }
496}
497
498static FdEventHandlerPtr
499findEventHelper(int revents, FdEventHandlerPtr events)
500{
501    FdEventHandlerPtr event = events;
502    while(event) {
503        if(revents & event->poll_events)
504            return event;
505        event = event->next;
506    }
507    return NULL;
508}
509
510
511
512static FdEventHandlerPtr
513findEvent(int revents, FdEventHandlerPtr events)
514{
515    FdEventHandlerPtr event;
516
517    assert(!(revents & POLLNVAL));
518   
519    if((revents & POLLHUP) || (revents & POLLERR)) {
520        event = findEventHelper(POLLOUT, events);
521        if(event) return event;
522
523        event = findEventHelper(POLLIN, events);
524        if(event) return event;
525        return NULL;
526    }
527
528    if(revents & POLLOUT) {
529        event = findEventHelper(POLLOUT, events);
530        if(event) return event;
531    }
532
533    if(revents & POLLIN) {
534        event = findEventHelper(POLLIN, events);
535        if(event) return event;
536    }
537    return NULL;
538}
539
540typedef struct _FdEventHandlerPoke {
541    int fd;
542    int what;
543    int status;
544} FdEventHandlerPokeRec, *FdEventHandlerPokePtr;
545
546static int
547pokeFdEventHandler(TimeEventHandlerPtr tevent)
548{
549    FdEventHandlerPokePtr poke = (FdEventHandlerPokePtr)tevent->data;
550    int fd = poke->fd;
551    int what = poke->what;
552    int status = poke->status;
553    int done;
554    FdEventHandlerPtr event, next;
555    int i;
556
557    for(i = 0; i < fdEventNum; i++) {
558        if(poll_fds[i].fd == fd)
559            break;
560    }
561
562    if(i >= fdEventNum)
563        return 1;
564
565    event = fdEvents[i];
566    while(event) {
567        next = event->next;
568        if(event->poll_events & what) {
569            done = event->handler(status, event);
570            if(done) {
571                if(fds_invalid)
572                    unregisterFdEvent(event);
573                else
574                    unregisterFdEventI(event, i);
575            }
576            if(fds_invalid)
577                break;
578        }
579        event = next;
580    }
581    return 1;
582}
583
584void 
585pokeFdEvent(int fd, int status, int what)
586{
587    TimeEventHandlerPtr handler;
588    FdEventHandlerPokeRec poke;
589
590    poke.fd = fd;
591    poke.status = status;
592    poke.what = what;
593
594    handler = scheduleTimeEvent(0, pokeFdEventHandler,
595                                sizeof(poke), &poke);
596    if(!handler) {
597        do_log(L_ERROR, "Couldn't allocate handler.\n");
598    }
599}
600
601int
602workToDo()
603{
604    struct timeval sleep_time;
605    int rc;
606
607    if(exitFlag)
608        return 1;
609
610    timeToSleep(&sleep_time);
611    gettimeofday(&current_time, NULL);
612    if(timeval_cmp(&sleep_time, &current_time) <= 0)
613        return 1;
614    rc = poll(poll_fds, fdEventNum, 0);
615    if(rc < 0) {
616        do_log_error(L_ERROR, errno, "Couldn't poll");
617        return 1;
618    }
619    return(rc >= 1);
620}
621   
622void
623eventLoop()
624{
625    struct timeval sleep_time, timeout;
626    int rc, i, done, n;
627    FdEventHandlerPtr event;
628    int fd0;
629
630    gettimeofday(&current_time, NULL);
631
632    while(1) {
633    again:
634        if(exitFlag) {
635            if(exitFlag < 3)
636                reopenLog();
637            if(exitFlag >= 2) {
638                discardObjects(1, 0);
639                if(exitFlag >= 3)
640                    return;
641                free_chunk_arenas();
642            } else {
643                writeoutObjects(1);
644            }
645            initForbidden();
646            exitFlag = 0;
647        }
648
649        timeToSleep(&sleep_time);
650        if(sleep_time.tv_sec == -1) {
651            rc = poll(poll_fds, fdEventNum,
652                      diskIsClean ? -1 : idleTime * 1000);
653        } else if(timeval_cmp(&sleep_time, &current_time) <= 0) {
654            runTimeEventQueue();
655            continue;
656        } else {
657            gettimeofday(&current_time, NULL);
658            if(timeval_cmp(&sleep_time, &current_time) <= 0) {
659                runTimeEventQueue();
660                continue;
661            } else {
662                int t;
663                timeval_minus(&timeout, &sleep_time, &current_time);
664                t = timeout.tv_sec * 1000 + (timeout.tv_usec + 999) / 1000;
665                rc = poll(poll_fds, fdEventNum,
666                          diskIsClean ? t : MIN(idleTime * 1000, t));
667            }
668        }
669
670        gettimeofday(&current_time, NULL);
671
672        if(rc < 0) {
673            if(errno == EINTR) {
674                continue;
675            } else if(errno == ENOMEM) {
676                free_chunk_arenas();
677                do_log(L_ERROR,
678                       "Couldn't poll: out of memory.  "
679                       "Sleeping for one second.\n");
680                sleep(1);
681            } else {
682                do_log_error(L_ERROR, errno, "Couldn't poll");
683                exitFlag = 3;
684            }
685            continue;
686        }
687
688        if(rc == 0) {
689            if(!diskIsClean) {
690                timeToSleep(&sleep_time);
691                if(timeval_cmp(&sleep_time, &current_time) > 0)
692                    writeoutObjects(0);
693            }
694            continue;
695        }
696
697        /* Rather than tracking all changes to the in-memory cache, we
698           assume that something changed whenever we see any activity. */
699        diskIsClean = 0;
700
701        fd0 =
702            (current_time.tv_usec ^ (current_time.tv_usec >> 16)) % fdEventNum;
703        n = rc;
704        for(i = 0; i < fdEventNum; i++) {
705            int j = (i + fd0) % fdEventNum;
706            if(n <= 0)
707                break;
708            if(poll_fds[j].revents) {
709                n--;
710                event = findEvent(poll_fds[j].revents, fdEvents[j]);
711                if(!event)
712                    continue;
713                done = event->handler(0, event);
714                if(done) {
715                    if(fds_invalid)
716                        unregisterFdEvent(event);
717                    else
718                        unregisterFdEventI(event, j);
719                }
720                if(fds_invalid) {
721                    fds_invalid = 0;
722                    goto again;
723                }
724            }
725        }
726    }
727}
728
729void
730initCondition(ConditionPtr condition)
731{
732    condition->handlers = NULL;
733}
734
735ConditionPtr
736makeCondition(void)
737{
738    ConditionPtr condition;
739    condition = malloc(sizeof(ConditionRec));
740    if(condition == NULL)
741        return NULL;
742    initCondition(condition);
743    return condition;
744}
745
746ConditionHandlerPtr
747conditionWait(ConditionPtr condition,
748              int (*handler)(int, ConditionHandlerPtr),
749              int dsize, void *data)
750{
751    ConditionHandlerPtr chandler;
752
753    assert(!in_signalCondition);
754
755    chandler = malloc(sizeof(ConditionHandlerRec) - 1 + dsize);
756    if(!chandler)
757        return NULL;
758
759    chandler->condition = condition;
760    chandler->handler = handler;
761    /* Let the compiler optimise the common case */
762    if(dsize == sizeof(void*))
763        memcpy(chandler->data, data, sizeof(void*));
764    else if(dsize > 0)
765        memcpy(chandler->data, data, dsize);
766
767    if(condition->handlers)
768        condition->handlers->previous = chandler;
769    chandler->next = condition->handlers;
770    chandler->previous = NULL;
771    condition->handlers = chandler;
772    return chandler;
773}
774
775void
776unregisterConditionHandler(ConditionHandlerPtr handler)
777{
778    ConditionPtr condition = handler->condition;
779
780    assert(!in_signalCondition);
781
782    if(condition->handlers == handler)
783        condition->handlers = condition->handlers->next;
784    if(handler->next)
785        handler->next->previous = handler->previous;
786    if(handler->previous)
787        handler->previous->next = handler->next;
788
789    free(handler);
790}
791
792void 
793abortConditionHandler(ConditionHandlerPtr handler)
794{
795    int done;
796    done = handler->handler(-1, handler);
797    assert(done);
798    unregisterConditionHandler(handler);
799}
800
801void
802signalCondition(ConditionPtr condition)
803{
804    ConditionHandlerPtr handler;
805    int done;
806
807    assert(!in_signalCondition);
808    in_signalCondition++;
809
810    handler = condition->handlers;
811    while(handler) {
812        ConditionHandlerPtr next = handler->next;
813        done = handler->handler(0, handler);
814        if(done) {
815            if(handler == condition->handlers)
816                condition->handlers = next;
817            if(next)
818                next->previous = handler->previous;
819            if(handler->previous)
820                handler->previous->next = next;
821            else
822                condition->handlers = next;
823            free(handler);
824        }
825        handler = next;
826    }
827    in_signalCondition--;
828}
829
830void
831polipoExit()
832{
833    exitFlag = 3;
834}
Note: See TracBrowser for help on using the browser.