Diff for /loncom/lonsql between versions 1.7 and 1.49

version 1.7, 2001/03/22 15:21:54 version 1.49, 2002/06/24 14:22:05
Line 1 Line 1
 #!/usr/bin/perl  #!/usr/bin/perl
   
   # The LearningOnline Network
   # lonsql - LON TCP-MySQL-Server Daemon for handling database requests.
   #
   # $Id$
   #
   # Copyright Michigan State University Board of Trustees
   #
   # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   #
   # LON-CAPA is free software; you can redistribute it and/or modify
   # it under the terms of the GNU General Public License as published by
   # the Free Software Foundation; either version 2 of the License, or
   # (at your option) any later version.
   #
   # LON-CAPA is distributed in the hope that it will be useful,
   # but WITHOUT ANY WARRANTY; without even the implied warranty of
   # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   # GNU General Public License for more details.
   #
   # You should have received a copy of the GNU General Public License
   # along with LON-CAPA; if not, write to the Free Software
   # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   #
   # /home/httpd/html/adm/gpl.txt
   #
   # http://www.lon-capa.org/
   #
   # YEAR=2000
 # lonsql-based on the preforker:harsha jagasia:date:5/10/00  # lonsql-based on the preforker:harsha jagasia:date:5/10/00
 # 7/25 Gerd Kortemeyer  # 7/25 Gerd Kortemeyer
 # many different dates Scott Harrison  # many different dates Scott Harrison
   # YEAR=2001
   # many different dates Scott Harrison
 # 03/22/2001 Scott Harrison  # 03/22/2001 Scott Harrison
   # 8/30 Gerd Kortemeyer
   # 10/17,11/28,11/29,12/20 Scott Harrison
   # YEAR=2001
   # 5/11 Scott Harrison
   #
   ###
   
   ###############################################################################
   ##                                                                           ##
   ## ORGANIZATION OF THIS PERL SCRIPT                                          ##
   ## 1. Modules used                                                           ##
   ## 2. Enable find subroutine                                                 ##
   ## 3. Read httpd config files and get variables                              ##
   ## 4. Make sure that database can be accessed                                ##
   ## 5. Make sure this process is running from user=www                        ##
   ## 6. Check if other instance is running                                     ##
   ## 7. POD (plain old documentation, CPAN style)                              ##
   ##                                                                           ##
   ###############################################################################
   
   use lib '/home/httpd/lib/perl/';
   use LONCAPA::Configuration;
   
 use IO::Socket;  use IO::Socket;
 use Symbol;  use Symbol;
 use POSIX;  use POSIX;
Line 13  use Fcntl; Line 67  use Fcntl;
 use Tie::RefHash;  use Tie::RefHash;
 use DBI;  use DBI;
   
   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;  $childmaxattempts=10;
 $run =0;#running counter to generate the query-id  $run =0;#running counter to generate the query-id
   
 # ------------------------------------ Read httpd access.conf and get variables  # -------------------------------- Read loncapa_apache.conf and loncapa.conf
 open (CONFIG,"/etc/httpd/conf/access.conf") || die "Can't read access.conf";  my $perlvarref=LONCAPA::Configuration::read_conf('loncapa_apache.conf',
                                                    'loncapa.conf');
   my %perlvar=%{$perlvarref};
   
 while ($configline=<CONFIG>) {  # ------------------------------------- Make sure that database can be accessed
     if ($configline =~ /PerlSetVar/) {  {
  my ($dummy,$varname,$varvalue)=split(/\s+/,$configline);      my $dbh;
         chomp($varvalue);      unless (
         $perlvar{$varname}=$varvalue;      $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;
     }      }
 }  }
 close(CONFIG);  
   
 # --------------------------------------------- Check if other instance running  # --------------------------------------------- Check if other instance running
   
Line 50  while ($configline=<CONFIG>) { Line 124  while ($configline=<CONFIG>) {
     chomp($ip);      chomp($ip);
   
     $hostip{$ip}=$id;      $hostip{$ip}=$id;
   
     if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; }      if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; }
   
     $PREFORK++;      $PREFORK++;
 }  }
 close(CONFIG);  close(CONFIG);
   
   $PREFORK=int($PREFORK/4);
   
 $unixsock = "mysqlsock";  $unixsock = "mysqlsock";
 my $localfile="$perlvar{'lonSockDir'}/$unixsock";  my $localfile="$perlvar{'lonSockDir'}/$unixsock";
 my $server;  my $server;
Line 114  sub logthis { Line 189  sub logthis {
     my $local=localtime($now);      my $local=localtime($now);
     print $fh "$local ($$): $message\n";      print $fh "$local ($$): $message\n";
 }  }
   
   # ------------------------------------------------------------------ Course log
   
   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 ($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));
   }
   
   # -------------------------------------------------------------------- User log
   
   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 ($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));
   }
   
   
 # ---------------------------------------------------- Fork once and dissociate  # ---------------------------------------------------- Fork once and dissociate
 $fpid=fork;  $fpid=fork;
 exit if $fpid;  exit if $fpid;
