#include <sys/syslog.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#ifndef FD_SET
#include <sys/select.h>
#endif
#include "efaxlib.h"
#include "efaxmsg.h"
#include "efaxos.h"
#ifdef USE_TERMIO
#include <termio.h>
#include <sys/ioctl.h>
#define termios termio
#define tcgetattr(fd, pt) ioctl(fd, TCGETA, pt)
#define tcsetattr(fd, x, pt) ioctl(fd, TCSETAW, pt)
#define cfsetospeed(pt, b) ((pt)->c_cflag = ((pt)->c_cflag & ~CBAUD) | b)
#define cfsetispeed(pt, b)
#define tcdrain(fd)
#else
#include <termios.h>
#endif
#ifdef TIOCSSOFTCAR
#include <sys/ioctl.h>
#endif
#ifdef __APPLE__
#include <sys/ioctl.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/serial/ioss.h>
#include <pthread.h>
#define SYSEVENT_CANSLEEP 0x1
#define SYSEVENT_WILLSLEEP 0x2
#define SYSEVENT_WOKE 0x4
typedef struct threaddatastruct
{
sysevent_t sysevent;
} threaddata_t;
static pthread_t sysEventThread = NULL;
static pthread_mutex_t sysEventThreadMutex = { 0 };
static pthread_cond_t sysEventThreadCond = { 0 };
static CFRunLoopRef sysEventRunloop = NULL;
static int sysEventPipes[2] = { -1, -1 };
sysevent_t sysevent;
static int clientEventFd = -1;
static char clientSocketName[] = "/var/run/efax";
static int sysEventMonitorUpdate(TFILE *f);
static void *sysEventThreadEntry();
static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument);
static int clientEventUpdate();
#endif
static int ttlock ( char *fname, int log );
static int ckfld ( char *field, int set, int get );
static int checktermio ( struct termios *t, TFILE *f );
static void tinit ( TFILE *f, int fd, int reverse, int hwfc );
static int ttlocked ( char *fname, int log );
static int ttunlock ( char *fname );
#ifndef CRTSCTS
#define CRTSCTS 0
#endif
#if defined(__APPLE__)
void notify(CFStringRef status, CFTypeRef value)
{
CFMutableDictionaryRef notification;
int i;
static CFNumberRef pid = NULL;
if (!pid)
{
i = getpid();
pid = CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type, &i);
}
notification = CFDictionaryCreateMutable(kCFAllocatorDefault, 3,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(notification, CFSTR("pid"), pid);
CFDictionaryAddValue(notification, CFSTR("status"), status);
if (value)
CFDictionaryAddValue(notification, CFSTR("value"), value);
#if 0
#pragma warn debugging code!
char buf[1024];
CFStringGetCString(status, buf, sizeof(buf), kCFStringEncodingUTF8);
syslog(LOG_ERR, buf);
#endif
CFNotificationCenterPostNotificationWithOptions(
CFNotificationCenterGetDistributedCenter(),
status,
CFSTR("com.apple.efax"),
notification, kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions);
CFRelease(notification);
}
#endif
int time_ms ( void )
{
struct timeval tv ;
gettimeofday ( &tv, NULL ) ;
return (int) ( tv.tv_usec / 1000 ) ;
}
long proc_ms ( void )
{
struct timeval t ;
static int init=0 ;
static struct timeval s ;
if ( ! init ) {
gettimeofday ( &s, 0 ) ;
init = 1 ;
}
gettimeofday ( &t, 0 ) ;
return ( t.tv_sec - s.tv_sec ) * 1000 + ( t.tv_usec - s.tv_usec ) / 1000 ;
}
void msleep ( int t )
{
struct timeval timeout ;
timeout.tv_sec = t / 1000 ;
timeout.tv_usec = ( t % 1000 ) * 1000 ;
if ( select ( 1 , 0 , 0 , 0 , &timeout ) < 0 )
msg ("ES2select failed in msleep:") ;
}
int tdata ( TFILE *f, int t )
{
int n, err=0 ;
fd_set fds ;
int maxfd;
struct timeval timeout ;
timeout.tv_sec = t / 10 ;
timeout.tv_usec = ( t % 10 ) * 100000 ;
FD_ZERO (&fds);
if (f->fd != -1)
FD_SET(f->fd, &fds);
maxfd = f->fd;
#ifdef __APPLE__
if ( sysEventPipes[0] != -1)
{
FD_SET(sysEventPipes[0], &fds);
if (sysEventPipes[0] > maxfd)
maxfd = sysEventPipes[0];
}
if ( clientEventFd != -1)
{
FD_SET(clientEventFd, &fds);
if (clientEventFd > maxfd)
maxfd = clientEventFd;
}
#endif
for (;;)
{
n = select ( maxfd + 1, &fds, 0, 0, t<0 ? 0 : &timeout ) ;
if (n == 0)
break;
if (n < 0)
{
if (errno == EINTR)
{
msg("Wselect() interrupted in tdata()");
continue;
}
msg("Eselect() failed in tdata():");
err = -2;
break;
}
#ifdef __APPLE__
if (sysEventPipes[0] != -1 && FD_ISSET(sysEventPipes[0], &fds))
{
if ((err = sysEventMonitorUpdate(f)))
break;
}
if (clientEventFd != -1 && FD_ISSET(clientEventFd, &fds))
{
if ((err = clientEventUpdate()) != 0)
break;
}
#endif
if (f->fd != -1 && FD_ISSET(f->fd, &fds))
{
err = 1;
break;
}
}
return err ;
}
int tundrflw ( TFILE *f, int t )
{
int n ;
n = tdata ( f, t ) ;
if ( n > 0 )
if ( ( n = read( f->fd, f->ibuf, IBUFSIZE ) ) < 0 )
msg ( "ES2fax device read:" ) ;
f->iq = ( f->ip = f->ibuf ) + ( n > 0 ? n : 0 ) ;
return n > 0 ? n : EOF ;
}
int tgetd ( TFILE *f, int t )
{
int c ;
if ( ( c = tgetc(f,t) ) < 0 )
c = EOF ;
else
if ( c != DLE )
c = f->ibitorder[c] ;
else {
c = tgetc(f,t) ;
if ( c == ETX )
c = -2 ;
else
if ( c == DLE || c == SUB )
c = f->ibitorder [ DLE ] ;
else
c = msg ( "W0invalid escape sequence (DLE-%s) in data", cname(c) ) ;
}
return c ;
}
int tput ( TFILE *f, uchar *p, int n )
{
int m=0 ;
while ( n > 0 && ( m = write( f->fd, p, n ) ) > 0 ) {
if ( m != n )
msg ( "Wonly wrote %d of %d bytes", m, n ) ;
n -= m ;
p += m ;
}
if ( m < 0 )
msg ( "ES2fax device write:" ) ;
return m ;
}
static int ckfld ( char *field, int set, int get )
{
return set == get ?
0 : msg ( "W1 termios.%s is 0%08o, not 0%08o", field, get, set ) ;
}
static int checktermio ( struct termios *t, TFILE *f )
{
struct termios s ;
int err=0 ;
s.c_iflag=s.c_oflag=s.c_lflag=s.c_cflag=s.c_cc[VMIN]=s.c_cc[VTIME]=0 ;
if ( tcgetattr ( f->fd , &s ) )
err = msg ("ES2tcgetattr failed:") ;
if ( ! err ) return
ckfld ( "iflag" , t->c_iflag, s.c_iflag ) ||
ckfld ( "oflag" , t->c_oflag , s.c_oflag ) ||
ckfld ( "lflag" , t->c_lflag , s.c_lflag ) ||
ckfld ( "cflag" , t->c_cflag & 0xFFFF , s.c_cflag & 0xFFFF) ||
ckfld ( "START" , t->c_cc[VSTART] , s.c_cc[VSTART] ) ||
ckfld ( "STOP" , t->c_cc[VSTOP] , s.c_cc[VSTOP] ) ||
ckfld ( "MIN" , t->c_cc[VMIN] , s.c_cc[VMIN] ) ||
ckfld ( "TIME" , t->c_cc[VTIME] , s.c_cc[VTIME] ) ;
return err ;
}
int ttymode ( TFILE *f, enum ttymodes mode )
{
int err=0, i ;
static struct termios t, oldt, *pt ;
static int saved=0 ;
if ( ! saved ) {
if ( tcgetattr ( f->fd, &oldt ) )
err = msg ( "ES2tcgetattr on fd=%d failed:", f->fd ) ;
else
saved=1 ;
}
t.c_iflag = IGNBRK | IGNPAR ;
t.c_oflag = 0 ;
t.c_cflag = CS8 | CREAD | CLOCAL ;
t.c_lflag = 0 ;
for ( i=0 ; i<NCCS ; i++ ) t.c_cc[i] = 0 ;
t.c_cc[VMIN] = 1 ;
t.c_cc[VTIME] = 0 ;
t.c_cc[VSTOP] = XOFF;
t.c_cc[VSTART] = XON;
pt = &t ;
switch ( mode ) {
case VOICESEND :
t.c_iflag |= IXON ;
t.c_cflag |= f->hwfc ? CRTSCTS : 0 ;
case VOICECOMMAND :
cfsetospeed ( pt, B38400 ) ;
cfsetispeed ( pt, B38400 ) ;
break ;
case SEND :
t.c_iflag |= IXON ;
t.c_cflag |= f->hwfc ? CRTSCTS : 0 ;
case COMMAND :
cfsetospeed ( pt, B19200 ) ;
cfsetispeed ( pt, B19200 ) ;
break ;
case DROPDTR :
cfsetospeed ( pt, B0 ) ;
break ;
case ORIGINAL :
if ( saved ) pt = &oldt ;
break ;
default :
err = msg ("E2can't happen(ttymode)") ;
break ;
}
if ( ! err && tcsetattr ( f->fd, TCSADRAIN, pt ) )
err = msg ( "ES2tcsetattr on fd=%d failed:", f->fd ) ;
if ( ! err && checktermio ( pt, f ) )
msg ( "Wterminal mode not set properly" ) ;
tcflow ( f->fd, TCOON ) ;
return err ;
}
static void tinit ( TFILE *f, int fd, int reverse, int hwfc )
{
f->ip = f->iq = f->ibuf ;
f->obitorder = normalbits ;
f->ibitorder = reverse ? reversebits : normalbits ;
f->fd = fd ;
f->hwfc = hwfc ;
if ( ! normalbits[1] ) initbittab () ;
}
int ttyopen ( TFILE *f, char *fname, int reverse, int hwfc )
{
int flags, err=0 ;
#if defined(__APPLE__)
int fd;
if ((fd = open(fname, O_RDWR | O_NONBLOCK, 0)) >= 0)
{
fcntl(fd, F_SETFL, 0);
if ((err = ioctl(fd, TIOCEXCL, 0)) != 0)
{
close(fd);
fd = -1;
err = 1;
}
else
{
if ((clientEventFd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
msg ( "W socket returned error %d - %s", (int)errno, strerror(errno));
else
{
struct sockaddr_un laddr;
mode_t mask;
bzero(&laddr, sizeof(laddr));
laddr.sun_family = AF_LOCAL;
strlcpy(laddr.sun_path, clientSocketName, sizeof(laddr.sun_path));
unlink(laddr.sun_path);
mask = umask(0);
err = bind(clientEventFd, (struct sockaddr *)&laddr, SUN_LEN(&laddr));
umask(mask);
if (err < 0)
{
msg ( "W bind returned error %d - %s", (int)errno, strerror(errno));
close(clientEventFd);
clientEventFd = -1;
}
else
{
if (listen(clientEventFd, 2) < 0)
{
msg ( "W listen returned error %d - %s(%d)", (int)errno, strerror(errno));
unlink(clientSocketName);
close(clientEventFd);
clientEventFd = -1;
}
}
}
err = 0;
}
}
tinit ( f, fd, reverse, hwfc ) ;
#else
tinit ( f, open ( fname, O_RDWR | O_NDELAY | O_NOCTTY ), reverse, hwfc ) ;
#endif
if ( f->fd < 0 ) {
if ( errno == EBUSY ) {
err = 1 ;
} else {
err = msg ( "ES2can't open serial port %s:", fname ) ;
}
}
if ( ! err ) {
if ( ( flags = fcntl( f->fd, F_GETFL, 0 ) ) < 0 ||
fcntl( f->fd, F_SETFL, ( flags & ~O_NDELAY ) ) < 0 )
err = msg ( "ES2fax device fcntl failed %s:", fname ) ;
}
#ifdef TIOCSSOFTCAR
{
int arg = 1 ;
if ( ! err )
if ( ioctl ( f->fd, TIOCSSOFTCAR, &arg ) )
msg ("WS unable to set software carrier:" ) ;
}
#endif
return err ;
}
int ttyclose ( TFILE *f )
{
if ( clientEventFd != -1 ) {
unlink(clientSocketName);
close(clientEventFd);
clientEventFd = -1;
}
if ( f->fd != -1 ) {
close(f->fd);
f->fd = -1;
}
return 0 ;
}
static int ttlocked ( char *fname, int log )
{
int err=0, ipid ;
FILE *f ;
pid_t pid = 0 ;
char buf [ EFAX_PATH_MAX ] = "" ;
if ( fname && *fname == BINLKFLAG ) fname++ ;
if ( fname && ( f = fopen ( fname , "r" ) ) ) {
if ( fread ( buf, sizeof(char), EFAX_PATH_MAX-1, f ) == sizeof(pid_t) ||
sscanf ( buf, "%d" , &ipid ) != 1 ) {
pid = * (pid_t *) buf ;
if ( log ) msg ("X+ read binary pid %d from %s", (int) pid, fname ) ;
} else {
char *p ;
pid = (pid_t) ipid ;
if ( log ) {
msg ( "X+ read HDB pid %d [", (int) pid ) ;
for ( p=buf ; *p ; p++ ) msg ( "X+ %s", cname ( *p ) ) ;
msg ( "X+ ] from %s", fname ) ;
}
}
if ( kill ( pid, 0 ) && errno == ESRCH ) {
if ( log ) msg ("X - stale" ) ;
if ( remove ( fname ) )
err = msg ( "ES2can't remove stale lock %s from pid %d:",
fname, pid ) ;
else
err = msg ( "I0removed stale lock %s from pid %d", fname, pid ) ;
} else {
if ( pid != getpid() ) {
err = 1 ;
if ( log ) msg ( "X1 (not our pid)" ) ;
} else {
err = 3 ;
if ( log ) msg ( "X3 (our pid)" ) ;
}
}
fclose ( f ) ;
}
return err ;
}
static int ttlock ( char *fname, int log )
{
int err=0, dirlen, bin=0 ;
FILE *f=0 ;
pid_t pid = getpid ( ) ;
char *p , buf [ EFAX_PATH_MAX ] = "" ;
if ( fname && *fname == BINLKFLAG ) {
fname++ ;
bin = 1 ;
}
err = ttlocked ( fname, log ) ;
if ( ! err ) {
dirlen = ( p = strrchr( fname , '/' ) ) ? p-fname+1 : strlen ( fname ) ;
sprintf ( buf , "%.*sTMP..%05d" , dirlen , fname , (int) pid ) ;
if ( ! ( f = fopen( buf, "w" ) ) )
err = msg ( "ES2can't open pre-lock file %s:", buf ) ;
}
if ( ! err && f ) {
if ( bin ) {
if ( fwrite ( &pid, sizeof(pid_t), 1, f ) < 1 )
err = msg ( "ES2can't write pre-lock file %s:", buf ) ;
} else {
if ( fprintf ( f, "%10d", (int) pid ) < 0 )
err = msg ( "ES2can't write pre-lock file %s:", buf ) ;
}
}
if ( ! err && f ) {
if ( rename ( buf , fname ) == 0 ) {
chmod ( fname , 0444 ) ;
msg ( "Xcreated %s lock file %s", bin ? "binary" : "text", fname ) ;
} else {
err = ttlocked ( fname, log ) ;
if ( ! err )
err = msg ( "ES2can't rename lock file %s to %s:", buf, fname ) ;
}
}
if ( f ) {
fclose ( f ) ;
if ( err ) remove ( buf ) ;
}
return err ;
}
static int ttunlock ( char *fname )
{
int err = 0 ;
if ( fname && *fname == BINLKFLAG ) fname++ ;
switch ( ttlocked ( fname, 1 ) ) {
case 0: break ;
case 1: err = msg ( "E1won't remove lock %s (not ours)" , fname ) ; break ;
case 2: err = 2 ; break ;
case 3:
if ( remove ( fname ) ) {
err = msg ( "ES2can't remove lock %s:", fname ) ;
} else {
err = msg ( "X0removed lock file %s", fname ) ;
}
break ;
default:
err = msg ( "E2can't happen (ttunlock)" ) ;
break ;
}
return err ;
}
int lockall ( TFILE *f, char **lkfiles, int log )
{
int err = 0 ;
char **p = lkfiles ;
#if defined(__APPLE__)
if (f->fd > 0)
{
int allowPremption = 0;
if ((err = ioctl(f->fd, IOSSPREEMPT, &allowPremption)) != 0)
err = 1;
}
#endif
while ( *p && ! err )
if ( ( err = ttlock ( *p++, log ) ) == 3 ) err = 0 ;
return err ;
}
int unlockall (TFILE *f, char **lkfiles )
{
int err = 0, i ;
char **p = lkfiles ;
while ( *p )
if ( ( i = ttunlock ( *p++ ) ) != 0 ) err = i ;
#if defined(__APPLE__)
int allowPremption = 1;
ioctl(f->fd, IOSSPREEMPT, &allowPremption);
#endif
return err ;
}
char *efaxbasename ( char *p )
{
return strrchr ( p , '/' ) ? strrchr ( p , '/' ) + 1 : p ;
}
#ifdef __APPLE__
void sysEventMonitorStart(void)
{
int flags;
pipe(sysEventPipes);
flags = fcntl(sysEventPipes[0], F_GETFL, 0);
fcntl(sysEventPipes[0], F_SETFL, flags | O_NONBLOCK);
pthread_mutex_init(&sysEventThreadMutex, NULL);
pthread_cond_init(&sysEventThreadCond, NULL);
pthread_create(&sysEventThread, NULL, sysEventThreadEntry, NULL);
}
void sysEventMonitorStop(void)
{
CFRunLoopRef rl;
if (sysEventThread)
{
pthread_mutex_lock(&sysEventThreadMutex);
if (sysEventRunloop == NULL)
pthread_cond_wait(&sysEventThreadCond, &sysEventThreadMutex);
rl = sysEventRunloop;
sysEventRunloop = NULL;
pthread_mutex_unlock(&sysEventThreadMutex);
if (rl)
CFRunLoopStop(rl);
pthread_join(sysEventThread, NULL);
pthread_mutex_destroy(&sysEventThreadMutex);
pthread_cond_destroy(&sysEventThreadCond);
}
if (sysEventPipes[0] >= 0)
{
close(sysEventPipes[0]);
close(sysEventPipes[1]);
sysEventPipes[0] = -1;
sysEventPipes[1] = -1;
}
}
static int sysEventMonitorUpdate(TFILE *f)
{
int err = 0;
if (read((int)sysEventPipes[0], &sysevent, sizeof(sysevent)) == sizeof(sysevent))
{
if ((sysevent.event & SYSEVENT_CANSLEEP))
{
if (waiting)
IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
else
{
msg("Isleep canceled because of active job");
IOCancelPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
}
}
if ((sysevent.event & SYSEVENT_WILLSLEEP))
{
if (waiting)
{
msg("Ipreparing to sleep...");
err = -6;
}
else
{
cleanup(6);
IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
exit(6);
}
}
if ((sysevent.event & SYSEVENT_WOKE))
{
IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
if (waiting)
err = -7;
}
}
return err;
}
static void *sysEventThreadEntry()
{
io_object_t powerNotifierObj;
IONotificationPortRef powerNotifierPort;
CFRunLoopSourceRef powerRLS = NULL;
threaddata_t threadData;
bzero(&threadData, sizeof(threadData));
threadData.sysevent.powerKernelPort = IORegisterForSystemPower(&threadData, &powerNotifierPort, sysEventPowerNotifier, &powerNotifierObj);
if (threadData.sysevent.powerKernelPort)
{
powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
}
pthread_mutex_lock(&sysEventThreadMutex);
sysEventRunloop = CFRunLoopGetCurrent();
pthread_cond_signal(&sysEventThreadCond);
pthread_mutex_unlock(&sysEventThreadMutex);
CFRunLoopRun();
if (powerRLS)
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
CFRunLoopSourceInvalidate(powerRLS);
CFRelease(powerRLS);
}
if (threadData.sysevent.powerKernelPort)
IODeregisterForSystemPower(&powerNotifierObj);
pthread_exit(NULL);
}
static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument)
{
threaddata_t *threadData = (threaddata_t *)context;
(void)service;
threadData->sysevent.event = 0;
switch (messageType)
{
case kIOMessageCanSystemPowerOff:
case kIOMessageCanSystemSleep:
threadData->sysevent.event = SYSEVENT_CANSLEEP;
break;
case kIOMessageSystemWillRestart:
case kIOMessageSystemWillPowerOff:
case kIOMessageSystemWillSleep:
threadData->sysevent.event = SYSEVENT_WILLSLEEP;
break;
case kIOMessageSystemHasPoweredOn:
threadData->sysevent.event = SYSEVENT_WOKE;
break;
case kIOMessageSystemWillNotPowerOff:
case kIOMessageSystemWillNotSleep:
case kIOMessageSystemWillPowerOn:
default:
IOAllowPowerChange(threadData->sysevent.powerKernelPort, (long)messageArgument);
break;
}
if (threadData->sysevent.event)
{
threadData->sysevent.powerNotificationID = (long)messageArgument;
write((int)sysEventPipes[1], &threadData->sysevent, sizeof(threadData->sysevent));
}
}
static int clientEventUpdate()
{
int n, err = 0;
int client_fd;
fd_set client_fds ;
struct timeval client_timeout ;
struct sockaddr_un client_addr;
char client_buf[255];
if ((client_fd = accept(clientEventFd, (struct sockaddr *)&client_addr, &n)) < 0)
msg ( "W0client accept error %d - %s", (int)errno, strerror(errno));
else
{
client_timeout.tv_sec = 1;
client_timeout.tv_usec = 0 ;
FD_ZERO (&client_fds);
FD_SET(client_fd, &client_fds);
n = select ( client_fd + 1, &client_fds, 0, 0, &client_timeout);
if (n <= 0)
msg ( "W0client select error %d - %s", (int)errno, strerror(errno));
else
{
n = recv(client_fd, client_buf, sizeof(client_buf)-1, 0);
if (n < 0)
msg ( "W0client recv error %d - %s", (int)errno, strerror(errno));
else if (n > 0)
{
client_buf[n-1] = '\0';
switch (client_buf[0])
{
case 'a':
msg ( "l manual answer");
manual_answer = 1;
if (answer_wait)
err = -7;
break;
case 'c':
msg ( "l cancel session");
err = -8;
unlink(clientSocketName);
close(clientEventFd);
clientEventFd = -1;
close(client_fd);
#if defined(__APPLE__)
notify(CFSTR("disconnecting"), NULL);
#endif
exit ( cleanup ( 5 ) ) ;
break;
default:
msg ( "W unknown client command \"%s\"", client_buf);
break;
}
}
close(client_fd);
}
}
return err;
}
#endif