Annotation of loncom/lonsql, revision 1.57

1.1       harris41    1: #!/usr/bin/perl
1.39      harris41    2: 
                      3: # The LearningOnline Network
1.40      harris41    4: # lonsql - LON TCP-MySQL-Server Daemon for handling database requests.
1.39      harris41    5: #
1.57    ! www         6: # $Id: lonsql,v 1.56 2003/07/25 17:07:06 bowersj2 Exp $
1.41      harris41    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: # http://www.lon-capa.org/
                     29: #
1.51      matthew    30: 
                     31: =pod
                     32: 
                     33: =head1 NAME
                     34: 
                     35: lonsql - LON TCP-MySQL-Server Daemon for handling database requests.
                     36: 
                     37: =head1 SYNOPSIS
                     38: 
                     39: This script should be run as user=www.  
                     40: Note that a lonsql.pid file contains the pid of the parent process.
                     41: 
1.56      bowersj2   42: =head1 OVERVIEW
1.51      matthew    43: 
1.56      bowersj2   44: The SQL database in LON-CAPA is used for catalog searches against
                     45: resource metadata only. The authoritative version of the resource
                     46: metadata is an XML-file on the normal file system (same file name as
                     47: resource plus ".meta"). The SQL-database is a cache of these files,
                     48: and can be reconstructed from the XML files at any time.
                     49: 
                     50: The current database is implemented assuming a non-adjustable
                     51: architecture involving these data fields (specific to each version of
                     52: a resource).
                     53: 
                     54: =over 4
                     55: 
                     56: =item * title
                     57: 
                     58: =item * author
                     59: 
                     60: =item * subject
                     61: 
                     62: =item * notes
                     63: 
                     64: =item * abstract
                     65: 
                     66: =item * mime
                     67: 
                     68: =item * language
                     69: 
                     70: =item * creationdate
                     71: 
                     72: =item * lastrevisiondate
                     73: 
                     74: =item * owner
                     75: 
                     76: =item * copyright 
                     77: 
                     78: =back 
                     79: 
                     80: =head2 Purpose within LON-CAPA
                     81: 
                     82: LON-CAPA is meant to distribute A LOT of educational content to A LOT
                     83: of people. It is ineffective to directly rely on contents within the
                     84: ext2 filesystem to be speedily scanned for on-the-fly searches of
                     85: content descriptions. (Simply put, it takes a cumbersome amount of
                     86: time to open, read, analyze, and close thousands of files.)
                     87: 
                     88: The solution is to index various data fields that are descriptive of
                     89: the educational resources on a LON-CAPA server machine in a
                     90: database. Descriptive data fields are referred to as "metadata". The
                     91: question then arises as to how this metadata is handled in terms of
                     92: the rest of the LON-CAPA network without burdening client and daemon
                     93: processes.
                     94: 
                     95: The obvious solution, using lonc to send a query to a lond process,
                     96: doesn't work so well in general as you can see in the following
                     97: example:
                     98: 
                     99:     lonc= loncapa client process    A-lonc= a lonc process on Server A
                    100:     lond= loncapa daemon process
                    101: 
                    102:                  database command
                    103:     A-lonc  --------TCP/IP----------------> B-lond
                    104: 
                    105: The problem emerges that A-lonc and B-lond are kept waiting for the
                    106: MySQL server to "do its stuff", or in other words, perform the
                    107: conceivably sophisticated, data-intensive, time-sucking database
                    108: transaction.  By tying up a lonc and lond process, this significantly
                    109: cripples the capabilities of LON-CAPA servers.
                    110: 
                    111: The solution is to offload the work onto another process, and use
                    112: lonc and lond just for requests and notifications of completed
                    113: processing:
                    114: 
                    115:                 database command
                    116: 
                    117:   A-lonc  ---------TCP/IP-----------------> B-lond =====> B-lonsql
                    118:          <---------------------------------/                |
                    119:            "ok, I'll get back to you..."                    |
                    120:                                                             |
                    121:                                                             /
                    122:   A-lond  <-------------------------------  B-lonc   <======
                    123:            "Guess what? I have the result!"
                    124: 
                    125: Of course, depending on success or failure, the messages may vary, but
                    126: the principle remains the same where a separate pool of children
                    127: processes (lonsql's) handle the MySQL database manipulations.
                    128: 
                    129: Thus, lonc and lond spend effectively no time waiting on results from
                    130: the database.
1.51      matthew   131: 
                    132: =head1 Internals
                    133: 
                    134: =over 4
                    135: 
                    136: =cut
                    137: 
                    138: use strict;
1.36      www       139: 
1.42      harris41  140: use lib '/home/httpd/lib/perl/';
                    141: use LONCAPA::Configuration;
                    142: 
1.2       harris41  143: use IO::Socket;
                    144: use Symbol;
1.1       harris41  145: use POSIX;
                    146: use IO::Select;
                    147: use IO::File;
                    148: use Socket;
                    149: use Fcntl;
                    150: use Tie::RefHash;
                    151: use DBI;
1.51      matthew   152: use File::Find;
                    153: 
                    154: ########################################################
                    155: ########################################################
                    156: 
                    157: =pod
                    158: 
                    159: =item Global Variables
                    160: 
                    161: =over 4
                    162: 
                    163: =item dbh
                    164: 
                    165: =back
                    166: 
                    167: =cut
                    168: 
                    169: ########################################################
                    170: ########################################################
                    171: my $dbh;
                    172: 
                    173: ########################################################
                    174: ########################################################
                    175: 
                    176: =pod 
                    177: 
                    178: =item Variables required for forking
1.1       harris41  179: 
1.51      matthew   180: =over 4
                    181: 
                    182: =item $MAX_CLIENTS_PER_CHILD
                    183: 
                    184: The number of clients each child should process.
                    185: 
                    186: =item %children 
                    187: 
                    188: The keys to %children  are the current child process IDs
                    189: 
                    190: =item $children
                    191: 
                    192: The current number of children
                    193: 
                    194: =back
                    195: 
                    196: =cut 
1.9       harris41  197: 
1.51      matthew   198: ########################################################
                    199: ########################################################
                    200: my $MAX_CLIENTS_PER_CHILD  = 5;   # number of clients each child should process
                    201: my %children               = ();  # keys are current child process IDs
                    202: my $children               = 0;   # current number of children
                    203:                                
                    204: ###################################################################
                    205: ###################################################################
                    206: 
                    207: =pod
                    208: 
                    209: =item Main body of code.
                    210: 
                    211: =over 4
1.45      www       212: 
1.51      matthew   213: =item Read data from loncapa_apache.conf and loncapa.conf.
                    214: 
                    215: =item Ensure we can access the database.
                    216: 
                    217: =item Determine if there are other instances of lonsql running.
                    218: 
                    219: =item Read the hosts file.
                    220: 
                    221: =item Create a socket for lonsql.
                    222: 
                    223: =item Fork once and dissociate from parent.
                    224: 
                    225: =item Write PID to disk.
                    226: 
                    227: =item Prefork children and maintain the population of children.
                    228: 
                    229: =back
                    230: 
                    231: =cut
                    232: 
                    233: ###################################################################
                    234: ###################################################################
                    235: my $childmaxattempts=10;
                    236: my $run =0;              # running counter to generate the query-id
                    237: #
                    238: # Read loncapa_apache.conf and loncapa.conf
                    239: #
1.53      harris41  240: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
1.51      matthew   241: my %perlvar=%{$perlvarref};
                    242: #
                    243: # Make sure that database can be accessed
                    244: #
                    245: my $dbh;
                    246: unless ($dbh = DBI->connect("DBI:mysql:loncapa","www",
                    247:                             $perlvar{'lonSqlAccess'},
                    248:                             { RaiseError =>0,PrintError=>0})) { 
                    249:     print "Cannot connect to database!\n";
                    250:     my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                    251:     my $subj="LON: $perlvar{'lonHostID'} Cannot connect to database!";
                    252:     system("echo 'Cannot connect to MySQL database!' |".
                    253:            " mailto $emailto -s '$subj' > /dev/null");
1.57    ! www       254: 
        !           255:     open(SMP,'>/home/httpd/html/lon-status/mysql.txt');
        !           256:     print SMP 'time='.time.'&mysql=defunct'."\n";
        !           257:     close(SMP);
        !           258: 
1.51      matthew   259:     exit 1;
                    260: } else {
                    261:     $dbh->disconnect;
                    262: }
1.52      matthew   263: 
1.51      matthew   264: #
                    265: # Check if other instance running
                    266: #
                    267: my $pidfile="$perlvar{'lonDaemons'}/logs/lonsql.pid";
                    268: if (-e $pidfile) {
                    269:    my $lfh=IO::File->new("$pidfile");
                    270:    my $pide=<$lfh>;
                    271:    chomp($pide);
                    272:    if (kill 0 => $pide) { die "already running"; }
                    273: }
1.52      matthew   274: 
1.49      www       275: #
1.51      matthew   276: # Read hosts file
1.49      www       277: #
1.51      matthew   278: my %hostip;
                    279: my $thisserver;
                    280: my $PREFORK=4; # number of children to maintain, at least four spare
                    281: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
                    282: while (my $configline=<CONFIG>) {
                    283:     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
                    284:     chomp($ip);
                    285:     $hostip{$ip}=$id;
                    286:     $thisserver=$name if ($id eq $perlvar{'lonHostID'});
                    287:     $PREFORK++;
1.45      www       288: }
1.51      matthew   289: close(CONFIG);
                    290: #
                    291: $PREFORK=int($PREFORK/4);
1.52      matthew   292: 
1.51      matthew   293: #
                    294: # Create a socket to talk to lond
                    295: #
                    296: my $unixsock = "mysqlsock";
                    297: my $localfile="$perlvar{'lonSockDir'}/$unixsock";
                    298: my $server;
                    299: unlink ($localfile);
                    300: unless ($server=IO::Socket::UNIX->new(Local    =>"$localfile",
                    301:                                       Type    => SOCK_STREAM,
                    302:                                       Listen => 10)) {
                    303:     print "in socket error:$@\n";
1.45      www       304: }
1.52      matthew   305: 
1.51      matthew   306: #
                    307: # Fork once and dissociate
1.52      matthew   308: #
1.51      matthew   309: my $fpid=fork;
1.1       harris41  310: exit if $fpid;
                    311: die "Couldn't fork: $!" unless defined ($fpid);
                    312: POSIX::setsid() or die "Can't start new session: $!";
1.52      matthew   313: 
1.51      matthew   314: #
                    315: # Write our PID on disk
                    316: my $execdir=$perlvar{'lonDaemons'};
1.1       harris41  317: open (PIDSAVE,">$execdir/logs/lonsql.pid");
                    318: print PIDSAVE "$$\n";
                    319: close(PIDSAVE);
                    320: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
1.52      matthew   321: 
1.51      matthew   322: #
                    323: # Ignore signals generated during initial startup
1.1       harris41  324: $SIG{HUP}=$SIG{USR1}='IGNORE';
1.51      matthew   325: # Now we are on our own    
                    326: #    Fork off our children.
1.2       harris41  327: for (1 .. $PREFORK) {
                    328:     make_new_child();
1.1       harris41  329: }
1.52      matthew   330: 
1.51      matthew   331: #
1.2       harris41  332: # Install signal handlers.
1.1       harris41  333: $SIG{CHLD} = \&REAPER;
                    334: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
                    335: $SIG{HUP}  = \&HUPSMAN;
1.52      matthew   336: 
1.51      matthew   337: #
1.1       harris41  338: # And maintain the population.
                    339: while (1) {
                    340:     sleep;                          # wait for a signal (i.e., child's death)
1.51      matthew   341:     for (my $i = $children; $i < $PREFORK; $i++) {
1.2       harris41  342:         make_new_child();           # top up the child pool
1.1       harris41  343:     }
                    344: }
                    345: 
1.51      matthew   346: ########################################################
                    347: ########################################################
                    348: 
                    349: =pod
                    350: 
                    351: =item &make_new_child
                    352: 
                    353: Inputs: None
                    354: 
                    355: Returns: None
                    356: 
                    357: =cut
1.2       harris41  358: 
1.51      matthew   359: ########################################################
                    360: ########################################################
1.1       harris41  361: sub make_new_child {
                    362:     my $pid;
                    363:     my $sigset;
1.51      matthew   364:     #
1.1       harris41  365:     # block signal for fork
                    366:     $sigset = POSIX::SigSet->new(SIGINT);
                    367:     sigprocmask(SIG_BLOCK, $sigset)
                    368:         or die "Can't block SIGINT for fork: $!\n";
1.51      matthew   369:     #
1.2       harris41  370:     die "fork: $!" unless defined ($pid = fork);
1.51      matthew   371:     #
1.1       harris41  372:     if ($pid) {
                    373:         # Parent records the child's birth and returns.
                    374:         sigprocmask(SIG_UNBLOCK, $sigset)
                    375:             or die "Can't unblock SIGINT for fork: $!\n";
                    376:         $children{$pid} = 1;
                    377:         $children++;
                    378:         return;
                    379:     } else {
1.2       harris41  380:         # Child can *not* return from this subroutine.
1.1       harris41  381:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
                    382:         # unblock signals
                    383:         sigprocmask(SIG_UNBLOCK, $sigset)
                    384:             or die "Can't unblock SIGINT for fork: $!\n";
1.2       harris41  385:         #open database handle
                    386: 	# making dbh global to avoid garbage collector
1.51      matthew   387: 	unless ($dbh = DBI->connect("DBI:mysql:loncapa","www",
                    388:                                     $perlvar{'lonSqlAccess'},
                    389:                                     { RaiseError =>0,PrintError=>0})) { 
                    390:             sleep(10+int(rand(20)));
                    391:             &logthis("<font color=blue>WARNING: Couldn't connect to database".
                    392:                      ": $@</font>");
                    393:                      #  "($st secs): $@</font>");
                    394:             print "database handle error\n";
                    395:             exit;
                    396:         }
                    397: 	# make sure that a database disconnection occurs with 
                    398:         # ending kill signals
1.2       harris41  399: 	$SIG{TERM}=$SIG{INT}=$SIG{QUIT}=$SIG{__DIE__}=\&DISCONNECT;
1.1       harris41  400:         # handle connections until we've reached $MAX_CLIENTS_PER_CHILD
1.51      matthew   401:         for (my $i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
                    402:             my $client = $server->accept() or last;
1.2       harris41  403:             # do something with the connection
1.1       harris41  404: 	    $run = $run+1;
1.2       harris41  405: 	    my $userinput = <$client>;
                    406: 	    chomp($userinput);
1.51      matthew   407:             #
1.45      www       408: 	    my ($conserver,$query,
                    409: 		$arg1,$arg2,$arg3)=split(/&/,$userinput);
                    410: 	    my $query=unescape($query);
1.51      matthew   411:             #
1.2       harris41  412:             #send query id which is pid_unixdatetime_runningcounter
1.51      matthew   413: 	    my $queryid = $thisserver;
1.2       harris41  414: 	    $queryid .="_".($$)."_";
                    415: 	    $queryid .= time."_";
                    416: 	    $queryid .= $run;
                    417: 	    print $client "$queryid\n";
1.51      matthew   418: 	    #
1.47      www       419: 	    &logthis("QUERY: $query - $arg1 - $arg2 - $arg3");
1.25      harris41  420: 	    sleep 1;
1.51      matthew   421:             #
1.45      www       422:             my $result='';
1.51      matthew   423:             #
                    424:             # At this point, query is received, query-ID assigned and sent 
                    425:             # back, $query eq 'logquery' will mean that this is a query 
                    426:             # against log-files
                    427:             if (($query eq 'userlog') || ($query eq 'courselog')) {
                    428:                 # beginning of log query
                    429:                 my $udom    = &unescape($arg1);
                    430:                 my $uname   = &unescape($arg2);
                    431:                 my $command = &unescape($arg3);
                    432:                 my $path    = &propath($udom,$uname);
                    433:                 if (-e "$path/activity.log") {
                    434:                     if ($query eq 'userlog') {
                    435:                         $result=&userlog($path,$command);
                    436:                     } else {
                    437:                         $result=&courselog($path,$command);
                    438:                     }
                    439:                 } else {
                    440:                     &logthis('Unable to do log query: '.$uname.'@'.$udom);
                    441:                     $result='no_such_file';
                    442:                 }
                    443:                 # end of log query
                    444:             } else {
                    445:                 # Do an sql query
                    446:                 $result = &do_sql_query($query,$arg1,$arg2);
                    447:             }
1.50      matthew   448:             # result does not need to be escaped because it has already been
                    449:             # escaped.
                    450:             #$result=&escape($result);
1.51      matthew   451:             # reply with result, append \n unless already there
1.44      www       452: 	    $result.="\n" unless ($result=~/\n$/);
1.17      harris41  453:             &reply("queryreply:$queryid:$result",$conserver);
1.1       harris41  454:         }
                    455:         # tidy up gracefully and finish
1.51      matthew   456:         #
                    457:         # close the database handle
1.2       harris41  458: 	$dbh->disconnect
1.51      matthew   459:             or &logthis("<font color=blue>WARNING: Couldn't disconnect".
                    460:                         " from database  $DBI::errstr : $@</font>");
1.1       harris41  461:         # this exit is VERY important, otherwise the child will become
                    462:         # a producer of more and more children, forking yourself into
                    463:         # process death.
                    464:         exit;
                    465:     }
1.2       harris41  466: }
1.1       harris41  467: 
1.51      matthew   468: ########################################################
                    469: ########################################################
                    470: 
                    471: =pod
                    472: 
                    473: =item &do_sql_query
                    474: 
                    475: Runs an sql metadata table query.
                    476: 
                    477: Inputs: $query, $custom, $customshow
                    478: 
                    479: Returns: A string containing escaped results.
                    480: 
                    481: =cut
                    482: 
                    483: ########################################################
                    484: ########################################################
                    485: {
                    486:     my @metalist;
                    487: 
                    488: sub process_file {
                    489:     if ( -e $_ &&  # file exists
                    490:          -f $_ &&  # and is a normal file
                    491:          /\.meta$/ &&  # ends in meta
                    492:          ! /^.+\.\d+\.[^\.]+\.meta$/  # is not a previous version
                    493:          ) {
                    494:         push(@metalist,$File::Find::name);
                    495:     }
                    496: }
                    497: 
                    498: sub do_sql_query {
                    499:     my ($query,$custom,$customshow) = @_;
                    500:     $custom     = &unescape($custom);
                    501:     $customshow = &unescape($customshow);
                    502:     #
                    503:     @metalist = ();
                    504:     #
                    505:     my $result = '';
                    506:     my @results = ();
                    507:     my @files;
                    508:     my $subsetflag=0;
                    509:     #
                    510:     if ($query) {
                    511:         #prepare and execute the query
                    512:         my $sth = $dbh->prepare($query);
                    513:         unless ($sth->execute()) {
                    514:             &logthis("<font color=blue>WARNING: ".
                    515:                      "Could not retrieve from database: $@</font>");
                    516:         } else {
                    517:             my $aref=$sth->fetchall_arrayref;
                    518:             foreach my $row (@$aref) {
                    519:                 push @files,@{$row}[3] if ($custom or $customshow);
                    520:                 my @b=map { &escape($_); } @$row;
                    521:                 push @results,join(",", @b);
                    522:                 # Build up the @files array with the LON-CAPA urls 
                    523:                 # of the resources.
                    524:             }
                    525:         }
                    526:     }
                    527:     # do custom metadata searching here and build into result
                    528:     return join("&",@results) if (! ($custom or $customshow));
                    529:     # Only get here if there is a custom query or custom show request
                    530:     &logthis("Doing custom query for $custom");
                    531:     if ($query) {
                    532:         @metalist=map {
                    533:             $perlvar{'lonDocRoot'}.$_.'.meta';
                    534:         } @files;
                    535:     } else {
                    536:         my $dir = "$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}";
                    537:         @metalist=(); 
                    538:         opendir(RESOURCES,$dir);
                    539:         my @homeusers=grep {
                    540:             &ishome($dir.'/'.$_);
                    541:         } grep {!/^\.\.?$/} readdir(RESOURCES);
                    542:         closedir RESOURCES;
                    543:         # Define the
                    544:         foreach my $user (@homeusers) {
                    545:             find (\&process_file,$dir.'/'.$user);
                    546:         }
                    547:     } 
                    548:     # if file is indicated in sql database and
                    549:     #     not part of sql-relevant query, do not pattern match.
                    550:     #
                    551:     # if file is not in sql database, output error.
                    552:     #
                    553:     # if file is indicated in sql database and is
                    554:     #     part of query result list, then do the pattern match.
                    555:     my $customresult='';
                    556:     my @results;
                    557:     foreach my $metafile (@metalist) {
                    558:         my $fh=IO::File->new($metafile);
                    559:         my @lines=<$fh>;
                    560:         my $stuff=join('',@lines);
                    561:         if ($stuff=~/$custom/s) {
                    562:             foreach my $f ('abstract','author','copyright',
                    563:                            'creationdate','keywords','language',
                    564:                            'lastrevisiondate','mime','notes',
                    565:                            'owner','subject','title') {
                    566:                 $stuff=~s/\n?\<$f[^\>]*\>.*?<\/$f[^\>]*\>\n?//s;
                    567:             }
                    568:             my $mfile=$metafile; 
                    569:             my $docroot=$perlvar{'lonDocRoot'};
                    570:             $mfile=~s/^$docroot//;
                    571:             $mfile=~s/\.meta$//;
                    572:             unless ($query) {
                    573:                 my $q2="SELECT * FROM metadata WHERE url ".
                    574:                     " LIKE BINARY '?'";
                    575:                 my $sth = $dbh->prepare($q2);
                    576:                 $sth->execute($mfile);
                    577:                 my $aref=$sth->fetchall_arrayref;
                    578:                 foreach my $a (@$aref) {
                    579:                     my @b=map { &escape($_)} @$a;
                    580:                     push @results,join(",", @b);
                    581:                 }
                    582:             }
                    583:             # &logthis("found: $stuff");
                    584:             $customresult.='&custom='.&escape($mfile).','.
                    585:                 escape($stuff);
                    586:         }
                    587:     }
                    588:     $result=join("&",@results) unless $query;
                    589:     $result.=$customresult;
                    590:     #
                    591:     return $result;
                    592: } # End of &do_sql_query
                    593: 
                    594: } # End of scoping curly braces for &process_file and &do_sql_query
                    595: ########################################################
                    596: ########################################################
                    597: 
                    598: =pod
                    599: 
                    600: =item &logthis
                    601: 
                    602: Inputs: $message, the message to log
                    603: 
                    604: Returns: nothing
                    605: 
                    606: Writes $message to the logfile.
                    607: 
                    608: =cut
                    609: 
                    610: ########################################################
                    611: ########################################################
                    612: sub logthis {
                    613:     my $message=shift;
                    614:     my $execdir=$perlvar{'lonDaemons'};
1.52      matthew   615:     my $fh=IO::File->new(">>$execdir/logs/lonsql.log");
1.51      matthew   616:     my $now=time;
                    617:     my $local=localtime($now);
                    618:     print $fh "$local ($$): $message\n";
1.2       harris41  619: }
1.1       harris41  620: 
1.2       harris41  621: # -------------------------------------------------- Non-critical communication
1.1       harris41  622: 
1.51      matthew   623: ########################################################
                    624: ########################################################
                    625: 
                    626: =pod
                    627: 
                    628: =item &subreply
                    629: 
                    630: Sends a command to a server.  Called only by &reply.
                    631: 
                    632: Inputs: $cmd,$server
                    633: 
                    634: Returns: The results of the message or 'con_lost' on error.
                    635: 
                    636: =cut
                    637: 
                    638: ########################################################
                    639: ########################################################
1.2       harris41  640: sub subreply {
                    641:     my ($cmd,$server)=@_;
                    642:     my $peerfile="$perlvar{'lonSockDir'}/$server";
                    643:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
                    644:                                       Type    => SOCK_STREAM,
                    645:                                       Timeout => 10)
                    646:        or return "con_lost";
                    647:     print $sclient "$cmd\n";
                    648:     my $answer=<$sclient>;
                    649:     chomp($answer);
1.51      matthew   650:     $answer="con_lost" if (!$answer);
1.2       harris41  651:     return $answer;
                    652: }