Line 181  sub make_new_child { Line 347  sub make_new_child {
         #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","123",{ RaiseError =>0,PrintError=>0})   $dbh = DBI->connect("DBI:mysql:loncapa","www",$perlvar{'lonSqlAccess'},{ RaiseError =>0,PrintError=>0})
  ) {    ) { 
             my $st=120+int(rand(240));               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  ($st secs): $@</font>");
     print "database handle error\n";      print "database handle error\n";
     sleep($st);  
     exit;      exit;
   
   };    };
Line 202  sub make_new_child { Line 367  sub make_new_child {
     my $userinput = <$client>;      my $userinput = <$client>;
     chomp($userinput);      chomp($userinput);
                   
     my ($conserver,$querytmp,$customtmp)=split(/&/,$userinput);      my ($conserver,$query,
     my $query=unescape($querytmp);   $arg1,$arg2,$arg3)=split(/&/,$userinput);
     my $custom=unescape($customtmp);      my $query=unescape($query);
   
             #send query id which is pid_unixdatetime_runningcounter              #send query id which is pid_unixdatetime_runningcounter
     $queryid = $thisserver;      $queryid = $thisserver;
Line 213  sub make_new_child { Line 378  sub make_new_child {
     $queryid .= $run;      $queryid .= $run;
     print $client "$queryid\n";      print $client "$queryid\n";
           
       &logthis("QUERY: $query - $arg1 - $arg2 - $arg3");
       sleep 1;
   
               my $result='';
   
   # ---------- At this point, query is received, query-ID assigned and sent back 
   # $query eq 'logquery' will mean that this is a query against log-files
   
   
      if (($query eq 'userlog') || ($query eq 'courselog')) {
   # ----------------------------------------------------- beginning of log query
   #
   # this goes against a user's log file
   #
          my $udom=&unescape($arg1);
          my $uname=&unescape($arg2);
                  my $command=&unescape($arg3);
                  my $path=&propath($udom,$uname);
                  if (-e "$path/activity.log") {
      if ($query eq 'userlog') {
                          $result=&userlog($path,$command);
                      } else {
                          $result=&courselog($path,$command);
                      }
                  } else {
      &logthis('Unable to do log query: '.$uname.'@'.$udom);
              $result='no_such_file';
          }
   # ------------------------------------------------------------ end of log query
             } else {
   # -------------------------------------------------------- This is an sql query
       my $custom=unescape($arg1);
       my $customshow=unescape($arg2);
             #prepare and execute the query              #prepare and execute the query
     my $sth = $dbh->prepare($query);      my $sth = $dbh->prepare($query);
     my $result;  
     unless ($sth->execute())      my @files;
     {      my $subsetflag=0;
  &logthis("<font color=blue>WARNING: Could not retrieve from database: $@</font>");      if ($query) {
  $result="";   unless ($sth->execute())
    {
       &logthis("<font color=blue>WARNING: Could not retrieve from database: $@</font>");
       $result="";
    }
    else {
       my $r1=$sth->fetchall_arrayref;
       my @r2;
       foreach (@$r1) {my $a=$_; 
    my @b=map {escape($_)} @$a;
    push @files,@{$a}[3];
    push @r2,join(",", @b)
    }
       $result=join("&",@r2);
    }
     }      }
     else {      # do custom metadata searching here and build into result
  my $r1=$sth->fetchall_arrayref;      if ($custom or $customshow) {
  my @r2; map {my $a=$_; my @b=map {escape($_)} @$a; push @r2,join(",", @b)} (@$r1);   &logthis("am going to do custom query for $custom");
  $result=join("&",@r2) . "\n";   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));
    # 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 @r2;
    foreach my $m (@metalist) {
       my $fh=IO::File->new($m);
       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 $m2=$m; my $docroot=$perlvar{'lonDocRoot'};
    $m2=~s/^$docroot//;
    $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
      }
   
     # do custom metadata searching here and build into result              # result does need to be escaped
   
               $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);
   
         }          }
Line 279  sub reply { Line 550  sub reply {
     }      }
   } else {    } else {
     $answer='self_reply';      $answer='self_reply';
       $answer=subreply($cmd,$server);
   }     } 
   return $answer;    return $answer;
 }  }
Line 298  sub unescape { Line 570  sub unescape {
     $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)
   sub ishome {
       my $author=shift;
       $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
       my ($udom,$uname)=split(/\//,$author);
       my $proname=propath($udom,$uname);
       if (-e $proname) {
    return 1;
       } else {
           return 0;
       }
   }
   
   # -------------------------------------------- Return path to profile directory
   # (copied from lond)
   sub propath {
       my ($udom,$uname)=@_;
       $udom=~s/\W//g;
       $uname=~s/\W//g;
       my $subdir=$uname.'__';
       $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
       my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
       return $proname;
   } 
   
   # ----------------------------------- POD (plain old documentation, CPAN style)
   
   =head1 NAME
   
   lonsql - LON TCP-MySQL-Server Daemon for handling database requests.
   
   =head1 SYNOPSIS
   
   This script should be run as user=www.  The following is an example invocation
   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
   
   linux
   
   =head1 SCRIPT CATEGORIES
   
   Server/Process
   
   =cut

Removed from v.1.7  
changed lines
  Added in v.1.49


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