/* * 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 "llpdrv.h" #include "pg_config.h" #include "socket.h" #include "trap.h" #ifdef USE_TRIP #include "trip.h" #else #include "semaphor.h" #endif #include #include #include #include #include #include #include /* * Define last resort defaults. * These should have been supplied by the Makefile -- do not edit here! */ #ifndef LOG_IDENT # define LOG_IDENT "llpdrv" #endif #ifndef LOG_FACILITY # define LOG_FACILITY LOG_USER #endif #ifndef LOG_FILE # define LOG_FILE "/tmp/llpdrv.log" #endif #ifndef FROM_AF # define FROM_AF AF_INET #endif #ifndef FROM_ADDR # define FROM_ADDR "llp%localhost" #endif #ifndef TO_AF # define TO_AF AF_INET #endif #ifndef TO_ADDR # define TO_ADDR "test01%localhost" #endif #ifndef DIST_AF # define DIST_AF AF_INET #endif #ifndef DIST_ADDR # define DIST_ADDR "test02%localhost" #endif #ifndef PROTO # define PROTO P_MINI #endif /* minimal delay before retry to access a resource that ran short * such as processes (fork()) or semaphores (SEM_ALLOC()). */ #ifndef RETRY_DELAY #define RETRY_DELAY 10 /* seconds */ #endif /* * End of last resort defaults. */ char *to_addr = TO_ADDR; /* to address */ int to_af = TO_AF; /* to address family */ static int proto = PROTO; /* protocol */ static char *from_addr = FROM_ADDR; /* from address */ static int from_af = FROM_AF; /* from address family */ static char *dist_addr = DIST_ADDR; /* distal address */ static int dist_af = DIST_AF; /* distal address family */ static bool dont_fork = FALSE; /* run in the foreground */ static bool dont_setsid= FALSE; /* don't create new process group */ int llp_errno; /* Variables which are used in main() and servingp_cleanup() */ static pid_t dist_pid = 0; static bool did_fork; /* whether the proximal serving process is a child of the proximal server */ /* Used in main() and serve_proximal() */ bool disconnect = FALSE; /* disconnect after a single transaction */ #ifndef USE_TRIP /* used by proximal and distal serving processes to meta-chron-ize */ semaphor mutex; #endif static void usage() __attribute__((__noreturn__)); static void finish(int) __attribute__((__noreturn__)); static void shutdown_all(); static void servingp_cleanup(int); /* does return if !did_fork */ static void reaper(int); static void usage(const char *program) { fprintf(stderr, "usage: %s " "[-1ahmyI] [[-iupt] from [[-i to] [-i dist]]]\n", program); exit(1); } /* * The following figure might show better than words how the llp * driver works in general: * | setup | bind | fork------+ | | exit disassociate . from terminal . | +--------do | +-------do | | | | | accept | | | | | connection | | | if socket is a tty dont fork but act like the child | | fork--------------+ from here on we must decide if | | | | we exit or continue on error | | close allocate conditions depending on if we | | connection semaphore... did fork or not. | | | | : | +-------+ fork--------------+ a distal server is | . . | : | always established | . . proximal : distal except if we only | . . server...mutex...server want a single trans- | . . | | action. | . . kill distal | reopen tty . server ......... exit | . . | . +------------------- proximal > proximal > distal process server serving server (and serving) process process process \____________ ____________/ V transient resident transient 1x 1x nx It is important to identify four different ``roles'' that the llp driver processes can assume: Startup process, proximal server process, proximal serv*ing* process and distal server process. First of all, there is the ``startup process'', which is the one that is usually called by execve from a shell. The setup process exits as soon as it has: - initialized some variables using the command line option arguments - bound a socket that can later accept connections - invoked the ``server process'' by fork - printing the server process' id to the standard output There is nothing else printed to the standard output, thus the server process' id can be strored easily for an eventual shutdown of the llp driver at a later point in time. The ``server process'' is the process that accepts connections by a client. However, it does not serve the client itself but invokes a ``serving process'' that does the real work, before it loops back to accept more connections. This is because the berkeley sockets are capable of handling multiple client-server connections that rendez-vous at the same socket in parallel (actually, rendez-vous is sequential but interaction is parallel). Since the tty(or pty)-socket abstraction is not a real berkeley socket, multiple interactions are not possible with tty-sockets. Anyway, the server process stays resident throughout the lifetime of a running llp driver. Thus the server process is the main process which has to be sent a termination signal in order to shut down the whole llp driver. The ``serving process'' is the process that serves the proximal client. The proximal connection -- in the following called ``the line'' is regarded as a full duplex serial line. This line is connected to another server, which is the destination of any transaction that is initiated from the proximal client. The destination server is connected by an INET socket and communication to it is driven by the so called ``internal protocol'' (see doc/INTERNAL). The protocol used at the proximal connection can be either a kind of ANSI X3.28, the ``hybrid protocol'' or the ``minimal protocol'' of which the latter two are described in HL7 v2.1 Appendix B. The capability of the proximal client to act as a server is propagated by the llp driver by means of the ``distal server'' which is a child process of the proximal serving process. The distal server binds an INET socket which can however be connected only once at a time. The distal connection is driven by the ``internal protocol''. Since the ``line'' is regarded as a non-multiplexed line, there can be at most one transaction at a time. Thus there is no distinction between a distal server and a distal serving process. However, there is still a need for a mutual exclusion mechanism that guarantees that *either* the proximal serving process *or* the distal serving process may write characters to or consume characters from the line. The process that has permissions is said to ``have the line''. The other process is blocked in a wait state. The llp driver can use two mechanisms for mutual exclusion. The one uses a semaphore while the other uses two signals for communication about the line. Both methods have advantages and disadvantages. The semaphore method is simple and clean since it trusts a service which is provided by the operating system. The signal method (also called the ``trip'' or ``triptrap'' method) is invented by me and -- though thoroughly tested -- might eventually fail. However, trip has the advantage to always give priority to one of the processes in case of contention. When driving the ANSI X3.28 protocol, it is useful to give priority to the proximal serving process once an ENQ is received from the proximal client. The distal connection can wait (or must retry) until the proximal client is back in the mood to serve. */ int main(int argc, char *argv[]) { int sfd; /* server file descriptor */ int dlevel = LOG_WARNING; #ifndef HAVE_SYSLOG const char *logfile = LOG_FILE; #endif /* * Parse command line arguments */ { char c; int *afs[3] = {&dist_af, &to_af, &from_af}; char **addrs[3] = {&dist_addr, &to_addr, &from_addr}; int acnt = 2; while ( ( c = getopt(argc, argv, "1:ahmi:u:p:It:l:x:yY") ) != FAIL ) { switch(c) { case '1': disconnect = TRUE; break; case 'i': if(acnt < 0) usage(argv[0]); *afs[acnt] = AF_INET; *addrs[acnt] = optarg; acnt--; break; case 'u': if(acnt < 2) usage(argv[0]); *afs[acnt] = AF_UNIX; *addrs[acnt] = optarg; acnt--; break; case 'p': if(acnt < 2) usage(argv[0]); *afs[acnt] = AF_PTY; *addrs[acnt] = optarg; acnt--; break; case 't': if(acnt < 2) usage(argv[0]); *afs[acnt] = AF_TTY; *addrs[acnt] = optarg; acnt--; break; case 'I': /* initiation mode on tty line */ accept_tty_immediately = TRUE; break; case 'a': proto = P_ANSI; break; case 'h': printf("sorry, hybrid protocol is not yet implemented\n"); exit(1); proto = P_HYBR; break; case 'm': proto = P_MINI; break; case 'y': dont_fork = TRUE; dont_setsid = FALSE; break; case 'Y': dont_fork = TRUE; dont_setsid = TRUE; break; #ifdef DEBUG case 'x': dlevel = atoi(optarg); if (dlevel > LOG_DEBUG) dlevel = LOG_DEBUG; if (dlevel < LOG_WARNING) dlevel = LOG_WARNING; break; #endif #ifndef HAVE_SYSLOG case 'l': logfile = optarg; break; #endif default: usage(argv[0]); } } /* to_af and dist_af must be AF_INET, since we need the OOB data facility */ if(to_af != AF_INET || dist_af != AF_INET) usage(argv[0]); } /* * Open syslog */ openlog(LOG_IDENT, LOG_NDELAY | LOG_PID, LOG_FACILITY); setlogmask(LOG_UPTO(dlevel)); /* * Initialize signal handlers */ ssignal(SIGHUP, finish, T_SINTR); ssignal(SIGTERM, finish, T_SINTR); ssignal(SIGINT, finish, T_SINTR); ssignal(SIGALRM, finish, T_SINTR); ssignal(SIGCHLD, reaper, 0); ssignal(SIGPIPE, SIG_IGN, 0); /* * Bind proximal server socket */ switch(from_af) { case AF_UNIX: sfd = bind_unix(from_addr); break; case AF_INET: sfd = bind_inet(from_addr); break; case AF_PTY: sfd = bind_pty(from_addr); break; case AF_TTY: sfd = bind_tty(from_addr); break; } if(sfd == FAIL) { fprintf(stderr, "bind %s: %s\n", from_addr, strerror(errno)); exit(1); } /* * Listen() on pure berkeley sockets */ if(from_af == AF_UNIX || from_af == AF_INET) listen(sfd, 5); /* * Fork (run in the background), disassociate from terminal */ if(!dont_fork) { pid_t pid = fork(); if(pid < 0) { fprintf(stderr, "%s:fork: %s", argv[0], strerror(errno)); finish(1); } if(pid > 0) /* parent */ { /* return the pid of the server */ printf("%d\n", (int)pid); /* parent exits here */ exit(0); } } /* * create new process group */ if(!dont_setsid) { if(setsid() == FAIL) { syslog(LOG_ERR, "setsid: %m"); finish(1); } } /* * Log start of server process */ { char fafopt; char propt; switch(from_af) { case AF_INET: fafopt = 'i'; break; case AF_UNIX: fafopt = 'u'; break; case AF_PTY: fafopt = 'p'; break; case AF_TTY: fafopt = 't'; break; } switch(proto) { case P_ANSI: propt = 'a'; break; case P_HYBR: propt = 'h'; break; case P_MINI: propt = 'm'; break; } syslog(LOG_INFO, "%s -%c -%c %s -i %s -i %s", argv[0], propt, fafopt, from_addr, to_addr, dist_addr); } /* * The main loop(s) begin here (see explanation above). */ do { int client_fd; pid_t pid; peername_t proxi_peer; pid_t proxi_pid; /* * This is the server process' loop */ do { DBG(syslog(LOG_DEBUG, "accepting connections at %s", from_addr)); switch(from_af) { case AF_UNIX: client_fd = accept_unix(sfd); break; case AF_INET: client_fd = accept_inet(sfd); break; case AF_PTY: client_fd = accept_pty(sfd); break; case AF_TTY: client_fd = accept_tty(sfd); break; } if (client_fd == FAIL) { syslog(LOG_ERR,"accept failed: %m"); /* We will never recover from this error condition by just trying again. Something really went wrong! */ finish(1); } syslog(LOG_INFO, "connection established"); switch(from_af) { case AF_UNIX: peername_unix(client_fd, proxi_peer); break; case AF_INET: peername_inet(client_fd, proxi_peer); break; case AF_TTY: peername_tty(client_fd, proxi_peer); break; case AF_PTY: peername_pty(client_fd, proxi_peer); break; } syslog(LOG_INFO, "peername: `%s'", proxi_peer); if(dont_fork || from_af == AF_PTY || from_af == AF_TTY) { did_fork=FALSE; } else { pid=fork(); if(pid == FAIL) { syslog(LOG_ERR, "fork server: %m"); /* A fork() error happens when resources (process number, * memory) limit run short. We might try again later * however we give up this session. */ close(client_fd); sleep(RETRY_DELAY); /* allow some processes to exit */ continue; } if(pid > 0) /* I am the Parent */ { close(client_fd); /* leave my child alone with new socket */ } else /* I am the child */ { close(sfd); /* don't need my parent's socket */ did_fork=TRUE; } } } /* parent (server) loops, others (serving process) go ahead */ while(did_fork && pid > 0); if(did_fork) { /* * The serving process is not allowed to finish everything, * especially not to remove the unix domain socket (if any). * However, it must free the semaphore (if any) and kill the * distal server (if any). */ ssignal(SIGHUP, servingp_cleanup, T_SINTR); ssignal(SIGTERM, servingp_cleanup, T_SINTR); ssignal(SIGINT, servingp_cleanup, T_SINTR); } #ifdef USE_TRIP /* Proximal and distal servers both need access to the just * created socket (refered to as `the line'). A handshake * mechanism is used, that is based on two signals, SIGCLAIM * and SIGYIELD. When a process needs the line, it signals * SIGCLAIM to the other process, who in turn sends a SIGYIELD * to acknowledge the claim and enters a wait state. When the * first process finishes using the line, it sends a SIGYIELD * to release the other process from wait state. */ /* * Block SIGCHLD in order to not disturb the initialization * procedure. The proximal server must unblock the SIGCHLD when * the handshake is initialized. */ ssignal(SIGCHLD, SIG_NULL, T_BLOCK); /* The handshake is initialized by the proximal server sending * a SIGYIELD to the distal server. The SIGYIELD is blocked * until both processes have initialized their line handshake * resources. */ atsignal(SIGYIELD, T_BLOCK) { syslog(LOG_ERR, "handshake init error: got SIGYIELD"); close(client_fd); /* This is a location that is jumped to from a later point. * Thus, a distal server might well be running which we have * to kill before we exit. */ servingp_cleanup(); continue; } #else /* !USE_TRIP */ if((mutex = SEM_ALLOC(0)) == NULL) { syslog(LOG_ERR, __FUNCTION__ ":SEM_ALLOC: %m"); /* A SEM_ALLOC() error happens when semaphores run short. * We might try again later however we give up this session. * We let the client hang for the delay time because we do * not want have the server be reconnected by the same * client too early. */ sleep(RETRY_DELAY); /* allow any semaphore to become available */ close(client_fd); if(did_fork) exit(1); else continue; } #endif /* !USE_TRIP */ /* Start distal server, i.e. the process that forwards * service of the proximal client. */ proxi_pid = getpid(); if(!disconnect) { dist_pid = fork(); if(dist_pid == FAIL) { syslog(LOG_ERR, "distal fork: %m"); /* A fork() error happens when resources (process number, * memory) limit run short. We might try again later * however we give up this session after the transaction * initiated by the client is ended. */ disconnect = TRUE; } DBG(syslog(LOG_DEBUG, "dist_pid = %d", dist_pid)); if(dist_pid == 0) { /* Reset the tremination signals to their defaults, * since the distal server must neither finish() nor * servingp_cleanup() when it receives a termination * signal. */ csignal(SIGHUP); csignal(SIGTERM); csignal(SIGINT); csignal(SIGALRM); #ifdef USE_TRIP serve_distal(proto, proxi_pid, proxi_peer, client_fd, dist_af, dist_addr); #else serve_distal(proto, mutex, proxi_peer, client_fd, dist_af, dist_addr); #endif /* distal server never returns (but exits) */ } } else /* disconnect after single transaction -- no distal service */ dist_pid = 0; /* Proximal Server: */ ssignal(SIGBUS, finish, T_SINTR); ssignal(SIGSEGV, finish, T_SINTR); ssignal(SIGABRT, finish, T_SINTR); ssignal(SIGQUIT, finish, T_SINTR); #ifdef USE_TRIP serve_proximal(proto, dist_pid, proxi_peer, client_fd); #else serve_proximal(proto, mutex, proxi_peer, client_fd); #endif /* serve_proximal returns only on fatal errors or if disconnect * is true. */ DBG(syslog(LOG_INFO,"disconnect")); close(client_fd); /* if we did fork, the serving process exits here */ servingp_cleanup(1); if(from_af == AF_PTY) if((sfd = reset_pty(sfd)) == FAIL) finish(1); if(from_af == AF_TTY) if((sfd = reset_tty(sfd)) == FAIL) finish(1); } while(!did_fork); syslog(LOG_ERR, "how did I come to this point??"); exit(2); } static void reaper(int sig) { int status; pid_t pid; DBG(syslog(LOG_DEBUG, "reaper called by signal %d `%s'", sig, sys_siglist[sig])); while((pid = wait3(&status, WNOHANG, 0)) > 0) { if(WIFEXITED(status)) { 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); } } /* FIXME */ finish(SIGCHLD); } /* finish cleans up the resources that are allocated within the * main process. */ static void finish(int res) { syslog(LOG_INFO, "finish parameter: %d", res); DBG(syslog(LOG_DEBUG, "finishing ...")); if(from_af == AF_UNIX) { syslog(LOG_INFO, "removing unix domain socket `%s'", from_addr); unlink(from_addr); } #ifndef USE_TRIP /* * remove the semaphor */ SEM_FREE(mutex); #endif shutdown_all(); DBG(syslog(LOG_DEBUG, "exiting.")); exit(res); } static void shutdown_all() { sigset_t set; DBG(syslog(LOG_DEBUG, "shut down llp driver")); /* Block SIGHUP and SIGTERM, which are to be sent to the process * group, block SIGINT in order to avoid reentry: */ sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGHUP); sigaddset(&set, SIGTERM); sigprocmask(SIG_BLOCK, &set, NULL); /* Gently terminate server processes: * * 1.) please them */ syslog(LOG_INFO, "sending HUP signal to server processes"); if(kill(0, SIGHUP) == FAIL) return; syslog(LOG_INFO, "waiting %d seconds for server processes to exit", WAIT_TIME); sleep(WAIT_TIME); /* 2.) urge them */ syslog(LOG_INFO, "sending TERM signal to server processes"); if(kill(0, SIGTERM) == FAIL) return; syslog(LOG_INFO, "waiting %d seconds for server processes to exit", WAIT_TIME); sleep(WAIT_TIME); return; } static void servingp_cleanup(int res) { if(dist_pid != 0) { DBG(syslog(LOG_INFO,"shutdown distal server")); kill(dist_pid, SIGTERM); } #ifndef USE_TRIP /* * remove the semaphor */ SEM_FREE(mutex); #endif if(did_fork) exit(res); }