1.1       harris41  653: 
1.51      matthew   654: ########################################################
                    655: ########################################################
                    656: 
                    657: =pod
                    658: 
                    659: =item &reply
                    660: 
                    661: Sends a command to a server.
                    662: 
                    663: Inputs: $cmd,$server
                    664: 
                    665: Returns: The results of the message or 'con_lost' on error.
                    666: 
                    667: =cut
                    668: 
                    669: ########################################################
                    670: ########################################################
1.2       harris41  671: sub reply {
                    672:   my ($cmd,$server)=@_;
                    673:   my $answer;
                    674:   if ($server ne $perlvar{'lonHostID'}) { 
                    675:     $answer=subreply($cmd,$server);
                    676:     if ($answer eq 'con_lost') {
                    677: 	$answer=subreply("ping",$server);
                    678:         $answer=subreply($cmd,$server);
                    679:     }
                    680:   } else {
                    681:     $answer='self_reply';
1.33      harris41  682:     $answer=subreply($cmd,$server);
1.2       harris41  683:   } 
                    684:   return $answer;
                    685: }
1.1       harris41  686: 
1.51      matthew   687: ########################################################
                    688: ########################################################
                    689: 
                    690: =pod
                    691: 
                    692: =item &escape
                    693: 
                    694: Escape special characters in a string.
