Annotation of loncom/lonsql, revision 1.83

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