/* * Copyright (c) 1995, 1996 Gunther Schadow. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "pg_config.h" #include #include #include #include #include #ifdef _AIX #include #define cfsetspeed(t, s) ({ cfsetispeed(t, s); cfsetospeed(t, s); }) #else #include #endif #include #include #include #include #include #include #include #include /* RADIOSHELL -- for the Radiometer ABL300, 500 and 600 series * * This program does nothing else than reading one peace of ABL300 output * into a buffer. Since ABL300 has no soft flow control capabilities but * is connected through a two wires fiber optical line there is no handshake. * On the other hand, the radiometer HL7 interface can not process the * incomming data at the required rate. The radioshell is a small program * that can read from the tty much more quickly. It buffers the data and * invokes the HL7 interface in the background. Moreover, it takes over * tty related configuration headache. * * We make use of the simple ``message'' format of the ABL300: Each line * is exactly 21 bytes long: 20 data bytes and 2 end of line chars (CR LF) * which are mapped to a single LF by the tty device (igncr flag set). * When such a data block is not ended by LF, we know that * something went wrong before and made us get out of sync. Thus we * issue a warning and discard the message that is currently being * sent. A message ends when the last two lines read have been blank. * A message is expected to never exceed 50 lines, thus the buffer is * 50 * 21 bytes + 1 = 1051 in length including a terminating NUL byte. * * Likewise the ABL500 and 600 series, though capable of doing soft * flow control, require the radioshell as well, since the ASTM to HL7 * translation might be too slow, especially when multiple subsequent * ASTM messages arive at a fast rate. The message format is * different: STX BLK ..... ETX. * * Arguments to this program are: the ABL300/500 selection flag, the * command to invoke in background, and an arbitrary number of * arguments to it and finally the tty. If the tty argument is a dash * `-', the standard input is used. If the command argument is a dash, * the configured radiometer program path is used. * * radioshell (3|5) (-|) (-|) */ #define MAXLINES 50 #define LINELEN 21 #define SPEED300 300 #define SPEED500 9600 #define CMDDEF300 PGBIN "/abl300 -" #define CMDDEF500 PGBIN "/abl500" #define IFLAG300 BRKINT | INPCK | IGNCR #define IFLAG500 BRKINT | IXOFF | IXON #define CFLAG300 CS7 | CREAD | PARENB | CLOCAL #define CFLAG500 CS8 | CREAD | CLOCAL #define LFLAG300 0 /* non canonical mode */ #define LFLAG500 0 /* non canonical mode */ #define VMIN300 ( LINELEN < MAX_INPUT ? LINELEN : MAX_INPUT ) #define VMIN500 1 #define VTIME300 100 /* 10s inter byte timer */ #define VTIME500 0 #define BUFSIZE500 8192 #ifndef APPLICATION # define APPLICATION "ABL" #endif #ifndef FACILITY # define FACILITY NULL #endif #define JOURNAL PGLOG "/" APPLICATION "/" FACILITY static void reaper(int); char buffer [MAXLINES*LINELEN>BUFSIZE500?MAXLINES*LINELEN:BUFSIZE500]; int line; int main(int argc, char *argv[]) { char *ttyfn = argv[argc - 1]; /* ABL300/500 line */ int tty = 0; /* defaults to stdin */ int i; /* general counter */ char *cmd; /* radiometer command */ size_t cmdlen = 0; bool abl500 = FALSE; /* The journal file template and derived names */ char *jfile = (char *)alloca(sizeof(JOURNAL "/ABL00000")); char *jfpid = &jfile[sizeof(JOURNAL "/AB")]; char *jfref = &jfile[sizeof(JOURNAL)]; strcpy(jfile, JOURNAL "/ABL00000"); switch(*argv[1]) { case '3': abl500 = FALSE; break; case '5': case '6': abl500 = TRUE; break; default: fprintf(stderr, "usage: radioshell (3|5) " "(-|) (-|)\n"); return 1; } /* Construct the command + argument string cmd */ if(strcmp(argv[2],"-") == 0) if(abl500) cmdlen = sizeof(CMDDEF500); else cmdlen = sizeof(CMDDEF300); for(i = 2; i < argc - 1; i++) cmdlen += strlen(argv[i]) + 1; cmd = (char *)alloca(cmdlen + 1); if(strcmp(argv[2],"-") == 0) if(abl500) strcpy(cmd, CMDDEF500 " "); else strcpy(cmd, CMDDEF300 " "); else cmd[0] = '\0'; for(i = 2; i < argc - 1; i++) { strcat(cmd, argv[i]); strcat(cmd, " "); } cmd[cmdlen] = '\0'; /* Start syslog */ openlog("radioshell", LOG_CONS #ifdef DEBUG | LOG_PERROR #endif | LOG_PID, LOG_USER); syslog(LOG_NOTICE, "radioshell started for %s on %s with %s", abl500?"ABL500":"ABL300", ttyfn, cmd); /* set stdin out err to /dev/null */ { int fd = open("/dev/null", O_RDWR); if(fd == -1) { syslog(LOG_ERR, "/dev/null: %m"); return 1; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); } /* Open the ABL line */ if(strcmp(ttyfn, "-") != 0) { int stty; /* this is just a configuration channel to set clocal */ struct termios tio; char *path = (char *)alloca(sizeof("/dev/") + strlen(ttyfn)); strcpy(path, "/dev/"); strcat(path, ttyfn); stty = open(path, O_RDONLY | O_NONBLOCK); if(stty == -1) { perror(path); syslog(LOG_ERR, "%s: %m", path); return 1; } if(tcgetattr(stty, &tio) == -1) { perror("tcgetattr"); syslog(LOG_ERR, "tcgetattr: %m"); return 1; } if(cfsetspeed(&tio, (abl500?SPEED500:SPEED300))) { perror("cfsetspeed"); syslog(LOG_ERR, "cfsetspeed: %m"); return 1; } if(abl500) { tio.c_iflag = IFLAG500; tio.c_cflag = CFLAG500; tio.c_lflag = LFLAG500; tio.c_cc[VMIN] = VMIN500; tio.c_cc[VTIME]= VTIME500; } else { tio.c_iflag = IFLAG300; tio.c_cflag = CFLAG300; tio.c_lflag = LFLAG300; tio.c_cc[VMIN] = VMIN300; tio.c_cc[VTIME]= VTIME300; } if(tcsetattr(stty, TCSANOW, &tio) == -1) { perror("tcsetattr"); syslog(LOG_ERR, "tcsetattr: %m"); return 1; } tty = open(path, O_RDWR); if(tty == -1) { perror(path); syslog(LOG_ERR, "%s: %m", path); return 1; } close(stty); } else if(!isatty(tty)) { fprintf(stderr, "%s is not a tty device", ttyfn); syslog(LOG_ERR, "%s is not a tty device", ttyfn); return 1; } signal(SIGCHLD, reaper); while(1) { int is_valid = 1; /* whether the message is valid */ int is_data= 0; /* whether the message contains any non-blank at all */ int line = 0; int offset = 0; int blanks = 0; /* the number of subsequent spaces */ pid_t pid; /* wait for any char to come and regularely send START characters */ int n; do { fd_set rfds; static const char cstart = 17; /* ^Q */ static struct timeval timeout = { 120 /* s */, 0 }; struct timeval *tvp = NULL; if(abl500) { tvp = &timeout; if(write(tty, &cstart, 1) < 1) { syslog(LOG_ALERT, "%s: %m", ttyfn); return 2; } } FD_ZERO(&rfds); FD_SET(tty, &rfds); n = select(tty + 1, &rfds, NULL, NULL, tvp); } while((n == -1 && errno == EINTR) || n == 0); if(n == -1) { syslog(LOG_ALERT, "%s: %m", ttyfn); return 2; } /* read the message */ if(abl500) { int bufsize; char *p; char ch; do /* seek for STX */ { n = read(tty, &ch, 1); if(n == -1 && errno != EINTR) { syslog(LOG_ALERT, "%s: %m", ttyfn); return 2; } } while(n != 1 || ch != 002); restarted: do /* get BLK */ { n = read(tty, &ch, 1); if(n == -1 && errno != EINTR) { syslog(LOG_ALERT, "%s: %m", ttyfn); return 2; } } while(n != 1); bufsize = BUFSIZE500; p = buffer; do { do /* get one byte of data */ { n = read(tty, &ch, 1); if(n == -1 && errno != EINTR) { syslog(LOG_ALERT, "%s: %m", ttyfn); return 2; } } while(n != 1); switch(ch) { case 002: /* STX -- restart ? */ syslog(LOG_WARNING, "message restarted"); goto restarted; case 003: /* ETX -- end of data */ is_valid = 1; is_data = 1; line = BUFSIZE500 - bufsize; /* a line is just one byte here */ goto end_of_message; default: *p++ = ch; bufsize--; } } while(bufsize > 0); syslog(LOG_ALERT, "radioshell buffer overflow (%d)", BUFSIZE500); return 2; } else /* abl300 */ { do { int ilen = 0; /* the actual length of the line */ /* read a line of LINELEN characters */ do { int n; do { n = read(tty, &buffer[offset + ilen], LINELEN - ilen); } while(n == -1 && errno == EINTR); if(n == -1) { syslog(LOG_ALERT, "%s: %m", ttyfn); return 2; } else if(n == 0) { /* end of file even though we have CLOCAL set? */ syslog(LOG_ALERT, "%s: oops? end of file, exiting", ttyfn); return 2; } else if(n < LINELEN) { /* MIN was set to read one line within TIME ( = 10 seconds ) this was not accomplished thus a new message is expected to come next */ syslog(LOG_ERR, "%s: timeout reading line %d", ttyfn, line), is_valid = 0; goto end_of_message; } else { /* check for a blank line and correct eol */ int max = ilen + n; if(max == LINELEN) max += offset - 1; else max += offset; i = offset + ilen; while(i < max) switch(buffer[i++]) { case '\n': { int delta = ( i - offset - ilen ); ilen = 0; /* we start a new line */ line++; n -= delta; /* still unchecked chars */ offset += LINELEN; /* the new offest */ max += LINELEN + delta; /* new actual end */ memcpy(&buffer[offset], &buffer[i], n); i += LINELEN + delta; /* the new index */ syslog(LOG_WARNING, "lines out of sync"); is_valid = 0; /* the message is discarded later */ continue; } break; case '\0': buffer[i++] = ' '; blanks++; syslog(LOG_WARNING, "%s: parity error detected", ttyfn); break; case ' ': blanks++; break; default: blanks = 0; is_data = 1; } ilen += n; } } while(ilen < LINELEN); line++; offset += LINELEN; } while(blanks < ( LINELEN - 1 )* 3); /* terminate the buffer with NUL */ buffer[line * LINELEN] = '\0'; } end_of_message: /* don't handle blank blocks at all */ if(!is_data) continue; /* process newly read message in a child process */ pid = fork(); if(pid == -1) { syslog(LOG_ALERT, "%s: could not fork %m", ttyfn); syslog(LOG_WARNING, "%s: message lost:\n%s", ttyfn, buffer); return 2; } else if(pid == 0) /* child */ { int jfd; int linelen = LINELEN; if(abl500) { linelen = 1; } sprintf(jfpid, "%05u", (u_int)getpid()); jfd = open(jfile, O_WRONLY | O_CREAT, 0664); if(jfd == -1) syslog(LOG_ERR, "%s: unable to open journal file `%s': %m", ttyfn, jfpid); else { size_t n = linelen * line; if(write(jfd, buffer, n) < n) syslog(LOG_ERR, "%s: error writing on journal file `%s': %m", ttyfn, jfpid); close(jfd); } if(!is_valid) { syslog(LOG_ERR, "%s(%s): message discarded due to errors", ttyfn, jfref); return 1; } else { FILE *pipe = popen(cmd, "w"); if(pipe == NULL) { syslog(LOG_ALERT, "%s(%s): unable to open pipe `%s': %m", ttyfn, jfref, cmd); return 2; } else if(fwrite(buffer, linelen, line, pipe) < line) { syslog(LOG_ERR, "%s(%s): `%s': %m", ttyfn, jfref, cmd); return 1; } pclose(pipe); syslog(LOG_DEBUG, "exiting normally"); return 0; } } } } static void reaper(int sig) { int status; pid_t pid; while((pid = wait3(&status, WNOHANG, 0)) > 0) { if(WIFEXITED(status) && WEXITSTATUS(status) != 0) { syslog(LOG_NOTICE, "process %d exited with %d", pid, WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { int tsig = WTERMSIG(status); syslog(LOG_NOTICE, "process %d received signal %d `%s'", pid, tsig, sys_siglist[tsig]); } else if(WIFSTOPPED(status)) { int ssig = WSTOPSIG(status); syslog(LOG_NOTICE, "process %d stopped by signal %d `s'", pid, ssig, sys_siglist[ssig]); kill(pid, SIGCONT); } } }