File:  [LON-CAPA] / loncom / Attic / lonc
Revision 1.34: download - view: text, annotated - select for diffs
Wed Mar 20 03:44:11 2002 UTC (22 years, 1 month ago) by foxr
Branches: MAIN
CVS tags: HEAD
Put the small timeout back into the read select for now.

    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.34 2002/03/20 03:44:11 foxr 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 Scott Harrison
   41: # 12/05 Gerd Kortemeyer
   42: # YEAR=2001
   43: # 01/10/01 Scott Harrison
   44: # 03/14/01,03/15,06/12,11/26,11/27,11/28 Gerd Kortemeyer
   45: # 12/20 Scott Harrison
   46: # YEAR=2002
   47: # 2/19/02,02/22/02,02/25/02 Gerd Kortemeyer
   48: # 3/07/02 Ron Fox 
   49: # based on nonforker from Perl Cookbook
   50: # - server who multiplexes without forking
   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 httpd access.conf and get variables
   75: &status("Read access.conf");
   76: open (CONFIG,"/etc/httpd/conf/access.conf") || die "Can't read access.conf";
   77: 
   78: while ($configline=<CONFIG>) {
   79:     if ($configline =~ /PerlSetVar/) {
   80: 	my ($dummy,$varname,$varvalue)=split(/\s+/,$configline);
   81:         chomp($varvalue);
   82:         $perlvar{$varname}=$varvalue;
   83:     }
   84: }
   85: close(CONFIG);
   86: 
   87: # ----------------------------- Make sure this process is running from user=www
   88: &status("Check user ID");
   89: my $wwwid=getpwnam('www');
   90: if ($wwwid!=$<) {
   91:    $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
   92:    $subj="LON: $perlvar{'lonHostID'} User ID mismatch";
   93:    system("echo 'User ID mismatch.  lonc must be run as user www.' |\
   94:  mailto $emailto -s '$subj' > /dev/null");
   95:    exit 1;
   96: }
   97: 
   98: # --------------------------------------------- Check if other instance running
   99: 
  100: my $pidfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
  101: 
  102: if (-e $pidfile) {
  103:    my $lfh=IO::File->new("$pidfile");
  104:    my $pide=<$lfh>;
  105:    chomp($pide);
  106:    if (kill 0 => $pide) { die "already running"; }
  107: }
  108: 
  109: # ------------------------------------------------------------- Read hosts file
  110: 
  111: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
  112: 
  113: while ($configline=<CONFIG>) {
  114:     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
  115:     chomp($ip);
  116:     if ($ip) {
  117:      $hostip{$id}=$ip;
  118:      $hostname{$id}=$name;
  119:     }
  120: }
  121: 
  122: close(CONFIG);
  123: 
  124: # -------------------------------------------------------- Routines for forking
  125: 
  126: %children               = ();       # keys are current child process IDs,
  127:                                     # values are hosts
  128: %childpid               = ();       # the other way around
  129: 
  130: %childatt               = ();       # number of attempts to start server
  131:                                     # for ID
  132: 
  133: $childmaxattempts=5;
  134: 
  135: # ---------------------------------------------------- Fork once and dissociate
  136: &status("Fork and dissociate");
  137: $fpid=fork;
  138: exit if $fpid;
  139: die "Couldn't fork: $!" unless defined ($fpid);
  140: 
  141: POSIX::setsid() or die "Can't start new session: $!";
  142: 
  143: $conserver='PARENT';
  144: 
  145: # ------------------------------------------------------- Write our PID on disk
  146: &status("Write PID");
  147: $execdir=$perlvar{'lonDaemons'};
  148: open (PIDSAVE,">$execdir/logs/lonc.pid");
  149: print PIDSAVE "$$\n";
  150: close(PIDSAVE);
  151: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
  152: 
  153: # ----------------------------- Ignore signals generated during initial startup
  154: $SIG{HUP}=$SIG{USR1}='IGNORE';
  155: # ------------------------------------------------------- Now we are on our own
  156:     
  157: # Fork off our children, one for every server
  158: 
  159: &status("Forking ...");
  160: 
  161: foreach $thisserver (keys %hostip) {
  162:     #if (&online($hostname{$thisserver})) {
  163:        make_new_child($thisserver);
  164:     #}
  165: }
  166: 
  167: &logthis("Done starting initial servers");
  168: # ----------------------------------------------------- Install signal handlers
  169: 
  170: 
  171: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
  172: $SIG{HUP}  = \&HUPSMAN;
  173: $SIG{USR1} = \&USRMAN;
  174: 
  175: # And maintain the population.
  176: while (1) {
  177:     my $deadpid = wait;		# Wait for the next child to die.
  178:                                     # See who died and start new one
  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 (@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: 
  318: tie %ready, 'Tie::RefHash';
  319: 
  320: nonblock($server);
  321: $select = IO::Select->new($server);
  322: 
  323: # Main loop: check reads/accepts, check writes, check ready to process
  324: while (1) {
  325:     my $client;
  326:     my $rv;
  327:     my $data;
  328: 
  329:     # check for new information on the connections we have
  330: 
  331:     # anything to read or accept?
  332: 
  333:     foreach $client ($select->can_read(00.10)) {
  334:         if ($client == $server) {
  335:             # accept a new connection
  336:             &status("Accept new connection: $conserver");
  337:             $client = $server->accept();
  338:             $select->add($client);
  339:             nonblock($client);
  340:         } else {
  341:             # read data
  342:             $data = '';
  343:             $rv   = $client->recv($data, POSIX::BUFSIZ, 0);
  344: 
  345:             unless (defined($rv) && length $data) {
  346:                 # This would be the end of file, so close the client
  347:                 delete $inbuffer{$client};
  348:                 delete $outbuffer{$client};
  349:                 delete $ready{$client};
  350: 
  351:                 &status("Idle");
  352:                 $select->remove($client);
  353:                 close $client;
  354:                 next;
  355:             }
  356: 
  357:             $inbuffer{$client} .= $data;
  358: 
  359: 
  360:             # test whether the data in the buffer or the data we
  361:             # just read means there is a complete request waiting
  362:             # to be fulfilled.  If there is, set $ready{$client}
  363:             # to the requests waiting to be fulfilled.
  364:             while ($inbuffer{$client} =~ s/(.*\n)//) {
  365:                 push( @{$ready{$client}}, $1 );
  366:             }
  367:         }
  368:     }
  369:     
  370:     # Any complete requests to process?
  371:     foreach $client (keys %ready) {
  372:         handle($client);
  373:     }
  374:  
  375:     # Buffers to flush?
  376:     foreach $client ($select->can_write(1)) {
  377:         # Skip this client if we have nothing to say
  378:         next unless exists $outbuffer{$client};
  379: 
  380:         $rv = $client->send($outbuffer{$client}, 0);
  381: 
  382:       unless ($outbuffer{$client} eq "con_lost\n") {
  383:         unless (defined $rv) {
  384:             # Whine, but move on.
  385:             &logthis("I was told I could write, but I can't.\n");
  386:             next;
  387:         }
  388:         $errno=$!;
  389:         if (($rv == length $outbuffer{$client}) ||
  390:             ($errno == POSIX::EWOULDBLOCK) || ($errno == 0)) {
  391:             substr($outbuffer{$client}, 0, $rv) = '';
  392:             delete $outbuffer{$client} unless length $outbuffer{$client};
  393:         } else {
  394:             # Couldn't write all the data, and it wasn't because
  395:             # it would have blocked.  Shutdown and move on.
  396: 
  397: 	    &logthis("Dropping data with ".$errno.": ".
  398:                      length($outbuffer{$client}).", $rv");
  399: 
  400:             delete $inbuffer{$client};
  401:             delete $outbuffer{$client};
  402:             delete $ready{$client};
  403: 
  404:             $select->remove($client);
  405:             close($client);
  406:             next;
  407:         }
  408:       } else {
  409: # -------------------------------------------------------- Wow, connection lost
  410:          &logthis(
  411:      "<font color=red>CRITICAL: Closing connection</font>");
  412: 	 &status("Connection lost");
  413:          $remotesock->shutdown(2);
  414:          &logthis("Attempting to open new connection");
  415:          &openremote($conserver);          
  416:       }
  417:     }
  418:    
  419: }
  420: }
  421: 
  422: # ------------------------------------------------------- End of make_new_child
  423: 
  424: # handle($socket) deals with all pending requests for $client
  425: sub handle {
  426:     # requests are in $ready{$client}
  427:     # send output to $outbuffer{$client}
  428:     my $client = shift;
  429:     my $request;
  430:     foreach $request (@{$ready{$client}}) {
  431: # ============================================================= Process request
  432:         # $request is the text of the request
  433:         # put text of reply into $outbuffer{$client}
  434: # ------------------------------------------------------------ Is this the end?
  435: 	chomp($request);
  436: 	if($DEBUG) {
  437:      &logthis("<font color=green> Request $request processing starts</font>");
  438:         }
  439:         if ($request eq "close_connection_exit\n") {
  440: 	    &status("Request close connection");
  441:            &logthis(
  442:      "<font color=red>CRITICAL: Request Close Connection ... exiting</font>");
  443:            $remotesock->shutdown(2);
  444:            $server->close();
  445:            exit;
  446:         }
  447: # -----------------------------------------------------------------------------
  448:         if ($request =~ /^encrypt\:/) {
  449: 	    my $cmd=$request;
  450:             $cmd =~ s/^encrypt\://;
  451:             chomp($cmd);
  452:             my $cmdlength=length($cmd);
  453:             $cmd.="         ";
  454:             my $encrequest='';
  455:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  456:                 $encrequest.=
  457:                     unpack("H16",$cipher->encrypt(substr($cmd,$encidx,8)));
  458:             }
  459:             $request="enc:$cmdlength:$encrequest";
  460:         }
  461: # --------------------------------------------------------------- Main exchange
  462: 	$answer = londtransaction($remotesock, $request, 300);
  463: 
  464: 	if($DEBUG) { 
  465: 	    &logthis("<font color=green> Request data exchange complete");
  466: 	}
  467: 	if ($@=~/timeout/) { 
  468: 	    $answer='';
  469: 	    &logthis(
  470: 		     "<font color=red>CRITICAL: Timeout: $request</font>");
  471: 	}  
  472: 
  473: 
  474:         if ($answer) {
  475: 	   if ($answer =~ /^enc/) {
  476:                my ($cmd,$cmdlength,$encinput)=split(/:/,$answer);
  477:                chomp($encinput);
  478: 	       $answer='';
  479:                for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
  480:                   $answer.=$cipher->decrypt(
  481:                    pack("H16",substr($encinput,$encidx,16))
  482:                   );
  483: 	       }
  484: 	      $answer=substr($answer,0,$cmdlength);
  485: 	      $answer.="\n";
  486: 	   }
  487: 	   if($DEBUG) {
  488: 	       &logthis("sending $answer to client\n");
  489: 	   }
  490:            $outbuffer{$client} .= $answer;
  491:         } else {
  492:            $outbuffer{$client} .= "con_lost\n";
  493:         }
  494: 
  495:      &status("Completed: $request");
  496: 	if($DEBUG) {
  497: 	    &logthis("<font color=green> Request processing complete</font>");
  498: 	}
  499: # ===================================================== Done processing request
  500:     }
  501:     delete $ready{$client};
  502: # -------------------------------------------------------------- End non-forker
  503:     if($DEBUG) {
  504: 	&logthis("<font color=green> requests for child handled</font>");
  505:     }
  506: }
  507: # ---------------------------------------------------------- End make_new_child
  508: }
  509: 
  510: # nonblock($socket) puts socket into nonblocking mode
  511: sub nonblock {
  512:     my $socket = shift;
  513:     my $flags;
  514: 
  515:     
  516:     $flags = fcntl($socket, F_GETFL, 0)
  517:             or die "Can't get flags for socket: $!\n";
  518:     fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
  519:             or die "Can't make socket nonblocking: $!\n";
  520: }
  521: 
  522: 
  523: sub openremote {
  524: # ---------------------------------------------------- Client to network server
  525: 
  526:     my $conserver=shift;
  527: 
  528: &status("Opening TCP");
  529:     my $st=120+int(rand(240)); # Sleep before opening:
  530: 
  531: unless (
  532:   $remotesock = IO::Socket::INET->new(PeerAddr => $hostip{$conserver},
  533:                                       PeerPort => $perlvar{'londPort'},
  534:                                       Proto    => "tcp",
  535:                                       Type     => SOCK_STREAM)
  536:    ) { 
  537: 
  538:        &logthis(
  539: "<font color=blue>WARNING: Couldn't connect to $conserver ($st secs): </font>");
  540:        sleep($st);
  541:        exit; 
  542:      };
  543: # ----------------------------------------------------------------- Init dialog
  544: 
  545: &logthis("<font color=green>INFO Connected to $conserver, initing </font>");
  546: &status("Init dialogue: $conserver");
  547: 
  548:     $answer = londtransaction($remotesock, "init", 60);
  549:     chomp($answer);
  550:     $answer = londtransaction($remotesock, $answer, 60);
  551:     chomp($answer);
  552:  
  553:      if ($@=~/timeout/) {
  554: 	 &logthis("Timed out during init.. exiting");
  555:          exit;
  556:      }
  557: 
  558: if ($answer ne 'ok') {
  559:        &logthis("Init reply: >$answer<");
  560:        my $st=120+int(rand(240));
  561:        &logthis(
  562: "<font color=blue>WARNING: Init failed ($st secs)</font>");
  563:        sleep($st);
  564:        exit; 
  565: }
  566: 
  567: sleep 5;
  568: &status("Ponging");
  569: print $remotesock "pong\n";
  570: $answer=<$remotesock>;
  571: chomp($answer);
  572: if ($answer!~/^$conserver/) {
  573:    &logthis("Pong reply: >$answer<");
  574: }
  575: # ----------------------------------------------------------- Initialize cipher
  576: 
  577: &status("Initialize cipher");
  578: print $remotesock "ekey\n";
  579: my $buildkey=<$remotesock>;
  580: my $key=$conserver.$perlvar{'lonHostID'};
  581: $key=~tr/a-z/A-Z/;
  582: $key=~tr/G-P/0-9/;
  583: $key=~tr/Q-Z/0-9/;
  584: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
  585: $key=substr($key,0,32);
  586: my $cipherkey=pack("H32",$key);
  587: if ($cipher=new IDEA $cipherkey) {
  588:    &logthis("Secure connection initialized");
  589: } else {
  590:    my $st=120+int(rand(240));
  591:    &logthis(
  592:      "<font color=blue>WARNING: ".
  593:      "Could not establish secure connection ($st secs)!</font>");
  594:    sleep($st);
  595:    exit;
  596: }
  597:     &logthis("<font color=green> Remote open success </font>");
  598: }
  599: 
  600: 
  601: 
  602: # grabs exception and records it to log before exiting
  603: sub catchexception {
  604:     my ($signal)=@_;
  605:     $SIG{QUIT}='DEFAULT';
  606:     $SIG{__DIE__}='DEFAULT';
  607:     chomp($signal);
  608:     &logthis("<font color=red>CRITICAL: "
  609:      ."ABNORMAL EXIT. Child $$ for server [$wasserver] died through "
  610:      ."\"$signal\" with parameter </font>");
  611:     die("Signal abend");
  612: }
  613: 
  614: # -------------------------------------- Routines to see if other box available
  615: 
  616: #sub online {
  617: #    my $host=shift;
  618: #    &status("Pinging ".$host);
  619: #    my $p=Net::Ping->new("tcp",20);
  620: #    my $online=$p->ping("$host");
  621: #    $p->close();
  622: #    undef ($p);
  623: #    return $online;
  624: #}
  625: 
  626: sub connected {
  627:     my ($local,$remote)=@_;
  628:     &status("Checking connection $local to $remote");
  629:     $local=~s/\W//g;
  630:     $remote=~s/\W//g;
  631: 
  632:     unless ($hostname{$local}) { return 'local_unknown'; }
  633:     unless ($hostname{$remote}) { return 'remote_unknown'; }
  634: 
  635:     #unless (&online($hostname{$local})) { return 'local_offline'; }
  636: 
  637:     my $ua=new LWP::UserAgent;
  638:     
  639:     my $request=new HTTP::Request('GET',
  640:       "http://".$hostname{$local}.'/cgi-bin/ping.pl?'.$remote);
  641: 
  642:     my $response=$ua->request($request);
  643: 
  644:     unless ($response->is_success) { return 'local_error'; }
  645: 
  646:     my $reply=$response->content;
  647:     $reply=(split("\n",$reply))[0];
  648:     $reply=~s/\W//g;
  649:     if ($reply ne $remote) { return $reply; }
  650:     return 'ok';
  651: }
  652: 
  653: 
  654: 
  655: sub hangup {
  656:     foreach (keys %children) {
  657:         $wasserver=$children{$_};
  658:         &status("Closing $wasserver");
  659:         &logthis('Closing '.$wasserver.': '.&subreply('exit',$wasserver));
  660:         &status("Kill PID $_ for $wasserver");
  661: 	kill ('INT',$_);
  662:     }
  663: }
  664: 
  665: sub HUNTSMAN {                      # signal handler for SIGINT
  666:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
  667:     &hangup();
  668:     my $execdir=$perlvar{'lonDaemons'};
  669:     unlink("$execdir/logs/lonc.pid");
  670:     &logthis("<font color=red>CRITICAL: Shutting down</font>");
  671:     exit;                           # clean up with dignity
  672: }
  673: 
  674: sub HUPSMAN {                      # signal handler for SIGHUP
  675:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
  676:     &hangup();
  677:     &logthis("<font color=red>CRITICAL: Restarting</font>");
  678:     unlink("$execdir/logs/lonc.pid");
  679:     my $execdir=$perlvar{'lonDaemons'};
  680:     exec("$execdir/lonc");         # here we go again
  681: }
  682: 
  683: sub checkchildren {
  684:     &initnewstatus();
  685:     &logstatus();
  686:     &logthis('Going to check on the children');
  687:     foreach (sort keys %children) {
  688: 	sleep 1;
  689:         unless (kill 'USR1' => $_) {
  690: 	    &logthis ('<font color=red>CRITICAL: Child '.$_.' is dead</font>');
  691:             &logstatus($$.' is dead');
  692:         } 
  693:     }
  694: }
  695: 
  696: sub USRMAN {
  697:     &logthis("USR1: Trying to establish connections again");
  698:     %childatt=();
  699:     &checkchildren();
  700: }
  701: 
  702: # -------------------------------------------------- Non-critical communication
  703: sub subreply { 
  704:  my ($cmd,$server)=@_;
  705:  my $answer='';
  706:  if ($server ne $perlvar{'lonHostID'}) { 
  707:     my $peerfile="$perlvar{'lonSockDir'}/$server";
  708:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
  709:                                       Type    => SOCK_STREAM,
  710:                                       Timeout => 10)
  711:        or return "con_lost";
  712: 
  713: 
  714:     $answer = londtransaction($sclient, $cmd, 10);
  715: 
  716:     if ((!$answer) || ($@=~/timeout/)) { $answer="con_lost"; }
  717:     $SIG{ALRM}='DEFAULT';
  718:     $SIG{__DIE__}=\&catchexception;
  719:  } else { $answer='self_reply'; }
  720:  return $answer;
  721: }
  722: 
  723: # --------------------------------------------------------------------- Logging
  724: 
  725: sub logthis {
  726:     my $message=shift;
  727:     my $execdir=$perlvar{'lonDaemons'};
  728:     my $fh=IO::File->new(">>$execdir/logs/lonc.log");
  729:     my $now=time;
  730:     my $local=localtime($now);
  731:     $lastlog=$local.': '.$message;
  732:     print $fh "$local ($$) [$conserver] [$status]: $message\n";
  733: }
  734: 
  735: #--------------------------------------  londtransaction:
  736: #  
  737: #  Performs a transaction with lond with timeout support.
  738: #    result = londtransaction(socket,request,timeout)
  739: #
  740: sub londtransaction {
  741:     my ($socket, $request, $tmo) = @_;
  742: 
  743:     if($DEBUG) {
  744: 	&logthis("londtransaction request: $request");
  745:     }
  746: 
  747:     # Set the signal handlers: ALRM for timeout and disble the others.
  748: 
  749:     $SIG{ALRM} = sub { die "timeout" };
  750:     $SIG{__DIE__} = 'DEFAULT';
  751:     
  752:     # Disable all but alarm so that only that can interupt the
  753:     # send /receive.
  754:     #
  755:     my $sigset = POSIX::SigSet->new(QUIT, USR1, HUP, INT, TERM);
  756:     my $priorsigs = POSIX::SigSet->new;
  757:     unless (defined sigprocmask(SIG_BLOCK, $sigset, $priorsigs)) {
  758: 	&logthis("<font color=red> CRITICAL -- londtransaction ".
  759: 		"failed to block signals </font>");
  760: 	die "could not block signals in londtransaction";
  761:     }
  762:     $answer = '';
  763:     #
  764:     #  Send request to lond.
  765:     #
  766:     eval { 
  767: 	alarm($tmo);
  768: 	print $socket "$request\n";
  769: 	alarm(0);
  770:     };
  771:     #  If request didn't timeout, try for the response.
  772:     #
  773: 
  774:     if ($@!~/timeout/) {
  775: 	eval {
  776: 	    alarm($tmo);
  777: 	    $answer = <$socket>;
  778: 	    if($DEBUG) {
  779: 		&logthis("Received $answer in londtransaction");
  780: 	    }
  781: 	    alarm(0);
  782: 	};
  783:     } else {
  784: 	if($DEBUG) {
  785: 	    &logthis("Timeout on send in londtransaction");
  786: 	}
  787:     }
  788:     if( ($@ =~ /timeout/)  && ($DEBUG)) {
  789: 	&logthis("Timeout on receive in londtransaction");
  790:     }
  791:     #
  792:     # Restore the initial sigmask set.
  793:     #
  794:     unless (defined sigprocmask(SIG_UNBLOCK, $priorsigs)) {
  795: 	&logthis("<font color=red> CRITICAL -- londtransaction ".
  796: 		"failed to re-enable signal processing. </font>");
  797: 	die "londtransaction failed to re-enable signals";
  798:     }
  799:     #
  800:     # go back to the prior handler set.
  801:     #
  802:     $SIG{ALRM} = 'DEFAULT';
  803:     $SIG{__DIE__} = \&cathcexception;
  804: 
  805:     #    chomp $answer;
  806:     if ($DEBUG) {
  807: 	&logthis("Returning $answer in londtransaction");
  808:     }
  809:     return $answer;
  810: 
  811: }
  812: 
  813: sub logperm {
  814:     my $message=shift;
  815:     my $execdir=$perlvar{'lonDaemons'};
  816:     my $now=time;
  817:     my $local=localtime($now);
  818:     my $fh=IO::File->new(">>$execdir/logs/lonnet.perm.log");
  819:     print $fh "$now:$message:$local\n";
  820: }
  821: # ------------------------------------------------------------------ Log status
  822: 
  823: sub logstatus {
  824:     my $docdir=$perlvar{'lonDocRoot'};
  825:     my $fh=IO::File->new(">>$docdir/lon-status/loncstatus.txt");
  826:     print $fh $$."\t".$conserver."\t".$status."\t".$lastlog."\n";
  827: }
  828: 
  829: sub initnewstatus {
  830:     my $docdir=$perlvar{'lonDocRoot'};
  831:     my $fh=IO::File->new(">$docdir/lon-status/loncstatus.txt");
  832:     my $now=time;
  833:     my $local=localtime($now);
  834:     print $fh "LONC status $local - parent $$\n\n";
  835: }
  836: 
  837: # -------------------------------------------------------------- Status setting
  838: 
  839: sub status {
  840:     my $what=shift;
  841:     my $now=time;
  842:     my $local=localtime($now);
  843:     $status=$local.': '.$what;
  844: }
  845: 
  846: 
  847: 
  848: # ----------------------------------- POD (plain old documentation, CPAN style)
  849: 
  850: =head1 NAME
  851: 
  852: lonc - LON TCP-MySQL-Server Daemon for handling database requests.
  853: 
  854: =head1 SYNOPSIS
  855: 
  856: Usage: B<lonc>
  857: 
  858: Should only be run as user=www.  This is a command-line script which
  859: is invoked by B<loncron>.  There is no expectation that a typical user
  860: will manually start B<lonc> from the command-line.  (In other words,
  861: DO NOT START B<lonc> YOURSELF.)
  862: 
  863: =head1 DESCRIPTION
  864: 
  865: Provides persistent TCP connections to the other servers in the network
  866: through multiplexed domain sockets
  867: 
  868: B<lonc> forks off children processes that correspond to the other servers
  869: in the network.  Management of these processes can be done at the
  870: parent process level or the child process level.
  871: 
  872:   After forking off the children, B<lonc> the B<parent> 
  873: executes a main loop which simply waits for processes to exit.
  874: As a process exits, a new process managing a link to the same
  875: peer as the exiting process is created.  
  876: 
  877: B<logs/lonc.log> is the location of log messages.
  878: 
  879: The process management is now explained in terms of linux shell commands,
  880: subroutines internal to this code, and signal assignments:
  881: 
  882: =over 4
  883: 
  884: =item *
  885: 
  886: PID is stored in B<logs/lonc.pid>
  887: 
  888: This is the process id number of the parent B<lonc> process.
  889: 
  890: =item *
  891: 
  892: SIGTERM and SIGINT
  893: 
  894: Parent signal assignment:
  895:  $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
  896: 
  897: Child signal assignment:
  898:  $SIG{INT}  = 'DEFAULT'; (and SIGTERM is DEFAULT also)
  899: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
  900:  to restart a new child.)
  901: 
  902: Command-line invocations:
  903:  B<kill> B<-s> SIGTERM I<PID>
  904:  B<kill> B<-s> SIGINT I<PID>
  905: 
  906: Subroutine B<HUNTSMAN>:
  907:  This is only invoked for the B<lonc> parent I<PID>.
  908: This kills all the children, and then the parent.
  909: The B<lonc.pid> file is cleared.
  910: 
  911: =item *
  912: 
  913: SIGHUP
  914: 
  915: Current bug:
  916:  This signal can only be processed the first time
  917: on the parent process.  Subsequent SIGHUP signals
  918: have no effect.
  919: 
  920: Parent signal assignment:
  921:  $SIG{HUP}  = \&HUPSMAN;
  922: 
  923: Child signal assignment:
  924:  none (nothing happens)
  925: 
  926: Command-line invocations:
  927:  B<kill> B<-s> SIGHUP I<PID>
  928: 
  929: Subroutine B<HUPSMAN>:
  930:  This is only invoked for the B<lonc> parent I<PID>,
  931: This kills all the children, and then the parent.
  932: The B<lonc.pid> file is cleared.
  933: 
  934: =item *
  935: 
  936: SIGUSR1
  937: 
  938: Parent signal assignment:
  939:  $SIG{USR1} = \&USRMAN;
  940: 
  941: Child signal assignment:
  942:  $SIG{USR1}= \&logstatus;
  943: 
  944: Command-line invocations:
  945:  B<kill> B<-s> SIGUSR1 I<PID>
  946: 
  947: Subroutine B<USRMAN>:
  948:  When invoked for the B<lonc> parent I<PID>,
  949: SIGUSR1 is sent to all the children, and the status of
  950: each connection is logged.
  951: 
  952: 
  953: =back
  954: 
  955: =head1 PREREQUISITES
  956: 
  957: POSIX
  958: IO::Socket
  959: IO::Select
  960: IO::File
  961: Socket
  962: Fcntl
  963: Tie::RefHash
  964: Crypt::IDEA
  965: 
  966: =head1 COREQUISITES
  967: 
  968: =head1 OSNAMES
  969: 
  970: linux
  971: 
  972: =head1 SCRIPT CATEGORIES
  973: 
  974: Server/Process
  975: 
  976: =cut

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