Annotation of loncom/lonmaxima, revision 1.32

1.1       www         1: #!/usr/bin/perl
                      2: #
                      3: # The LearningOnline Network with CAPA
                      4: # Connect to MAXIMA CAS
                      5: #
1.32    ! albertel    6: # $Id: lonmaxima,v 1.31 2007/08/02 13:04:55 bisitz Exp $
1.1       www         7: #
                      8: # Copyright Michigan State University Board of Trustees
                      9: #
                     10: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                     11: #
                     12: # LON-CAPA is free software; you can redistribute it and/or modify
                     13: # it under the terms of the GNU General Public License as published by
                     14: # the Free Software Foundation; either version 2 of the License, or
                     15: # (at your option) any later version.
                     16: #
                     17: # LON-CAPA is distributed in the hope that it will be useful,
                     18: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     19: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     20: # GNU General Public License for more details.
                     21: #
                     22: # You should have received a copy of the GNU General Public License
                     23: # along with LON-CAPA; if not, write to the Free Software
                     24: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     25: #
                     26: # /home/httpd/html/adm/gpl.txt
                     27: #
                     28: 
                     29: # 
                     30: # http://www.lon-capa.org/
                     31: #
1.2       www        32: 
1.16      www        33: use Expect; 
1.1       www        34: use IO::Select;
1.2       www        35: use IO::Socket;
                     36: use IO::File;
                     37: use Symbol;
                     38: use POSIX;
                     39: use lib '/home/httpd/lib/perl/';
                     40: use LONCAPA::Configuration;
                     41:  
1.3       albertel   42: use strict;
                     43: 
                     44: # global variables
                     45: my $PREFORK                = 5;        # number of children to maintain
1.17      www        46: my $MAX_CLIENTS_PER_CHILD  = 50;       # number of clients each child should process
1.3       albertel   47: my %children               = ();       # keys are current child process IDs
                     48: my $children               = 0;        # current number of children
                     49: my $status;                            # string for current status
1.5       albertel   50: my $pidfile;                           # file containg parent process pid
                     51: my $port;                              # path to UNIX socket file
                     52: my %perlvar;                           # configuration file info
                     53: my $lastlog;                           # last string that was logged
1.18      www        54: 
1.16      www        55: use vars qw($PREFORK $MAX_CLIENTS_PER_CHILD %children $children $status
1.12      www        56: 	    $pidfile $port %perlvar $lastlog);
1.2       www        57:  
                     58: # ------------------------------------------------------------ Service routines 
                     59: sub REAPER {                        # takes care of dead children 
                     60:                                     # and MAXIMA processes
                     61:     $SIG{CHLD} = \&REAPER;
                     62:     my $pid = wait;
1.6       albertel   63:     $children--;
                     64:     delete($children{$pid});
1.2       www        65: }
                     66:  
                     67: sub HUNTSMAN {                      # signal handler for SIGINT
                     68:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
1.6       albertel   69:     kill('INT' => keys(%children));
1.2       www        70:     unlink($pidfile);
                     71:     unlink($port);
                     72:     &logthis('---- Shutdown ----');
                     73:     exit;                           # clean up with dignity
                     74: }
                     75: 
                     76: 
                     77:  
                     78: # --------------------------------------------------------------------- Logging
                     79:  
                     80: sub logthis {
1.4       albertel   81:     my ($message)=@_;
1.2       www        82:     my $execdir=$perlvar{'lonDaemons'};
                     83:     my $fh=IO::File->new(">>$execdir/logs/lonmaxima.log");
                     84:     my $now=time;
                     85:     my $local=localtime($now);
                     86:     $lastlog=$local.': '.$message;
                     87:     print $fh "$local ($$): $message\n";
                     88: }
                     89:  
                     90: # -------------------------------------------------------------- Status setting
                     91:  
                     92: sub status {
1.4       albertel   93:     my ($what)=@_;
1.2       www        94:     my $now=time;
                     95:     my $local=localtime($now);
                     96:     $status=$local.': '.$what;
                     97:     $0='lonmaxima: '.$what.' '.$local;
                     98: }
                     99:  
                    100: # -------------------------------------------------------- Escape Special Chars
                    101:  
                    102: sub escape {
1.4       albertel  103:     my ($str)=@_;
1.2       www       104:     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
                    105:     return $str;
                    106: }
                    107:  
                    108: # ----------------------------------------------------- Un-Escape Special Chars
                    109:  
                    110: sub unescape {
1.4       albertel  111:     my ($str)=@_;
1.2       www       112:     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
                    113:     return $str;
                    114: }
                    115:  
                    116: # ------------------------ grabs exception and records it to log before exiting
                    117: sub catchexception {
                    118:     my ($signal)=@_;
                    119:     $SIG{QUIT}='DEFAULT';
                    120:     $SIG{__DIE__}='DEFAULT';
                    121:     chomp($signal);
1.5       albertel  122:     &logthis("<font color=\"red\">CRITICAL: "
                    123: 	     ."ABNORMAL EXIT. Child $$ died through "
                    124: 	     ."\"$signal\"</font>");
1.2       www       125:     die("Signal abend");
                    126: }
