File:  [LON-CAPA] / loncom / Attic / lonc
Revision 1.47: download - view: text, annotated - select for diffs
Mon Feb 24 19:56:30 2003 UTC (21 years, 2 months ago) by albertel
Branches: MAIN
CVS tags: version_0_6_2, HEAD
- timeouts are bad, kill the child off and hope for something better on restart

    1: #!/usr/bin/perl
    2: 
    3: # The LearningOnline Network
    4: # lonc - LON TCP-Client Domain-Socket-Server
    5: # provides persistent TCP connections to the other servers in the network
    6: # through multiplexed domain sockets
    7: #
    8: # $Id: lonc,v 1.47 2003/02/24 19:56:30 albertel Exp $
    9: #
   10: # Copyright Michigan State University Board of Trustees
   11: #
   12: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   13: #
   14: # LON-CAPA is free software; you can redistribute it and/or modify
   15: # it under the terms of the GNU General Public License as published by
   16: # the Free Software Foundation; either version 2 of the License, or
   17: # (at your option) any later version.
   18: #
   19: # LON-CAPA is distributed in the hope that it will be useful,
   20: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   21: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   22: # GNU General Public License for more details.
   23: #
   24: # You should have received a copy of the GNU General Public License
   25: # along with LON-CAPA; if not, write to the Free Software
   26: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   27: #
   28: # /home/httpd/html/adm/gpl.txt
   29: #
   30: # http://www.lon-capa.org/
   31: #
   32: # PID in subdir logs/lonc.pid
   33: # kill kills
   34: # HUP restarts
   35: # USR1 tries to open connections again
   36: 
   37: # 6/4/99,6/5,6/7,6/8,6/9,6/10,6/11,6/12,7/14,7/19,
   38: # 10/8,10/9,10/15,11/18,12/22,
   39: # 2/8,7/25 Gerd Kortemeyer
   40: # 12/05 Gerd Kortemeyer
   41: # YEAR=2001
   42: # 03/14/01,03/15,06/12,11/26,11/27,11/28 Gerd Kortemeyer
   43: # YEAR=2002
   44: # 2/19/02,02/22/02,02/25/02 Gerd Kortemeyer
   45: # 3/07/02 Ron Fox 
   46: # based on nonforker from Perl Cookbook
   47: # - server who multiplexes without forking
   48: 
   49: use lib '/home/httpd/lib/perl/';
   50: use LONCAPA::Configuration;
   51: 
   52: use POSIX;
   53: use IO::Socket;
   54: use IO::Select;
   55: use IO::File;
   56: use Socket;
   57: use Fcntl;
   58: use Tie::RefHash;
   59: use Crypt::IDEA;
   60: #use Net::Ping;
   61: use LWP::UserAgent();
   62: 
   63: $status='';
   64: $lastlog='';
   65: $conserver='SHELL';
   66: $DEBUG = 0;			# Set to 1 for annoyingly complete logs.
   67: 
   68: # -------------------------------- Set signal handlers to record abnormal exits
   69: 
   70: &status("Init exception handlers");
   71: $SIG{QUIT}=\&catchexception;
   72: $SIG{__DIE__}=\&catchexception;
   73: 
   74: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
   75: &status("Read loncapa.conf and loncapa_apache.conf");
   76: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
   77: my %perlvar=%{$perlvarref};
   78: undef $perlvarref;
   79: 
   80: # ----------------------------- Make sure this process is running from user=www
   81: &status("Check user ID");
   82: my $wwwid=getpwnam('www');
   83: if ($wwwid!=$<) {
   84:    $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
   85:    $subj="LON: $perlvar{'lonHostID'} User ID mismatch";
   86:    system("echo 'User ID mismatch.  lonc must be run as user www.' |\
   87:  mailto $emailto -s '$subj' > /dev/null");
   88:    exit 1;
   89: }
   90: 
   91: # --------------------------------------------- Check if other instance running
   92: 
   93: my $pidfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
   94: 
   95: if (-e $pidfile) {
   96:    my $lfh=IO::File->new("$pidfile");
   97:    my $pide=<$lfh>;
   98:    chomp($pide);
   99:    if (kill 0 => $pide) { die "already running"; }
  100: }
  101: 
  102: # ------------------------------------------------------------- Read hosts file
  103: 
  104: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
  105: 
  106: while ($configline=<CONFIG>) {
  107:     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
  108:     chomp($ip);
  109:     if ($ip) {
  110:      $hostip{$id}=$ip;
  111:      $hostname{$id}=$name;
  112:     }
  113: }
  114: 
  115: close(CONFIG);
  116: 
  117: # -------------------------------------------------------- Routines for forking
  118: 
  119: %children               = ();       # keys are current child process IDs,
  120:                                     # values are hosts
  121: %childpid               = ();       # the other way around
  122: 
  123: %childatt               = ();       # number of attempts to start server
  124:                                     # for ID
  125: 
  126: $childmaxattempts=5;
  127: 
  128: # ---------------------------------------------------- Fork once and dissociate
  129: &status("Fork and dissociate");
  130: $fpid=fork;
  131: exit if $fpid;
  132: die "Couldn't fork: $!" unless defined ($fpid);
  133: 
  134: POSIX::setsid() or die "Can't start new session: $!";
  135: 
  136: $conserver='PARENT';
  137: 
  138: # ------------------------------------------------------- Write our PID on disk
  139: &status("Write PID");
  140: $execdir=$perlvar{'lonDaemons'};
  141: open (PIDSAVE,">$execdir/logs/lonc.pid");
  142: print PIDSAVE "$$\n";
  143: close(PIDSAVE);
  144: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
  145: 
  146: # ----------------------------- Ignore signals generated during initial startup
  147: $SIG{HUP}=$SIG{USR1}='IGNORE';
  148: # ------------------------------------------------------- Now we are on our own
  149:     
  150: # Fork off our children, one for every server
  151: 
  152: &status("Forking ...");
  153: 
  154: foreach $thisserver (keys %hostip) {
  155:     #if (&online($hostname{$thisserver})) {
  156:        make_new_child($thisserver);
  157:     #}
  158: }
  159: 
  160: &logthis("Done starting initial servers");
  161: # ----------------------------------------------------- Install signal handlers
  162: 
  163: 
  164: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
  165: $SIG{HUP}  = \&HUPSMAN;
  166: $SIG{USR1} = \&USRMAN;
  167: 
  168: # And maintain the population.
  169: while (1) {
  170:     my $deadpid = wait;		# Wait for the next child to die.
  171:                                 # See who died and start new one
  172:                                 # or a signal (e.g. USR1 for restart).
  173:                                 # if a signal, the wait will fail
  174:                                 # This is ordinarily detected by
  175:                                 # checking for the existence of the
  176:                                 # pid index inthe children hash since
  177:                                 # the return value from a failed wait is -1
  178:                                 # which is an impossible PID.
  179:     &status("Woke up");
  180:     my $skipping='';
  181: 
  182:     if(exists($children{$deadpid})) {
  183: 
  184: 	$thisserver = $children{$deadpid}; # Look name of dead guy's peer.
  185: 
  186: 	delete($children{$deadpid}); # Get rid of dead hash entry.
  187: 
  188: 	if($childatt{$thisserver} < $childmaxattempts) {
  189: 	    $childatt{$thisserver}++;
  190: 	    &logthis(
  191: 	       "<font color=yellow>INFO: Trying to reconnect for $thisserver "
  192:             ."($childatt{$thisserver} of $childmaxattempts attempts)</font>"); 
  193: 	    make_new_child($thisserver);
  194: 	
  195: 	}
  196: 	else {
  197: 	    $skipping .= $thisserver.' ';
  198: 	}
  199: 	if($skipping) {
  200: 	    &logthis("<font color=blue>WARNING: Skipped $skipping</font>");
  201:   
  202: 	}
  203:     }
  204: 
  205: }
  206: 
  207: 
  208: 
  209: sub make_new_child {
  210:    
  211:     $newserver=shift;
  212:     my $pid;
  213:     my $sigset;
  214:     &logthis("Attempting to start child for server $newserver");
  215:     # block signal for fork
  216:     $sigset = POSIX::SigSet->new(SIGINT);
  217:     sigprocmask(SIG_BLOCK, $sigset)
  218:         or die "Can't block SIGINT for fork: $!\n";
  219:     
  220:     die "fork: $!" unless defined ($pid = fork);
  221:     
  222:     if ($pid) {
  223:         # Parent records the child's birth and returns.
  224:         sigprocmask(SIG_UNBLOCK, $sigset)
  225:             or die "Can't unblock SIGINT for fork: $!\n";
  226:         $children{$pid} = $newserver;
  227:         $childpid{$newserver} = $pid;
  228:         return;
  229:     } else {
  230:         $conserver=$newserver;
  231:         # Child can *not* return from this subroutine.
  232:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
  233:         $SIG{USR1}= \&logstatus;
  234:    
  235:         # unblock signals
  236:         sigprocmask(SIG_UNBLOCK, $sigset)
  237:             or die "Can't unblock SIGINT for fork: $!\n";
  238: 
  239: # ----------------------------- This is the modified main program of non-forker
  240: 
  241: $port = "$perlvar{'lonSockDir'}/$conserver";
  242: 
  243: unlink($port);
  244: 
  245: # -------------------------------------------------------------- Open other end
  246: 
  247: &openremote($conserver);
  248: 	&logthis("<font color=green> Connection to $conserver open </font>");
  249: # ----------------------------------------- We're online, send delayed messages
  250:     &status("Checking for delayed messages");
  251: 
  252:     my @allbuffered;
  253:     my $path="$perlvar{'lonSockDir'}/delayed";
  254:     opendir(DIRHANDLE,$path);
  255:     @allbuffered=grep /\.$conserver$/, readdir DIRHANDLE;
  256:     closedir(DIRHANDLE);
  257:     my $dfname;
  258:     foreach (sort @allbuffered) {
  259:         &status("Sending delayed: $_");
  260:         $dfname="$path/$_";
  261:         if($DEBUG) { &logthis('Sending '.$dfname); }
  262:         my $wcmd;
  263:         {
  264:          my $dfh=IO::File->new($dfname);
  265:          $cmd=<$dfh>;
  266:         }
  267:         chomp($cmd);
  268:         my $bcmd=$cmd;
  269:         if ($cmd =~ /^encrypt\:/) {
  270: 	    my $rcmd=$cmd;
  271:             $rcmd =~ s/^encrypt\://;
  272:             chomp($rcmd);
  273:             my $cmdlength=length($rcmd);
  274:             $rcmd.="         ";
  275:             my $encrequest='';
  276:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  277:                 $encrequest.=
  278:                     unpack("H16",$cipher->encrypt(substr($rcmd,$encidx,8)));
  279:             }
  280:             $cmd="enc:$cmdlength:$encrequest\n";
  281:         }
  282: 	$answer = londtransaction($remotesock, $cmd, 60);
  283: 	chomp($answer);
  284: 
  285:         if (($answer ne '') && ($@!~/timeout/)) {
  286: 	    unlink("$dfname");
  287:             &logthis("Delayed $cmd: >$answer<");
  288:             &logperm("S:$conserver:$bcmd");
  289:         }        
  290:     }
  291: 	if($DEBUG) { &logthis("<font color=green> Delayed transactions sent"); }
  292: 
  293: # ------------------------------------------------------- Listen to UNIX socket
  294: &status("Opening socket");
  295: unless (
  296:   $server = IO::Socket::UNIX->new(Local  => $port,
  297:                                   Type   => SOCK_STREAM,
  298:                                   Listen => 10 )
  299:    ) { 
  300:        my $st=120+int(rand(240));
  301:        &logthis(
  302:          "<font color=blue>WARNING: ".
  303:          "Can't make server socket ($st secs):  .. exiting</font>");
  304:        sleep($st);
  305:        exit; 
  306:      };
  307:    
  308: # -----------------------------------------------------------------------------
  309: 
  310: &logthis("<font color=green>$conserver online</font>");
  311: 
  312: # -----------------------------------------------------------------------------
  313: # begin with empty buffers
  314: %inbuffer  = ();
  315: %outbuffer = ();
  316: %ready     = ();
  317: %servers   = ();	# To be compatible with make filevector.  indexed by
  318: 			# File ids, values are sockets.
  319: 			# note that the accept socket is omitted.
  320: 
  321: tie %ready, 'Tie::RefHash';
  322: 
  323: # nonblock($server);
  324: # $select = IO::Select->new($server);
  325: 
  326: # Main loop: check reads/accepts, check writes, check ready to process
  327: 
  328: status("Main loop $conserver");
  329: while (1) {
  330:     my $client;
  331:     my $rv;
  332:     my $data;
  333: 
  334:     my $infdset;		# bit vec of fd's to select on input.
  335: 
  336:     my $outfdset;		# Bit vec of fd's to select on output.
  337: 
  338: 
  339:     $infdset = MakeFileVector(\%servers);
  340:     $outfdset= MakeFileVector(\%outbuffer);
  341:     vec($infdset, $server->fileno, 1) = 1;
  342:     if($DEBUG) {
  343: 	&logthis("Adding ".$server->fileno.
  344: 		 " to input select vector (listner)".
  345: 		 unpack("b*",$infdset)."\n");
  346:     }
  347:     DoSelect(\$infdset, \$outfdset); # Wait for input.
  348:     if($DEBUG) {
  349: 	&logthis("Doselect completed!");
  350: 	&logthis("ins = ".unpack("b*",$infdset)."\n");
  351: 	&logthis("outs= ".unpack("b*",$outfdset)."\n");
  352: 		 
  353:     }
  354: 
  355:     # Checkfor new connections:
  356:     if (vec($infdset, $server->fileno, 1)) {
  357: 	if($DEBUG) {
  358: 	    &logthis("New connection established");
  359: 	}
  360: 	# accept a new connection
  361: 	&status("Accept new connection: $conserver");
  362: 	$client = $server->accept();
  363: 	if($DEBUG) {
  364: 	    &logthis("New client fd = ".$client->fileno."\n");
  365: 	}
  366: 	$servers{$client->fileno} = $client;
  367: 	nonblock($client);
  368: 	$client->sockopt(SO_KEEPALIVE, 1);# Enable monitoring of
  369: 	                                  # connection liveness.
  370:     }
  371:     HandleInput($infdset, \%servers, \%inbuffer, \%outbuffer, \%ready);
  372:     HandleOutput($outfdset, \%servers, \%outbuffer, \%inbuffer,
  373: 		 \%ready);
  374: # -------------------------------------------------------- Wow, connection lost
  375: 
  376: }
  377:    
  378:     }
  379: }
  380: 
  381: # ------------------------------------------------------- End of make_new_child
  382: 
  383: 
  384: #
  385: #  Make a vector of file descriptors to wait for in a select.
  386: #  parameters:
  387: #     \%fdhash  -reference to a hash which has IO::Socket's as indices.  
  388: #                We only care about the indices, not the values.
  389: #  A select vector is created from all indices of the hash.
  390: 
  391: sub MakeFileVector
  392: {
  393:     my $fdhash = shift;
  394:     my $selvar = "";
  395: 
  396:     foreach $socket (keys %$fdhash) {
  397: 	if($DEBUG) {
  398: 	    &logthis("Adding  ".$socket.
  399: 		     "to select vector. (client)\n");
  400: 	}
  401: 	vec($selvar, $socket, 1) = 1;
  402:     }
  403:     return $selvar;
  404: }
  405: 
  406: 
  407: #
  408: #  HandleOutput:
  409: #    Processes output on a buffered set of file descriptors which are
  410: #    ready to be read.
  411: #  Parameters:
  412: #    $selvector - Vector of file descriptors which are writable.
  413: #    \%sockets  - Vector of socket references indexed by socket.
  414: #    \%buffers  - Reference to a hash containing output buffers.
  415: #                 Hashes are indexed by sockets.  The file descriptors of some
  416: #                 of those sockets will be present in $selvector.
  417: #                 For each one of those, we will attempt to write the output
  418: #                 buffer to the socket.  Note that we will assume that
  419: #                 the sockets are being run in non blocking mode.
  420: #   \%inbufs    - Reference to hash containing input buffers.
  421: #   \%readys    - Reference to hash containing flags for items with complete
  422: #                 requests.
  423: #
  424: sub HandleOutput
  425: {
  426:     my $selvector = shift;
  427:     my $sockets   = shift;
  428:     my $buffers   = shift;
  429:     my $inbufs    = shift;
  430:     my $readys    = shift;
  431:     my $sock;
  432: 
  433:     if($DEBUG) {
  434: 	&logthis("HandleOutput entered\n");
  435:     }
  436: 
  437:     foreach $sock (keys %$sockets) {
  438: 	my $socket = $sockets->{$sock};
  439: 	if(vec($selvector, $sock, 1)) { # $socket is writable.
  440: 	    if($DEBUG) {
  441: 		&logthis("Sending $buffers->{$sock} \n");
  442: 	    }
  443: 	    my $rv = $socket->send($buffers->{$sock}, 0);
  444: 	    $errno = $!;
  445: 	    unless ($buffers->{$sock} eq "con_lost\n") {
  446: 		unless (defined $rv) { # Write failed... could be EINTR
  447: 		    unless ($errno == POSIX::EINTR) {
  448: 			&logthis("Write failed on writable socket");
  449: 		    }		# EINTR is not an error .. just retry.
  450: 		    next;
  451: 		}
  452: 		if( ($rv == length $buffers->{$sock})    ||
  453: 		    ($errno == POSIX::EWOULDBLOCK)       ||
  454: 		    ($errno == POSIX::EAGAIN)            || # same as above.
  455: 		    ($errno == POSIX::EINTR)             || # signal during IO
  456: 		    ($errno == 0)) {
  457: 		    substr($buffers->{$sock}, 0, $rv)=""; # delete written part
  458: 		    delete $buffers->{$sock} unless length $buffers->{$sock};
  459: 		} else {
  460: 		    # For some reason the write failed with an error code
  461: 		    # we didn't look for.  Shutdown the socket.
  462: 		    &logthis("Unable to write data with ".$errno.": ".
  463: 			     "Dropping data: ".length($buffers->{$sock}).
  464: 			     ", $rv");
  465: 		    #
  466: 		    # kill off the buffers in the hash:
  467: 
  468: 		    delete $buffers->{$sock};
  469: 		    delete $inbufs->{$sock};
  470: 		    delete $readys->{$sock};
  471: 
  472: 		    close($socket); # Close the client socket.
  473: 		    next;
  474: 		}
  475: 	    } else {		# Kludgy way to mark lond connection lost.
  476: 		&logthis(
  477: 		 "<font color=red>CRITICAL lond connection lost</font>");
  478: 		status("Connection lost");
  479: 		$remotesock->shutdown(2);
  480: 		&logthis("Attempting to open a new connection");
  481: 		&openremote($conserver);
  482: 	    }
  483: 		   
  484: 	}
  485:     }
  486: 
  487: }
  488: #
  489: #   HandleInput - Deals with input on client sockets.
  490: #                 Each socket has an associated input buffer.
  491: #                 For each readable socket, the currently available
  492: #                 data is appended to this buffer.
  493: #                 If necessary, the buffer is created.
  494: #                 On various failures, we may shutdown the client.
  495: #  Parameters:
  496: #     $selvec   - Vector of readable sockets.
  497: #     \%sockets - Refers to the  Hash of sockets indexed by sockets.  
  498: #                 Each of these may or may not have it's fd bit set 
  499: #                 in the $selvec.
  500: #     \%ibufs   - Refers to the hash of input buffers indexed by socket.
  501: #     \%obufs   - Hash of output buffers indexed by socket. 
  502: #     \%ready   - Hash of ready flags indicating the existence of a completed
  503: #                 Request.
  504: sub HandleInput 
  505: {
  506: 
  507:     # Marshall the parameters.   Note that the hashes are actually
  508:     # references not values.
  509: 
  510:     my $selvec  = shift;
  511:     my $sockets = shift;
  512:     my $ibufs   = shift;
  513:     my $obufs   = shift;
  514:     my $ready   = shift;
  515:     my $sock;
  516: 
  517:     if($DEBUG) {
  518: 	&logthis("Entered HandleInput\n");
  519:     }
  520:     foreach $sock (keys %$sockets) {
  521: 	my $socket = $sockets->{$sock};
  522: 	if(vec($selvec, $sock, 1)) { # Socket which is readable.
  523: 
  524: 	    #  Attempt to read the data and do error management.
  525: 	    my $data = '';
  526: 	    my $rv = $socket->recv($data, POSIX::BUFSIZ, 0);
  527: 	    if($DEBUG) {
  528: 		&logthis("Received $data from socket");
  529: 	    }
  530: 	    unless (defined($rv) && length $data) {
  531: 
  532: 		# Read an end of file.. this is a disconnect from the peer.
  533: 
  534: 		delete $sockets->{$sock};
  535: 		delete $ibufs->{$sock};
  536: 		delete $obufs->{$sock};
  537: 		delete $ready->{$sock};
  538: 
  539: 		status("Idle");
  540: 		close $socket;
  541: 		next;
  542: 	    }
  543: 	    #  Append the read data to the input buffer. If the buffer
  544: 	    # now contains a \n the request is complete and we can 
  545: 	    # mark this in the $ready hash (one request for each \n.)
  546: 
  547: 	    $ibufs->{$sock} .= $data;
  548: 	    while($ibufs->{$sock} =~ s/(.*\n)//) {
  549: 		push(@{$ready->{$sock}}, $1);
  550: 	    }
  551: 	    
  552: 	}
  553:     }
  554:     #  Now handle any requests which are ready:
  555: 
  556:     foreach $client (keys %ready) {
  557: 	handle($client);
  558:     }
  559: }
  560: 
  561: # DoSelect:  does a select with no timeout.  On signal (errno == EINTR), 
  562: #            the select is retried until there are items in the returned
  563: #            vectors.  
  564: #
  565: # Parameters:
  566: #   \$readvec   - Reference to a vector of file descriptors to 
  567: #                 check for readability.
  568: #   \$writevec  - Reference to a vector of file descriptors to check for
  569: #                 writability.
  570: #  On exit, the referents are modified with vectors indicating which 
  571: #  file handles are readable/writable.
  572: #
  573: sub DoSelect {
  574:     my $readvec = shift;
  575:     my $writevec= shift;
  576:     my $outs;
  577:     my $ins;
  578: 
  579:     while (1) {
  580: 	my $nfds = select( $ins = $$readvec, $outs = $$writevec, undef, undef);
  581: 	if($nfds) {
  582: 	    if($DEBUG) {
  583: 		&logthis("select exited with ".$nfds." fds\n");
  584: 		&logthis("ins = ".unpack("b*",$ins).
  585: 			 " readvec = ".unpack("b*",$$readvec)."\n");
  586: 		&logthis("outs = ".unpack("b*",$outs).
  587: 			 " writevec = ".unpack("b*",$$writevec)."\n");
  588: 	    }
  589: 	    $$readvec  = $ins;
  590: 	    $$writevec = $outs;
  591: 	    return;
  592: 	} else {
  593: 	    if($DEBUG) {
  594: 		&logthis("Select exited with no bits set in mask\n");
  595: 	    }
  596: 	    die "Select failed" unless $! == EINTR;
  597: 	}
  598:     }
  599: }
  600: 
  601: # handle($socket) deals with all pending requests for $client
  602: #
  603: sub handle {
  604:     # requests are in $ready{$client}
  605:     # send output to $outbuffer{$client}
  606:     my $client = shift;
  607:     my $request;
  608:     foreach $request (@{$ready{$client}}) {
  609: # ============================================================= Process request
  610:         # $request is the text of the request
  611:         # put text of reply into $outbuffer{$client}
  612: # ------------------------------------------------------------ Is this the end?
  613: 	chomp($request);
  614: 	if($DEBUG) {
  615:      &logthis("<font color=green> Request $request processing starts</font>");
  616:         }
  617:         if ($request eq "close_connection_exit\n") {
  618: 	    &status("Request close connection");
  619:            &logthis(
  620:      "<font color=red>CRITICAL: Request Close Connection ... exiting</font>");
  621:            $remotesock->shutdown(2);
  622:            $server->close();
  623:            exit;
  624:         }
  625: # -----------------------------------------------------------------------------
  626:         if ($request =~ /^encrypt\:/) {
  627: 	    my $cmd=$request;
  628:             $cmd =~ s/^encrypt\://;
  629:             chomp($cmd);
  630:             my $cmdlength=length($cmd);
  631:             $cmd.="         ";
  632:             my $encrequest='';
  633:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  634:                 $encrequest.=
  635:                     unpack("H16",$cipher->encrypt(substr($cmd,$encidx,8)));
  636:             }
  637:             $request="enc:$cmdlength:$encrequest";
  638:         }
  639: # --------------------------------------------------------------- Main exchange
  640: 	$answer = londtransaction($remotesock, $request, 300);
  641: 
  642: 	if($DEBUG) { 
  643: 	    &logthis("<font color=green> Request data exchange complete");
  644: 	}
  645: 	if ($@=~/timeout/) { 
  646: 	    $answer='';
  647: 	    &logthis(
  648: 		     "<font color=red>CRITICAL: Timeout: $request</font>");
  649: 	}  
  650: 
  651: 
  652:         if ($answer) {
  653: 	   if ($answer =~ /^enc/) {
  654:                my ($cmd,$cmdlength,$encinput)=split(/:/,$answer);
  655:                chomp($encinput);
  656: 	       $answer='';
  657:                for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
  658:                   $answer.=$cipher->decrypt(
  659:                    pack("H16",substr($encinput,$encidx,16))
  660:                   );
  661: 	       }
  662: 	      $answer=substr($answer,0,$cmdlength);
  663: 	      $answer.="\n";
  664: 	   }
  665: 	   if($DEBUG) {
  666: 	       &logthis("sending $answer to client\n");
  667: 	   }
  668:            $outbuffer{$client} .= $answer;
  669:         } else {
  670:            $outbuffer{$client} .= "con_lost\n";
  671:         }
  672: 
  673:      &status("Completed: $request");
  674: 	if($DEBUG) {
  675: 	    &logthis("<font color=green> Request processing complete</font>");
  676: 	}
  677: # ===================================================== Done processing request
  678:     }
  679:     delete $ready{$client};
  680: # -------------------------------------------------------------- End non-forker
  681:     if($DEBUG) {
  682: 	&logthis("<font color=green> requests for child handled</font>");
  683:     }
  684: }
  685: # ---------------------------------------------------------- End make_new_child
  686: 
  687: # nonblock($socket) puts socket into nonblocking mode
  688: sub nonblock {
  689:     my $socket = shift;
  690:     my $flags;
  691: 
  692:     
  693:     $flags = fcntl($socket, F_GETFL, 0)
  694:             or die "Can't get flags for socket: $!\n";
  695:     fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
  696:             or die "Can't make socket nonblocking: $!\n";
  697: }
  698: 
  699: 
  700: sub openremote {
  701: # ---------------------------------------------------- Client to network server
  702: 
  703:     my $conserver=shift;
  704: 
  705: &status("Opening TCP $conserver");
  706:     my $st=120+int(rand(240)); # Sleep before opening:
  707: 
  708: unless (
  709:   $remotesock = IO::Socket::INET->new(PeerAddr => $hostip{$conserver},
  710:                                       PeerPort => $perlvar{'londPort'},
  711:                                       Proto    => "tcp",
  712:                                       Type     => SOCK_STREAM)
  713:    ) { 
  714: 
  715:        &logthis(
  716: "<font color=blue>WARNING: Couldn't connect to $conserver ($st secs): </font>");
  717:        sleep($st);
  718:        exit; 
  719:      };
  720: # ----------------------------------------------------------------- Init dialog
  721: 
  722: &logthis("<font color=green>INFO Connected to $conserver, initing </font>");
  723: &status("Init dialogue: $conserver");
  724: 
  725:     $answer = londtransaction($remotesock, "init", 60);
  726:     chomp($answer);
  727:     $answer = londtransaction($remotesock, $answer, 60);
  728:     chomp($answer);
  729:  
  730:      if ($@=~/timeout/) {
  731: 	 &logthis("Timed out during init.. exiting");
  732:          exit;
  733:      }
  734: 
  735: if ($answer ne 'ok') {
  736:        &logthis("Init reply: >$answer<");
  737:        my $st=120+int(rand(240));
  738:        &logthis(
  739: "<font color=blue>WARNING: Init failed ($st secs)</font>");
  740:        sleep($st);
  741:        exit; 
  742: }
  743: 
  744: sleep 5;
  745: &status("Ponging $conserver");
  746: print $remotesock "pong\n";
  747: $answer=<$remotesock>;
  748: chomp($answer);
  749: if ($answer!~/^$conserver/) {
  750:    &logthis("Pong reply: >$answer<");
  751: }
  752: # ----------------------------------------------------------- Initialize cipher
  753: 
  754: &status("Initialize cipher");
  755: print $remotesock "ekey\n";
  756: my $buildkey=<$remotesock>;
  757: my $key=$conserver.$perlvar{'lonHostID'};
  758: $key=~tr/a-z/A-Z/;
  759: $key=~tr/G-P/0-9/;
  760: $key=~tr/Q-Z/0-9/;
  761: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
  762: $key=substr($key,0,32);
  763: my $cipherkey=pack("H32",$key);
  764: if ($cipher=new IDEA $cipherkey) {
  765:    &logthis("Secure connection initialized");
  766: } else {
  767:    my $st=120+int(rand(240));
  768:    &logthis(
  769:      "<font color=blue>WARNING: ".
  770:      "Could not establish secure connection ($st secs)!</font>");
  771:    sleep($st);
  772:    exit;
  773: }
  774:     &logthis("<font color=green> Remote open success </font>");
  775: }
  776: 
  777: 
  778: 
  779: # grabs exception and records it to log before exiting
  780: sub catchexception {
  781:     my ($signal)=@_;
  782:     $SIG{QUIT}='DEFAULT';
  783:     $SIG{__DIE__}='DEFAULT';
  784:     chomp($signal);
  785:     &logthis("<font color=red>CRITICAL: "
  786:      ."ABNORMAL EXIT. Child $$ for server [$wasserver] died through "
  787:      ."\"$signal\" with parameter </font>");
  788:     die("Signal abend");
  789: }
  790: 
  791: # -------------------------------------- Routines to see if other box available
  792: 
  793: #sub online {
  794: #    my $host=shift;
  795: #    &status("Pinging ".$host);
  796: #    my $p=Net::Ping->new("tcp",20);
  797: #    my $online=$p->ping("$host");
  798: #    $p->close();
  799: #    undef ($p);
  800: #    return $online;
  801: #}
  802: 
  803: sub connected {
  804:     my ($local,$remote)=@_;
  805:     &status("Checking connection $local to $remote");
  806:     $local=~s/\W//g;
  807:     $remote=~s/\W//g;
  808: 
  809:     unless ($hostname{$local}) { return 'local_unknown'; }
  810:     unless ($hostname{$remote}) { return 'remote_unknown'; }
  811: 
  812:     #unless (&online($hostname{$local})) { return 'local_offline'; }
  813: 
  814:     my $ua=new LWP::UserAgent;
  815:     
  816:     my $request=new HTTP::Request('GET',
  817:       "http://".$hostname{$local}.'/cgi-bin/ping.pl?'.$remote);
  818: 
  819:     my $response=$ua->request($request);
  820: 
  821:     unless ($response->is_success) { return 'local_error'; }
  822: 
  823:     my $reply=$response->content;
  824:     $reply=(split("\n",$reply))[0];
  825:     $reply=~s/\W//g;
  826:     if ($reply ne $remote) { return $reply; }
  827:     return 'ok';
  828: }
  829: 
  830: 
  831: 
  832: sub hangup {
  833:     foreach (keys %children) {
  834:         $wasserver=$children{$_};
  835:         &status("Closing $wasserver");
  836:         &logthis('Closing '.$wasserver.': '.&subreply('exit',$wasserver));
  837:         &status("Kill PID $_ for $wasserver");
  838: 	kill ('INT',$_);
  839:     }
  840: }
  841: 
  842: sub HUNTSMAN {                      # signal handler for SIGINT
  843:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
  844:     &hangup();
  845:     my $execdir=$perlvar{'lonDaemons'};
  846:     unlink("$execdir/logs/lonc.pid");
  847:     &logthis("<font color=red>CRITICAL: Shutting down</font>");
  848:     exit;                           # clean up with dignity
  849: }
  850: 
  851: sub HUPSMAN {                      # signal handler for SIGHUP
  852:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
  853:     &hangup();
  854:     &logthis("<font color=red>CRITICAL: Restarting</font>");
  855:     unlink("$execdir/logs/lonc.pid");
  856:     my $execdir=$perlvar{'lonDaemons'};
  857:     exec("$execdir/lonc");         # here we go again
  858: }
  859: 
  860: sub checkchildren {
  861:     &initnewstatus();
  862:     &logstatus();
  863:     &logthis('Going to check on the children');
  864:     foreach (sort keys %children) {
  865: 	sleep 1;
  866:         unless (kill 'USR1' => $_) {
  867: 	    &logthis ('<font color=red>CRITICAL: Child '.$_.' is dead</font>');
  868:             &logstatus($$.' is dead');
  869:         } 
  870:     }
  871: }
  872: 
  873: sub USRMAN {
  874:     &logthis("USR1: Trying to establish connections again");
  875:     #
  876:     #  It is really important not to just clear the childatt hash or we will
  877:     #  lose all memory of the children.  What we really want to do is this:
  878:     #  For each index where childatt is >= $childmaxattempts
  879:     #  Zero the associated counter and do a make_child for the host.
  880:     #  Regardles, the childatt entry is zeroed:
  881:     my $host;
  882:     foreach $host (keys %childatt) {
  883: 	if ($childatt{$host} >= $childmaxattempts) {
  884: 	    $childatt{$host} = 0;
  885: 	    &logthis("<font color=green>INFO: Restarting child for server: "
  886: 		     .$host."</font>\n");
  887: 	    make_new_child($host);
  888: 	}
  889: 	else {
  890: 	    $childatt{$host} = 0;
  891: 	}
  892:     }
  893:     &checkchildren();		# See if any children are still dead...
  894: }
  895: 
  896: # -------------------------------------------------- Non-critical communication
  897: sub subreply { 
  898:  my ($cmd,$server)=@_;
  899:  my $answer='';
  900:  if ($server ne $perlvar{'lonHostID'}) { 
  901:     my $peerfile="$perlvar{'lonSockDir'}/$server";
  902:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
  903:                                       Type    => SOCK_STREAM,
  904:                                       Timeout => 10)
  905:        or return "con_lost";
  906: 
  907: 
  908:     $answer = londtransaction($sclient, $cmd, 10);
  909: 
  910:     if ((!$answer) || ($@=~/timeout/)) { $answer="con_lost"; }
  911:     $SIG{ALRM}='DEFAULT';
  912:     $SIG{__DIE__}=\&catchexception;
  913:  } else { $answer='self_reply'; }
  914:  return $answer;
  915: }
  916: 
  917: # --------------------------------------------------------------------- Logging
  918: 
  919: sub logthis {
  920:     my $message=shift;
  921:     my $execdir=$perlvar{'lonDaemons'};
  922:     my $fh=IO::File->new(">>$execdir/logs/lonc.log");
  923:     my $now=time;
  924:     my $local=localtime($now);
  925:     $lastlog=$local.': '.$message;
  926:     print $fh "$local ($$) [$conserver] [$status]: $message\n";
  927: }
  928: 
  929: #--------------------------------------  londtransaction:
  930: #  
  931: #  Performs a transaction with lond with timeout support.
  932: #    result = londtransaction(socket,request,timeout)
  933: #
  934: sub londtransaction {
  935:     my ($socket, $request, $tmo) = @_;
  936: 
  937:     if($DEBUG) {
  938: 	&logthis("londtransaction request: $request");
  939:     }
  940: 
  941:     # Set the signal handlers: ALRM for timeout and disble the others.
  942: 
  943:     $SIG{ALRM} = sub { die "timeout" };
  944:     $SIG{__DIE__} = 'DEFAULT';
  945:     
  946:     # Disable all but alarm so that only that can interupt the
  947:     # send /receive.
  948:     #
  949:     my $sigset = POSIX::SigSet->new(QUIT, USR1, HUP, INT, TERM);
  950:     my $priorsigs = POSIX::SigSet->new;
  951:     unless (defined sigprocmask(SIG_BLOCK, $sigset, $priorsigs)) {
  952: 	&logthis("<font color=red> CRITICAL -- londtransaction ".
  953: 		"failed to block signals </font>");
  954: 	die "could not block signals in londtransaction";
  955:     }
  956:     $answer = '';
  957:     #
  958:     #  Send request to lond.
  959:     #
  960:     eval { 
  961: 	alarm($tmo);
  962: 	print $socket "$request\n";
  963: 	alarm(0);
  964:     };
  965:     #  If request didn't timeout, try for the response.
  966:     #
  967: 
  968:     if ($@!~/timeout/) {
  969: 	eval {
  970: 	    alarm($tmo);
  971: 	    $answer = <$socket>;
  972: 	    if($DEBUG) {
  973: 		&logthis("Received $answer in londtransaction");
  974: 	    }
  975: 	    alarm(0);
  976: 	};
  977:     } else {
  978: 	&logthis("lonc - suiciding on send Timeout");
  979: 	die("lonc - suiciding on send Timeout");
  980:     }
  981:     if ($@ =~ /timeout/) {
  982: 	&logthis("lonc - suiciding on send Timeout");
  983: 	die("lonc - suiciding on send Timeout");
  984:     }
  985:     #
  986:     # Restore the initial sigmask set.
  987:     #
  988:     unless (defined sigprocmask(SIG_UNBLOCK, $priorsigs)) {
  989: 	&logthis("<font color=red> CRITICAL -- londtransaction ".
  990: 		"failed to re-enable signal processing. </font>");
  991: 	die "londtransaction failed to re-enable signals";
  992:     }
  993:     #
  994:     # go back to the prior handler set.
  995:     #
  996:     $SIG{ALRM} = 'DEFAULT';
  997:     $SIG{__DIE__} = \&cathcexception;
  998: 
  999:     #    chomp $answer;
 1000:     if ($DEBUG) {
 1001: 	&logthis("Returning $answer in londtransaction");
 1002:     }
 1003:     return $answer;
 1004: 
 1005: }
 1006: 
 1007: sub logperm {
 1008:     my $message=shift;
 1009:     my $execdir=$perlvar{'lonDaemons'};
 1010:     my $now=time;
 1011:     my $local=localtime($now);
 1012:     my $fh=IO::File->new(">>$execdir/logs/lonnet.perm.log");
 1013:     print $fh "$now:$message:$local\n";
 1014: }
 1015: # ------------------------------------------------------------------ Log status
 1016: 
 1017: sub logstatus {
 1018:     my $docdir=$perlvar{'lonDocRoot'};
 1019:     my $fh=IO::File->new(">>$docdir/lon-status/loncstatus.txt");
 1020:     print $fh $$."\t".$conserver."\t".$status."\t".$lastlog."\n";
 1021: }
 1022: 
 1023: sub initnewstatus {
 1024:     my $docdir=$perlvar{'lonDocRoot'};
 1025:     my $fh=IO::File->new(">$docdir/lon-status/loncstatus.txt");
 1026:     my $now=time;
 1027:     my $local=localtime($now);
 1028:     print $fh "LONC status $local - parent $$\n\n";
 1029: }
 1030: 
 1031: # -------------------------------------------------------------- Status setting
 1032: 
 1033: sub status {
 1034:     my $what=shift;
 1035:     my $now=time;
 1036:     my $local=localtime($now);
 1037:     $status=$local.': '.$what;
 1038:     $0='lonc: '.$what.' '.$local;
 1039: }
 1040: 
 1041: 
 1042: 
 1043: # ----------------------------------- POD (plain old documentation, CPAN style)
 1044: 
 1045: =head1 NAME
 1046: 
 1047: lonc - LON TCP-MySQL-Server Daemon for handling database requests.
 1048: 
 1049: =head1 SYNOPSIS
 1050: 
 1051: Usage: B<lonc>
 1052: 
 1053: Should only be run as user=www.  This is a command-line script which
 1054: is invoked by B<loncron>.  There is no expectation that a typical user
 1055: will manually start B<lonc> from the command-line.  (In other words,
 1056: DO NOT START B<lonc> YOURSELF.)
 1057: 
 1058: =head1 DESCRIPTION
 1059: 
 1060: Provides persistent TCP connections to the other servers in the network
 1061: through multiplexed domain sockets
 1062: 
 1063: B<lonc> forks off children processes that correspond to the other servers
 1064: in the network.  Management of these processes can be done at the
 1065: parent process level or the child process level.
 1066: 
 1067:   After forking off the children, B<lonc> the B<parent> 
 1068: executes a main loop which simply waits for processes to exit.
 1069: As a process exits, a new process managing a link to the same
 1070: peer as the exiting process is created.  
 1071: 
 1072: B<logs/lonc.log> is the location of log messages.
 1073: 
 1074: The process management is now explained in terms of linux shell commands,
 1075: subroutines internal to this code, and signal assignments:
 1076: 
 1077: =over 4
 1078: 
 1079: =item *
 1080: 
 1081: PID is stored in B<logs/lonc.pid>
 1082: 
 1083: This is the process id number of the parent B<lonc> process.
 1084: 
 1085: =item *
 1086: 
 1087: SIGTERM and SIGINT
 1088: 
 1089: Parent signal assignment:
 1090:  $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
 1091: 
 1092: Child signal assignment:
 1093:  $SIG{INT}  = 'DEFAULT'; (and SIGTERM is DEFAULT also)
 1094: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
 1095:  to restart a new child.)
 1096: 
 1097: Command-line invocations:
 1098:  B<kill> B<-s> SIGTERM I<PID>
 1099:  B<kill> B<-s> SIGINT I<PID>
 1100: 
 1101: Subroutine B<HUNTSMAN>:
 1102:  This is only invoked for the B<lonc> parent I<PID>.
 1103: This kills all the children, and then the parent.
 1104: The B<lonc.pid> file is cleared.
 1105: 
 1106: =item *
 1107: 
 1108: SIGHUP
 1109: 
 1110: Current bug:
 1111:  This signal can only be processed the first time
 1112: on the parent process.  Subsequent SIGHUP signals
 1113: have no effect.
 1114: 
 1115: Parent signal assignment:
 1116:  $SIG{HUP}  = \&HUPSMAN;
 1117: 
 1118: Child signal assignment:
 1119:  none (nothing happens)
 1120: 
 1121: Command-line invocations:
 1122:  B<kill> B<-s> SIGHUP I<PID>
 1123: 
 1124: Subroutine B<HUPSMAN>:
 1125:  This is only invoked for the B<lonc> parent I<PID>,
 1126: This kills all the children, and then the parent.
 1127: The B<lonc.pid> file is cleared.
 1128: 
 1129: =item *
 1130: 
 1131: SIGUSR1
 1132: 
 1133: Parent signal assignment:
 1134:  $SIG{USR1} = \&USRMAN;
 1135: 
 1136: Child signal assignment:
 1137:  $SIG{USR1}= \&logstatus;
 1138: 
 1139: Command-line invocations:
 1140:  B<kill> B<-s> SIGUSR1 I<PID>
 1141: 
 1142: Subroutine B<USRMAN>:
 1143:  When invoked for the B<lonc> parent I<PID>,
 1144: SIGUSR1 is sent to all the children, and the status of
 1145: each connection is logged.
 1146: 
 1147: 
 1148: =back
 1149: 
 1150: =head1 PREREQUISITES
 1151: 
 1152: POSIX
 1153: IO::Socket
 1154: IO::Select
 1155: IO::File
 1156: Socket
 1157: Fcntl
 1158: Tie::RefHash
 1159: Crypt::IDEA
 1160: 
 1161: =head1 COREQUISITES
 1162: 
 1163: =head1 OSNAMES
 1164: 
 1165: linux
 1166: 
 1167: =head1 SCRIPT CATEGORIES
 1168: 
 1169: Server/Process
 1170: 
 1171: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>