1 /*
2  * Copyright 1996, 1997, 1998, 1999 by Daniel B. Suthers,
3  * Pleasanton Ca. 94588 USA
4  * E-MAIL dbs@tanj.com
5  *
6  */
7 
8 /*
9  *   This program is free software: you can redistribute it and/or modify
10  *   it under the terms of the GNU General Public License as published by
11  *   the Free Software Foundation, either version 3 of the License, or
12  *   (at your option) any later version.
13  *
14  *   This program is distributed in the hope that it will be useful,
15  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *   GNU General Public License for more details.
18  *
19  *   You should have received a copy of the GNU General Public License
20  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23 
24 
25 /* This module is to be called by the first process to run under HEYU.
26  * It:
27  * 1) Locks the TTY port by putting it's pid in LOCKDIR/LCK..ttyX
28  * 2) Validates any existing HEYU locks in LOCKDIR/LCK..heyu.relay.ttyX
29  *    and sets a lock in LOCKDIR/LCK..heyu.relay.ttyX  with it's PID if none exists.
30  * 3) Starts reading from the TTY port associated with the CM11A
31  *    and writing the raw bytes to SPOOLDIR/heyu.out.ttyX
32  *    The heyu.out.ttyX file will be deleted if it exists and created new.
33  * 4) Upon SIGHUP signal will truncate the .in file.... someday, but not yet
34  * 5) Upon SIGTERM or SIGINT will...
35  *    Close the tty port
36  *    unlink the TTY lock
37  *    unlink the heyu.relay.ttyX lock
38  *    unlink the heyu.out.ttyX file
39  *    unlink the x10_tty file
40  */
41 
42 #ifdef SCO
43 #define _IBCS2
44 #endif
45 
46 #include <stdio.h>
47 #include <ctype.h>
48 #include <signal.h>
49 #include <errno.h>
50 
51 #include <string.h>
52 #include <syslog.h>
53 #ifdef LINUX
54 #include <sys/resource.h>
55 #endif
56 #include <limits.h>
57 #include <sys/types.h>
58 #include <sys/stat.h>
59 #include <fcntl.h>
60 #include <unistd.h>
61 #include <time.h>
62 #include <stdlib.h>
63 #include "x10.h"
64 #include "process.h"
65 
66 #ifdef pid_t
67 #define PID_T pid_t
68 #else
69 #define PID_T long
70 #endif
71 
72 extern int tty;
73 extern int verbose;
74 extern int i_am_relay;
75 extern void quit(), error();
76 
77 extern unsigned char alert_ack[];
78 extern CONFIG config;
79 extern CONFIG *configp;
80 
81 char spoolfile[PATH_LEN + 1];
82 
83 static char saved_auxport[PATH_LEN + 1] = "";
84 static char saved_suffixaux[PATH_LEN + 1] = "";
85 
86 static char restartfile[PATH_LEN + 1];
87 
88 int interrupted = 0;
89 void alarmist(int signo);
90 void flag_intr();
91 void cleanup_files();
92 extern int is_ring( void );
93 extern int heyu_parent;
94 int relay_ri_control( void );
95 int port_status_init ( int, char, char );
96 
97 extern struct opt_st *optptr;
98 
99 #if 0
100 int seconds;
101 int check_flag;
102 int port_health_check( int );
103 int relay_ping( int, int );
104 #endif
105 
106 
107 /* tty should be the device that we are going to use.  It should be a fully
108  * qualified path name (/dev/tty2), but may be just the device (tty2)
109  */
110 
start_relay(char * tty_name)111 int start_relay ( char *tty_name )
112 {
113    unsigned char ibuff[80];
114    long child;
115    PID_T pid;
116    int outfd;
117    int j, count, expected;
118    int powerfail, in_sync;
119    int count_5a;
120    int first_byte;
121    char argv[2][5];
122    char spoolfilename[PATH_LEN + 1];
123    char relayfilename[PATH_LEN + 1];
124    char writefilename[PATH_LEN + 1];
125    struct stat file_buf;
126    extern char *argptr;
127    extern int ttylock(), c_setclock(), lock_for_write(), munlock();
128    extern int c_stop_cm11a_poll(int, char **);
129    extern int setup_tty(), setup_sp_tty(), port_locked;
130    extern int write_restart_error( char * );
131    extern PID_T lockpid( char * );
132    time_t pfail_time, starttime, boottime = 0;
133    unsigned char bootflag;
134    unsigned char alert_count;
135    extern int relay_powerfail_script(void);
136    PID_T was_locked;
137    char saved_auxport[PATH_LEN + 1] = "";
138 
139    int is_idle;
140    int is_err = 0;
141    extern int check_for_engine();
142    extern int sxread(int, unsigned char *, int, int);
143    int restart_aux( char * );
144 
145    int ignoret;
146 
147    first_byte = 1;
148    in_sync = 0;
149    alert_count = 0;
150    was_locked = (PID_T)0;
151    is_idle = 0;
152 
153    strcpy(saved_auxport, configp->ttyaux);
154    strcpy(saved_suffixaux, configp->suffixaux);
155 
156    /* set up the spool file name */
157    sprintf(spoolfilename, "%s%s", SPOOLFILE, configp->suffix);
158    sprintf(relayfilename, "%s%s", RELAYFILE, configp->suffix);
159    sprintf(writefilename, "%s%s", WRITEFILE, configp->suffix);
160    spoolfile[0] = '\0';
161    strcat(spoolfile, SPOOLDIR);
162 
163    if ( spoolfile[strlen(spoolfile) - 1] != '/' )
164        strcat(spoolfile, "/");
165 
166    if ( stat(spoolfile, &file_buf) < 0 ) {
167        char tmpbuf[sizeof(spoolfile) + 100];
168        sprintf(tmpbuf, "The directory %s does not exist or is not writable.",
169                spoolfile);
170        error(tmpbuf);
171    }
172    strcat(spoolfile, spoolfilename);
173 
174    /* is a relay in place ? */
175     if ( lockpid(relayfilename) > (PID_T)1)  {
176        if ( stat(spoolfile, &file_buf) < 0 )  {
177 	   char tmpbuf[sizeof(spoolfile) + 100];
178 	   sprintf(tmpbuf, "The file %s does not exist or is not writable.",
179 		   spoolfile);
180 	   error(tmpbuf);
181        }
182        if ( verbose )
183           printf("There was already a relay running (pid = %ld)\n",
184 		(long)lockpid(relayfilename) );
185 
186        return(-1);		/* there was a valid relay running */
187     }
188     else {
189         if ( verbose )
190                printf("Relay lockfile not found - spawning heyu_relay.\n");
191 	/* we will spawn a relay process */
192 	child = fork();
193 	if ( child > 0 )  {
194             if ( heyu_parent == D_CMDLINE )
195                printf("starting heyu_relay\n");
196 	    sleep(3);		/* give child time to set up */
197 	    return(1);		/* this is parent process */
198 	}
199 	if ( child < 0 )  {	     /* This is an error */
200 	    perror("I could not spawn heyu_relay process");
201 	    syslog(LOG_DAEMON | LOG_ERR, "I could not spawn heyu_relay process.\n");
202 	    quit();
203 	}
204     }
205 
206     /* from this point out, it should be the child. */
207 
208     close(0);
209     close(1);
210     close(2);
211     strcpy(argptr, "heyu_relay");
212     pid = setsid();   /* break control terminal affiliation */
213     openlog( "heyu_relay", 0, LOG_DAEMON);
214     if ( pid == (PID_T)(-1) )  {
215 	syslog(LOG_ERR, "relay setsid failed--\n");
216 	quit(1);
217     }
218     else {
219 	syslog(LOG_ERR, "relay setting up-\n");
220         boottime = time(NULL);
221     }
222 
223 
224     /* Ok. We're alone now. */
225 
226     strncpy2(restartfile, pathspec(RESTART_RELAY_FILE), PATH_LEN);
227     unlink(restartfile);
228 
229     if ( ttylock(relayfilename) < 0 )  {
230 	syslog(LOG_ERR, "Could not set up the heyu relay lock-");
231         exit(0);	/* a competing process must have started up
232 	                 * in the lastfew milliseconds
233 			 */
234     }
235 
236     setup_tty(1);	/* open the real tty */
237     i_am_relay = 1;	/* set flag so calling function will clean up. */
238 
239     port_status_init(tty, DTR_INIT, RTS_INIT);
240 
241     unlink(spoolfile);
242 #ifdef REVERT_PERMS
243     outfd=open(spoolfile, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0777);
244 #else
245     outfd=open(spoolfile, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0666);
246 #endif
247     setup_sp_tty();   /* Per DBS 3/27/2006 */
248 
249     if ( outfd < 0 )  {
250         syslog(LOG_ERR, "Trouble creating spoolfile (%s)", spoolfile);
251 	quit();
252     }
253 #ifdef REVERT_PERMS
254     chmod(spoolfile, 0777);
255 #endif
256 
257     (void) signal(SIGINT, flag_intr);
258     (void) signal(SIGTERM, flag_intr);
259     (void) signal(SIGHUP, flag_intr);
260 
261     /* certain codes come out 1 second apart.  These are the 5a and a5
262      * codes.  They indicate the CM11A wants a response, ie a polling
263      * sequence indicator.
264      * In order to handle powerfails, we have to timestamp each a5 character
265      * as it comes in.  Three 0xa5 characters in a row, 1 second apart
266      * would indicate a power fail condition that needs a reply.
267      * If the very first byte received is a5 or 5a, it's a condition
268      * that needs a reply.
269      * As an alternative, a leading byte that's more than 1 second from the
270      * previous one __may__ be a polling sequence.
271      * Adding a counter to make sure it was a standalone byte may help when
272      * something like a checkum just happens to equal 0xa5.
273      */
274     powerfail = 0;	/* increment this each time a 0xa5 is seen */
275     strncpy2(argv[0], " ", sizeof(argv[0]) - 1); /* set a vector that can be used by c_setclock() */
276     strncpy2(argv[1], " ", sizeof(argv[1]) - 1); /* set a vector that can be used by c_setclock() */
277     count_5a = 0;
278     pfail_time = time(NULL);
279 
280     in_sync = 1;
281 
282     if ( configp->ring_ctrl == DISABLE )
283         relay_ri_control();
284 
285     while ( 1 )  {
286 	alarm(0);  /* just in case I ever forget */
287 
288         /* Check if restart needed */
289         if ( is_idle && stat(restartfile, &file_buf) == 0 && is_err == 0 ) {
290            if ( reread_config() != 0 ) {
291               is_err = 1;
292               syslog(LOG_ERR, "relay reconfiguration failed!\n");
293               write_restart_error(restartfile);
294            }
295            else {
296               send_x10state_command(ST_RESTART, 0);
297               syslog(LOG_ERR, "relay reconfiguration-\n");
298               strcpy(saved_auxport, configp->ttyaux);
299               strcpy(saved_suffixaux, configp->suffixaux);
300               unlink(restartfile);
301            }
302         }
303 
304         /* Check the spool file to make sure it has not exceeded limits */
305         stat(spoolfile, &file_buf);
306 
307         if ( ((unsigned long)file_buf.st_size > configp->spool_max && is_idle) ||
308               file_buf.st_size > SPOOLFILE_ABSMAX )  {
309             send_x10state_command(ST_REWIND, 0);
310             sleep(2);
311             ignoret = ftruncate(outfd, (off_t)0);
312             lseek(outfd, (off_t)0, SEEK_END);
313         }
314 #if 0
315         else if ( configp->lockup_check > 0 ) {
316            seconds = time(NULL) % 60;
317            if ( seconds > 30 && check_flag == 0 ) {
318               check_flag = 1;
319               if ( (configp->lockup_check & CHECK_PORT) && (fstat(tty, &file_buf) < 0) ) {
320                  send_x10state_command(ST_LOCKUP, CHECK_PORT);
321               }
322               else if ( (configp->lockup_check & CHECK_CM11) && (relay_ping(tty, 1) != 0) ) {
323                  send_x10state_command(ST_LOCKUP, CHECK_CM11);
324               }
325            }
326            else if ( seconds <= 30 ) {
327               check_flag = 0;
328            }
329         }
330 #endif
331 
332         starttime = time(NULL);
333 
334         count = sxread(tty, ibuff, 1, 5);
335 
336         if ( count <= 0 ) {
337             is_idle = 1;
338             continue;
339         }
340         is_idle = 0;
341 
342 
343 	if ( (time(NULL) - starttime) > 5 )  {
344 	    /* we must be in sync if it's been a while since the first byte */
345 	    in_sync = 1;
346 	}
347 	if ( (time(NULL) - starttime) > 2 )  {
348             /* Cancel the checksum 0x5A alert if any of the alert_ack bytes */
349             /* or the 0x5A is overdue.                                      */
350             alert_count = 0;
351 	}
352 
353         /* Check for the three special alert bytes in a row which */
354         /* indicate that the next 0x5A is a checksum rather than  */
355         /* an incoming X10 signal.                                */
356         if ( ibuff[0] == alert_ack[0] )
357            alert_count = 1;
358         else if ( ibuff[0] == alert_ack[1] && alert_count == 1 )
359            alert_count = 2;
360         else if ( ibuff[0] == alert_ack[2] && alert_count == 2 )
361            alert_count = 3;
362         else if ( ibuff[0] == 0x5A && alert_count == 3 ) {
363             /* This is the 0x5a checksum */
364             ignoret = write(outfd, ibuff, 1);
365             alert_count = 0;
366             continue;
367         }
368         else
369            alert_count = 0;
370 
371 
372 	if ( ibuff[0] != 0x5a )  {
373 	    /* just write any unknown data */
374 	    ignoret = write(outfd,ibuff, 1);
375         }
376 #if 0
377 	if ( ibuff[0] == 0x5a && (count_5a == 0 || in_sync == 1 ) )  {
378 	    /* write the first 0x5a that's seen */
379 	    ignoret = write(outfd,ibuff, 1);
380 
381 	    /* if we've reached this point and the RI is still asserted, it
382 	     * should be time for sending 0xc3, NO?
383 	     * let's try that next time that I'm deep into the code.
384 	     */
385 	}
386 #endif
387 	if ( ibuff[0] == 0x5a && count == 1)  {
388 
389             /* CM11A has a character to read */
390 	    if ( ++count_5a > 1 || (count_5a == 1 && in_sync == 1) )  {
391                 /* After a (configurable) short delay, tell the CM11A to send it */
392                 millisleep(configp->cm11a_query_delay);
393 		ibuff[1] = 0xC3;
394 
395 		/* if( lock_for_write() == 0 ) */
396 		{
397 		    port_locked = 1;
398 		    ignoret = write(tty,ibuff + 1, 1);
399 		    munlock(writefilename);
400                     port_locked = 0;
401 		}
402 
403 		/* read the number of bytes to read. If it's greater than
404 		 * the size of the buffer, let the outer loop copy it
405 		 * to the spoolfile. (out of sync... Noise, etc )
406 		 * If it's 1 byte, there's the chance that it's a special
407 		 * Like power fail or hail request.
408 		 */
409 
410 	         count = sxread(tty, ibuff + 1, 1, 5); 	/* Get number of bytes which follow */
411 
412 	         if ( count == 1 ) {
413                      /* so far so good */
414 
415 		     expected = ibuff[1];
416 
417                      if ( expected == 0x5a ) {
418                         /* CM11A quirk - sometimes send 0xC3, get nothing */
419                         /* back until another 0x5A one second later.      */
420 			count_5a = 1;
421 			in_sync = 0;
422                         continue;
423                      }
424 
425 		     if ( expected > 20 )  {
426                          ignoret = write(outfd,ibuff, 2);
427                          /* Too many. We must not be synced. */
428 		         in_sync = 0;
429 		         continue; /* go to outer while to grab this */
430 		     }
431 
432                      count = sxread(tty, ibuff + 2, expected, 5);
433 
434 		     if ( count != expected )  {
435 		         /* This should be too few. so we aren't in sync yet. */
436                          ibuff[1] = count;
437 		         ignoret = write(outfd,ibuff, count + 2);
438 		         in_sync = 0;
439 		         continue; /* go to outer while to grab this */
440 		     }
441 		     if ( count == expected )  {
442 			count_5a = 0;
443 			in_sync = 1;
444 			ignoret = write(outfd,ibuff, count + 2);
445                      }
446 
447 	         }
448 	         else  {
449 	             /* we did not get any response, so let the outer loop handle it. */
450 	             continue;
451 	         }
452 	    }
453 	}
454 	else  {
455 	    count_5a = 0;
456 	}
457 
458 	if ( ibuff[0] == 0xa5 && count == 1 )  {
459             /* CM11A reports a power fail */
460 	    if ( powerfail == 0 )  {
461                 /* set timestamp for the first poll */
462 	        pfail_time = time(NULL);
463 	    }
464 	    if ( (first_byte == 1) || (powerfail++ >= 2) )  {
465 		if ( (powerfail >= 3) &&
466 		    ((pfail_time != 0) && ((time(NULL) - pfail_time) > 2)) )  {
467 		    /* 3 bytes of 'a5' in a row over a period of 3 seconds
468 			  means a power failure*/
469 
470                     /* Set lock file if not already set */
471                     if ( (was_locked = lockpid(writefilename)) == (PID_T)0 ) {
472                         if ( lock_for_write() < 0 )
473                             error("Program exiting.\n");
474                     }
475                     port_locked = 1;
476 
477 		    powerfail = 0;
478 
479                     bootflag = (time(NULL) - boottime) > ATSTART_DELAY ?
480                         R_NOTATSTART : R_ATSTART ;
481 
482                     if ( configp->device_type & DEV_CM10A ) {
483                        c_cm10a_init(1, (char **)argv);
484                     }
485 		    else if ( configp->pfail_update == NO ) {
486 		       c_stop_cm11a_poll(1, (char **)argv);
487                     }
488                     else {
489 		       c_setclock(1, (char **)argv);
490                     }
491 
492                     if ( configp->ring_ctrl == DISABLE )
493                        relay_ri_control();
494 
495 		    pfail_time = 0;
496 		    in_sync = 1;
497 
498                     /* Remove the lock file if we locked it, */
499                     /* otherwise leave it.                   */
500                     if ( was_locked == (PID_T)0 ) {
501                        munlock(writefilename);
502                        port_locked = 0;
503                     }
504 
505                     /* Launch a powerfail script directly from the relay */
506                     /* (Don't use for Heyu commands.)                    */
507                     relay_powerfail_script();
508 
509                     /* Notify state engine of the powerfail and let it */
510                     /* launch a powerfail script.                      */
511                     if ( bootflag & R_ATSTART && check_for_engine() != 0 ) {
512                        /* Give the engine a little more time for startup */
513                        for ( j = 0; j < 20; j++ ) {
514                           millisleep(100);
515                           if ( check_for_engine() == 0 )
516                              break;
517                        }
518                     }
519                     send_x10state_command(ST_PFAIL, bootflag);
520 		}
521 	    }
522 	}
523 	else  {
524 	    powerfail = 0;
525 	    pfail_time = 0;
526 	}
527 	first_byte = 0;
528 	ibuff[0] = '\0';
529 
530     }  /* End while() loop */
531 
532     /* return(0); */
533 }
534 
alarmist(int signo)535 void alarmist(int signo)
536 {
537    return;
538 }
539 
540 
cleanup_files(void)541 void cleanup_files ( void )
542 {
543     extern char spoolfile[];
544     char buffer[PATH_LEN + 1];
545 
546     sprintf(buffer, "%s/LCK..%s%s", LOCKDIR, RELAYFILE, configp->suffix);
547     unlink(buffer);
548     sprintf(buffer, "%s/LCK..%s%s", LOCKDIR, WRITEFILE, configp->suffix);
549     unlink(buffer);
550     sprintf(buffer, "%s/LCK..%s%s", LOCKDIR, STATE_LOCKFILE, configp->suffix);
551     unlink(buffer);
552     sprintf(buffer, "%s/LCK.%s", LOCKDIR, configp->suffix);
553     unlink(buffer);
554     unlink(spoolfile);
555     unlink(restartfile);
556     return;
557 }
558 
559 
flag_intr(int signo)560 void flag_intr( int signo )
561 {
562     extern int munlock();
563     char buffer[PATH_LEN + 1];
564     PID_T pid;
565     PID_T lockpid();
566 
567     interrupted = 1;
568     (void) signal(SIGTERM, flag_intr);
569     syslog(LOG_ERR, "interrupt received\n");
570 
571     if ( configp->ttyaux[0] ) {
572        sprintf(buffer, "%s%s", AUXFILE, configp->suffixaux);
573        if ( (pid = lockpid(buffer)) > 0 ) {
574           kill(pid, SIGTERM);
575        }
576        munlock(buffer);
577        munlock(configp->ttyaux);
578     }
579     else if ( *saved_auxport ) {
580        sprintf(buffer, "%s%s", AUXFILE, saved_suffixaux);
581        if ( (pid = lockpid(buffer)) > 0 ) {
582           kill(pid, SIGTERM);
583        }
584        munlock(buffer);
585        munlock(saved_auxport);
586     }
587 
588     sprintf(buffer, "%s%s", STATE_LOCKFILE, configp->suffix);
589     if ( (pid = lockpid(buffer)) > 0 ) {
590        kill(pid, SIGTERM);
591        munlock(buffer);
592     }
593 
594     sprintf(buffer, "%s%s", RELAYFILE, configp->suffix);
595     munlock(buffer);
596     munlock(configp->tty);
597     sprintf(buffer, "%s%s", WRITEFILE, configp->suffix);
598     munlock(buffer);
599 
600     unlink(spoolfile);
601     unlink(restartfile);
602 
603     exit(0);
604 }
605 
606 
607 
608 /*---------------------------------------------------------------+
609  |  Quick check to verify the aux port is not in use by an       |
610  |  earlier invocation of Heyu.  Return 0 if OK, 1 otherwise.    |
611  +---------------------------------------------------------------*/
quick_ports_check(void)612 int quick_ports_check ( void )
613 {
614    struct stat statbuf;
615    char lockpath[PATH_LEN + 1];
616 
617    if ( !configp->ttyaux[0] )
618       return 0;
619 
620    sprintf(lockpath, "%s/LCK.%s", LOCKDIR, configp->suffixaux);
621    if ( stat(lockpath, &statbuf) != 0 )
622       return 0;
623 
624    sprintf(lockpath, "%s/LCK.%s", LOCKDIR, configp->suffix);
625    if ( stat(lockpath, &statbuf) != 0 )
626       return 1;
627 
628    return 0;
629 }
630 
631