1.5       albertel  127: 
1.16      www       128: 
                    129: 
1.2       www       130: # ---------------------------------------------------------------- Main program
                    131: # -------------------------------- Set signal handlers to record abnormal exits
                    132:  
                    133:  
                    134: $SIG{'QUIT'}=\&catchexception;
                    135: $SIG{__DIE__}=\&catchexception;
                    136:  
                    137: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
                    138: &status("Read loncapa.conf and loncapa_apache.conf");
1.3       albertel  139: %perlvar=%{&LONCAPA::Configuration::read_conf('loncapa.conf')};
1.2       www       140:  
                    141: # ----------------------------- Make sure this process is running from user=www
                    142: my $wwwid=getpwnam('www');
                    143: if ($wwwid!=$<) {
1.5       albertel  144:     my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                    145:     my $subj="LON: User ID mismatch";
                    146:     system("echo 'User ID mismatch.  lonmaxima must be run as user www.' |\
1.2       www       147:  mailto $emailto -s '$subj' > /dev/null");
1.5       albertel  148:     exit 1;
1.2       www       149: }
                    150:  
                    151: # --------------------------------------------- Check if other instance running
                    152:  
                    153: $pidfile="$perlvar{'lonDaemons'}/logs/lonmaxima.pid";
                    154:  
                    155: if (-e $pidfile) {
1.5       albertel  156:     my $lfh=IO::File->new("$pidfile");
                    157:     my $pide=<$lfh>;
                    158:     chomp($pide);
1.6       albertel  159:     if (kill(0 => $pide)) { die "already running"; }
1.2       www       160: }
1.5       albertel  161: 
1.2       www       162: # ------------------------------------------------------- Listen to UNIX socket
                    163: &status("Opening socket");
                    164:  
                    165: $port = "$perlvar{'lonSockDir'}/maximasock";
                    166:  
                    167: unlink($port);
                    168:  
                    169: 
1.6       albertel  170: my $server = IO::Socket::UNIX->new(Local  => $port,
                    171: 				   Type   => SOCK_STREAM,
                    172: 				   Listen => 10 );
                    173: if (!$server) {
                    174:     my $st=120+int(rand(240));
                    175: 
                    176:     &logthis("<font color=blue>WARNING: ".
                    177: 	     "Can't make server socket ($st secs):  .. exiting</font>");
                    178: 
                    179:     sleep($st);
                    180:     exit;
                    181: }
1.2       www       182:     
                    183:  
                    184: # ---------------------------------------------------- Fork once and dissociate
                    185:  
                    186: my $fpid=fork;
                    187: exit if $fpid;
1.6       albertel  188: die("Couldn't fork: $!") unless defined($fpid);
1.2       www       189:  
                    190: POSIX::setsid() or die "Can't start new session: $!";
                    191:  
                    192: # ------------------------------------------------------- Write our PID on disk
                    193:  
                    194: my $execdir=$perlvar{'lonDaemons'};
