/* efaxos.c - O/S-dependent routines Copyright 1995, Ed Casas */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FD_SET #include /* for AIX */ #endif #include "efaxlib.h" #include "efaxmsg.h" #include "efaxos.h" #ifdef USE_TERMIO #include #include #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 #endif #ifdef TIOCSSOFTCAR #include #endif #ifdef __APPLE__ #include #include #include #include #include #include #include #include /* * Constants... */ #define SYSEVENT_CANSLEEP 0x1 /* Decide whether to allow sleep or not */ #define SYSEVENT_WILLSLEEP 0x2 /* Computer will go to sleep */ #define SYSEVENT_WOKE 0x4 /* Computer woke from sleep */ #define SYSEVENT_MODEMADDED 0x8 /* Modem was added */ #define SYSEVENT_MODEMREMOVED 0x10 /* Modem was removed */ /* * Structures... */ typedef struct threaddatastruct /*** Thread context data ****/ { sysevent_t sysevent; /* System event */ } threaddata_t; /* * Globals... */ static pthread_t sysEventThread = NULL; /* Thread to host a runloop */ static pthread_mutex_t sysEventThreadMutex = { 0 }; /* Coordinates access to shared gloabals */ static pthread_cond_t sysEventThreadCond = { 0 }; /* Thread initialization complete condition */ static CFRunLoopRef sysEventRunloop = NULL; /* The runloop. Access must be protected! */ static int sysEventPipes[2] = { -1, -1 }; /* Pipes for system event notifications */ sysevent_t sysevent; /* The system event */ static int clientEventFd = -1; /* Listening socket for client commands */ static const char clientSocketName[] = "/var/run/efax"; /* Listener's domain socket name */ /* * Prototypes... */ 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(); static void deviceNotifier(void *context, io_iterator_t iterator); #endif /* __APPLE__ */ 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__) /* Send Mac OS X notification */ void notify(CFStringRef status, CFTypeRef value) { CFMutableDictionaryRef notification; int i; static CFNumberRef pid = NULL; if (!pid) { i = getpid(); pid = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i); } notification = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(notification, CFSTR("pid"), pid); CFDictionaryAddValue(notification, CFSTR("status"), status); if (value) CFDictionaryAddValue(notification, CFSTR("value"), value); CFNotificationCenterPostNotificationWithOptions( CFNotificationCenterGetDistributedCenter(), status, CFSTR("com.apple.efax"), notification, kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions); CFRelease(notification); } #endif /* The milliseconds portion of the current time. If your system does not provide gettimeofday(3) you can safely substitute a dummy function that returns 0. This will cause the milliseconds portion of time stamps to be printed as 0. */ int time_ms ( void ) { struct timeval tv ; gettimeofday ( &tv, NULL ) ; return (int) ( tv.tv_usec / 1000 ) ; } /* Process elapsed time in milliseconds. This is used for ``virtual flow control'' only. */ 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 ; } /* Wait for t millisecond (for systems without usleep). */ 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:") ; } /* Return number of characters ready to read or < 0 on error. t is tenths of a second of idle time before timing out. If t is negative, waits forever. 1: characters ready to read 0: timeout -2: select() failed -6: preparing to sleep -7: woke / manual answer -8: modem detected / cancel session */ int tdata ( TFILE *f, int t ) { int n, err=0 ; fd_set fds ; int maxfd; struct timeval timeout ; static int modem_added = 0; if (modem_added && f->modem_wait) { modem_added = 0; return TDATA_MODEMADDED; } 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; /* timeout */ if (n < 0) { if (errno == EINTR) { if (f->signal) { int sig = f->signal; f->signal = 0; (f->onsig)(sig); } else msg("Wselect() interrupted in tdata()"); continue; } msg("Eselect() failed in tdata():"); err = TDATA_SELECTERR; break; } /* else n > 0 */ #ifdef __APPLE__ if (sysEventPipes[0] != -1 && FD_ISSET(sysEventPipes[0], &fds)) { err = sysEventMonitorUpdate(f); if (err == TDATA_MODEMADDED && !f->modem_wait) { /* If a modem was added but we're not interested just remember it for later... */ modem_added = 1; err = 0; } if (err) 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 ; } /* tundrflw is called only by the tgetc() macro when the buffer is empty. t is maximum idle time before giving up. Returns number of characters read or EOF on timeout or errors. */ 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 ; } /* tgetd returns the next data character after removing DLE escapes, DLE-ETX terminators and fixing bit order. Evaluates to the next character, EOF on error/timeout, or -2 on DLE-ETX. */ 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 { /* escape sequence */ 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 ; } /* Write buffer to modem. Returns 0 or EOF on error. */ int tput ( TFILE *f, const char *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 ; } /* Compare current termios state with termios struct t. Returns 0 if equal, 1 otherwise. */ 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 ; } /* Set serial port mode. Sets raw, 8-bit, 19.2 kbps mode with no flow control or as required. Break and parity errors are ignored. CLOCAL means DCD is ignored since some modems apparently drop it during the fax session. Flow control is only used when sending. Returns 0 or 2 on error. */ 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 ; ihwfc ? 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 ; } /* msg("lttymode: tcsetattr(%d, TCSADRAIN,...)", f->fd); */ 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 ) ; /* in case XON got lost */ return err ; } /* Initialize TFILE data structure. Bit ordering: serial devices transmit LS bit first. T.4/T.30 says MS bit is sent first. `Normal' order therefore reverses bit order. */ 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 () ; } /* Open a serial fax device as a TFILE. Returns 0 if OK, 1 if busy, 2 on error. */ 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 && errno == ENOENT) { /* * Wait for a device added notification... */ /* Let tdata() know we're interested in modem add events... */ f->modem_wait = 1; /* Allow idle sleep while we're waiting for a modem... */ waiting = 1; do { msg("Iwaiting for modem"); err = tdata(f, -1); if (err == TDATA_MODEMADDED) msg("Imodem detected..."); } while ((fd = open(fname, O_RDWR | O_NONBLOCK, 0)) < 0 && errno == ENOENT); /* We're no longer insterested in modem add or idle sleep events... */ f->modem_wait = 0; waiting = 0; } if (fd >= 0) { // clear the O_NONBLOCK flag on the port fcntl(fd, F_SETFL, 0); // Set exclusive open flag, returns EBUSY if somebody beat us to it. if ((err = ioctl(fd, TIOCEXCL, 0)) != 0) { close(fd); fd = -1; err = 1; } else { /* * Use a domain socket to receive commands from client applications. */ 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 ; } /* Close a serial fax device. Returns 0 if OK. */ int ttyclose ( TFILE *f ) { /* * Close the listener first so the next efax process can use the same domain socket... */ if ( clientEventFd != -1 ) { unlink(clientSocketName); close(clientEventFd); clientEventFd = -1; } if ( f->fd != -1 ) { close(f->fd); f->fd = -1; } return 0 ; } /* UUCP-style device locking using lock files */ /* Test for UUCP lock file & remove stale locks. Returns 0 on null file name or if no longer locked, 1 if locked by another pid, 2 on error, 3 if locked by us. */ 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 ; } /* Create UUCP (text or binary) lock file. Returns 0 on null file name or if created, 1 if locked by another pid, 2 on error, 3 if locked by us. */ 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 ) ; snprintf ( buf , sizeof(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 ; } /* Remove lock file. Returns 0 on null file name, doesn't exist, or was removed, 1 if the lock is to another pid, 2 on errors. */ 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 ; } /* Lock all lock files and possibly log attempt if log=1. Returns 0 if all locks [already] applied, 1 if any are locked to other pids, 2 on any errors. */ int lockall ( TFILE *f, char **lkfiles, int log ) { int err = 0 ; char **p = lkfiles ; #if defined(__APPLE__) msg("llockall: disallow premption (fd %d)", f->fd); if (f->fd > 0) { int allowPremption = 0; if ((err = ioctl(f->fd, IOSSPREEMPT, &allowPremption)) != 0) err = 1; } #endif /* __APPLE__ */ while ( *p && ! err ) if ( ( err = ttlock ( *p++, log ) ) == 3 ) err = 0 ; return err ; } /* Remove all lock files. Returns 0 if all locks removed, 2 on errors. */ 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; msg("llockall: allow premption (fd %d)", f->fd); ioctl(f->fd, IOSSPREEMPT, &allowPremption); #endif /* __APPLE__ */ return err ; } /* Return basename of the argument or the whole thing if can't find it. */ char *efaxbasename ( char *p ) { return strrchr ( p , '/' ) ? strrchr ( p , '/' ) + 1 : p ; } #ifdef __APPLE__ /* * 'sysEventMonitorStart()' - Start system event notifications */ void sysEventMonitorStart(void) { int flags; pipe(sysEventPipes); /* * Set non-blocking mode on the descriptor we will be receiving notification events on. */ flags = fcntl(sysEventPipes[0], F_GETFL, 0); fcntl(sysEventPipes[0], F_SETFL, flags | O_NONBLOCK); /* * Start the thread that runs the runloop... */ pthread_mutex_init(&sysEventThreadMutex, NULL); pthread_cond_init(&sysEventThreadCond, NULL); pthread_create(&sysEventThread, NULL, sysEventThreadEntry, NULL); } /* * 'sysEventMonitorStop()' - Stop system event notifications */ void sysEventMonitorStop(void) { CFRunLoopRef rl; /* The runloop */ if (sysEventThread) { /* * Make sure the thread has completed it's initialization and * stored it's runloop reference in the shared global. */ 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; } } /* * 'sysEventMonitorUpdate()' - Handle power & network system events. * * Returns non-zero if a higher level event needs to be handeled. */ static int sysEventMonitorUpdate(TFILE *f) { int err = 0; /* * Drain the event pipe... */ if (read((int)sysEventPipes[0], &sysevent, sizeof(sysevent)) == sizeof(sysevent)) { if ((sysevent.event & SYSEVENT_CANSLEEP)) { /* * If we're waiting for the phone to ring allow the idle sleep, otherwise * block it so we can finish the current session. */ 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 we're waiting return an error so answer can reset the modem and close the port, * otherwise cancel the current session right here. */ if (waiting) { msg("Ipreparing to sleep..."); err = TDATA_SLEEP; } else { msg("Iterminating to sleep or shutdown..."); cleanup(6); IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID); exit(6); } } if ((sysevent.event & SYSEVENT_WOKE)) { IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID); if (waiting) err = TDATA_WAKE; } if ((sysevent.event & SYSEVENT_MODEMADDED)) err = TDATA_MODEMADDED; if ((sysevent.event & SYSEVENT_MODEMREMOVED)) { msg("Imodem removed..."); cleanup(4); exit(4); } } return err; } /* * 'sysEventThreadEntry()' - A thread to run a runloop on. * Receives power & network change notifications. */ static void *sysEventThreadEntry() { io_object_t powerNotifierObj; /* Power notifier object */ IONotificationPortRef powerNotifierPort; /* Power notifier port */ CFRunLoopSourceRef powerRLS = NULL; /* Power runloop source */ threaddata_t threadData; /* Thread context data for the runloop notifiers */ IONotificationPortRef addNotification, /* Add notification port */ removeNotification; /* Remove notification port */ io_iterator_t addIterator, /* Add iterator */ removeIterator; /* Remove iterator */ mach_port_t masterPort; /* Master port */ kern_return_t kr; /* Kernel error */ CFMutableDictionaryRef classesToMatch; /* Dictionary to match */ static const sysevent_t sysevent_modemadded = { SYSEVENT_MODEMADDED }; /* Modem added event */ static const sysevent_t sysevent_modemremoved = { SYSEVENT_MODEMREMOVED }; /* Modem removed event */ bzero(&threadData, sizeof(threadData)); addNotification = \ removeNotification = NULL; addIterator = \ removeIterator = IO_OBJECT_NULL; /* * Register for power state change notifications. */ threadData.sysevent.powerKernelPort = IORegisterForSystemPower(&threadData, &powerNotifierPort, sysEventPowerNotifier, &powerNotifierObj); if (threadData.sysevent.powerKernelPort) { powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort); CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode); } /* * Register for IOKit serial device added & removed notifications. */ kr = IOMasterPort(bootstrap_port, &masterPort); if (kr == kIOReturnSuccess && masterPort != MACH_PORT_NULL) { if ((classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue)) != NULL) { CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType)); /* * Each IOServiceAddMatchingNotification() call consumes a dictionary reference * so retain it for the second call below. */ CFRetain(classesToMatch); removeNotification = IONotificationPortCreate(masterPort); kr = IOServiceAddMatchingNotification( removeNotification, kIOTerminatedNotification, classesToMatch, &deviceNotifier, (void*)&sysevent_modemremoved, &removeIterator); if (kr == kIOReturnSuccess && removeIterator != IO_OBJECT_NULL) { deviceNotifier((void*)&sysevent_modemremoved, removeIterator); CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(removeNotification), kCFRunLoopDefaultMode); } addNotification = IONotificationPortCreate(masterPort); kr = IOServiceAddMatchingNotification(addNotification, kIOMatchedNotification, classesToMatch, &deviceNotifier, (void*)&sysevent_modemadded, &addIterator); if (kr == kIOReturnSuccess && addIterator != IO_OBJECT_NULL) { deviceNotifier((void*)&sysevent_modemadded, addIterator); CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(addNotification), kCFRunLoopDefaultMode); } } mach_port_deallocate(mach_task_self(), masterPort); } /* * Store our runloop in a global so the main thread can * use it to stop us. */ pthread_mutex_lock(&sysEventThreadMutex); sysEventRunloop = CFRunLoopGetCurrent(); pthread_cond_signal(&sysEventThreadCond); pthread_mutex_unlock(&sysEventThreadMutex); /* * Disappear into the runloop until it's stopped by the main thread. */ CFRunLoopRun(); /* * Clean up before exiting. */ if (addIterator != IO_OBJECT_NULL) IOObjectRelease(addIterator); if (addNotification != NULL) IONotificationPortDestroy(addNotification); if (removeIterator != IO_OBJECT_NULL) IOObjectRelease(removeIterator); if (removeNotification != NULL) IONotificationPortDestroy(removeNotification); if (powerRLS) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode); CFRunLoopSourceInvalidate(powerRLS); } if (threadData.sysevent.powerKernelPort) IODeregisterForSystemPower(&powerNotifierObj); pthread_exit(NULL); } /* * 'sysEventPowerNotifier()' - . */ static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument) { threaddata_t *threadData = (threaddata_t *)context; /* Thread context data */ (void)service; /* anti-compiler-warning-code */ 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) { /* * Send the event to the main thread. */ threadData->sysevent.powerNotificationID = (long)messageArgument; write((int)sysEventPipes[1], &threadData->sysevent, sizeof(threadData->sysevent)); } } /* * 'clientEventUpdate()' - Read process a command from an incoming client connection. */ static int clientEventUpdate() { int n, err = 0; int client_fd; fd_set client_fds ; struct timeval client_timeout ; struct sockaddr_un client_addr; socklen_t addrlen; char client_buf[255]; /* * Accept the incomming connection request... */ if ((client_fd = accept(clientEventFd, (struct sockaddr *)&client_addr, &addrlen)) < 0) msg ( "W0client accept error %d - %s", (int)errno, strerror(errno)); else { /* * Give the client 1 second to send us a command... */ 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 > 0) */ { 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': /* Manual answer... */ msg ( "l manual answer"); manual_answer = 1; if (answer_wait) /* Only return an error if we're waiting for activity... */ err = TDATA_MANANSWER; break; case 'c': /* Cancel current session... */ msg ( "l cancel session"); err = TDATA_CANCEL; /* * Close the listen socket so we're not interupped while cleaning up... */ unlink(clientSocketName); close(clientEventFd); clientEventFd = -1; close(client_fd); #if defined(__APPLE__) notify(CFSTR("disconnecting"), NULL); #endif exit ( cleanup ( 7 ) ) ; break; /* anti-compiler warning */ default: msg ( "W unknown client command \"%s\"", client_buf); break; } } close(client_fd); } } return err; } /* * 'deviceNotifier()' - Called when a serial or modem device is added or removed. */ static void deviceNotifier(void *context, io_iterator_t iterator) { int matched = false; /* Matched the right device? */ io_service_t obj; /* IOKit object */ CFTypeRef cfstr; /* CFString */ char bsdpath[PATH_MAX + 1]; /* BSD path ("/dev/") */ /* * Iterate over the devices looking for one that matches the modem to use * (always drain the iterator so we get future notifications). */ while ((obj = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { if (!matched && (cfstr = IORegistryEntryCreateCFProperty(obj, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0))) { CFStringGetCString(cfstr, bsdpath, sizeof(bsdpath), kCFStringEncodingUTF8); CFRelease(cfstr); matched = strcmp(bsdpath, faxfile) == 0; } if (!matched && (cfstr = IORegistryEntryCreateCFProperty(obj, CFSTR(kIODialinDeviceKey), kCFAllocatorDefault, 0))) { CFStringGetCString(cfstr, bsdpath, sizeof(bsdpath), kCFStringEncodingUTF8); CFRelease(cfstr); matched = strcmp(bsdpath, faxfile) == 0; } IOObjectRelease(obj); } /* * If we matched send the event to the main thread. */ if (matched) write((int)sysEventPipes[1], (sysevent_t *)context, sizeof(sysevent)); } #endif /* __APPLE__ */