1/*
2		efaxos.c - O/S-dependent routines
3		    Copyright 1995, Ed Casas
4*/
5
6#include <errno.h>
7#include <fcntl.h>
8#include <signal.h>
9#include <stdio.h>
10#include <string.h>
11#include <sys/types.h>
12#include <sys/stat.h>
13#include <sys/time.h>
14#include <sys/times.h>
15#include <sys/socket.h>
16#include <sys/un.h>
17#include <unistd.h>
18
19#ifndef FD_SET
20#include <sys/select.h>		/* for AIX */
21#endif
22
23#include "efaxlib.h"
24#include "efaxmsg.h"
25#include "efaxos.h"
26
27#ifdef USE_TERMIO
28#include <termio.h>
29#include <sys/ioctl.h>
30#define termios termio
31#define tcgetattr(fd, pt) ioctl(fd, TCGETA, pt)
32#define tcsetattr(fd, x, pt) ioctl(fd, TCSETAW, pt)
33#define cfsetospeed(pt, b) ((pt)->c_cflag = ((pt)->c_cflag & ~CBAUD) | b)
34#define cfsetispeed(pt, b)
35#define tcdrain(fd)
36#else
37#include <termios.h>
38#endif
39
40#ifdef TIOCSSOFTCAR
41#include <sys/ioctl.h>
42#endif
43
44#ifdef __APPLE__
45#include <sys/ioctl.h>
46#include <mach/mach_port.h>
47#include <IOKit/IOKitLib.h>
48#include <IOKit/IOMessage.h>
49#include <IOKit/serial/IOSerialKeys.h>
50#include <IOKit/serial/ioss.h>
51#include <IOKit/usb/IOUSBLib.h>
52#include <pthread.h>
53
54/*
55 * Constants...
56 */
57
58#define SYSEVENT_CANSLEEP	0x1	/* Decide whether to allow sleep or not */
59#define SYSEVENT_WILLSLEEP	0x2	/* Computer will go to sleep */
60#define SYSEVENT_WOKE		0x4	/* Computer woke from sleep */
61#define SYSEVENT_MODEMADDED	0x8	/* Modem was added */
62#define SYSEVENT_MODEMREMOVED	0x10	/* Modem was removed */
63
64
65/*
66 * Structures...
67 */
68
69typedef struct threaddatastruct		/*** Thread context data  ****/
70{
71  sysevent_t		sysevent;	/* System event */
72} threaddata_t;
73
74
75/*
76 * Globals...
77 */
78
79static pthread_t	sysEventThread = NULL;		/* Thread to host a runloop */
80static pthread_mutex_t	sysEventThreadMutex = { 0 };	/* Coordinates access to shared gloabals */
81static pthread_cond_t	sysEventThreadCond = { 0 };	/* Thread initialization complete condition */
82static CFRunLoopRef	sysEventRunloop = NULL;		/* The runloop. Access must be protected! */
83static int		sysEventPipes[2] = { -1, -1 };	/* Pipes for system event notifications */
84sysevent_t		sysevent;			/* The system event */
85
86static int		clientEventFd = -1;		/* Listening socket for client commands */
87static const char	clientSocketName[] = "/var/run/efax";
88							/* Listener's domain socket name */
89
90/*
91 * Prototypes...
92 */
93
94static int  sysEventMonitorUpdate(TFILE *f);
95static void *sysEventThreadEntry();
96static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument);
97static int clientEventUpdate();
98static void deviceNotifier(void *context, io_iterator_t iterator);
99
100#endif	/* __APPLE__ */
101
102static int  ttlock ( char *fname, int log );
103static int  ckfld ( char *field, int set, int get );
104static int  checktermio ( struct termios *t, TFILE *f );
105static void tinit ( TFILE *f, int fd, int reverse, int hwfc );
106static int  ttlocked ( char *fname, int log );
107static int  ttunlock ( char *fname );
108
109
110#ifndef CRTSCTS
111#define CRTSCTS 0
112#endif
113
114
115#if defined(__APPLE__)
116/* Send Mac OS X notification */
117void notify(CFStringRef status, CFTypeRef value)
118{
119  CFMutableDictionaryRef notification;
120  int i;
121  static CFNumberRef pid = NULL;
122
123  if (!pid)
124  {
125    i = getpid();
126    pid = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i);
127  }
128
129  notification = CFDictionaryCreateMutable(kCFAllocatorDefault, 3,
130				&kCFTypeDictionaryKeyCallBacks,
131				&kCFTypeDictionaryValueCallBacks);
132
133  CFDictionaryAddValue(notification, CFSTR("pid"), pid);
134  CFDictionaryAddValue(notification, CFSTR("status"), status);
135
136  if (value)
137    CFDictionaryAddValue(notification, CFSTR("value"), value);
138
139  CFNotificationCenterPostNotificationWithOptions(
140	CFNotificationCenterGetDistributedCenter(),
141	status,
142	CFSTR("com.apple.efax"),
143	notification, kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions);
144
145  CFRelease(notification);
146}
147#endif
148
149/* The milliseconds portion of the current time.  If your system
150   does not provide gettimeofday(3) you can safely substitute a
151   dummy function that returns 0.  This will cause the
152   milliseconds portion of time stamps to be printed as 0. */
153
154int time_ms ( void )
155{
156  struct timeval tv ;
157  gettimeofday ( &tv, NULL ) ;
158  return (int) ( tv.tv_usec / 1000 ) ;
159}
160
161
162/* Process elapsed time in milliseconds.  This is used for
163   ``virtual flow control'' only. */
164
165long proc_ms ( void )
166{
167  struct timeval t ;
168  static int init=0 ;
169  static struct timeval s ;
170  if ( ! init ) {
171    gettimeofday ( &s, 0 ) ;
172    init = 1 ;
173  }
174  gettimeofday ( &t, 0 ) ;
175  return ( t.tv_sec - s.tv_sec ) * 1000 + ( t.tv_usec - s.tv_usec ) / 1000 ;
176}
177
178
179/* Wait for t millisecond (for systems without usleep). */
180
181void msleep ( int t )
182{
183  struct timeval timeout ;
184  timeout.tv_sec  = t / 1000 ;
185  timeout.tv_usec = ( t % 1000 ) * 1000 ;
186  if ( select ( 1 , 0 , 0 , 0 , &timeout ) < 0 )
187    msg ("ES2select failed in msleep:") ;
188}
189
190
191/* Return number of characters ready to read or < 0 on error.  t
192   is tenths of a second of idle time before timing out.  If t is
193   negative, waits forever.
194
195    1: characters ready to read
196    0: timeout
197   -2: select() failed
198   -6: preparing to sleep
199   -7: woke / manual answer
200   -8: modem detected / cancel session
201 */
202
203int tdata ( TFILE *f, int t )
204{
205  int n, err=0 ;
206  fd_set fds ;
207  int maxfd;
208  struct timeval timeout ;
209  static int modem_added = 0;
210
211  if (modem_added && f->modem_wait)
212  {
213    modem_added = 0;
214    return TDATA_MODEMADDED;
215  }
216
217  timeout.tv_sec  = t / 10 ;
218  timeout.tv_usec = ( t % 10 ) * 100000 ;
219
220  FD_ZERO (&fds);
221
222  if (f->fd != -1)
223    FD_SET(f->fd, &fds);
224
225  maxfd = f->fd;
226
227#ifdef __APPLE__
228  if ( sysEventPipes[0] != -1)
229  {
230    FD_SET(sysEventPipes[0], &fds);
231
232    if (sysEventPipes[0] > maxfd)
233      maxfd = sysEventPipes[0];
234  }
235
236  if ( clientEventFd != -1)
237  {
238    FD_SET(clientEventFd, &fds);
239
240    if (clientEventFd > maxfd)
241      maxfd = clientEventFd;
242  }
243#endif
244
245  for (;;)
246  {
247    n = select ( maxfd + 1, &fds, 0, 0, t<0 ? 0 : &timeout ) ;
248
249    if (n == 0)
250      break;		/* timeout */
251
252    if (n < 0)
253    {
254      if (errno == EINTR)
255      {
256        if (f->signal)
257        {
258          int sig = f->signal;
259          f->signal = 0;
260
261          (f->onsig)(sig);
262        }
263        else
264	  msg("Wselect() interrupted in tdata()");
265	continue;
266      }
267
268      msg("Eselect() failed in tdata():");
269      err = TDATA_SELECTERR;
270      break;
271    }
272
273    /* else n > 0 */
274
275#ifdef __APPLE__
276    if (sysEventPipes[0] != -1 && FD_ISSET(sysEventPipes[0], &fds))
277    {
278      err = sysEventMonitorUpdate(f);
279
280      if (err == TDATA_MODEMADDED && !f->modem_wait)
281      {
282        /* If a modem was added but we're not interested just remember it for later... */
283	modem_added = 1;
284	err = 0;
285      }
286
287      if (err)
288	break;
289    }
290
291    if (clientEventFd != -1 && FD_ISSET(clientEventFd, &fds))
292    {
293      if ((err = clientEventUpdate()) != 0)
294	break;
295    }
296#endif
297
298    if (f->fd != -1 && FD_ISSET(f->fd, &fds))
299    {
300      err = 1;
301      break;
302    }
303  }
304
305  return err ;
306}
307
308
309/* tundrflw is called only by the tgetc() macro when the buffer
310   is empty.  t is maximum idle time before giving up. Returns
311   number of characters read or EOF on timeout or errors.  */
312
313int tundrflw ( TFILE *f, int t )
314{
315  int n ;
316
317  n = tdata ( f, t ) ;
318
319  if ( n > 0 )
320    if ( ( n = read( f->fd, f->ibuf, IBUFSIZE ) ) < 0 )
321      msg ( "ES2fax device read:" ) ;
322
323  f->iq = ( f->ip = f->ibuf ) + ( n > 0 ? n : 0 ) ;
324
325  return n > 0 ? n : EOF ;
326}
327
328
329/* tgetd returns the next data character after removing DLE
330   escapes, DLE-ETX terminators and fixing bit order. Evaluates
331   to the next character, EOF on error/timeout, or -2 on DLE-ETX.  */
332
333int tgetd ( TFILE *f, int t )
334{
335  int c ;
336
337  if ( ( c = tgetc(f,t) ) < 0 )
338    c = EOF ;
339  else
340    if ( c != DLE )
341      c = f->ibitorder[c] ;
342    else {			/* escape sequence */
343      c = tgetc(f,t) ;
344      if ( c == ETX )
345	c = -2 ;
346      else
347	if ( c == DLE || c == SUB )
348	  c = f->ibitorder [ DLE ] ;
349	else
350	  c = msg ( "W0invalid escape sequence (DLE-%s) in data", cname(c) ) ;
351    }
352
353  return c ;
354}
355
356/* Write buffer to modem.  Returns 0 or EOF on error. */
357
358int tput ( TFILE *f, const char *p, int n )
359{
360  int m=0 ;
361
362  while ( n > 0 && ( m = write( f->fd, p, n ) ) > 0 ) {
363    if ( m != n )
364      msg ( "Wonly wrote %d of %d bytes", m, n ) ;
365    n -= m ;
366    p += m ;
367  }
368
369  if ( m < 0 )
370    msg ( "ES2fax device write:" ) ;
371
372  return m ;
373}
374
375
376/* Compare current termios state with termios struct t. Returns 0 if equal,
377   1 otherwise. */
378
379static int ckfld ( char *field, int set, int get )
380{
381  return set == get ?
382    0 : msg ( "W1 termios.%s is 0%08o, not 0%08o", field, get, set ) ;
383}
384
385static int checktermio ( struct termios *t, TFILE *f )
386{
387  struct termios s ;
388  int err=0 ;
389  s.c_iflag=s.c_oflag=s.c_lflag=s.c_cflag=s.c_cc[VMIN]=s.c_cc[VTIME]=0 ;
390  if ( tcgetattr ( f->fd , &s ) )
391    err = msg ("ES2tcgetattr failed:") ;
392
393 if ( ! err ) return
394   ckfld ( "iflag" , t->c_iflag, s.c_iflag ) ||
395     ckfld ( "oflag" , t->c_oflag , s.c_oflag ) ||
396       ckfld ( "lflag" , t->c_lflag , s.c_lflag ) ||
397	 ckfld ( "cflag" , t->c_cflag & 0xFFFF , s.c_cflag & 0xFFFF) ||
398	   ckfld ( "START" , t->c_cc[VSTART] , s.c_cc[VSTART] ) ||
399	     ckfld ( "STOP" , t->c_cc[VSTOP] , s.c_cc[VSTOP] ) ||
400	       ckfld ( "MIN" , t->c_cc[VMIN] , s.c_cc[VMIN] ) ||
401		 ckfld ( "TIME" , t->c_cc[VTIME] , s.c_cc[VTIME] ) ;
402  return err ;
403}
404
405
406/* Set serial port mode. Sets raw, 8-bit, 19.2 kbps mode with no
407   flow control or as required.  Break and parity errors are
408   ignored.  CLOCAL means DCD is ignored since some modems
409   apparently drop it during the fax session.  Flow control is
410   only used when sending.  Returns 0 or 2 on error. */
411
412int ttymode ( TFILE *f, enum ttymodes mode )
413{
414  int err=0, i ;
415  static struct termios t, oldt, *pt ;
416  static int saved=0 ;
417
418  if ( ! saved ) {
419    if ( tcgetattr ( f->fd, &oldt ) )
420      err = msg ( "ES2tcgetattr on fd=%d failed:", f->fd ) ;
421    else
422      saved=1 ;
423  }
424
425  t.c_iflag = IGNBRK | IGNPAR ;
426  t.c_oflag = 0 ;
427  t.c_cflag = CS8 | CREAD | CLOCAL ;
428  t.c_lflag = 0 ;
429
430  for ( i=0 ; i<NCCS ; i++ ) t.c_cc[i] = 0 ;
431
432  t.c_cc[VMIN]  = 1 ;
433  t.c_cc[VTIME] = 0 ;
434  t.c_cc[VSTOP] = XOFF;
435  t.c_cc[VSTART] = XON;
436
437  pt = &t ;
438
439  switch ( mode ) {
440  case VOICESEND :
441    t.c_iflag |= IXON ;
442    t.c_cflag |= f->hwfc ? CRTSCTS : 0 ;
443  case VOICECOMMAND :
444    cfsetospeed ( pt, B38400 ) ;
445    cfsetispeed ( pt, B38400 ) ;
446    break ;
447  case SEND :
448    t.c_iflag |= IXON ;
449    t.c_cflag |= f->hwfc ? CRTSCTS : 0 ;
450  case COMMAND :
451    cfsetospeed ( pt, B19200 ) ;
452    cfsetispeed ( pt, B19200 ) ;
453    break ;
454  case DROPDTR :
455    cfsetospeed ( pt, B0 ) ;
456    break ;
457  case ORIGINAL :
458    if ( saved ) pt = &oldt ;
459    break ;
460  default :
461    err = msg ("E2can't happen(ttymode)") ;
462    break ;
463  }
464
465  /* msg("lttymode: tcsetattr(%d, TCSADRAIN,...)", f->fd); */
466
467  if ( ! err && tcsetattr ( f->fd, TCSADRAIN, pt ) )
468    err = msg ( "ES2tcsetattr on fd=%d failed:", f->fd ) ;
469
470  if ( ! err && checktermio ( pt, f ) )
471    msg ( "Wterminal mode not set properly" ) ;
472
473  tcflow ( f->fd, TCOON ) ;	/* in case XON got lost */
474
475  return err ;
476}
477
478
479/* Initialize TFILE data structure. Bit ordering: serial devices
480   transmit LS bit first.  T.4/T.30 says MS bit is sent
481   first. `Normal' order therefore reverses bit order.  */
482
483static void tinit ( TFILE *f, int fd, int reverse, int hwfc )
484{
485  f->ip = f->iq = f->ibuf ;
486  f->obitorder = normalbits ;
487  f->ibitorder = reverse ? reversebits : normalbits ;
488  f->fd = fd ;
489  f->hwfc = hwfc ;
490  if ( ! normalbits[1] ) initbittab () ;
491}
492
493
494/* Open a serial fax device as a TFILE.  Returns 0 if OK, 1 if
495   busy, 2 on error. */
496
497int ttyopen ( TFILE *f, char *fname, int reverse, int hwfc )
498{
499  int flags, err=0 ;
500
501#if defined(__APPLE__)
502  int fd;
503
504  if ((fd = open(fname, O_RDWR | O_NONBLOCK, 0)) < 0 && errno == ENOENT)
505  {
506   /*
507    * Wait for a device added notification...
508    */
509
510    /* Let tdata() know we're interested in modem add events... */
511    f->modem_wait = 1;
512
513    /* Allow idle sleep while we're waiting for a modem... */
514    waiting = 1;
515
516    do
517    {
518      msg("Iwaiting for modem");
519
520      err = tdata(f, -1);
521
522      if (err == TDATA_MODEMADDED)
523	msg("Imodem detected...");
524
525    } while ((fd = open(fname, O_RDWR | O_NONBLOCK, 0)) < 0 && errno == ENOENT);
526
527    /* We're no longer insterested in modem add or idle sleep events... */
528    f->modem_wait = 0;
529    waiting = 0;
530  }
531
532  if (fd >= 0)
533  {
534    // clear the O_NONBLOCK flag on the port
535    fcntl(fd, F_SETFL, 0);
536
537    // Set exclusive open flag, returns EBUSY if somebody beat us to it.
538    if ((err = ioctl(fd, TIOCEXCL, 0)) != 0)
539    {
540      close(fd);
541      fd = -1;
542      err = 1;
543    }
544    else
545    {
546      /*
547       * Use a domain socket to receive commands from client applications.
548       */
549
550      if ((clientEventFd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
551	msg ( "W socket returned error %d - %s", (int)errno, strerror(errno));
552      else
553      {
554	struct sockaddr_un laddr;
555	mode_t mask;
556
557	bzero(&laddr, sizeof(laddr));
558	laddr.sun_family = AF_LOCAL;
559	strlcpy(laddr.sun_path, clientSocketName, sizeof(laddr.sun_path));
560	unlink(laddr.sun_path);
561	mask = umask(0);
562	err = bind(clientEventFd, (struct sockaddr *)&laddr, SUN_LEN(&laddr));
563	umask(mask);
564
565	if (err < 0)
566	{
567	  msg ( "W bind returned error %d - %s", (int)errno, strerror(errno));
568	  close(clientEventFd);
569	  clientEventFd = -1;
570	}
571	else
572	{
573	  if (listen(clientEventFd, 2) < 0)
574	  {
575	    msg ( "W listen returned error %d - %s(%d)", (int)errno, strerror(errno));
576	    unlink(clientSocketName);
577	    close(clientEventFd);
578	    clientEventFd = -1;
579	  }
580	}
581      }
582      err = 0;
583    }
584  }
585
586  tinit ( f, fd, reverse, hwfc ) ;
587
588#else
589  tinit ( f, open ( fname, O_RDWR | O_NDELAY | O_NOCTTY ), reverse, hwfc ) ;
590#endif
591  if ( f->fd < 0 ) {
592    if ( errno == EBUSY ) {
593      err = 1 ;
594    } else {
595      err = msg ( "ES2can't open serial port %s:", fname ) ;
596    }
597  }
598
599  if ( ! err ) {
600    if ( ( flags = fcntl( f->fd, F_GETFL, 0 ) ) < 0 ||
601	fcntl( f->fd, F_SETFL, ( flags & ~O_NDELAY ) ) < 0 )
602      err = msg ( "ES2fax device fcntl failed %s:", fname ) ;
603  }
604
605#ifdef TIOCSSOFTCAR
606  {
607    int arg = 1 ;
608    if ( ! err )
609      if ( ioctl ( f->fd, TIOCSSOFTCAR, &arg ) )
610	msg ("WS unable to set software carrier:" ) ;
611  }
612#endif
613
614  return err ;
615}
616
617/* Close a serial fax device.  Returns 0 if OK. */
618
619int ttyclose ( TFILE *f )
620{
621  /*
622   * Close the listener first so the next efax process can use the same domain socket...
623   */
624
625  if ( clientEventFd != -1 ) {
626    unlink(clientSocketName);
627    close(clientEventFd);
628    clientEventFd = -1;
629  }
630
631  if ( f->fd != -1 ) {
632    close(f->fd);
633    f->fd = -1;
634  }
635
636  return 0 ;
637}
638
639	/* UUCP-style device locking using lock files */
640
641/* Test for UUCP lock file & remove stale locks. Returns 0 on null file
642   name or if no longer locked, 1 if locked by another pid, 2 on error, 3
643   if locked by us. */
644
645static int ttlocked ( char *fname, int log )
646{
647  int err=0, ipid ;
648  FILE *f ;
649  pid_t pid = 0 ;
650  char buf [ EFAX_PATH_MAX ] = "" ;
651
652  if ( fname && *fname == BINLKFLAG ) fname++ ;
653
654  if ( fname && ( f = fopen ( fname , "r" ) ) ) {
655
656    if ( fread ( buf, sizeof(char), EFAX_PATH_MAX-1, f )  == sizeof(pid_t) ||
657	sscanf ( buf, "%d" , &ipid ) != 1 ) {
658      pid = * (pid_t *) buf ;
659      if ( log ) msg ("X+ read binary pid %d from %s", (int) pid, fname ) ;
660    } else {
661      char *p ;
662      pid = (pid_t) ipid ;
663      if ( log ) {
664	msg ( "X+ read HDB pid %d [",  (int) pid ) ;
665	for ( p=buf ; *p ; p++ ) msg ( "X+ %s", cname ( *p ) ) ;
666	msg ( "X+ ] from %s", fname ) ;
667      }
668    }
669
670    if ( kill ( pid, 0 ) && errno == ESRCH ) {
671      if ( log ) msg ("X  - stale" ) ;
672      if ( remove ( fname ) )
673	err = msg ( "ES2can't remove stale lock %s from pid %d:",
674		   fname, pid ) ;
675      else
676	err = msg ( "I0removed stale lock %s from pid %d", fname, pid ) ;
677    } else {
678      if ( pid != getpid() ) {
679	err = 1 ;
680	if ( log ) msg ( "X1  (not our pid)" ) ;
681      } else {
682	err = 3 ;
683	if ( log ) msg ( "X3  (our pid)" ) ;
684      }
685    }
686    fclose ( f ) ;
687  }
688  return err ;
689}
690
691
692/* Create UUCP (text or binary) lock file.  Returns 0 on null
693   file name or if created, 1 if locked by another pid, 2 on
694   error, 3 if locked by us. */
695
696static int ttlock ( char *fname, int log )
697{
698  int err=0, dirlen, bin=0 ;
699  FILE *f=0 ;
700  pid_t pid = getpid ( ) ;
701  char *p , buf [ EFAX_PATH_MAX ] = "" ;
702
703  if ( fname && *fname == BINLKFLAG ) {
704    fname++ ;
705    bin = 1 ;
706  }
707
708  err = ttlocked ( fname, log ) ;
709
710  if ( ! err ) {
711    dirlen = ( p = strrchr( fname , '/' ) ) ? p-fname+1 : strlen ( fname ) ;
712    snprintf ( buf , sizeof(buf), "%.*sTMP..%05d" , dirlen , fname , (int) pid ) ;
713    if ( ! ( f = fopen( buf, "w" ) ) )
714      err = msg ( "ES2can't open pre-lock file %s:", buf ) ;
715  }
716
717  if ( ! err && f ) {
718    if ( bin ) {
719      if ( fwrite ( &pid, sizeof(pid_t), 1, f ) < 1 )
720	err = msg ( "ES2can't write pre-lock file %s:", buf ) ;
721    } else {
722      if ( fprintf ( f, "%10d", (int) pid ) < 0 )
723	err = msg ( "ES2can't write pre-lock file %s:", buf ) ;
724    }
725  }
726
727  if ( ! err && f ) {
728    if ( rename ( buf , fname ) == 0 ) {
729      chmod ( fname , 0444 ) ;
730      msg ( "Xcreated %s lock file %s", bin ? "binary" : "text", fname ) ;
731    } else {
732      err = ttlocked ( fname, log ) ;
733      if ( ! err )
734	err = msg ( "ES2can't rename lock file %s to %s:", buf, fname ) ;
735    }
736  }
737
738  if ( f ) {
739    fclose ( f ) ;
740    if ( err ) remove ( buf ) ;
741  }
742
743  return err ;
744}
745
746
747/* Remove lock file.  Returns 0 on null file name, doesn't exist, or was
748   removed, 1 if the lock is to another pid, 2 on errors. */
749
750static int ttunlock ( char *fname )
751{
752  int err = 0 ;
753
754  if ( fname && *fname == BINLKFLAG ) fname++ ;
755
756  switch ( ttlocked ( fname, 1 ) ) {
757  case 0: break ;
758  case 1: err = msg ( "E1won't remove lock %s (not ours)" , fname ) ; break ;
759  case 2: err = 2 ; break ;
760  case 3:
761    if ( remove ( fname ) ) {
762      err = msg ( "ES2can't remove lock %s:", fname ) ;
763    } else {
764      err = msg ( "X0removed lock file %s", fname ) ;
765    }
766    break ;
767  default:
768    err = msg ( "E2can't happen (ttunlock)" ) ;
769    break ;
770  }
771  return err ;
772}
773
774
775/* Lock all lock files and possibly log attempt if log=1.
776   Returns 0 if all locks [already] applied, 1 if any are locked
777   to other pids, 2 on any errors. */
778
779int lockall ( TFILE *f, char **lkfiles, int log )
780{
781  int err = 0 ;
782  char **p = lkfiles ;
783
784#if defined(__APPLE__)
785  msg("llockall: disallow premption (fd %d)", f->fd);
786  if (f->fd > 0)
787  {
788    int allowPremption = 0;
789    if ((err = ioctl(f->fd, IOSSPREEMPT, &allowPremption)) != 0)
790      err = 1;
791  }
792
793#endif	/* __APPLE__ */
794
795  while ( *p && ! err )
796    if ( ( err = ttlock ( *p++, log ) ) == 3 ) err = 0 ;
797  return err ;
798}
799
800
801/* Remove all lock files.  Returns 0 if all locks removed, 2 on
802   errors. */
803
804int unlockall (TFILE *f, char **lkfiles )
805{
806  int err = 0, i ;
807
808  char **p = lkfiles ;
809  while ( *p )
810    if ( ( i = ttunlock ( *p++ ) ) != 0 ) err = i ;
811
812#if defined(__APPLE__)
813  int allowPremption = 1;
814  msg("llockall: allow premption (fd %d)", f->fd);
815  ioctl(f->fd, IOSSPREEMPT, &allowPremption);
816#endif	/* __APPLE__ */
817
818  return err ;
819}
820
821/* Return basename of the argument or the whole thing if can't
822   find it. */
823
824char *efaxbasename ( char *p )
825{
826  return strrchr ( p , '/' ) ? strrchr ( p , '/' ) + 1 : p ;
827}
828
829
830#ifdef __APPLE__
831
832/*
833 * 'sysEventMonitorStart()' - Start system event notifications
834 */
835
836void sysEventMonitorStart(void)
837{
838  int flags;
839
840  pipe(sysEventPipes);
841
842 /*
843  * Set non-blocking mode on the descriptor we will be receiving notification events on.
844  */
845
846  flags = fcntl(sysEventPipes[0], F_GETFL, 0);
847  fcntl(sysEventPipes[0], F_SETFL, flags | O_NONBLOCK);
848
849 /*
850  * Start the thread that runs the runloop...
851  */
852
853  pthread_mutex_init(&sysEventThreadMutex, NULL);
854  pthread_cond_init(&sysEventThreadCond, NULL);
855  pthread_create(&sysEventThread, NULL, sysEventThreadEntry, NULL);
856}
857
858
859/*
860 * 'sysEventMonitorStop()' - Stop system event notifications
861 */
862
863void sysEventMonitorStop(void)
864{
865  CFRunLoopRef	rl;		/* The runloop */
866
867
868  if (sysEventThread)
869  {
870   /*
871    * Make sure the thread has completed it's initialization and
872    * stored it's runloop reference in the shared global.
873    */
874
875    pthread_mutex_lock(&sysEventThreadMutex);
876
877    if (sysEventRunloop == NULL)
878      pthread_cond_wait(&sysEventThreadCond, &sysEventThreadMutex);
879
880    rl = sysEventRunloop;
881    sysEventRunloop = NULL;
882
883    pthread_mutex_unlock(&sysEventThreadMutex);
884
885    if (rl)
886      CFRunLoopStop(rl);
887
888    pthread_join(sysEventThread, NULL);
889    pthread_mutex_destroy(&sysEventThreadMutex);
890    pthread_cond_destroy(&sysEventThreadCond);
891  }
892
893  if (sysEventPipes[0] >= 0)
894  {
895    close(sysEventPipes[0]);
896    close(sysEventPipes[1]);
897
898    sysEventPipes[0] = -1;
899    sysEventPipes[1] = -1;
900  }
901}
902
903
904/*
905 * 'sysEventMonitorUpdate()' - Handle power & network system events.
906 *
907 *  Returns non-zero if a higher level event needs to be handeled.
908 */
909
910static int sysEventMonitorUpdate(TFILE *f)
911{
912  int		err = 0;
913
914 /*
915  * Drain the event pipe...
916  */
917
918  if (read((int)sysEventPipes[0], &sysevent, sizeof(sysevent)) == sizeof(sysevent))
919  {
920    if ((sysevent.event & SYSEVENT_CANSLEEP))
921    {
922     /*
923      * If we're waiting for the phone to ring allow the idle sleep, otherwise
924      * block it so we can finish the current session.
925      */
926      if (waiting)
927        IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
928      else
929      {
930        msg("Isleep canceled because of active job");
931        IOCancelPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
932      }
933    }
934
935    if ((sysevent.event & SYSEVENT_WILLSLEEP))
936    {
937     /*
938      * If we're waiting return an error so answer can reset the modem and close the port,
939      * otherwise cancel the current session right here.
940      */
941      if (waiting)
942      {
943	msg("Ipreparing to sleep...");
944	err = TDATA_SLEEP;
945      }
946      else
947      {
948	msg("Iterminating to sleep or shutdown...");
949	cleanup(6);
950	IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
951	exit(6);
952      }
953    }
954
955    if ((sysevent.event & SYSEVENT_WOKE))
956    {
957      IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
958      if (waiting)
959	err = TDATA_WAKE;
960    }
961
962    if ((sysevent.event & SYSEVENT_MODEMADDED))
963      err = TDATA_MODEMADDED;
964
965    if ((sysevent.event & SYSEVENT_MODEMREMOVED))
966    {
967      msg("Imodem removed...");
968      cleanup(4);
969      exit(4);
970    }
971  }
972
973  return err;
974}
975
976
977/*
978 * 'sysEventThreadEntry()' - A thread to run a runloop on.
979 *		       Receives power & network change notifications.
980 */
981
982static void *sysEventThreadEntry()
983{
984  io_object_t		  powerNotifierObj;	/* Power notifier object */
985  IONotificationPortRef   powerNotifierPort;	/* Power notifier port */
986  CFRunLoopSourceRef	  powerRLS = NULL;	/* Power runloop source */
987  threaddata_t		  threadData;		/* Thread context data for the runloop notifiers */
988  IONotificationPortRef	  addNotification,	/* Add notification port */
989			  removeNotification;	/* Remove notification port */
990  io_iterator_t		  addIterator,		/* Add iterator */
991			  removeIterator;	/* Remove iterator */
992  mach_port_t		  masterPort;		/* Master port */
993  kern_return_t		  kr;			/* Kernel error */
994  CFMutableDictionaryRef  classesToMatch;	/* Dictionary to match */
995  static const sysevent_t sysevent_modemadded   = { SYSEVENT_MODEMADDED };
996						/* Modem added event */
997  static const sysevent_t sysevent_modemremoved = { SYSEVENT_MODEMREMOVED };
998						/* Modem removed event */
999
1000  bzero(&threadData, sizeof(threadData));
1001  addNotification    = \
1002  removeNotification = NULL;
1003  addIterator    = \
1004  removeIterator = IO_OBJECT_NULL;
1005
1006 /*
1007  * Register for power state change notifications.
1008  */
1009
1010  threadData.sysevent.powerKernelPort = IORegisterForSystemPower(&threadData, &powerNotifierPort, sysEventPowerNotifier, &powerNotifierObj);
1011  if (threadData.sysevent.powerKernelPort)
1012  {
1013    powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
1014    CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
1015  }
1016
1017 /*
1018  * Register for IOKit serial device added & removed notifications.
1019  */
1020
1021  kr = IOMasterPort(bootstrap_port, &masterPort);
1022
1023  if (kr == kIOReturnSuccess && masterPort != MACH_PORT_NULL)
1024  {
1025    if ((classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue)) != NULL)
1026    {
1027      CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
1028
1029     /*
1030      * Each IOServiceAddMatchingNotification() call consumes a dictionary reference
1031      * so retain it for the second call below.
1032      */
1033
1034      CFRetain(classesToMatch);
1035
1036      removeNotification = IONotificationPortCreate(masterPort);
1037      kr = IOServiceAddMatchingNotification( removeNotification,
1038					     kIOTerminatedNotification,
1039					     classesToMatch,
1040					     &deviceNotifier,
1041					     (void*)&sysevent_modemremoved,
1042					     &removeIterator);
1043
1044      if (kr == kIOReturnSuccess && removeIterator != IO_OBJECT_NULL)
1045      {
1046	deviceNotifier((void*)&sysevent_modemremoved, removeIterator);
1047	CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(removeNotification), kCFRunLoopDefaultMode);
1048      }
1049
1050      addNotification = IONotificationPortCreate(masterPort);
1051      kr = IOServiceAddMatchingNotification(addNotification,
1052					    kIOMatchedNotification,
1053					    classesToMatch,
1054					    &deviceNotifier,
1055					    (void*)&sysevent_modemadded,
1056					    &addIterator);
1057
1058      if (kr == kIOReturnSuccess && addIterator != IO_OBJECT_NULL)
1059      {
1060	deviceNotifier((void*)&sysevent_modemadded, addIterator);
1061	CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(addNotification), kCFRunLoopDefaultMode);
1062      }
1063    }
1064    mach_port_deallocate(mach_task_self(), masterPort);
1065  }
1066
1067 /*
1068  * Store our runloop in a global so the main thread can
1069  * use it to stop us.
1070  */
1071
1072  pthread_mutex_lock(&sysEventThreadMutex);
1073
1074  sysEventRunloop = CFRunLoopGetCurrent();
1075
1076  pthread_cond_signal(&sysEventThreadCond);
1077  pthread_mutex_unlock(&sysEventThreadMutex);
1078
1079 /*
1080  * Disappear into the runloop until it's stopped by the main thread.
1081  */
1082
1083  CFRunLoopRun();
1084
1085 /*
1086  * Clean up before exiting.
1087  */
1088
1089  if (addIterator != IO_OBJECT_NULL)
1090    IOObjectRelease(addIterator);
1091
1092  if (addNotification != NULL)
1093    IONotificationPortDestroy(addNotification);
1094
1095  if (removeIterator != IO_OBJECT_NULL)
1096    IOObjectRelease(removeIterator);
1097
1098  if (removeNotification != NULL)
1099    IONotificationPortDestroy(removeNotification);
1100
1101  if (powerRLS)
1102  {
1103    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
1104    CFRunLoopSourceInvalidate(powerRLS);
1105  }
1106
1107  if (threadData.sysevent.powerKernelPort)
1108    IODeregisterForSystemPower(&powerNotifierObj);
1109
1110  pthread_exit(NULL);
1111}
1112
1113
1114/*
1115 * 'sysEventPowerNotifier()' - .
1116 */
1117
1118static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument)
1119{
1120  threaddata_t	*threadData = (threaddata_t *)context;	/* Thread context data */
1121  (void)service;					/* anti-compiler-warning-code */
1122
1123  threadData->sysevent.event = 0;
1124
1125  switch (messageType)
1126  {
1127  case kIOMessageCanSystemPowerOff:
1128  case kIOMessageCanSystemSleep:
1129    threadData->sysevent.event = SYSEVENT_CANSLEEP;
1130    break;
1131
1132  case kIOMessageSystemWillRestart:
1133  case kIOMessageSystemWillPowerOff:
1134  case kIOMessageSystemWillSleep:
1135    threadData->sysevent.event = SYSEVENT_WILLSLEEP;
1136    break;
1137
1138  case kIOMessageSystemHasPoweredOn:
1139    threadData->sysevent.event = SYSEVENT_WOKE;
1140    break;
1141
1142  case kIOMessageSystemWillNotPowerOff:
1143  case kIOMessageSystemWillNotSleep:
1144  case kIOMessageSystemWillPowerOn:
1145  default:
1146    IOAllowPowerChange(threadData->sysevent.powerKernelPort, (long)messageArgument);
1147    break;
1148  }
1149
1150  if (threadData->sysevent.event)
1151  {
1152   /*
1153    * Send the event to the main thread.
1154    */
1155    threadData->sysevent.powerNotificationID = (long)messageArgument;
1156    write((int)sysEventPipes[1], &threadData->sysevent, sizeof(threadData->sysevent));
1157  }
1158}
1159
1160
1161/*
1162 * 'clientEventUpdate()' - Read process a command from an incoming client connection.
1163 */
1164
1165static int clientEventUpdate()
1166{
1167  int n, err = 0;
1168  int client_fd;
1169  fd_set client_fds ;
1170  struct timeval client_timeout ;
1171  struct sockaddr_un client_addr;
1172  socklen_t addrlen;
1173  char client_buf[255];
1174
1175  /*
1176   * Accept the incomming connection request...
1177   */
1178
1179  if ((client_fd = accept(clientEventFd, (struct sockaddr *)&client_addr, &addrlen)) < 0)
1180    msg ( "W0client accept error %d - %s", (int)errno, strerror(errno));
1181  else
1182  {
1183    /*
1184     * Give the client 1 second to send us a command...
1185     */
1186
1187    client_timeout.tv_sec  = 1;
1188    client_timeout.tv_usec = 0 ;
1189
1190    FD_ZERO (&client_fds);
1191    FD_SET(client_fd, &client_fds);
1192
1193    n = select ( client_fd + 1, &client_fds, 0, 0, &client_timeout);
1194
1195    if (n <= 0)
1196      msg ( "W0client select error %d - %s", (int)errno, strerror(errno));
1197    else	/* (n > 0) */
1198    {
1199      n = recv(client_fd, client_buf, sizeof(client_buf)-1, 0);
1200      if (n < 0)
1201	msg ( "W0client recv error %d - %s", (int)errno, strerror(errno));
1202      else if (n > 0)
1203      {
1204	client_buf[n-1] = '\0';
1205
1206	switch (client_buf[0])
1207	{
1208	case 'a':			/* Manual answer... */
1209	  msg ( "l manual answer");
1210	  manual_answer = 1;
1211
1212	  if (answer_wait)		/* Only return an error if we're waiting for activity... */
1213	    err = TDATA_MANANSWER;
1214	  break;
1215
1216	case 'c':			/* Cancel current session... */
1217	  msg ( "l cancel session");
1218	  err = TDATA_CANCEL;
1219
1220	  /*
1221	   * Close the listen socket so we're not interupped while cleaning up...
1222	   */
1223
1224	  unlink(clientSocketName);
1225	  close(clientEventFd);
1226	  clientEventFd = -1;
1227	  close(client_fd);
1228
1229#if defined(__APPLE__)
1230	  notify(CFSTR("disconnecting"), NULL);
1231#endif
1232	  exit ( cleanup ( 7 ) ) ;
1233	  break;			/* anti-compiler warning */
1234
1235	default:
1236	  msg ( "W unknown client command \"%s\"", client_buf);
1237	  break;
1238	}
1239      }
1240
1241      close(client_fd);
1242    }
1243  }
1244  return err;
1245}
1246
1247
1248/*
1249 * 'deviceNotifier()' - Called when a serial or modem device is added or removed.
1250 */
1251
1252static void deviceNotifier(void *context, io_iterator_t iterator)
1253{
1254  int		matched = false;	/* Matched the right device? */
1255  io_service_t	obj;			/* IOKit object */
1256  CFTypeRef	cfstr;			/* CFString */
1257  char		bsdpath[PATH_MAX + 1];	/* BSD path ("/dev/<something>") */
1258
1259 /*
1260  * Iterate over the devices looking for one that matches the modem to use
1261  * (always drain the iterator so we get future notifications).
1262  */
1263
1264  while ((obj = IOIteratorNext(iterator)) != IO_OBJECT_NULL)
1265  {
1266    if (!matched && (cfstr = IORegistryEntryCreateCFProperty(obj, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0)))
1267    {
1268      CFStringGetCString(cfstr, bsdpath, sizeof(bsdpath), kCFStringEncodingUTF8);
1269      CFRelease(cfstr);
1270      matched = strcmp(bsdpath, faxfile) == 0;
1271    }
1272
1273    if (!matched && (cfstr = IORegistryEntryCreateCFProperty(obj, CFSTR(kIODialinDeviceKey), kCFAllocatorDefault, 0)))
1274    {
1275      CFStringGetCString(cfstr, bsdpath, sizeof(bsdpath), kCFStringEncodingUTF8);
1276      CFRelease(cfstr);
1277      matched = strcmp(bsdpath, faxfile) == 0;
1278    }
1279
1280    IOObjectRelease(obj);
1281  }
1282
1283 /*
1284  * If we matched send the event to the main thread.
1285  */
1286
1287  if (matched)
1288    write((int)sysEventPipes[1], (sysevent_t *)context, sizeof(sysevent));
1289}
1290
1291#endif	/* __APPLE__ */
1292