1.5       albertel  195: open(PIDSAVE,">$execdir/logs/lonmaxima.pid");
1.2       www       196: print PIDSAVE "$$\n";
                    197: close(PIDSAVE);
                    198: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
                    199: &status('Starting');
1.6       albertel  200:      
1.2       www       201: 
1.10      albertel  202: # Install signal handlers.
                    203: $SIG{CHLD} = \&REAPER;
                    204: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
1.16      www       205:  
1.2       www       206: # Fork off our children.
                    207: for (1 .. $PREFORK) {
1.16      www       208:     &make_new_child($server);
1.2       www       209: }
                    210:  
                    211: # And maintain the population.
                    212: while (1) {
                    213:     &status('Parent process, sleeping');
                    214:     sleep;                          # wait for a signal (i.e., child's death)
1.3       albertel  215:     for (my $i = $children; $i < $PREFORK; $i++) {
1.2       www       216:         &status('Parent process, starting child');
1.16      www       217:         &make_new_child($server);           # top up the child pool
1.2       www       218:     }
                    219: }
                    220:                                                                                 
                    221: sub make_new_child {
1.16      www       222:     my ($server) = @_;
1.4       albertel  223: 
1.2       www       224:     # block signal for fork
1.4       albertel  225:     my $sigset = POSIX::SigSet->new(SIGINT);
1.2       www       226:     sigprocmask(SIG_BLOCK, $sigset)
1.6       albertel  227:         or die("Can't block SIGINT for fork: $!\n");
1.2       www       228:      
1.6       albertel  229:     die("fork: $!") unless defined(my $pid = fork);
1.2       www       230:      
                    231:     if ($pid) {
                    232:         # Parent records the child's birth and returns.
                    233:         sigprocmask(SIG_UNBLOCK, $sigset)
1.6       albertel  234:             or die("Can't unblock SIGINT for fork: $!\n");
1.16      www       235:         $children{$pid} = 1;
1.2       www       236:         $children++;
                    237:         return;
                    238:     } else {
                    239:         # Child can *not* return from this subroutine.
1.30      albertel  240:         
1.2       www       241:      
                    242:         # unblock signals
                    243:         sigprocmask(SIG_UNBLOCK, $sigset)
1.6       albertel  244:             or die("Can't unblock SIGINT for fork: $!\n");
1.15      www       245: 
1.17      www       246:         &logthis('New process started');
                    247: 
1.16      www       248:         my $command=Expect->spawn('maxima');
1.30      albertel  249: 	# soft/hard_close can take awhile and we really
                    250:         # don't care we just want it gone
                    251: 	$SIG{INT} = sub {
                    252: 	    my $pid = $command->pid();
                    253: 	    kill('KILL'=>$pid);
                    254: 	    exit; 
                    255: 	};
                    256: 
                    257: 	$command->log_stdout(0);
1.27      albertel  258: 	#$command->log_file("$execdir/logs/lonmaxima.session.log");
1.16      www       259:         &getmaximaoutput($command);
                    260: 
1.13      www       261:         for (my $i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
1.26      raeburn   262:             &status('Accepting connections');
                    263:             my $client = $server->accept()     or last;
1.29      www       264:             print $command ("display2d:false;kill(all);\n");
1.27      albertel  265: 	    &getmaximaoutput($command,2);
1.26      raeburn   266:             &sync($command);
                    267:             my $syntaxerr = 0;
                    268:             while (my $cmd=<$client>) {
                    269:                 &status('Processing command');
                    270:                 print $command &unescape($cmd);
                    271:                 my ($reply,$syntaxerr) = &getmaximaoutput($command,1);
                    272:                 print $client &escape($reply)."\n";
                    273:                 if ($syntaxerr) {
                    274:                     last;
                    275:                 } elsif ($reply=~/^Error\:/) {
                    276:                     &logthis('Died through '.$reply);
                    277:                     $client->close();
                    278:                     $command->hard_close();     
                    279:                     exit;
                    280:                 }
                    281: 	        &sync($command);
                    282:                 &status('Waiting for commands');
                    283:             }
1.13      www       284:         }
1.4       albertel  285: 
1.12      www       286:         # tidy up gracefully and finish
1.4       albertel  287: 
1.16      www       288:         $command->soft_close();
1.1       www       289: 
1.2       www       290:         # this exit is VERY important, otherwise the child will become
                    291:         # a producer of more and more children, forking yourself into
                    292:         # process death.
                    293:         exit;
                    294:     }
                    295: }