1.3       harris41  695: 
1.51      matthew   696: Inputs: string to escape
                    697: 
                    698: Returns: The input string with special characters escaped.
                    699: 
                    700: =cut
                    701: 
                    702: ########################################################
                    703: ########################################################
1.3       harris41  704: sub escape {
                    705:     my $str=shift;
                    706:     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
                    707:     return $str;
                    708: }
                    709: 
1.51      matthew   710: ########################################################
                    711: ########################################################
                    712: 
                    713: =pod
                    714: 
                    715: =item &unescape
                    716: 
                    717: Unescape special characters in a string.
1.3       harris41  718: 
1.51      matthew   719: Inputs: string to unescape
                    720: 
                    721: Returns: The input string with special characters unescaped.
                    722: 
                    723: =cut
                    724: 
                    725: ########################################################
                    726: ########################################################
1.3       harris41  727: sub unescape {
                    728:     my $str=shift;
                    729:     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
                    730:     return $str;
                    731: }
1.34      harris41  732: 
1.51      matthew   733: ########################################################
                    734: ########################################################
                    735: 
                    736: =pod
                    737: 
                    738: =item &ishome
                    739: 
                    740: Determine if the current machine is the home server for a user.
                    741: The determination is made by checking the filesystem for the users information.
                    742: 
                    743: Inputs: $author
                    744: 
                    745: Returns: 0 - this is not the authors home server, 1 - this is.
                    746: 
                    747: =cut
                    748: 
                    749: ########################################################
                    750: ########################################################
