/* * 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" IDENT("@(#) proximal.c (Gunther Schadow) 10/13/96"); #include "llpdrv.h" #include "socket.h" #include "trip.h" #include "trap.h" #include "intern.h" #include "ansi.h" #include #include #include #include #include #include #ifdef _AIX #include #endif #define ANSI_WAIT_TIMEOUT 60 /* seconds */ #define CONNECT_RETRY_DELAY 10 /* seconds */ /* serve_proximal * * Serve the proximal site, i.e. the site which is already connected. * Wait for any character to appar on the line, then claim it and * connect to the server. Forward a message to the server and receive * the response sending it back to the line. Disconnect from server * and release the line and restart waiting for characters on the line. * This function returns only if the line became unusable for some * reason. */ void serve_proximal(int proto, #ifdef USE_TRIP pid_t dist_pid, #else semaphor mutex, #endif const char *client_name, int client_fd) { #ifdef USE_TRIP bool do_handshake = ( dist_pid != 0 ); #endif int server_fd; FILE* server_fp; ssignal(SIGPIPE, SIG_IGN, 0); #ifdef USE_TRIP /* * Setup signal handlers for line managemant */ atsignal(SIGCLAIM, T_SINTR) { DBG(syslog(LOG_DEBUG, "proximal server yields the line")); yield_line(dist_pid); syslog(LOG_INFO, "proximal server in wait state"); wait_line(); } /* in initialization phase SIGYIELD is unblocked here */ atsignal(SIGYIELD, T_SINTR) { DBG(syslog(LOG_DEBUG, "proximal server released from wait state")); goto serve_proxi; } /* * Unblock the CHLD signal. */ ssignal(SIGCHLD, SIG_NULL, 0); /* The handshake is initialized by the proximal server sending * a SIGYIELD to the distal server. The distal server has SIGYIELD * blocked until initialized, thus we can savely send SIGYIELD now. */ DBG(syslog(LOG_DEBUG, "proximal server starts handshake")); yield_line(proxi_pid); serve_proxi: #endif do { bool cont = FALSE; fd_set rfds, efds; int nfds; struct timeval asto = { ANSI_WAIT_TIMEOUT , 0 }; struct timeval *to; alarm(0); /* release any previous timout */ if(proto == P_ANSI) to = &asto; else to = NULL; DBG(syslog(LOG_DEBUG, "waiting for proximal site to send data ...")); FD_ZERO(&rfds); FD_SET(client_fd, &rfds); FD_ZERO(&efds); FD_SET(client_fd, &efds); do { nfds = select(FD_SETSIZE, &rfds, NULL, &efds, to); } while(nfds == FAIL && errno == EINTR); if(nfds == FAIL) /* failed */ { syslog(LOG_ERR, __FUNCTION__ ":select: %m"); kill(0, SIGABRT); /* programming error */ pause(); } DBG(syslog(LOG_DEBUG, "proximal server claims the line")); #ifdef USE_TRIP if(do_handshake) { if(claim_line(dist_pid, PRI_HIGH) == FAIL) { syslog(LOG_WARNING, "distal server disappeared, " "disabling handshake"); do_handshake = FALSE; } } #else /* Use the semaphore instead */ /* try to decrease the semaphor but don't just hang if it is * occupied, rather try once again and hang until the semaphor * is finally increased. Now loop back and watch again on the * line. This way, the proximal server does always try and get * trapped in the PROBEREN when something happens on the line * due to actions of the distal server. */ SEM_MODE(mutex, IPC_NOWAIT); DBG( int semval; syslog(LOG_DEBUG, __FUNCTION__ ": PROBEREN"); semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); if(PROBEREN(mutex) == FAIL) { if(errno == EAGAIN) { syslog(LOG_WARNING, "proximal server could not get the line"); SEM_MODE(mutex, 0); DBG( int semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); /* P_again: */ if(PROBEREN(mutex) == FAIL) { if(errno == EINTR) /* goto P_again */; else goto P_fault; } else { DBG( int semval; syslog(LOG_DEBUG, __FUNCTION__ ": VERHOGEN"); semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); if(VERHOGEN(mutex) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ":PROBEREN: %m"); return; } DBG( int semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); continue; } } P_fault: syslog(LOG_ERR, __FUNCTION__ ":PROBEREN: %m"); return; } DBG(int semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); #endif /* * Set a gross timer, this timer should have a very long value, * since it needs to stop the process in order to recover any * dynamic allocations in the data segement. */ alarm(10 /* minutes */ * 60 /* seconds per minute */); /* at select timeout try a ENQ-ACK-EOT transaction just to know that we are still on line */ if(nfds == 0) if(proto != P_ANSI) goto fail_client; else { ASTRM *asp; DBG(syslog(LOG_DEBUG, "polling for line status")); asp = asattach(client_fd, 1, 0); if(asp == NULL) { syslog(LOG_ERR, "cannot attach: %m"); } if(asmode(asp, AS_OUT, 0) == FAIL) switch(AS_ERRNO(asp)) { case AS_ERINT: break; case AS_EIO: syslog(LOG_ERR, "lost connection: %m"); goto fail_client; default: syslog(LOG_ERR, "lost connection: %s", ansi_errlist[AS_ERRNO(asp)]); if(proto == P_ANSI) { /* Mandatory Disconnect */ _as_send_ctrl(asp, NULL, A_DEOT, 0); } asdetach(asp, -1); goto fail_client; } asdetach(asp, 1); goto finish; } do { /* it is not possible to keep these variables local to the * case block below, since we might loop if cont is true! * only needed for minimal or hybrid protocol. */ int save_fd; FILE* client_fp; /* * Reset the gross timer, this timer should have a very long * value, since it needs to stop the process in order to * recover any dynamic allocations in the data segement. */ alarm(10 /* minutes */ * 60 /* seconds per minute */); /* * Connect to the server */ DBG(syslog(LOG_DEBUG, "connect server %s", to_addr)); if(to_af != AF_INET) { syslog(LOG_ERR, "will not connect a server by AF_%d", to_af); /* A programming error, to which we react bye abort of * the whole process group. */ kill(0, SIGABRT); pause(); } connect_again: server_fd = connect_inet(to_addr); if(server_fd == FAIL) { switch(errno) { case ECONNREFUSED: case ETIMEDOUT: case ENETUNREACH: syslog(LOG_ERR, "connection to %s failed: %m", to_addr); sleep(CONNECT_RETRY_DELAY); goto connect_again; default: syslog(LOG_ERR, "connection to %s failed: %m", to_addr); kill(0, SIGTERM); /* give up */ pause(); } } /* We cannot recover if fdopen fails */ CHK(server_fp = fdopen(server_fd, "r+"), NULL, return); /* * Communicate proximal peername to server */ if(send_peername(server_fp, client_name) == FAIL) { syslog(LOG_ERR, "send_peername failed: %m"); fclose(server_fp); goto finish; } /* * Interact depending on protocol */ switch(proto) { case P_ANSI: { ASTRM* client_asp; /* asattach(), i2ansi() and ansi2i() will detach the * ASTRM upon FAIL */ CHK(client_asp = asattach(client_fd, 0, 0), NULL, goto fail_client); /* request */ CHK(ansi2i(client_asp, server_fp), FAIL, goto xact_fail); /* response */ CHK(i2ansi(server_fp, client_asp), FAIL, goto xact_fail); /* transaction successful -- now detach the ASTRM gracefully */ CHK(asdetach(client_asp, 1), FAIL, goto fail_client); } break; case P_HYBR: syslog(LOG_ERR, "hybrid protocol is not implemented"); kill(0, SIGABRT); pause(); case P_MINI: if(!cont) /* if we are here for the first time */ { /* since fdclose will later close the file descriptor itself, we will have fdopen act on a duplicate. */ save_fd = dup(client_fd); CHK(client_fp = fdopen(save_fd, "r+"), NULL, /* however, fdopen *might* leave the fd unclosed when it fails! */ close(save_fd); goto fail_client); } /* request */ CHK(mini2i(client_fp, server_fp), FAIL, /* fclose *might* leave the fd open when it fails on fflush(){write()}! */ close(save_fd); goto xact_fail); /* result */ CHK(i2mini(server_fp, client_fp), FAIL, /* fclose *might* leave the fd open when it fails on fflush(){write()}! */ close(save_fd); goto xact_fail); /* transaction successful -- see if there are more characters on the line */ syslog(LOG_INFO, "minimal llp: %d bytes left", CHARS_LEFT(client_fp)); cont = CHARS_LEFT(client_fp) ? TRUE : FALSE; if(!cont) { /* close the line */ CHK(fclose(client_fp), EOF, /* fclose *might* leave the fd open when it fails on fflush(){write()}! */ close(save_fd); goto fail_client); } break; } fclose(server_fp); syslog(LOG_INFO, "transaction succeeded"); } while(cont); finish: #if USE_TRIP if(do_handshake) { DBG(syslog(LOG_DEBUG, "proximal server yields line to %d", dist_pid)); yield_line(dist_pid); } #else DBG( int semval; syslog(LOG_DEBUG, __FUNCTION__ ": VERHOGEN"); semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); if(VERHOGEN(mutex) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ":VERHOGEN: %m"); return; } DBG( int semval = semctl(mutex->id, 0, GETVAL, 0); syslog(LOG_DEBUG, "mutex = %d", semval); ); #endif } while(!disconnect); xact_fail: fclose(server_fp); syslog(LOG_WARNING, "transaction failed"); if(llp_errno == RF_EXTERN) goto quit; else goto finish; fail_client: fclose(server_fp); syslog(LOG_WARNING, "client interaction failed"); quit: alarm(0); /* release any previous timout */ return; }