1.4       albertel  296: 
1.18      www       297: {
                    298:     my $counter;
                    299:     sub sync {
                    300: 	my ($command)=@_;
                    301: 	$counter++;
                    302: 	my $expect=$counter.time;
                    303: 	print $command "$expect;\n";
                    304: 	while (1) {
1.25      raeburn   305: 	    my $output=&getmaximaoutput($command,1);
1.18      www       306: 	    if (($output=~/\Q$expect\E/) || ($output=~/^Error\:/)) {
                    307: 		return;
                    308: 	    }
                    309: 	}
                    310:     }
                    311: }
                    312: 
1.16      www       313: sub getmaximaoutput {
1.25      raeburn   314:     my ($command,$numcheck)=@_;
                    315:     my $regexp = '\(\%i\d+\)';
1.26      raeburn   316:     my $syntaxerr=0;
1.25      raeburn   317:     if ($numcheck) {
1.27      albertel  318:        	if ($numcheck eq 2) {
                    319: 	    # command was the killall so should get a full reset on
                    320: 	    # command numbers
                    321: 	    $regexp = '(\(\%i(1)\)|Incorrect syntax\:)';
                    322: 	} elsif ($command->match() =~ /\(\%i(\d+)\)/) {
1.25      raeburn   323:             my $nextmatch = $1+1;
1.26      raeburn   324:             $regexp = '(\(\%i'.$nextmatch.'\)|Incorrect syntax\:)';
                    325:         }
                    326:     }
                    327:     my $timeout = 20;
1.27      albertel  328:     my (undef,$error,$matched,$output) =
                    329: 	$command->expect($timeout, -re => $regexp);
                    330: 
                    331:     if ($numcheck && $matched eq 'Incorrect syntax:') {
                    332: 	$syntaxerr = 1;
                    333: 	if (wantarray) {
                    334: 	    return ($matched,$syntaxerr);
                    335: 	} else {
                    336: 	    return $matched;
                    337: 	}
1.25      raeburn   338:     }
1.17      www       339:     if ($error) {
1.27      albertel  340: 	return 'Error: '.$error;
1.17      www       341:     }
1.23      raeburn   342:     $output =~ s/\r+//g; # Remove Windows-style linebreaks
1.16      www       343:     my $foundoutput=0;
1.32    ! albertel  344:     my $found_label=0;
1.16      www       345:     my $realoutput='';
                    346:     foreach my $line (split(/\n/,$output)) {
                    347:        if ($line=~/\;/) { $foundoutput=1; next; }
                    348:        if (!$foundoutput) { next; }
1.23      raeburn   349:        if ($line=~/^Incorrect syntax:/) { $syntaxerr = 1; next; }
1.32    ! albertel  350:        if ($line=~ /^(\(\%o\d+\))(.+)$/){
1.31      bisitz    351:            my $label = $1;
                    352:            $line = $2;
                    353:            $label =~s/\S/ /g;
1.26      raeburn   354:            $line=$label.$line;
1.32    ! albertel  355: 	   $found_label=1;
        !           356:        }
        !           357:        if ($found_label) {
        !           358: 	   $realoutput.=$line."\n";
1.16      www       359:        }
                    360:     }
1.23      raeburn   361:     if (wantarray) {
1.26      raeburn   362:         return ($realoutput,$syntaxerr);
1.23      raeburn   363:     } else {
                    364:         return $realoutput;
                    365:     }
1.15      www       366: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.