1.34      harris41  751: sub ishome {
                    752:     my $author=shift;
                    753:     $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
                    754:     my ($udom,$uname)=split(/\//,$author);
                    755:     my $proname=propath($udom,$uname);
                    756:     if (-e $proname) {
                    757: 	return 1;
                    758:     } else {
                    759:         return 0;
                    760:     }
                    761: }
                    762: 
1.51      matthew   763: ########################################################
                    764: ########################################################
                    765: 
                    766: =pod
                    767: 
                    768: =item &propath
                    769: 
                    770: Inputs: user name, user domain
                    771: 
                    772: Returns: The full path to the users directory.
                    773: 
                    774: =cut
                    775: 
                    776: ########################################################
                    777: ########################################################
1.34      harris41  778: sub propath {
                    779:     my ($udom,$uname)=@_;
                    780:     $udom=~s/\W//g;
                    781:     $uname=~s/\W//g;
                    782:     my $subdir=$uname.'__';
                    783:     $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
                    784:     my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
                    785:     return $proname;
                    786: } 
1.40      harris41  787: 
1.51      matthew   788: ########################################################
                    789: ########################################################
                    790: 
                    791: =pod
                    792: 
                    793: =item &courselog
                    794: 
                    795: Inputs: $path, $command
                    796: 
                    797: Returns: unescaped string of values.
                    798: 
                    799: =cut
                    800: 
                    801: ########################################################
                    802: ########################################################
                    803: sub courselog {
                    804:     my ($path,$command)=@_;
                    805:     my %filters=();
                    806:     foreach (split(/\:/,&unescape($command))) {
                    807: 	my ($name,$value)=split(/\=/,$_);
                    808:         $filters{$name}=$value;
                    809:     }
                    810:     my @results=();
                    811:     open(IN,$path.'/activity.log') or return ('file_error');
                    812:     while (my $line=<IN>) {
                    813:         chomp($line);
                    814:         my ($timestamp,$host,$log)=split(/\:/,$line);
                    815: #
                    816: # $log has the actual log entries; currently still escaped, and
                    817: # %26(timestamp)%3a(url)%3a(user)%3a(domain)
                    818: # then additionally
                    819: # %3aPOST%3a(name)%3d(value)%3a(name)%3d(value)
                    820: # or
                    821: # %3aCSTORE%3a(name)%3d(value)%26(name)%3d(value)
                    822: #
                    823: # get delimiter between timestamped entries to be &&&
                    824:         $log=~s/\%26(\d+)\%3a/\&\&\&$1\%3a/g;
                    825: # now go over all log entries 
                    826:         foreach (split(/\&\&\&/,&unescape($log))) {
                    827: 	    my ($time,$res,$uname,$udom,$action,@values)=split(/\:/,$_);
                    828:             my $values=&unescape(join(':',@values));
                    829:             $values=~s/\&/\:/g;
                    830:             $res=&unescape($res);
                    831:             my $include=1;
                    832:             if (($filters{'username'}) && ($uname ne $filters{'username'})) 
                    833:                                                                { $include=0; }
                    834:             if (($filters{'domain'}) && ($udom ne $filters{'domain'})) 
                    835:                                                                { $include=0; }
                    836:             if (($filters{'url'}) && ($res!~/$filters{'url'}/)) 
                    837:                                                                { $include=0; }
                    838:             if (($filters{'start'}) && ($time<$filters{'start'})) 
                    839:                                                                { $include=0; }
                    840:             if (($filters{'end'}) && ($time>$filters{'end'})) 
                    841:                                                                { $include=0; }
                    842:             if (($filters{'action'} eq 'view') && ($action)) 
                    843:                                                                { $include=0; }
                    844:             if (($filters{'action'} eq 'submit') && ($action ne 'POST')) 
                    845:                                                                { $include=0; }
                    846:             if (($filters{'action'} eq 'grade') && ($action ne 'CSTORE')) 
                    847:                                                                { $include=0; }
                    848:             if ($include) {
                    849: 	       push(@results,($time<1000000000?'0':'').$time.':'.$res.':'.
                    850:                                             $uname.':'.$udom.':'.
                    851:                                             $action.':'.$values);
                    852:             }
                    853:        }
                    854:     }
                    855:     close IN;
                    856:     return join('&',sort(@results));
                    857: }
                    858: 
                    859: ########################################################
                    860: ########################################################
                    861: 
                    862: =pod
                    863: 
                    864: =item &userlog
                    865: 
                    866: Inputs: $path, $command
                    867: 
                    868: Returns: unescaped string of values.
1.40      harris41  869: 
1.51      matthew   870: =cut
1.40      harris41  871: 
1.51      matthew   872: ########################################################
                    873: ########################################################
                    874: sub userlog {
                    875:     my ($path,$command)=@_;
                    876:     my %filters=();
                    877:     foreach (split(/\:/,&unescape($command))) {
                    878: 	my ($name,$value)=split(/\=/,$_);
                    879:         $filters{$name}=$value;
                    880:     }
                    881:     my @results=();
                    882:     open(IN,$path.'/activity.log') or return ('file_error');
                    883:     while (my $line=<IN>) {
                    884:         chomp($line);
                    885:         my ($timestamp,$host,$log)=split(/\:/,$line);
                    886:         $log=&unescape($log);
                    887:         my $include=1;
                    888:         if (($filters{'start'}) && ($timestamp<$filters{'start'})) 
                    889:                                                              { $include=0; }
                    890:         if (($filters{'end'}) && ($timestamp>$filters{'end'})) 
                    891:                                                              { $include=0; }
                    892:         if (($filters{'action'} eq 'log') && ($log!~/^Log/)) { $include=0; }
                    893:         if (($filters{'action'} eq 'check') && ($log!~/^Check/)) 
                    894:                                                              { $include=0; }
                    895:         if ($include) {
                    896: 	   push(@results,$timestamp.':'.$log);
                    897:         }
                    898:     }
                    899:     close IN;
                    900:     return join('&',sort(@results));
1.52      matthew   901: }
                    902: 
                    903: ########################################################
                    904: ########################################################
                    905: 
                    906: =pod
                    907: 
                    908: =item Functions required for forking
                    909: 
                    910: =over 4
                    911: 
                    912: =item REAPER
                    913: 
                    914: REAPER takes care of dead children.
                    915: 
                    916: =item HUNTSMAN
                    917: 
                    918: Signal handler for SIGINT.
                    919: 
                    920: =item HUPSMAN
                    921: 
                    922: Signal handler for SIGHUP
                    923: 
                    924: =item DISCONNECT
                    925: 
                    926: Disconnects from database.
                    927: 
                    928: =back
                    929: 
                    930: =cut
                    931: 
                    932: ########################################################
                    933: ########################################################
                    934: sub REAPER {                   # takes care of dead children
                    935:     $SIG{CHLD} = \&REAPER;
                    936:     my $pid = wait;
                    937:     $children --;
                    938:     &logthis("Child $pid died");
                    939:     delete $children{$pid};
                    940: }
                    941: 
                    942: sub HUNTSMAN {                      # signal handler for SIGINT
                    943:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
                    944:     kill 'INT' => keys %children;
                    945:     my $execdir=$perlvar{'lonDaemons'};
                    946:     unlink("$execdir/logs/lonsql.pid");
                    947:     &logthis("<font color=red>CRITICAL: Shutting down</font>");
                    948:     $unixsock = "mysqlsock";
                    949:     my $port="$perlvar{'lonSockDir'}/$unixsock";
                    950:     unlink($port);
                    951:     exit;                           # clean up with dignity
                    952: }
                    953: 
                    954: sub HUPSMAN {                      # signal handler for SIGHUP
                    955:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
                    956:     kill 'INT' => keys %children;
                    957:     close($server);                # free up socket
                    958:     &logthis("<font color=red>CRITICAL: Restarting</font>");
                    959:     my $execdir=$perlvar{'lonDaemons'};
                    960:     $unixsock = "mysqlsock";
                    961:     my $port="$perlvar{'lonSockDir'}/$unixsock";
                    962:     unlink($port);
                    963:     exec("$execdir/lonsql");         # here we go again
                    964: }
                    965: 
                    966: sub DISCONNECT {
                    967:     $dbh->disconnect or 
                    968:     &logthis("<font color=blue>WARNING: Couldn't disconnect from database ".
                    969:              " $DBI::errstr : $@</font>");
                    970:     exit;
1.51      matthew   971: }
1.40      harris41  972: 
                    973: 
                    974: 
                    975: 
                    976: 
                    977: 
                    978: 
                    979: 
                    980: 
                    981: 
                    982: 
1.51      matthew   983: # ----------------------------------- POD (plain old documentation, CPAN style)
1.40      harris41  984: 
1.51      matthew   985: =pod
1.40      harris41  986: 
1.51      matthew   987: =back
1.40      harris41  988: 
                    989: =cut

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.