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