2012-06-22 04:05:17 +00:00
|
|
|
/*
|
|
|
|
Serval Distributed Numbering Architecture (DNA)
|
|
|
|
Copyright (C) 2012 Paul Gardner-Stephen
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU General Public License
|
|
|
|
as published by the Free Software Foundation; either version 2
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2012-11-20 03:13:51 +00:00
|
|
|
#include <poll.h>
|
2012-06-22 04:05:17 +00:00
|
|
|
#include "serval.h"
|
2012-12-11 05:29:46 +00:00
|
|
|
#include "conf.h"
|
2012-11-20 03:13:51 +00:00
|
|
|
#include "str.h"
|
2012-07-10 10:29:46 +00:00
|
|
|
#include "strbuf.h"
|
2012-07-11 04:45:02 +00:00
|
|
|
#include "strbuf_helpers.h"
|
2012-06-22 04:05:17 +00:00
|
|
|
|
|
|
|
#define MAX_WATCHED_FDS 128
|
|
|
|
struct pollfd fds[MAX_WATCHED_FDS];
|
|
|
|
int fdcount=0;
|
2012-07-02 03:49:54 +00:00
|
|
|
struct sched_ent *fd_callbacks[MAX_WATCHED_FDS];
|
|
|
|
struct sched_ent *next_alarm=NULL;
|
2012-07-12 00:45:16 +00:00
|
|
|
struct sched_ent *next_deadline=NULL;
|
2012-07-02 06:34:00 +00:00
|
|
|
struct profile_total poll_stats={NULL,0,"Idle (in poll)",0,0,0};
|
2012-06-25 05:05:55 +00:00
|
|
|
|
2012-10-16 06:28:24 +00:00
|
|
|
#define alloca_alarm_name(alarm) ((alarm)->stats ? alloca_str_toprint((alarm)->stats->name) : "Unnamed")
|
|
|
|
|
|
|
|
void list_alarms()
|
|
|
|
{
|
2012-07-13 03:21:27 +00:00
|
|
|
DEBUG("Alarms;");
|
2012-08-09 02:44:32 +00:00
|
|
|
time_ms_t now = gettime_ms();
|
2012-07-13 03:21:27 +00:00
|
|
|
struct sched_ent *alarm;
|
2012-10-16 01:41:26 +00:00
|
|
|
|
|
|
|
for (alarm = next_deadline; alarm; alarm = alarm->_next)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("%p %s deadline in %lldms", alarm->function, alloca_alarm_name(alarm), alarm->deadline - now);
|
2012-10-16 01:41:26 +00:00
|
|
|
|
2012-07-13 03:21:27 +00:00
|
|
|
for (alarm = next_alarm; alarm; alarm = alarm->_next)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("%p %s in %lldms, deadline in %lldms", alarm->function, alloca_alarm_name(alarm), alarm->alarm - now, alarm->deadline - now);
|
2012-10-16 01:41:26 +00:00
|
|
|
|
2012-07-13 03:21:27 +00:00
|
|
|
DEBUG("File handles;");
|
2012-07-02 03:49:54 +00:00
|
|
|
int i;
|
2012-07-13 03:21:27 +00:00
|
|
|
for (i = 0; i < fdcount; ++i)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("%s watching #%d", alloca_alarm_name(fd_callbacks[i]), fds[i].fd);
|
2012-06-22 06:22:00 +00:00
|
|
|
}
|
|
|
|
|
2012-10-16 06:28:24 +00:00
|
|
|
int deadline(struct sched_ent *alarm)
|
|
|
|
{
|
2012-07-12 00:45:16 +00:00
|
|
|
struct sched_ent *node = next_deadline, *last = NULL;
|
|
|
|
if (alarm->deadline < alarm->alarm)
|
|
|
|
alarm->deadline = alarm->alarm;
|
|
|
|
|
|
|
|
while(node!=NULL){
|
2012-10-16 01:41:26 +00:00
|
|
|
if (node->deadline > alarm->deadline)
|
2012-07-12 00:45:16 +00:00
|
|
|
break;
|
|
|
|
last = node;
|
|
|
|
node = node->_next;
|
|
|
|
}
|
|
|
|
if (last == NULL){
|
|
|
|
next_deadline = alarm;
|
|
|
|
}else{
|
2012-10-16 01:41:26 +00:00
|
|
|
last->_next = alarm;
|
2012-07-12 00:45:16 +00:00
|
|
|
}
|
|
|
|
alarm->_prev = last;
|
|
|
|
if(node!=NULL)
|
|
|
|
node->_prev = alarm;
|
|
|
|
alarm->_next = node;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-24 04:43:50 +00:00
|
|
|
int is_scheduled(const struct sched_ent *alarm)
|
|
|
|
{
|
|
|
|
return alarm->_next || alarm->_prev || alarm == next_alarm || alarm == next_deadline;
|
|
|
|
}
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
// add an alarm to the list of scheduled function calls.
|
|
|
|
// simply populate .alarm with the absolute time, and .function with the method to call.
|
|
|
|
// on calling .poll.revents will be zero.
|
2012-10-16 06:28:24 +00:00
|
|
|
int _schedule(struct __sourceloc __whence, struct sched_ent *alarm)
|
|
|
|
{
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2013-02-15 19:52:37 +00:00
|
|
|
DEBUGF("schedule(alarm=%s) called from %s() %s:%d",
|
|
|
|
alloca_alarm_name(alarm),
|
|
|
|
__whence.function,__whence.file,__whence.line);
|
|
|
|
if (!alarm->stats)
|
|
|
|
WARNF("schedule() called from %s() %s:%d without supplying an alarm name",
|
|
|
|
__whence.function,__whence.file,__whence.line);
|
2012-10-16 06:28:24 +00:00
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
struct sched_ent *node = next_alarm, *last = NULL;
|
2012-07-12 00:45:16 +00:00
|
|
|
|
2012-10-24 04:43:50 +00:00
|
|
|
if (is_scheduled(alarm))
|
|
|
|
FATAL("Scheduling an alarm that is already scheduled");
|
2012-10-16 01:41:26 +00:00
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
if (!alarm->function)
|
|
|
|
return WHY("Can't schedule if you haven't set the function pointer");
|
|
|
|
|
2013-06-19 06:32:34 +00:00
|
|
|
time_ms_t now = gettime_ms();
|
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
if (alarm->deadline < alarm->alarm)
|
|
|
|
alarm->deadline = alarm->alarm;
|
|
|
|
|
2013-06-19 06:32:34 +00:00
|
|
|
if (now - alarm->deadline > 1000){
|
|
|
|
// 1000ms ago? thats silly, if you keep doing it noone else will get a turn.
|
|
|
|
WHYF("Alarm %s tried to schedule a deadline %lldms ago, from %s() %s:%d",
|
|
|
|
alloca_alarm_name(alarm),
|
|
|
|
(now - alarm->deadline),
|
|
|
|
__whence.function,__whence.file,__whence.line);
|
|
|
|
}
|
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
// if the alarm has already expired, move straight to the deadline queue
|
2013-06-19 06:32:34 +00:00
|
|
|
if (alarm->alarm <= now)
|
2012-07-12 00:45:16 +00:00
|
|
|
return deadline(alarm);
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
while(node!=NULL){
|
|
|
|
if (node->alarm > alarm->alarm)
|
2012-06-27 07:24:42 +00:00
|
|
|
break;
|
2012-07-02 03:49:54 +00:00
|
|
|
last = node;
|
|
|
|
node = node->_next;
|
2012-06-25 05:05:55 +00:00
|
|
|
}
|
2012-07-02 03:49:54 +00:00
|
|
|
if (last == NULL){
|
|
|
|
next_alarm = alarm;
|
|
|
|
}else{
|
|
|
|
last->_next=alarm;
|
|
|
|
}
|
|
|
|
alarm->_prev = last;
|
|
|
|
if(node!=NULL)
|
|
|
|
node->_prev = alarm;
|
|
|
|
alarm->_next = node;
|
|
|
|
|
2012-06-22 04:05:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
// remove a function from the schedule before it has fired
|
|
|
|
// safe to unschedule twice...
|
2012-10-16 06:28:24 +00:00
|
|
|
int _unschedule(struct __sourceloc __whence, struct sched_ent *alarm)
|
|
|
|
{
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("unschedule(alarm=%s)", alloca_alarm_name(alarm));
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
struct sched_ent *prev = alarm->_prev;
|
|
|
|
struct sched_ent *next = alarm->_next;
|
2012-07-02 05:50:30 +00:00
|
|
|
|
|
|
|
if (prev)
|
2012-07-02 03:49:54 +00:00
|
|
|
prev->_next = next;
|
|
|
|
else if(next_alarm==alarm)
|
|
|
|
next_alarm = next;
|
2012-07-12 00:45:16 +00:00
|
|
|
else if(next_deadline==alarm)
|
|
|
|
next_deadline = next;
|
2012-07-02 05:50:30 +00:00
|
|
|
|
|
|
|
if (next)
|
2012-07-02 03:49:54 +00:00
|
|
|
next->_prev = prev;
|
2012-07-02 05:50:30 +00:00
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
alarm->_prev = NULL;
|
|
|
|
alarm->_next = NULL;
|
2012-06-22 04:05:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
// start watching a file handle, call this function again if you wish to change the event mask
|
2012-10-16 06:28:24 +00:00
|
|
|
int _watch(struct __sourceloc __whence, struct sched_ent *alarm)
|
|
|
|
{
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("watch(alarm=%s)", alloca_alarm_name(alarm));
|
2013-02-15 20:21:33 +00:00
|
|
|
if (!alarm->stats)
|
|
|
|
WARNF("watch() called from %s() %s:%d without supplying an alarm name",
|
|
|
|
__whence.function,__whence.file,__whence.line);
|
2012-10-16 06:28:24 +00:00
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
if (!alarm->function)
|
|
|
|
return WHY("Can't watch if you haven't set the function pointer");
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
if (alarm->_poll_index>=0 && fd_callbacks[alarm->_poll_index]==alarm){
|
|
|
|
// updating event flags
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("Updating watch %s, #%d for %d", alloca_alarm_name(alarm), alarm->poll.fd, alarm->poll.events);
|
2012-07-02 03:49:54 +00:00
|
|
|
}else{
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("Adding watch %s, #%d for %d", alloca_alarm_name(alarm), alarm->poll.fd, alarm->poll.events);
|
2012-07-02 03:49:54 +00:00
|
|
|
if (fdcount>=MAX_WATCHED_FDS)
|
|
|
|
return WHY("Too many file handles to watch");
|
|
|
|
fd_callbacks[fdcount]=alarm;
|
2012-07-23 08:59:57 +00:00
|
|
|
alarm->poll.revents = 0;
|
2012-07-02 03:49:54 +00:00
|
|
|
alarm->_poll_index=fdcount;
|
|
|
|
fdcount++;
|
|
|
|
}
|
|
|
|
fds[alarm->_poll_index]=alarm->poll;
|
|
|
|
return 0;
|
|
|
|
}
|
2012-06-22 04:05:17 +00:00
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
// stop watching a file handle
|
2012-10-16 06:28:24 +00:00
|
|
|
int _unwatch(struct __sourceloc __whence, struct sched_ent *alarm)
|
|
|
|
{
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("unwatch(alarm=%s)", alloca_alarm_name(alarm));
|
|
|
|
|
2012-07-02 03:49:54 +00:00
|
|
|
int index = alarm->_poll_index;
|
|
|
|
if (index <0 || fds[index].fd!=alarm->poll.fd)
|
|
|
|
return WHY("Attempted to unwatch a handle that is not being watched");
|
|
|
|
|
|
|
|
fdcount--;
|
|
|
|
if (index!=fdcount){
|
|
|
|
// squash fds
|
|
|
|
fds[index] = fds[fdcount];
|
|
|
|
fd_callbacks[index] = fd_callbacks[fdcount];
|
|
|
|
fd_callbacks[index]->_poll_index=index;
|
2012-06-22 04:05:17 +00:00
|
|
|
}
|
2012-07-02 03:49:54 +00:00
|
|
|
fds[fdcount].fd=-1;
|
|
|
|
fd_callbacks[fdcount]=NULL;
|
|
|
|
alarm->_poll_index=-1;
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io)
|
2012-10-16 06:28:24 +00:00
|
|
|
DEBUGF("%s stopped watching #%d for %d", alloca_alarm_name(alarm), alarm->poll.fd, alarm->poll.events);
|
2012-07-02 03:49:54 +00:00
|
|
|
return 0;
|
2012-06-22 04:05:17 +00:00
|
|
|
}
|
|
|
|
|
2012-10-16 06:28:24 +00:00
|
|
|
static void call_alarm(struct sched_ent *alarm, int revents)
|
|
|
|
{
|
2013-02-15 20:21:33 +00:00
|
|
|
IN();
|
2013-06-18 03:57:26 +00:00
|
|
|
if (!alarm)
|
|
|
|
FATAL("Attempted to call with no alarm");
|
2012-07-02 03:49:54 +00:00
|
|
|
struct call_stats call_stats;
|
2012-07-12 00:45:16 +00:00
|
|
|
call_stats.totals = alarm->stats;
|
2012-07-02 05:50:30 +00:00
|
|
|
|
2013-02-15 20:21:33 +00:00
|
|
|
if (config.debug.io) DEBUGF("Calling alarm/callback %p ('%s')",
|
|
|
|
alarm, alloca_alarm_name(alarm));
|
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
if (call_stats.totals)
|
2012-11-12 04:04:30 +00:00
|
|
|
fd_func_enter(__HERE__, &call_stats);
|
2012-07-02 03:49:54 +00:00
|
|
|
|
|
|
|
alarm->poll.revents = revents;
|
|
|
|
alarm->function(alarm);
|
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
if (call_stats.totals)
|
2012-11-12 04:04:30 +00:00
|
|
|
fd_func_exit(__HERE__, &call_stats);
|
2013-02-15 20:21:33 +00:00
|
|
|
|
|
|
|
if (config.debug.io) DEBUGF("Alarm %p returned",alarm);
|
|
|
|
|
|
|
|
OUT();
|
2012-06-25 05:05:55 +00:00
|
|
|
}
|
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
int fd_poll()
|
2012-06-22 04:05:17 +00:00
|
|
|
{
|
2013-02-15 20:21:33 +00:00
|
|
|
IN();
|
2012-11-20 05:39:12 +00:00
|
|
|
int i, r=0;
|
2012-07-12 00:45:16 +00:00
|
|
|
int ms=60000;
|
2012-08-09 02:44:32 +00:00
|
|
|
time_ms_t now = gettime_ms();
|
2012-07-12 00:45:16 +00:00
|
|
|
|
2012-11-20 05:39:12 +00:00
|
|
|
if (!next_alarm && !next_deadline && fdcount==0)
|
2013-02-15 20:21:33 +00:00
|
|
|
RETURN(0);
|
2012-11-20 05:39:12 +00:00
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
/* move alarms that have elapsed to the deadline queue */
|
|
|
|
while (next_alarm!=NULL&&next_alarm->alarm <=now){
|
2012-07-02 03:49:54 +00:00
|
|
|
struct sched_ent *alarm = next_alarm;
|
|
|
|
unschedule(alarm);
|
2012-07-12 00:45:16 +00:00
|
|
|
deadline(alarm);
|
2012-07-02 03:49:54 +00:00
|
|
|
}
|
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
/* work out how long we can block in poll */
|
|
|
|
if (next_deadline)
|
|
|
|
ms = 0;
|
|
|
|
else if (next_alarm){
|
|
|
|
ms = next_alarm->alarm - now;
|
|
|
|
}
|
2012-07-02 03:49:54 +00:00
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
/* Make sure we don't have any silly timeouts that will make us wait forever. */
|
2012-07-02 03:49:54 +00:00
|
|
|
if (ms<0) ms=0;
|
2012-06-25 05:05:55 +00:00
|
|
|
|
2012-07-12 00:45:16 +00:00
|
|
|
/* check if any file handles have activity */
|
2012-07-02 03:49:54 +00:00
|
|
|
{
|
|
|
|
struct call_stats call_stats;
|
2012-07-12 00:45:16 +00:00
|
|
|
call_stats.totals=&poll_stats;
|
2012-11-12 04:04:30 +00:00
|
|
|
fd_func_enter(__HERE__, &call_stats);
|
2012-11-20 05:39:12 +00:00
|
|
|
if (fdcount==0){
|
|
|
|
if (ms>=1000)
|
|
|
|
sleep(ms/1000);
|
|
|
|
else
|
|
|
|
usleep(ms*1000);
|
|
|
|
}else{
|
2013-02-15 20:21:33 +00:00
|
|
|
if (config.debug.io) DEBUGF("poll(X,%d,%d)",fdcount,ms);
|
2012-11-20 05:39:12 +00:00
|
|
|
r = poll(fds, fdcount, ms);
|
2012-12-11 05:29:46 +00:00
|
|
|
if (config.debug.io) {
|
2012-11-20 05:39:12 +00:00
|
|
|
strbuf b = strbuf_alloca(1024);
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fdcount; ++i) {
|
|
|
|
if (i)
|
|
|
|
strbuf_puts(b, ", ");
|
|
|
|
strbuf_sprintf(b, "%d:", fds[i].fd);
|
|
|
|
strbuf_append_poll_events(b, fds[i].events);
|
|
|
|
strbuf_putc(b, ':');
|
|
|
|
strbuf_append_poll_events(b, fds[i].revents);
|
|
|
|
}
|
|
|
|
DEBUGF("poll(fds=(%s), fdcount=%d, ms=%d) = %d", strbuf_str(b), fdcount, ms, r);
|
2012-07-11 04:45:02 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-12 04:04:30 +00:00
|
|
|
fd_func_exit(__HERE__, &call_stats);
|
2012-07-30 07:52:38 +00:00
|
|
|
now=gettime_ms();
|
2012-07-02 03:49:54 +00:00
|
|
|
}
|
2013-06-14 05:39:18 +00:00
|
|
|
|
|
|
|
// Reading new data takes priority over everything else
|
|
|
|
// Are any handles marked with POLLIN?
|
|
|
|
int in_count=0;
|
|
|
|
if (r>0){
|
|
|
|
for (i=0;i<fdcount;i++)
|
|
|
|
if (fds[i].revents & POLLIN)
|
|
|
|
in_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call one alarm function, but only if its deadline time has elapsed OR there is no incoming file activity */
|
|
|
|
if (next_deadline && (next_deadline->deadline <=now || (in_count==0))){
|
2012-07-12 00:45:16 +00:00
|
|
|
struct sched_ent *alarm = next_deadline;
|
|
|
|
unschedule(alarm);
|
|
|
|
call_alarm(alarm, 0);
|
2012-07-30 07:52:38 +00:00
|
|
|
now=gettime_ms();
|
2013-06-14 05:39:18 +00:00
|
|
|
|
|
|
|
// after running a timed alarm, unless we already know there is data to read we want to check for more incoming IO before we send more outgoing.
|
|
|
|
if (in_count==0)
|
|
|
|
RETURN(1);
|
2012-07-12 00:45:16 +00:00
|
|
|
}
|
|
|
|
|
2012-06-22 04:05:17 +00:00
|
|
|
/* If file descriptors are ready, then call the appropriate functions */
|
|
|
|
if (r>0) {
|
2013-06-18 02:43:14 +00:00
|
|
|
for(i=fdcount -1;i>=0;i--){
|
2012-06-22 04:05:17 +00:00
|
|
|
if (fds[i].revents) {
|
2013-06-14 05:39:18 +00:00
|
|
|
// if any handles have POLLIN set, don't process any other handles
|
|
|
|
if (!(fds[i].revents&POLLIN || in_count==0))
|
|
|
|
continue;
|
|
|
|
|
2012-08-08 05:27:27 +00:00
|
|
|
int fd = fds[i].fd;
|
2012-07-10 07:03:39 +00:00
|
|
|
/* Call the alarm callback with the socket in non-blocking mode */
|
2013-04-26 07:21:31 +00:00
|
|
|
errno=0;
|
2012-08-08 05:27:27 +00:00
|
|
|
set_nonblock(fd);
|
2013-04-26 07:21:31 +00:00
|
|
|
// Work around OSX behaviour that doesn't set POLLERR on
|
|
|
|
// devices that have been deconfigured, e.g., a USB serial adapter
|
|
|
|
// that has been removed.
|
|
|
|
if (errno == ENXIO) fds[i].revents|=POLLERR;
|
2012-07-02 03:49:54 +00:00
|
|
|
call_alarm(fd_callbacks[i], fds[i].revents);
|
2012-08-08 05:27:27 +00:00
|
|
|
/* The alarm may have closed and unwatched the descriptor, make sure this descriptor still matches */
|
2013-06-18 06:58:26 +00:00
|
|
|
if (i<fdcount && fds[i].fd == fd){
|
|
|
|
if (set_block(fds[i].fd))
|
|
|
|
FATALF("Alarm %p %s has a bad descriptor that wasn't closed!", fd_callbacks[i], alloca_alarm_name(fd_callbacks[i]));
|
|
|
|
}
|
2012-06-22 04:05:17 +00:00
|
|
|
}
|
2013-06-14 05:39:18 +00:00
|
|
|
}
|
2012-06-22 04:05:17 +00:00
|
|
|
}
|
2013-02-15 20:21:33 +00:00
|
|
|
RETURN(1);
|
2013-02-16 17:47:24 +00:00
|
|
|
OUT();
|
2012-06-22 04:05:17 +00:00
|
|
|
}
|