File:  [LON-CAPA] / loncom / Attic / lonc
Revision 1.8: download - view: text, annotated - select for diffs
Tue Dec 5 03:23:59 2000 UTC (23 years, 6 months ago) by harris41
Branches: MAIN
CVS tags: HEAD
added in exception handling to catch abnormal exiting through 'DIE'
and 'QUIT' signals -Scott

    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: # PID in subdir logs/lonc.pid
    9: # kill kills
   10: # HUP restarts
   11: # USR1 tries to open connections again
   12: 
   13: # 6/4/99,6/5,6/7,6/8,6/9,6/10,6/11,6/12,7/14,7/19,
   14: # 10/8,10/9,10/15,11/18,12/22,
   15: # 2/8,7/25 Gerd Kortemeyer 
   16: # based on nonforker from Perl Cookbook
   17: # - server who multiplexes without forking
   18: 
   19: use POSIX;
   20: use IO::Socket;
   21: use IO::Select;
   22: use IO::File;
   23: use Socket;
   24: use Fcntl;
   25: use Tie::RefHash;
   26: use Crypt::IDEA;
   27: 
   28: $childmaxattempts=10;
   29: 
   30: # -------------------------------- Set signal handlers to record abnormal exits
   31: 
   32: $SIG{'QUIT'}=\&catchexception;
   33: $SIG{__DIE__}=\&catchexception;
   34: 
   35: # ------------------------------------ Read httpd access.conf and get variables
   36: 
   37: open (CONFIG,"/etc/httpd/conf/access.conf") 
   38:     || catchdie "Can't read access.conf";
   39: 
   40: while ($configline=<CONFIG>) {
   41:     if ($configline =~ /PerlSetVar/) {
   42: 	my ($dummy,$varname,$varvalue)=split(/\s+/,$configline);
   43:         chomp($varvalue);
   44:         $perlvar{$varname}=$varvalue;
   45:     }
   46: }
   47: close(CONFIG);
   48: 
   49: # --------------------------------------------- Check if other instance running
   50: 
   51: my $pidfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
   52: 
   53: if (-e $pidfile) {
   54:    my $lfh=IO::File->new("$pidfile");
   55:    my $pide=<$lfh>;
   56:    chomp($pide);
   57:    if (kill 0 => $pide) { catchdie "already running"; }
   58: }
   59: 
   60: # ------------------------------------------------------------- Read hosts file
   61: 
   62: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") 
   63:     || catchdie "Can't read host file";
   64: 
   65: while ($configline=<CONFIG>) {
   66:     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
   67:     chomp($ip);
   68:     $hostip{$id}=$ip;
   69: }
   70: close(CONFIG);
   71: 
   72: # -------------------------------------------------------- Routines for forking
   73: 
   74: %children               = ();       # keys are current child process IDs,
   75:                                     # values are hosts
   76: %childpid               = ();       # the other way around
   77: 
   78: %childatt               = ();       # number of attempts to start server
   79:                                     # for ID
   80: 
   81: sub REAPER {                        # takes care of dead children
   82:     $SIG{CHLD} = \&REAPER;
   83:     my $pid = wait;
   84:     my $wasserver=$children{$pid};
   85:     &logthis("<font color=red>CRITICAL: "
   86:      ."Child $pid for server $wasserver died ($childatt{$wasserver})</font>");
   87:     delete $children{$pid};
   88:     delete $childpid{$wasserver};
   89:     my $port = "$perlvar{'lonSockDir'}/$wasserver";
   90:     unlink($port);
   91: }
   92: 
   93: sub HUNTSMAN {                      # signal handler for SIGINT
   94:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
   95:     kill 'INT' => keys %children;
   96:     my $execdir=$perlvar{'lonDaemons'};
   97:     unlink("$execdir/logs/lonc.pid");
   98:     &logthis("<font color=red>CRITICAL: Shutting down</font>");
   99:     exit;                           # clean up with dignity
  100: }
  101: 
  102: sub HUPSMAN {                      # signal handler for SIGHUP
  103:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
  104:     kill 'INT' => keys %children;
  105:     &logthis("<font color=red>CRITICAL: Restarting</font>");
  106:     my $execdir=$perlvar{'lonDaemons'};
  107:     exec("$execdir/lonc");         # here we go again
  108: }
  109: 
  110: sub USRMAN {
  111:     &logthis("USR1: Trying to establish connections again");
  112:     foreach $thisserver (keys %hostip) {
  113: 	$answer=subreply("ping",$thisserver);
  114:         &logthis("USR1: Ping $thisserver "
  115:         ."(pid >$childpid{$thisserver}<, $childatt{thisserver} attempts): "
  116:         ." >$answer<");
  117:     }
  118:     %childatt=();
  119: }
  120: 
  121: # -------------------------------------------------- Non-critical communication
  122: sub subreply { 
  123:  my ($cmd,$server)=@_;
  124:  my $answer='';
  125:  if ($server ne $perlvar{'lonHostID'}) { 
  126:     my $peerfile="$perlvar{'lonSockDir'}/$server";
  127:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
  128:                                       Type    => SOCK_STREAM,
  129:                                       Timeout => 10)
  130:        or return "con_lost";
  131:     print $sclient "$cmd\n";
  132:     my $answer=<$sclient>;
  133:     chomp($answer);
  134:     if (!$answer) { $answer="con_lost"; }
  135:  } else { $answer='self_reply'; }
  136:  return $answer;
  137: }
  138: 
  139: # --------------------------------------------------------------------- Logging
  140: 
  141: sub logthis {
  142:     my $message=shift;
  143:     my $execdir=$perlvar{'lonDaemons'};
  144:     my $fh=IO::File->new(">>$execdir/logs/lonc.log");
  145:     my $now=time;
  146:     my $local=localtime($now);
  147:     print $fh "$local ($$): $message\n";
  148: }
  149: 
  150: 
  151: sub logperm {
  152:     my $message=shift;
  153:     my $execdir=$perlvar{'lonDaemons'};
  154:     my $now=time;
  155:     my $local=localtime($now);
  156:     my $fh=IO::File->new(">>$execdir/logs/lonnet.perm.log");
  157:     print $fh "$now:$message:$local\n";
  158: }
  159: 
  160: # ---------------------------------------------------- Fork once and dissociate
  161: 
  162: $fpid=fork;
  163: exit if $fpid;
  164: catchdie "Couldn't fork: $!" unless defined ($fpid);
  165: 
  166: POSIX::setsid() or catchdie "Can't start new session: $!";
  167: 
  168: # ------------------------------------------------------- Write our PID on disk
  169: 
  170: $execdir=$perlvar{'lonDaemons'};
  171: open (PIDSAVE,">$execdir/logs/lonc.pid");
  172: print PIDSAVE "$$\n";
  173: close(PIDSAVE);
  174: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
  175: 
  176: # ----------------------------- Ignore signals generated during initial startup
  177: $SIG{HUP}=$SIG{USR1}='IGNORE';
  178: # ------------------------------------------------------- Now we are on our own
  179:     
  180: # Fork off our children, one for every server
  181: 
  182: foreach $thisserver (keys %hostip) {
  183:     make_new_child($thisserver);
  184: }
  185: 
  186: &logthis("Done starting initial servers");
  187: # ----------------------------------------------------- Install signal handlers
  188: 
  189: $SIG{CHLD} = \&REAPER;
  190: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
  191: $SIG{HUP}  = \&HUPSMAN;
  192: $SIG{USR1} = \&USRMAN;
  193: 
  194: # And maintain the population.
  195: while (1) {
  196:     sleep;                          # wait for a signal (i.e., child's death)
  197:                                     # See who died and start new one
  198:     foreach $thisserver (keys %hostip) {
  199:         if (!$childpid{$thisserver}) {
  200: 	    if ($childatt{$thisserver}<=$childmaxattempts) {
  201: 	       $childatt{$thisserver}++;
  202:                &logthis(
  203:    "<font color=yellow>INFO: Trying to reconnect for $thisserver "
  204:   ."($childatt{$thisserver} of $childmaxattempts attempts)</font>"); 
  205:                make_new_child($thisserver);
  206: 	    }
  207:         }       
  208:     }
  209: }
  210: 
  211: 
  212: sub make_new_child {
  213:    
  214:     my $conserver=shift;
  215:     my $pid;
  216:     my $sigset;
  217:     &logthis("Attempting to start child for server $conserver");
  218:     # block signal for fork
  219:     $sigset = POSIX::SigSet->new(SIGINT);
  220:     sigprocmask(SIG_BLOCK, $sigset)
  221:         or catchdie "Can't block SIGINT for fork: $!\n";
  222:     
  223:     catchdie "fork: $!" unless defined ($pid = fork);
  224:     
  225:     if ($pid) {
  226:         # Parent records the child's birth and returns.
  227:         sigprocmask(SIG_UNBLOCK, $sigset)
  228:             or catchdie "Can't unblock SIGINT for fork: $!\n";
  229:         $children{$pid} = $conserver;
  230:         $childpid{$conserver} = $pid;
  231:         return;
  232:     } else {
  233:         # Child can *not* return from this subroutine.
  234:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
  235:     
  236:         # unblock signals
  237:         sigprocmask(SIG_UNBLOCK, $sigset)
  238:             or catchdie "Can't unblock SIGINT for fork: $!\n";
  239: 
  240: # ----------------------------- This is the modified main program of non-forker
  241: 
  242: $port = "$perlvar{'lonSockDir'}/$conserver";
  243: 
  244: unlink($port);
  245: # ---------------------------------------------------- Client to network server
  246: unless (
  247:   $remotesock = IO::Socket::INET->new(PeerAddr => $hostip{$conserver},
  248:                                       PeerPort => $perlvar{'londPort'},
  249:                                       Proto    => "tcp",
  250:                                       Type     => SOCK_STREAM)
  251:    ) { 
  252:        my $st=120+int(rand(240));
  253:        &logthis(
  254: "<font color=blue>WARNING: Couldn't connect $conserver ($st secs): $@</font>");
  255:        sleep($st);
  256:        exit; 
  257:      };
  258: # --------------------------------------- Send a ping to make other end do USR1
  259: print $remotesock "init\n";
  260: $answer=<$remotesock>;
  261: print $remotesock "$answer";
  262: $answer=<$remotesock>;
  263: chomp($answer);
  264: &logthis("Init reply for $conserver: >$answer<");
  265: sleep 5;
  266: print $remotesock "pong\n";
  267: $answer=<$remotesock>;
  268: chomp($answer);
  269: &logthis("Pong reply for $conserver: >$answer<");
  270: # ----------------------------------------------------------- Initialize cipher
  271: 
  272: print $remotesock "ekey\n";
  273: my $buildkey=<$remotesock>;
  274: my $key=$conserver.$perlvar{'lonHostID'};
  275: $key=~tr/a-z/A-Z/;
  276: $key=~tr/G-P/0-9/;
  277: $key=~tr/Q-Z/0-9/;
  278: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
  279: $key=substr($key,0,32);
  280: my $cipherkey=pack("H32",$key);
  281: if ($cipher=new IDEA $cipherkey) {
  282:    &logthis("Secure connection inititalized: $conserver");
  283: } else {
  284:    my $st=120+int(rand(240));
  285:    &logthis(
  286:      "<font color=blue>WARNING: ".
  287:      "Could not establish secure connection, $conserver ($st secs)!</font>");
  288:    sleep($st);
  289:    exit;
  290: }
  291: 
  292: # ----------------------------------------- We're online, send delayed messages
  293: 
  294:     my @allbuffered;
  295:     my $path="$perlvar{'lonSockDir'}/delayed";
  296:     opendir(DIRHANDLE,$path);
  297:     @allbuffered=grep /\.$conserver$/, readdir DIRHANDLE;
  298:     closedir(DIRHANDLE);
  299:     my $dfname;
  300:     map {
  301:         $dfname="$path/$_";
  302:         &logthis($dfname);
  303:         my $wcmd;
  304:         {
  305:          my $dfh=IO::File->new($dfname);
  306:          $cmd=<$dfh>;
  307:         }
  308:         chomp($cmd);
  309:         my $bcmd=$cmd;
  310:         if ($cmd =~ /^encrypt\:/) {
  311: 	    my $rcmd=$cmd;
  312:             $rcmd =~ s/^encrypt\://;
  313:             chomp($rcmd);
  314:             my $cmdlength=length($rcmd);
  315:             $rcmd.="         ";
  316:             my $encrequest='';
  317:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  318:                 $encrequest.=
  319:                     unpack("H16",$cipher->encrypt(substr($rcmd,$encidx,8)));
  320:             }
  321:             $cmd="enc:$cmdlength:$encrequest\n";
  322:         }
  323: 
  324:         print $remotesock "$cmd\n";
  325:         $answer=<$remotesock>;
  326: 	chomp($answer);
  327:         if ($answer ne '') {
  328: 	    unlink("$dfname");
  329:             &logthis("Delayed $cmd to $conserver: >$answer<");
  330:             &logperm("S:$conserver:$bcmd");
  331:         }        
  332:     } @allbuffered;
  333: 
  334: # ------------------------------------------------------- Listen to UNIX socket
  335: unless (
  336:   $server = IO::Socket::UNIX->new(Local  => $port,
  337:                                   Type   => SOCK_STREAM,
  338:                                   Listen => 10 )
  339:    ) { 
  340:        my $st=120+int(rand(240));
  341:        &logthis(
  342:          "<font color=blue>WARNING: ".
  343:          "Can't make server socket $conserver ($st secs): $@</font>");
  344:        sleep($st);
  345:        exit; 
  346:      };
  347: 
  348: # -----------------------------------------------------------------------------
  349: 
  350: &logthis("<font color=green>$conserver online</font>");
  351: 
  352: # -----------------------------------------------------------------------------
  353: # begin with empty buffers
  354: %inbuffer  = ();
  355: %outbuffer = ();
  356: %ready     = ();
  357: 
  358: tie %ready, 'Tie::RefHash';
  359: 
  360: nonblock($server);
  361: $select = IO::Select->new($server);
  362: 
  363: # Main loop: check reads/accepts, check writes, check ready to process
  364: while (1) {
  365:     my $client;
  366:     my $rv;
  367:     my $data;
  368: 
  369:     # check for new information on the connections we have
  370: 
  371:     # anything to read or accept?
  372:     foreach $client ($select->can_read(1)) {
  373: 
  374:         if ($client == $server) {
  375:             # accept a new connection
  376: 
  377:             $client = $server->accept();
  378:             $select->add($client);
  379:             nonblock($client);
  380:         } else {
  381:             # read data
  382:             $data = '';
  383:             $rv   = $client->recv($data, POSIX::BUFSIZ, 0);
  384: 
  385:             unless (defined($rv) && length $data) {
  386:                 # This would be the end of file, so close the client
  387:                 delete $inbuffer{$client};
  388:                 delete $outbuffer{$client};
  389:                 delete $ready{$client};
  390: 
  391:                 $select->remove($client);
  392:                 close $client;
  393:                 next;
  394:             }
  395: 
  396:             $inbuffer{$client} .= $data;
  397: 
  398:             # test whether the data in the buffer or the data we
  399:             # just read means there is a complete request waiting
  400:             # to be fulfilled.  If there is, set $ready{$client}
  401:             # to the requests waiting to be fulfilled.
  402:             while ($inbuffer{$client} =~ s/(.*\n)//) {
  403:                 push( @{$ready{$client}}, $1 );
  404:             }
  405:         }
  406:     }
  407: 
  408:     # Any complete requests to process?
  409:     foreach $client (keys %ready) {
  410:         handle($client);
  411:     }
  412: 
  413:     # Buffers to flush?
  414:     foreach $client ($select->can_write(1)) {
  415:         # Skip this client if we have nothing to say
  416:         next unless exists $outbuffer{$client};
  417: 
  418:         $rv = $client->send($outbuffer{$client}, 0);
  419:         unless (defined $rv) {
  420:             # Whine, but move on.
  421:             warn "I was told I could write, but I can't.\n";
  422:             next;
  423:         }
  424:         if (($rv == length $outbuffer{$client}) ||
  425:             ($! == POSIX::EWOULDBLOCK)) {
  426:             substr($outbuffer{$client}, 0, $rv) = '';
  427:             delete $outbuffer{$client} unless length $outbuffer{$client};
  428:         } else {
  429:             # Couldn't write all the data, and it wasn't because
  430:             # it would have blocked.  Shutdown and move on.
  431:             delete $inbuffer{$client};
  432:             delete $outbuffer{$client};
  433:             delete $ready{$client};
  434: 
  435:             $select->remove($client);
  436:             close($client);
  437:             next;
  438:         }
  439:     }
  440: }
  441: }
  442: 
  443: # ------------------------------------------------------- End of make_new_child
  444: 
  445: # handle($socket) deals with all pending requests for $client
  446: sub handle {
  447:     # requests are in $ready{$client}
  448:     # send output to $outbuffer{$client}
  449:     my $client = shift;
  450:     my $request;
  451: 
  452:     foreach $request (@{$ready{$client}}) {
  453: # ============================================================= Process request
  454:         # $request is the text of the request
  455:         # put text of reply into $outbuffer{$client}
  456: # -----------------------------------------------------------------------------
  457:         if ($request =~ /^encrypt\:/) {
  458: 	    my $cmd=$request;
  459:             $cmd =~ s/^encrypt\://;
  460:             chomp($cmd);
  461:             my $cmdlength=length($cmd);
  462:             $cmd.="         ";
  463:             my $encrequest='';
  464:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  465:                 $encrequest.=
  466:                     unpack("H16",$cipher->encrypt(substr($cmd,$encidx,8)));
  467:             }
  468:             $request="enc:$cmdlength:$encrequest\n";
  469:         }
  470:         print $remotesock "$request";
  471:         $answer=<$remotesock>;
  472:         if ($answer) {
  473: 	   if ($answer =~ /^enc/) {
  474:                my ($cmd,$cmdlength,$encinput)=split(/:/,$answer);
  475:                chomp($encinput);
  476: 	       $answer='';
  477:                for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
  478:                   $answer.=$cipher->decrypt(
  479:                    pack("H16",substr($encinput,$encidx,16))
  480:                   );
  481: 	       }
  482: 	      $answer=substr($answer,0,$cmdlength);
  483: 	      $answer.="\n";
  484: 	   }
  485:            $outbuffer{$client} .= $answer;
  486:         } else {
  487:            $outbuffer{$client} .= "con_lost\n";
  488:         }
  489: 
  490: # ===================================================== Done processing request
  491:     }
  492:     delete $ready{$client};
  493: # -------------------------------------------------------------- End non-forker
  494: }
  495: # ---------------------------------------------------------- End make_new_child
  496: }
  497: 
  498: # nonblock($socket) puts socket into nonblocking mode
  499: sub nonblock {
  500:     my $socket = shift;
  501:     my $flags;
  502: 
  503:     
  504:     $flags = fcntl($socket, F_GETFL, 0)
  505:             or catchdie "Can't get flags for socket: $!\n";
  506:     fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
  507:             or catchdie "Can't make socket nonblocking: $!\n";
  508: }
  509: 
  510: # grabs exception and records it to log before exiting
  511: sub catchexception {
  512:     my ($signal)=@_;
  513:     &logthis("<font color=red>CRITICAL: "
  514:      ."ABNORMAL EXIT. Child $$ for server $wasserver died through "
  515:      ."$signal with this parameter->[$@]</font>");
  516:     die($@);
  517: }
  518: 
  519: # grabs exception and records it to log before exiting
  520: # NOTE: we must NOT use the regular (non-overrided) die function in
  521: # the code because a handler CANNOT be attached to it
  522: # (despite what some of the documentation says about SIG{__DIE__}.
  523: sub catchdie {
  524:     my ($message)=@_;
  525:     &logthis("<font color=red>CRITICAL: "
  526:      ."ABNORMAL EXIT. Child $$ for server $wasserver died through "
  527:      ."\_\_DIE\_\_ with this parameter->[$message]</font>");
  528:     die($message);
  529: }
  530: 

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