Diff for /loncom/lonsql between versions 1.50 and 1.75

version 1.50, 2002/07/05 15:07:59 version 1.75, 2006/02/10 09:47:36
Line 27 Line 27
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # YEAR=2000  
 # lonsql-based on the preforker:harsha jagasia:date:5/10/00  =pod
 # 7/25 Gerd Kortemeyer  
 # many different dates Scott Harrison  =head1 NAME
 # YEAR=2001  
 # many different dates Scott Harrison  lonsql - LON TCP-MySQL-Server Daemon for handling database requests.
 # 03/22/2001 Scott Harrison  
 # 8/30 Gerd Kortemeyer  =head1 SYNOPSIS
 # 10/17,11/28,11/29,12/20 Scott Harrison  
 # YEAR=2001  This script should be run as user=www.  
 # 5/11 Scott Harrison  Note that a lonsql.pid file contains the pid of the parent process.
 #  
 ###  =head1 OVERVIEW
   
 ###############################################################################  =head2 Purpose within LON-CAPA
 ##                                                                           ##  
 ## ORGANIZATION OF THIS PERL SCRIPT                                          ##  LON-CAPA is meant to distribute A LOT of educational content to A LOT
 ## 1. Modules used                                                           ##  of people. It is ineffective to directly rely on contents within the
 ## 2. Enable find subroutine                                                 ##  ext2 filesystem to be speedily scanned for on-the-fly searches of
 ## 3. Read httpd config files and get variables                              ##  content descriptions. (Simply put, it takes a cumbersome amount of
 ## 4. Make sure that database can be accessed                                ##  time to open, read, analyze, and close thousands of files.)
 ## 5. Make sure this process is running from user=www                        ##  
 ## 6. Check if other instance is running                                     ##  The solution is to index various data fields that are descriptive of
 ## 7. POD (plain old documentation, CPAN style)                              ##  the educational resources on a LON-CAPA server machine in a
 ##                                                                           ##  database. Descriptive data fields are referred to as "metadata". The
 ###############################################################################  question then arises as to how this metadata is handled in terms of
   the rest of the LON-CAPA network without burdening client and daemon
   processes.
   
   The obvious solution, using lonc to send a query to a lond process,
   doesn't work so well in general as you can see in the following
   example:
   
       lonc= loncapa client process    A-lonc= a lonc process on Server A
       lond= loncapa daemon process
   
                    database command
       A-lonc  --------TCP/IP----------------> B-lond
   
   The problem emerges that A-lonc and B-lond are kept waiting for the
   MySQL server to "do its stuff", or in other words, perform the
   conceivably sophisticated, data-intensive, time-sucking database
   transaction.  By tying up a lonc and lond process, this significantly
   cripples the capabilities of LON-CAPA servers.
   
   The solution is to offload the work onto another process, and use
   lonc and lond just for requests and notifications of completed
   processing:
   
                   database command
   
     A-lonc  ---------TCP/IP-----------------> B-lond =====> B-lonsql
            <---------------------------------/                |
              "ok, I'll get back to you..."                    |
                                                               |
                                                               /
     A-lond  <-------------------------------  B-lonc   <======
              "Guess what? I have the result!"
   
   Of course, depending on success or failure, the messages may vary, but
   the principle remains the same where a separate pool of children
   processes (lonsql's) handle the MySQL database manipulations.
   
   Thus, lonc and lond spend effectively no time waiting on results from
   the database.
   
   =head1 Internals
   
   =over 4
   
   =cut
   
   use strict;
   
 use lib '/home/httpd/lib/perl/';  use lib '/home/httpd/lib/perl/';
 use LONCAPA::Configuration;  use LONCAPA::Configuration;
   use LONCAPA::lonmetadata();
   
 use IO::Socket;  use IO::Socket;
 use Symbol;  use Symbol;
Line 66  use Socket; Line 114  use Socket;
 use Fcntl;  use Fcntl;
 use Tie::RefHash;  use Tie::RefHash;
 use DBI;  use DBI;
   use File::Find;
   use localenroll;
   
 my @metalist;  ########################################################
 # ----------------- Code to enable 'find' subroutine listing of the .meta files  ########################################################
 require "find.pl";  
 sub wanted {  
     (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&  
     -f _ &&  
     /^.*\.meta$/ && !/^.+\.\d+\.[^\.]+\.meta$/ &&  
     push(@metalist,"$dir/$_");  
 }  
   
 $childmaxattempts=10;  
 $run =0;#running counter to generate the query-id  
   
 # -------------------------------- Read loncapa_apache.conf and loncapa.conf  
 my $perlvarref=LONCAPA::Configuration::read_conf('loncapa_apache.conf',  
                                                  'loncapa.conf');  
 my %perlvar=%{$perlvarref};  
   
 # ------------------------------------- Make sure that database can be accessed  =pod
 {  
     my $dbh;  
     unless (  
     $dbh = DBI->connect("DBI:mysql:loncapa","www",$perlvar{'lonSqlAccess'},{ RaiseError =>0,PrintError=>0})  
     ) {   
  print "Cannot connect to database!\n";  
  $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";  
  $subj="LON: $perlvar{'lonHostID'} Cannot connect to database!";  
  system("echo 'Cannot connect to MySQL database!' |\  
  mailto $emailto -s '$subj' > /dev/null");  
  exit 1;  
     }  
     else {  
  $dbh->disconnect;  
     }  
 }  
   
 # --------------------------------------------- Check if other instance running  =item Global Variables
   
 my $pidfile="$perlvar{'lonDaemons'}/logs/lonsql.pid";  =over 4
   
 if (-e $pidfile) {  =item dbh
    my $lfh=IO::File->new("$pidfile");  
    my $pide=<$lfh>;  
    chomp($pide);  
    if (kill 0 => $pide) { die "already running"; }  
 }  
   
 # ------------------------------------------------------------- Read hosts file  =back
 $PREFORK=4; # number of children to maintain, at least four spare  
   
 open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";  =cut
   
 while ($configline=<CONFIG>) {  ########################################################
     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);  ########################################################
     chomp($ip);  my $dbh;
   
     $hostip{$ip}=$id;  ########################################################
     if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; }  ########################################################
   
     $PREFORK++;  =pod 
 }  
 close(CONFIG);  
   
 $PREFORK=int($PREFORK/4);  =item Variables required for forking
   
 $unixsock = "mysqlsock";  =over 4
 my $localfile="$perlvar{'lonSockDir'}/$unixsock";  
 my $server;  
 unlink ($localfile);  
 unless ($server=IO::Socket::UNIX->new(Local    =>"$localfile",  
   Type    => SOCK_STREAM,  
   Listen => 10))  
 {  
     print "in socket error:$@\n";  
 }  
   
 # -------------------------------------------------------- Routines for forking  =item $MAX_CLIENTS_PER_CHILD
 # global variables  
 $MAX_CLIENTS_PER_CHILD  = 5;        # number of clients each child should process  
 %children               = ();       # keys are current child process IDs  
 $children               = 0;        # current number of children  
   
 sub REAPER {                        # takes care of dead children  The number of clients each child should process.
     $SIG{CHLD} = \&REAPER;  
     my $pid = wait;  
     $children --;  
     &logthis("Child $pid died");  
     delete $children{$pid};  
 }  
   
 sub HUNTSMAN {                      # signal handler for SIGINT  =item %children 
     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children  
     kill 'INT' => keys %children;  
     my $execdir=$perlvar{'lonDaemons'};  
     unlink("$execdir/logs/lonsql.pid");  
     &logthis("<font color=red>CRITICAL: Shutting down</font>");  
     $unixsock = "mysqlsock";  
     my $port="$perlvar{'lonSockDir'}/$unixsock";  
     unlink(port);  
     exit;                           # clean up with dignity  
 }  
   
 sub HUPSMAN {                      # signal handler for SIGHUP  The keys to %children  are the current child process IDs
     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children  
     kill 'INT' => keys %children;  
     close($server);                # free up socket  
     &logthis("<font color=red>CRITICAL: Restarting</font>");  
     my $execdir=$perlvar{'lonDaemons'};  
     $unixsock = "mysqlsock";  
     my $port="$perlvar{'lonSockDir'}/$unixsock";  
     unlink(port);  
     exec("$execdir/lonsql");         # here we go again  
 }  
   
 sub logthis {  =item $children
     my $message=shift;  
     my $execdir=$perlvar{'lonDaemons'};  
     my $fh=IO::File->new(">>$execdir/logs/lonsqlfinal.log");  
     my $now=time;  
     my $local=localtime($now);  
     print $fh "$local ($$): $message\n";  
 }  
   
 # ------------------------------------------------------------------ Course log  The current number of children
   
 sub courselog {  =back
     my ($path,$command)=@_;  
     my %filters=();  =cut 
     foreach (split(/\:/,&unescape($command))) {  
  my ($name,$value)=split(/\=/,$_);  ########################################################
         $filters{$name}=$value;  ########################################################
     }  my $MAX_CLIENTS_PER_CHILD  = 5;   # number of clients each child should process
     my @results=();  my %children               = ();  # keys are current child process IDs
     open(IN,$path.'/activity.log') or return ('file_error');  my $children               = 0;   # current number of children
     while ($line=<IN>) {                                 
         chomp($line);  ###################################################################
         my ($timestamp,$host,$log)=split(/\:/,$line);  ###################################################################
   
   =pod
   
   =item Main body of code.
   
   =over 4
   
   =item Read data from loncapa_apache.conf and loncapa.conf.
   
   =item Ensure we can access the database.
   
   =item Determine if there are other instances of lonsql running.
   
   =item Read the hosts file.
   
   =item Create a socket for lonsql.
   
   =item Fork once and dissociate from parent.
   
   =item Write PID to disk.
   
   =item Prefork children and maintain the population of children.
   
   =back
   
   =cut
   
   ###################################################################
   ###################################################################
   my $childmaxattempts=10;
   my $run =0;              # running counter to generate the query-id
 #  #
 # $log has the actual log entries; currently still escaped, and  # Read loncapa_apache.conf and loncapa.conf
 # %26(timestamp)%3a(url)%3a(user)%3a(domain)  
 # then additionally  
 # %3aPOST%3a(name)%3d(value)%3a(name)%3d(value)  
 # or  
 # %3aCSTORE%3a(name)%3d(value)%26(name)%3d(value)  
 #  #
 # get delimiter between timestamped entries to be &&&  my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
         $log=~s/\%26(\d+)\%3a/\&\&\&$1\%3a/g;  my %perlvar=%{$perlvarref};
 # now go over all log entries   #
         foreach (split(/\&\&\&/,&unescape($log))) {  # Write the /home/www/.my.cnf file 
     my ($time,$res,$uname,$udom,$action,@values)=split(/\:/,$_);  my $conf_file = '/home/www/.my.cnf';
             my $values=&unescape(join(':',@values));  if (! -e $conf_file) {
             $values=~s/\&/\:/g;      if (open MYCNF, ">$conf_file") {
             $res=&unescape($res);          print MYCNF <<"ENDMYCNF";
             my $include=1;  [client]
             if (($filters{'username'}) && ($uname ne $filters{'username'}))   user=www
                                                                { $include=0; }  password=$perlvar{'lonSqlAccess'}
             if (($filters{'domain'}) && ($udom ne $filters{'domain'}))   ENDMYCNF
                                                                { $include=0; }          close MYCNF;
             if (($filters{'url'}) && ($res!~/$filters{'url'}/))       } else {
                                                                { $include=0; }          warn "Unable to write $conf_file, continuing";
             if (($filters{'start'}) && ($time<$filters{'start'}))   
                                                                { $include=0; }  
             if (($filters{'end'}) && ($time>$filters{'end'}))   
                                                                { $include=0; }  
             if (($filters{'action'} eq 'view') && ($action))   
                                                                { $include=0; }  
             if (($filters{'action'} eq 'submit') && ($action ne 'POST'))   
                                                                { $include=0; }  
             if (($filters{'action'} eq 'grade') && ($action ne 'CSTORE'))   
                                                                { $include=0; }  
             if ($include) {  
        push(@results,($time<1000000000?'0':'').$time.':'.$res.':'.  
                                             $uname.':'.$udom.':'.  
                                             $action.':'.$values);  
             }  
        }  
     }      }
     close IN;  
     return join('&',sort(@results));  
 }  }
   
 # -------------------------------------------------------------------- User log  
   
 sub userlog {  #
     my ($path,$command)=@_;  # Make sure that database can be accessed
     my %filters=();  #
     foreach (split(/\:/,&unescape($command))) {  my $dbh;
  my ($name,$value)=split(/\=/,$_);  unless ($dbh = DBI->connect("DBI:mysql:loncapa","www",
         $filters{$name}=$value;                              $perlvar{'lonSqlAccess'},
     }                              { RaiseError =>0,PrintError=>0})) { 
     my @results=();      print "Cannot connect to database!\n";
     open(IN,$path.'/activity.log') or return ('file_error');      my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
     while ($line=<IN>) {      my $subj="LON: $perlvar{'lonHostID'} Cannot connect to database!";
         chomp($line);      system("echo 'Cannot connect to MySQL database!' |".
         my ($timestamp,$host,$log)=split(/\:/,$line);             " mailto $emailto -s '$subj' > /dev/null");
         $log=&unescape($log);  
         my $include=1;      open(SMP,'>/home/httpd/html/lon-status/mysql.txt');
         if (($filters{'start'}) && ($timestamp<$filters{'start'}))       print SMP 'time='.time.'&mysql=defunct'."\n";
                                                              { $include=0; }      close(SMP);
         if (($filters{'end'}) && ($timestamp>$filters{'end'}))   
                                                              { $include=0; }      exit 1;
         if (($filters{'action'} eq 'log') && ($log!~/^Log/)) { $include=0; }  } else {
         if (($filters{'action'} eq 'check') && ($log!~/^Check/))       unlink('/home/httpd/html/lon-status/mysql.txt');
                                                              { $include=0; }      $dbh->disconnect;
         if ($include) {  }
    push(@results,$timestamp.':'.$log);  
         }  #
     }  # Check if other instance running
     close IN;  #
     return join('&',sort(@results));  my $pidfile="$perlvar{'lonDaemons'}/logs/lonsql.pid";
   if (-e $pidfile) {
      my $lfh=IO::File->new("$pidfile");
      my $pide=<$lfh>;
      chomp($pide);
      if (kill 0 => $pide) { die "already running"; }
 }  }
   
   #
   # Read hosts file
   #
   my $thisserver;
   my %hostname;
   my $PREFORK=4; # number of children to maintain, at least four spare
   open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
   while (my $configline=<CONFIG>) {
       my ($id,$domain,$role,$name)=split(/:/,$configline);
       $name=~s/\s//g;
       $thisserver=$name if ($id eq $perlvar{'lonHostID'});
       $hostname{$id}=$name;
       #$PREFORK++;
   }
   close(CONFIG);
   #
   #$PREFORK=int($PREFORK/4);
   
 # ---------------------------------------------------- Fork once and dissociate  #
 $fpid=fork;  # Create a socket to talk to lond
   #
   my $unixsock = "mysqlsock";
   my $localfile="$perlvar{'lonSockDir'}/$unixsock";
   my $server;
   unlink ($localfile);
   unless ($server=IO::Socket::UNIX->new(Local    =>"$localfile",
                                         Type    => SOCK_STREAM,
                                         Listen => 10)) {
       print "in socket error:$@\n";
   }
   
   #
   # Fork once and dissociate
   #
   my $fpid=fork;
 exit if $fpid;  exit if $fpid;
 die "Couldn't fork: $!" unless defined ($fpid);  die "Couldn't fork: $!" unless defined ($fpid);
   
 POSIX::setsid() or die "Can't start new session: $!";  POSIX::setsid() or die "Can't start new session: $!";
   
 # ------------------------------------------------------- Write our PID on disk  #
   # Write our PID on disk
 $execdir=$perlvar{'lonDaemons'};  my $execdir=$perlvar{'lonDaemons'};
 open (PIDSAVE,">$execdir/logs/lonsql.pid");  open (PIDSAVE,">$execdir/logs/lonsql.pid");
 print PIDSAVE "$$\n";  print PIDSAVE "$$\n";
 close(PIDSAVE);  close(PIDSAVE);
 &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");  &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
   
 # ----------------------------- Ignore signals generated during initial startup  #
   # Ignore signals generated during initial startup
 $SIG{HUP}=$SIG{USR1}='IGNORE';  $SIG{HUP}=$SIG{USR1}='IGNORE';
 # ------------------------------------------------------- Now we are on our own      # Now we are on our own    
 # Fork off our children.  #    Fork off our children.
 for (1 .. $PREFORK) {  for (1 .. $PREFORK) {
     make_new_child();      make_new_child();
 }  }
   
   #
 # Install signal handlers.  # Install signal handlers.
 $SIG{CHLD} = \&REAPER;  $SIG{CHLD} = \&REAPER;
 $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;  $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
 $SIG{HUP}  = \&HUPSMAN;  $SIG{HUP}  = \&HUPSMAN;
   
   #
 # And maintain the population.  # And maintain the population.
 while (1) {  while (1) {
     sleep;                          # wait for a signal (i.e., child's death)      sleep;                          # wait for a signal (i.e., child's death)
     for ($i = $children; $i < $PREFORK; $i++) {      for (my $i = $children; $i < $PREFORK; $i++) {
         make_new_child();           # top up the child pool          make_new_child();           # top up the child pool
     }      }
 }  }
   
   ########################################################
   ########################################################
   
   =pod
   
   =item &make_new_child
   
   Inputs: None
   
   Returns: None
   
   =cut
   
   ########################################################
   ########################################################
 sub make_new_child {  sub make_new_child {
     my $pid;      my $pid;
     my $sigset;      my $sigset;
           #
     # block signal for fork      # block signal for fork
     $sigset = POSIX::SigSet->new(SIGINT);      $sigset = POSIX::SigSet->new(SIGINT);
     sigprocmask(SIG_BLOCK, $sigset)      sigprocmask(SIG_BLOCK, $sigset)
         or die "Can't block SIGINT for fork: $!\n";          or die "Can't block SIGINT for fork: $!\n";
           #
     die "fork: $!" unless defined ($pid = fork);      die "fork: $!" unless defined ($pid = fork);
           #
     if ($pid) {      if ($pid) {
         # Parent records the child's birth and returns.          # Parent records the child's birth and returns.
         sigprocmask(SIG_UNBLOCK, $sigset)          sigprocmask(SIG_UNBLOCK, $sigset)
Line 338  sub make_new_child { Line 363  sub make_new_child {
     } else {      } else {
         # Child can *not* return from this subroutine.          # Child can *not* return from this subroutine.
         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before          $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
       
         # unblock signals          # unblock signals
         sigprocmask(SIG_UNBLOCK, $sigset)          sigprocmask(SIG_UNBLOCK, $sigset)
             or die "Can't unblock SIGINT for fork: $!\n";              or die "Can't unblock SIGINT for fork: $!\n";
   
   
         #open database handle          #open database handle
  # making dbh global to avoid garbage collector   # making dbh global to avoid garbage collector
  unless (   unless ($dbh = DBI->connect("DBI:mysql:loncapa","www",
  $dbh = DBI->connect("DBI:mysql:loncapa","www",$perlvar{'lonSqlAccess'},{ RaiseError =>0,PrintError=>0})                                      $perlvar{'lonSqlAccess'},
  ) {                                       { RaiseError =>0,PrintError=>0})) { 
              sleep(10+int(rand(20)));              sleep(10+int(rand(20)));
     &logthis("<font color=blue>WARNING: Couldn't connect to database  ($st secs): $@</font>");              &logthis("<font color='blue'>WARNING: Couldn't connect to database".
     print "database handle error\n";                       ": $@</font>");
     exit;                       #  "($st secs): $@</font>");
               print "database handle error\n";
   };              exit;
  # make sure that a database disconnection occurs with ending kill signals          }
    # make sure that a database disconnection occurs with 
           # ending kill signals
  $SIG{TERM}=$SIG{INT}=$SIG{QUIT}=$SIG{__DIE__}=\&DISCONNECT;   $SIG{TERM}=$SIG{INT}=$SIG{QUIT}=$SIG{__DIE__}=\&DISCONNECT;
   
         # handle connections until we've reached $MAX_CLIENTS_PER_CHILD          # handle connections until we've reached $MAX_CLIENTS_PER_CHILD
         for ($i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {          for (my $i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
             $client = $server->accept()     or last;              my $client = $server->accept() or last;
               
             # do something with the connection              # do something with the connection
     $run = $run+1;      $run = $run+1;
     my $userinput = <$client>;      my $userinput = <$client>;
     chomp($userinput);      chomp($userinput);
                       $userinput=~s/\:(\w+)$//;
               my $searchdomain=$1;
               #
     my ($conserver,$query,      my ($conserver,$query,
  $arg1,$arg2,$arg3)=split(/&/,$userinput);   $arg1,$arg2,$arg3)=split(/&/,$userinput);
     my $query=unescape($query);      my $query=unescape($query);
               #
             #send query id which is pid_unixdatetime_runningcounter              #send query id which is pid_unixdatetime_runningcounter
     $queryid = $thisserver;      my $queryid = $thisserver;
     $queryid .="_".($$)."_";      $queryid .="_".($$)."_";
     $queryid .= time."_";      $queryid .= time."_";
     $queryid .= $run;      $queryid .= $run;
     print $client "$queryid\n";      print $client "$queryid\n";
           #
     &logthis("QUERY: $query - $arg1 - $arg2 - $arg3");      # &logthis("QUERY: $query - $arg1 - $arg2 - $arg3");
     sleep 1;      sleep 1;
               #
             my $result='';              my $result='';
               #
 # ---------- At this point, query is received, query-ID assigned and sent back               # At this point, query is received, query-ID assigned and sent 
 # $query eq 'logquery' will mean that this is a query against log-files              # back, $query eq 'logquery' will mean that this is a query 
               # against log-files
               if (($query eq 'userlog') || ($query eq 'courselog')) {
    if (($query eq 'userlog') || ($query eq 'courselog')) {                  # beginning of log query
 # ----------------------------------------------------- beginning of log query                  my $udom    = &unescape($arg1);
 #                  my $uname   = &unescape($arg2);
 # this goes against a user's log file                  my $command = &unescape($arg3);
 #                  my $path    = &propath($udom,$uname);
        my $udom=&unescape($arg1);                  if (-e "$path/activity.log") {
        my $uname=&unescape($arg2);                      if ($query eq 'userlog') {
                my $command=&unescape($arg3);                          $result=&userlog($path,$command);
                my $path=&propath($udom,$uname);                      } else {
                if (-e "$path/activity.log") {                          $result=&courselog($path,$command);
    if ($query eq 'userlog') {                      }
                        $result=&userlog($path,$command);                  } else {
                    } else {                      &logthis('Unable to do log query: '.$uname.'@'.$udom);
                        $result=&courselog($path,$command);                      $result='no_such_file';
                    }                  }
                } else {                  # end of log query
    &logthis('Unable to do log query: '.$uname.'@'.$udom);              } elsif (($query eq 'fetchenrollment') || 
            $result='no_such_file';       ($query eq 'institutionalphotos')) {
        }                  # retrieve institutional class lists
 # ------------------------------------------------------------ end of log query                  my $dom = &unescape($arg1);
           } else {                  my %affiliates = ();
 # -------------------------------------------------------- This is an sql query                  my %replies = ();
     my $custom=unescape($arg1);                  my $locresult = '';
     my $customshow=unescape($arg2);                  my $querystr = &unescape($arg3);
             #prepare and execute the query                  foreach (split/%%/,$querystr) {
     my $sth = $dbh->prepare($query);                      if (/^([^=]+)=([^=]+)$/) {
                           @{$affiliates{$1}} = split/,/,$2;
     my @files;                      }
     my $subsetflag=0;                  }
     if ($query) {                  if ($query eq 'fetchenrollment') { 
  unless ($sth->execute())                      $locresult = &localenroll::fetch_enrollment($dom,\%affiliates,\%replies);
  {                  } elsif ($query eq 'institutionalphotos') {
     &logthis("<font color=blue>WARNING: Could not retrieve from database: $@</font>");                      my $crs = &unescape($arg2);
     $result="";      eval {
  }   local($SIG{__DIE__})='DEFAULT';
  else {   $locresult = &localenroll::institutional_photos($dom,$crs,\%affiliates,\%replies,'update');
     my $r1=$sth->fetchall_arrayref;      };
     my @r2;      if ($@) {
     foreach (@$r1) {my $a=$_;    $locresult = 'error';
  my @b=map {escape($_)} @$a;  
  push @files,@{$a}[3];  
  push @r2,join(",", @b)  
  }  
     $result=join("&",@r2);  
  }  
     }  
     # do custom metadata searching here and build into result  
     if ($custom or $customshow) {  
  &logthis("am going to do custom query for $custom");  
  if ($query) {  
     @metalist=map {$perlvar{'lonDocRoot'}.$_.'.meta'} @files;  
  }  
  else {  
     @metalist=(); pop @metalist;  
     opendir(RESOURCES,"$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}");  
     my @homeusers=grep  
           {&ishome("$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}/$_")}  
           grep {!/^\.\.?$/} readdir(RESOURCES);  
     closedir RESOURCES;  
     foreach my $user (@homeusers) {  
  &find("$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}/$user");  
     }      }
  }                  }
 # &logthis("FILELIST:" . join(":::",@metalist));                  $result = &escape($locresult.':');
  # if file is indicated in sql database and                  if ($locresult) {
  # not part of sql-relevant query, do not pattern match.                      $result .= &escape(join(':',map{$_.'='.$replies{$_}} keys %replies));
  # if file is not in sql database, output error.                  }
  # if file is indicated in sql database and is              } elsif ($query eq 'prepare activity log') {
  # part of query result list, then do the pattern match.                  my ($cid,$domain) = map {&unescape($_);} ($arg1,$arg2);
  my $customresult='';                  &logthis('preparing activity log tables for '.$cid);
  my @r2;                  my $command = 
  foreach my $m (@metalist) {                      qq{$perlvar{'lonDaemons'}/parse_activity_log.pl -course=$cid -domain=$domain};
     my $fh=IO::File->new($m);                  system($command);
     my @lines=<$fh>;                  &logthis($command);
     my $stuff=join('',@lines);                  my $returnvalue = $?>>8;
     if ($stuff=~/$custom/s) {                  if ($returnvalue) {
  foreach my $f ('abstract','author','copyright',                      $result = 'error: parse_activity_log.pl returned '.
        'creationdate','keywords','language',                          $returnvalue;
        'lastrevisiondate','mime','notes',                  } else {
        'owner','subject','title') {                      $result = 'success';
     $stuff=~s/\n?\<$f[^\>]*\>.*?<\/$f[^\>]*\>\n?//s;                  }
  }              } else {
  my $m2=$m; my $docroot=$perlvar{'lonDocRoot'};                  # Do an sql query
  $m2=~s/^$docroot//;                  $result = &do_sql_query($query,$arg1,$arg2,$searchdomain);
  $m2=~s/\.meta$//;              }
  unless ($query) {  
     my $q2="select * from metadata where url like binary '$m2'";  
     my $sth = $dbh->prepare($q2);  
     $sth->execute();  
     my $r1=$sth->fetchall_arrayref;  
     foreach (@$r1) {my $a=$_;   
  my @b=map {escape($_)} @$a;  
  push @files,@{$a}[3];  
  push @r2,join(",", @b)  
  }  
  }  
 # &logthis("found: $stuff");  
  $customresult.='&custom='.escape($m2).','.escape($stuff);  
     }  
  }  
  $result=join("&",@r2) unless $query;  
  $result.=$customresult;  
     }  
 # ------------------------------------------------------------ end of sql query  
    }  
   
             # result does not need to be escaped because it has already been              # result does not need to be escaped because it has already been
             # escaped.              # escaped.
             #$result=&escape($result);              #$result=&escape($result);
   
     # reply with result, append \n unless already there  
   
     $result.="\n" unless ($result=~/\n$/);  
             &reply("queryreply:$queryid:$result",$conserver);              &reply("queryreply:$queryid:$result",$conserver);
   
         }          }
       
         # tidy up gracefully and finish          # tidy up gracefully and finish
           #
         #close the database handle          # close the database handle
  $dbh->disconnect   $dbh->disconnect
    or &logthis("<font color=blue>WARNING: Couldn't disconnect from database  $DBI::errstr ($st secs): $@</font>");              or &logthis("<font color='blue'>WARNING: Couldn't disconnect".
                               " from database  $DBI::errstr : $@</font>");
         # this exit is VERY important, otherwise the child will become          # this exit is VERY important, otherwise the child will become
         # a producer of more and more children, forking yourself into          # a producer of more and more children, forking yourself into
         # process death.          # process death.
Line 517  sub make_new_child { Line 492  sub make_new_child {
     }      }
 }  }
   
 sub DISCONNECT {  ########################################################
     $dbh->disconnect or   ########################################################
     &logthis("<font color=blue>WARNING: Couldn't disconnect from database  $DBI::errstr ($st secs): $@</font>");  
     exit;  =pod
   
   =item &do_sql_query
   
   Runs an sql metadata table query.
   
   Inputs: $query, $custom, $customshow
   
   Returns: A string containing escaped results.
   
   =cut
   
   ########################################################
   ########################################################
   {
       my @metalist;
   
   sub process_file {
       if ( -e $_ &&  # file exists
            -f $_ &&  # and is a normal file
            /\.meta$/ &&  # ends in meta
            ! /^.+\.\d+\.[^\.]+\.meta$/  # is not a previous version
            ) {
           push(@metalist,$File::Find::name);
       }
   }
   
   sub do_sql_query {
       my ($query,$custom,$customshow,$searchdomain) = @_;
   
   #
   # limit to searchdomain if given and table is metadata
   #
       if (($searchdomain) && ($query=~/FROM metadata/)) {
    $query.=' HAVING (domain="'.$searchdomain.'")';
       }
   #    &logthis('doing query ('.$searchdomain.')'.$query);
   
   
   
       $custom     = &unescape($custom);
       $customshow = &unescape($customshow);
       #
       @metalist = ();
       #
       my $result = '';
       my @results = ();
       my @files;
       my $subsetflag=0;
       #
       if ($query) {
           #prepare and execute the query
    &logthis($query);
           my $sth = $dbh->prepare($query);
           unless ($sth->execute()) {
               &logthis('<font color="blue">'.
                        'WARNING: Could not retrieve from database:'.
                        $sth->errstr().'</font>');
           } else {
               my $aref=$sth->fetchall_arrayref;
               foreach my $row (@$aref) {
                   push @files,@{$row}[3] if ($custom or $customshow);
                   my @b=map { &escape($_); } @$row;
                   push @results,join(",", @b);
                   # Build up the @files array with the LON-CAPA urls 
                   # of the resources.
               }
           }
       }
       # do custom metadata searching here and build into result
       return join("&",@results) if (! ($custom or $customshow));
       # Only get here if there is a custom query or custom show request
       &logthis("Doing custom query for $custom");
       if ($query) {
           @metalist=map {
               $perlvar{'lonDocRoot'}.$_.'.meta';
           } @files;
       } else {
           my $dir = "$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}";
           @metalist=(); 
           opendir(RESOURCES,$dir);
           my @homeusers=grep {
               &ishome($dir.'/'.$_);
           } grep {!/^\.\.?$/} readdir(RESOURCES);
           closedir RESOURCES;
           # Define the
           foreach my $user (@homeusers) {
               find (\&process_file,$dir.'/'.$user);
           }
       } 
       # if file is indicated in sql database and
       #     not part of sql-relevant query, do not pattern match.
       #
       # if file is not in sql database, output error.
       #
       # if file is indicated in sql database and is
       #     part of query result list, then do the pattern match.
       my $customresult='';
       my @results;
       foreach my $metafile (@metalist) {
           my $fh=IO::File->new($metafile);
           my @lines=<$fh>;
           my $stuff=join('',@lines);
           if ($stuff=~/$custom/s) {
               foreach my $f ('abstract','author','copyright',
                              'creationdate','keywords','language',
                              'lastrevisiondate','mime','notes',
                              'owner','subject','title') {
                   $stuff=~s/\n?\<$f[^\>]*\>.*?<\/$f[^\>]*\>\n?//s;
               }
               my $mfile=$metafile; 
               my $docroot=$perlvar{'lonDocRoot'};
               $mfile=~s/^$docroot//;
               $mfile=~s/\.meta$//;
               unless ($query) {
                   my $q2="SELECT * FROM metadata WHERE url ".
                       " LIKE BINARY '?'";
                   my $sth = $dbh->prepare($q2);
                   $sth->execute($mfile);
                   my $aref=$sth->fetchall_arrayref;
                   foreach my $a (@$aref) {
                       my @b=map { &escape($_)} @$a;
                       push @results,join(",", @b);
                   }
               }
               # &logthis("found: $stuff");
               $customresult.='&custom='.&escape($mfile).','.
                   escape($stuff);
           }
       }
       $result=join("&",@results) unless $query;
       $result.=$customresult;
       #
       return $result;
   } # End of &do_sql_query
   
   } # End of scoping curly braces for &process_file and &do_sql_query
   ########################################################
   ########################################################
   
   =pod
   
   =item &logthis
   
   Inputs: $message, the message to log
   
   Returns: nothing
   
   Writes $message to the logfile.
   
   =cut
   
   ########################################################
   ########################################################
   sub logthis {
       my $message=shift;
       my $execdir=$perlvar{'lonDaemons'};
       my $fh=IO::File->new(">>$execdir/logs/lonsql.log");
       my $now=time;
       my $local=localtime($now);
       print $fh "$local ($$): $message\n";
 }  }
   
 # -------------------------------------------------- Non-critical communication  # -------------------------------------------------- Non-critical communication
   
   ########################################################
   ########################################################
   
   =pod
   
   =item &subreply
   
   Sends a command to a server.  Called only by &reply.
   
   Inputs: $cmd,$server
   
   Returns: The results of the message or 'con_lost' on error.
   
   =cut
   
   ########################################################
   ########################################################
 sub subreply {  sub subreply {
     my ($cmd,$server)=@_;      my ($cmd,$server)=@_;
     my $peerfile="$perlvar{'lonSockDir'}/$server";      my $peerfile="$perlvar{'lonSockDir'}/".$hostname{$server};
     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",      my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
                                       Type    => SOCK_STREAM,                                        Type    => SOCK_STREAM,
                                       Timeout => 10)                                        Timeout => 10)
        or return "con_lost";         or return "con_lost";
     print $sclient "$cmd\n";      print $sclient "sethost:$server:$cmd\n";
     my $answer=<$sclient>;      my $answer=<$sclient>;
     chomp($answer);      chomp($answer);
     if (!$answer) { $answer="con_lost"; }      $answer="con_lost" if (!$answer);
     return $answer;      return $answer;
 }  }
   
   ########################################################
   ########################################################
   
   =pod
   
   =item &reply
   
   Sends a command to a server.
   
   Inputs: $cmd,$server
   
   Returns: The results of the message or 'con_lost' on error.
   
   =cut
   
   ########################################################
   ########################################################
 sub reply {  sub reply {
   my ($cmd,$server)=@_;    my ($cmd,$server)=@_;
   my $answer;    my $answer;
Line 555  sub reply { Line 724  sub reply {
   return $answer;    return $answer;
 }  }
   
 # -------------------------------------------------------- Escape Special Chars  ########################################################
   ########################################################
   
   =pod
   
   =item &escape
   
   Escape special characters in a string.
   
   Inputs: string to escape
   
   Returns: The input string with special characters escaped.
   
   =cut
   
   ########################################################
   ########################################################
 sub escape {  sub escape {
     my $str=shift;      my $str=shift;
     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;      $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
     return $str;      return $str;
 }  }
   
 # ----------------------------------------------------- Un-Escape Special Chars  ########################################################
   ########################################################
   
   =pod
   
   =item &unescape
   
   Unescape special characters in a string.
   
   Inputs: string to unescape
   
   Returns: The input string with special characters unescaped.
   
   =cut
   
   ########################################################
   ########################################################
 sub unescape {  sub unescape {
     my $str=shift;      my $str=shift;
     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;      $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
     return $str;      return $str;
 }  }
   
 # --------------------------------------- Is this the home server of an author?  ########################################################
 # (copied from lond, modification of the return value)  ########################################################
   
   =pod
   
   =item &ishome
   
   Determine if the current machine is the home server for a user.
   The determination is made by checking the filesystem for the users information.
   
   Inputs: $author
   
   Returns: 0 - this is not the authors home server, 1 - this is.
   
   =cut
   
   ########################################################
   ########################################################
 sub ishome {  sub ishome {
     my $author=shift;      my $author=shift;
     $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;      $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
Line 585  sub ishome { Line 800  sub ishome {
     }      }
 }  }
   
 # -------------------------------------------- Return path to profile directory  ########################################################
 # (copied from lond)  ########################################################
   
   =pod
   
   =item &propath
   
   Inputs: user name, user domain
   
   Returns: The full path to the users directory.
   
   =cut
   
   ########################################################
   ########################################################
 sub propath {  sub propath {
     my ($udom,$uname)=@_;      my ($udom,$uname)=@_;
     $udom=~s/\W//g;      $udom=~s/\W//g;
Line 597  sub propath { Line 825  sub propath {
     return $proname;      return $proname;
 }   } 
   
 # ----------------------------------- POD (plain old documentation, CPAN style)  ########################################################
   ########################################################
   
 =head1 NAME  =pod
   
 lonsql - LON TCP-MySQL-Server Daemon for handling database requests.  =item &courselog
   
 =head1 SYNOPSIS  Inputs: $path, $command
   
 This script should be run as user=www.  The following is an example invocation  Returns: unescaped string of values.
 from the loncron script.  Note that a lonsql.pid file contains the pid of  
 the parent process.  
   
     if (-e $lonsqlfile) {  
  my $lfh=IO::File->new("$lonsqlfile");  
  my $lonsqlpid=<$lfh>;  
  chomp($lonsqlpid);  
  if (kill 0 => $lonsqlpid) {  
     print $fh "<h3>lonsql at pid $lonsqlpid responding</h3>";  
     $restartflag=0;  
  } else {  
     $errors++; $errors++;  
     print $fh "<h3>lonsql at pid $lonsqlpid not responding</h3>";  
  $restartflag=1;  
  print $fh   
     "<h3>Decided to clean up stale .pid file and restart lonsql</h3>";  
  }  
     }  
     if ($restartflag==1) {  
  $errors++;  
          print $fh '<br><font color="red">Killall lonsql: '.  
                     system('killall lonsql').' - ';  
                     sleep 60;  
                     print $fh unlink($lonsqlfile).' - '.  
                               system('killall -9 lonsql').  
                     '</font><br>';  
  print $fh "<h3>lonsql not running, trying to start</h3>";  
  system(  
  "$perlvar{'lonDaemons'}/lonsql 2>>$perlvar{'lonDaemons'}/logs/lonsql_errors");  
  sleep 10;  
   
 =head1 DESCRIPTION  
   
 Not yet written.  
   
 =head1 README  
   
 Not yet written.  
   
 =head1 PREREQUISITES  
   
 IO::Socket  
 Symbol  
 POSIX  
 IO::Select  
 IO::File  
 Socket  
 Fcntl  
 Tie::RefHash  
 DBI  
   
 =head1 COREQUISITES  
   
 =head1 OSNAMES  =cut
   
   ########################################################
   ########################################################
   sub courselog {
       my ($path,$command)=@_;
       my %filters=();
       foreach (split(/\:/,&unescape($command))) {
    my ($name,$value)=split(/\=/,$_);
           $filters{$name}=$value;
       }
       my @results=();
       open(IN,$path.'/activity.log') or return ('file_error');
       while (my $line=<IN>) {
           chomp($line);
           my ($timestamp,$host,$log)=split(/\:/,$line);
   #
   # $log has the actual log entries; currently still escaped, and
   # %26(timestamp)%3a(url)%3a(user)%3a(domain)
   # then additionally
   # %3aPOST%3a(name)%3d(value)%3a(name)%3d(value)
   # or
   # %3aCSTORE%3a(name)%3d(value)%26(name)%3d(value)
   #
   # get delimiter between timestamped entries to be &&&
           $log=~s/\%26(\d+)\%3a/\&\&\&$1\%3a/g;
   # now go over all log entries 
           foreach (split(/\&\&\&/,&unescape($log))) {
       my ($time,$res,$uname,$udom,$action,@values)=split(/\:/,$_);
               my $values=&unescape(join(':',@values));
               $values=~s/\&/\:/g;
               $res=&unescape($res);
               my $include=1;
               if (($filters{'username'}) && ($uname ne $filters{'username'})) 
                                                                  { $include=0; }
               if (($filters{'domain'}) && ($udom ne $filters{'domain'})) 
                                                                  { $include=0; }
               if (($filters{'url'}) && ($res!~/$filters{'url'}/)) 
                                                                  { $include=0; }
               if (($filters{'start'}) && ($time<$filters{'start'})) 
                                                                  { $include=0; }
               if (($filters{'end'}) && ($time>$filters{'end'})) 
                                                                  { $include=0; }
               if (($filters{'action'} eq 'view') && ($action)) 
                                                                  { $include=0; }
               if (($filters{'action'} eq 'submit') && ($action ne 'POST')) 
                                                                  { $include=0; }
               if (($filters{'action'} eq 'grade') && ($action ne 'CSTORE')) 
                                                                  { $include=0; }
               if ($include) {
          push(@results,($time<1000000000?'0':'').$time.':'.$res.':'.
                                               $uname.':'.$udom.':'.
                                               $action.':'.$values);
               }
          }
       }
       close IN;
       return join('&',sort(@results));
   }
   
   ########################################################
   ########################################################
   
   =pod
   
   =item &userlog
   
   Inputs: $path, $command
   
   Returns: unescaped string of values.
   
   =cut
   
   ########################################################
   ########################################################
   sub userlog {
       my ($path,$command)=@_;
       my %filters=();
       foreach (split(/\:/,&unescape($command))) {
    my ($name,$value)=split(/\=/,$_);
           $filters{$name}=$value;
       }
       my @results=();
       open(IN,$path.'/activity.log') or return ('file_error');
       while (my $line=<IN>) {
           chomp($line);
           my ($timestamp,$host,$log)=split(/\:/,$line);
           $log=&unescape($log);
           my $include=1;
           if (($filters{'start'}) && ($timestamp<$filters{'start'})) 
                                                                { $include=0; }
           if (($filters{'end'}) && ($timestamp>$filters{'end'})) 
                                                                { $include=0; }
           if (($filters{'action'} eq 'log') && ($log!~/^Log/)) { $include=0; }
           if (($filters{'action'} eq 'check') && ($log!~/^Check/)) 
                                                                { $include=0; }
           if ($include) {
      push(@results,$timestamp.':'.$log);
           }
       }
       close IN;
       return join('&',sort(@results));
   }
   
   ########################################################
   ########################################################
   
   =pod
   
   =item Functions required for forking
   
   =over 4
   
   =item REAPER
   
   REAPER takes care of dead children.
   
   =item HUNTSMAN
   
   Signal handler for SIGINT.
   
   =item HUPSMAN
   
   Signal handler for SIGHUP
   
   =item DISCONNECT
   
   Disconnects from database.
   
   =back
   
   =cut
   
   ########################################################
   ########################################################
   sub REAPER {                   # takes care of dead children
       $SIG{CHLD} = \&REAPER;
       my $pid = wait;
       $children --;
       &logthis("Child $pid died");
       delete $children{$pid};
   }
   
   sub HUNTSMAN {                      # signal handler for SIGINT
       local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
       kill 'INT' => keys %children;
       my $execdir=$perlvar{'lonDaemons'};
       unlink("$execdir/logs/lonsql.pid");
       &logthis("<font color='red'>CRITICAL: Shutting down</font>");
       $unixsock = "mysqlsock";
       my $port="$perlvar{'lonSockDir'}/$unixsock";
       unlink($port);
       exit;                           # clean up with dignity
   }
   
   sub HUPSMAN {                      # signal handler for SIGHUP
       local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
       kill 'INT' => keys %children;
       close($server);                # free up socket
       &logthis("<font color='red'>CRITICAL: Restarting</font>");
       my $execdir=$perlvar{'lonDaemons'};
       $unixsock = "mysqlsock";
       my $port="$perlvar{'lonSockDir'}/$unixsock";
       unlink($port);
       exec("$execdir/lonsql");         # here we go again
   }
   
   sub DISCONNECT {
       $dbh->disconnect or 
       &logthis("<font color='blue'>WARNING: Couldn't disconnect from database ".
                " $DBI::errstr : $@</font>");
       exit;
   }
   
 linux  
   
 =head1 SCRIPT CATEGORIES  =pod
   
 Server/Process  =back
   
 =cut  =cut

Removed from v.1.50  
changed lines
  Added in v.1.75


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.