Annotation of nsdl/lonsql, revision 1.2
1.1 www 1: #!/usr/bin/perl
2:
3: # The LearningOnline Network
1.2 ! www 4: # lonsql - LON TCP-NSDL Query Handler.
1.1 www 5: #
1.2 ! www 6: # $Id: lonsql,v 1.68 2005/11/07 15:43:03 raeburn Exp $
1.1 www 7: #
8: # Copyright Michigan State University Board of Trustees
9: #
10: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
11: #
12: # LON-CAPA is free software; you can redistribute it and/or modify
13: # it under the terms of the GNU General Public License as published by
14: # the Free Software Foundation; either version 2 of the License, or
15: # (at your option) any later version.
16: #
17: # LON-CAPA is distributed in the hope that it will be useful,
18: # but WITHOUT ANY WARRANTY; without even the implied warranty of
19: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20: # GNU General Public License for more details.
21: #
22: # You should have received a copy of the GNU General Public License
23: # along with LON-CAPA; if not, write to the Free Software
24: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25: #
26: # /home/httpd/html/adm/gpl.txt
27: #
28: # http://www.lon-capa.org/
29: #
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:
42: =head1 OVERVIEW
43:
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.
95:
96: =head1 Internals
97:
98: =over 4
99:
100: =cut
101:
102: use strict;
103:
104: use lib '/home/httpd/lib/perl/';
105: use LONCAPA::Configuration;
106: use LONCAPA::lonmetadata();
107:
108: use IO::Socket;
109: use Symbol;
110: use POSIX;
111: use IO::Select;
112: use IO::File;
113: use Socket;
114: use Fcntl;
115: use Tie::RefHash;
1.2 ! www 116:
1.1 www 117: use File::Find;
118: use localenroll;
119:
120: ########################################################
121: ########################################################
122:
123: =pod
124:
125: =item Variables required for forking
126:
127: =over 4
128:
129: =item $MAX_CLIENTS_PER_CHILD
130:
131: The number of clients each child should process.
132:
133: =item %children
134:
135: The keys to %children are the current child process IDs
136:
137: =item $children
138:
139: The current number of children
140:
141: =back
142:
143: =cut
144:
145: ########################################################
146: ########################################################
147: my $MAX_CLIENTS_PER_CHILD = 5; # number of clients each child should process
148: my %children = (); # keys are current child process IDs
149: my $children = 0; # current number of children
150:
151: ###################################################################
152: ###################################################################
153:
154: =pod
155:
156: =item Main body of code.
157:
158: =over 4
159:
160: =item Read data from loncapa_apache.conf and loncapa.conf.
161:
162: =item Ensure we can access the database.
163:
164: =item Determine if there are other instances of lonsql running.
165:
166: =item Read the hosts file.
167:
168: =item Create a socket for lonsql.
169:
170: =item Fork once and dissociate from parent.
171:
172: =item Write PID to disk.
173:
174: =item Prefork children and maintain the population of children.
175:
176: =back
177:
178: =cut
179:
180: ###################################################################
181: ###################################################################
182: my $childmaxattempts=10;
183: my $run =0; # running counter to generate the query-id
184: #
185: # Read loncapa_apache.conf and loncapa.conf
186: #
187: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
188: my %perlvar=%{$perlvarref};
189: #
190: # Write the /home/www/.my.cnf file
191: my $conf_file = '/home/www/.my.cnf';
192: if (! -e $conf_file) {
193: if (open MYCNF, ">$conf_file") {
194: print MYCNF <<"ENDMYCNF";
195: [client]
196: user=www
197: password=$perlvar{'lonSqlAccess'}
198: ENDMYCNF
199: close MYCNF;
200: } else {
201: warn "Unable to write $conf_file, continuing";
202: }
203: }
204:
205:
206: #
207: # Check if other instance running
208: #
209: my $pidfile="$perlvar{'lonDaemons'}/logs/lonsql.pid";
210: if (-e $pidfile) {
211: my $lfh=IO::File->new("$pidfile");
212: my $pide=<$lfh>;
213: chomp($pide);
214: if (kill 0 => $pide) { die "already running"; }
215: }
216:
217: #
218: # Read hosts file
219: #
220: my $thisserver;
221: my $PREFORK=4; # number of children to maintain, at least four spare
222: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
223: while (my $configline=<CONFIG>) {
224: my ($id,$domain,$role,$name)=split(/:/,$configline);
225: $name=~s/\s//g;
226: $thisserver=$name if ($id eq $perlvar{'lonHostID'});
227: #$PREFORK++;
228: }
229: close(CONFIG);
230: #
231: #$PREFORK=int($PREFORK/4);
232:
233: #
234: # Create a socket to talk to lond
235: #
236: my $unixsock = "mysqlsock";
237: my $localfile="$perlvar{'lonSockDir'}/$unixsock";
238: my $server;
239: unlink ($localfile);
240: unless ($server=IO::Socket::UNIX->new(Local =>"$localfile",
241: Type => SOCK_STREAM,
242: Listen => 10)) {
243: print "in socket error:$@\n";
244: }
245:
246: #
247: # Fork once and dissociate
248: #
249: my $fpid=fork;
250: exit if $fpid;
251: die "Couldn't fork: $!" unless defined ($fpid);
252: POSIX::setsid() or die "Can't start new session: $!";
253:
254: #
255: # Write our PID on disk
256: my $execdir=$perlvar{'lonDaemons'};
257: open (PIDSAVE,">$execdir/logs/lonsql.pid");
258: print PIDSAVE "$$\n";
259: close(PIDSAVE);
260: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
261:
262: #
263: # Ignore signals generated during initial startup
264: $SIG{HUP}=$SIG{USR1}='IGNORE';
265: # Now we are on our own
266: # Fork off our children.
267: for (1 .. $PREFORK) {
268: make_new_child();
269: }
270:
271: #
272: # Install signal handlers.
273: $SIG{CHLD} = \&REAPER;
274: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
275: $SIG{HUP} = \&HUPSMAN;
276:
277: #
278: # And maintain the population.
279: while (1) {
280: sleep; # wait for a signal (i.e., child's death)
281: for (my $i = $children; $i < $PREFORK; $i++) {
282: make_new_child(); # top up the child pool
283: }
284: }
285:
286: ########################################################
287: ########################################################
288:
289: =pod
290:
291: =item &make_new_child
292:
293: Inputs: None
294:
295: Returns: None
296:
297: =cut
298:
299: ########################################################
300: ########################################################
301: sub make_new_child {
302: my $pid;
303: my $sigset;
304: #
305: # block signal for fork
306: $sigset = POSIX::SigSet->new(SIGINT);
307: sigprocmask(SIG_BLOCK, $sigset)
308: or die "Can't block SIGINT for fork: $!\n";
309: #
310: die "fork: $!" unless defined ($pid = fork);
311: #
312: if ($pid) {
313: # Parent records the child's birth and returns.
314: sigprocmask(SIG_UNBLOCK, $sigset)
315: or die "Can't unblock SIGINT for fork: $!\n";
316: $children{$pid} = 1;
317: $children++;
318: return;
319: } else {
320: # Child can *not* return from this subroutine.
321: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
322: # unblock signals
323: sigprocmask(SIG_UNBLOCK, $sigset)
324: or die "Can't unblock SIGINT for fork: $!\n";
1.2 ! www 325:
1.1 www 326: $SIG{TERM}=$SIG{INT}=$SIG{QUIT}=$SIG{__DIE__}=\&DISCONNECT;
327: # handle connections until we've reached $MAX_CLIENTS_PER_CHILD
328: for (my $i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
329: my $client = $server->accept() or last;
330: # do something with the connection
331: $run = $run+1;
332: my $userinput = <$client>;
333: chomp($userinput);
334: #
335: my ($conserver,$query,
336: $arg1,$arg2,$arg3)=split(/&/,$userinput);
337: my $query=unescape($query);
338: #
339: #send query id which is pid_unixdatetime_runningcounter
340: my $queryid = $thisserver;
341: $queryid .="_".($$)."_";
342: $queryid .= time."_";
343: $queryid .= $run;
344: print $client "$queryid\n";
345: #
346: # &logthis("QUERY: $query - $arg1 - $arg2 - $arg3");
347: sleep 1;
348: #
349: my $result='';
350: #
351: # At this point, query is received, query-ID assigned and sent
352: # back, $query eq 'logquery' will mean that this is a query
353: # against log-files
354: if (($query eq 'userlog') || ($query eq 'courselog')) {
355: # beginning of log query
356: my $udom = &unescape($arg1);
357: my $uname = &unescape($arg2);
358: my $command = &unescape($arg3);
359: my $path = &propath($udom,$uname);
360: if (-e "$path/activity.log") {
361: if ($query eq 'userlog') {
362: $result=&userlog($path,$command);
363: } else {
364: $result=&courselog($path,$command);
365: }
366: } else {
367: &logthis('Unable to do log query: '.$uname.'@'.$udom);
368: $result='no_such_file';
369: }
370: # end of log query
371: } elsif ($query eq 'fetchenrollment') {
372: # retrieve institutional class lists
373: my $dom = &unescape($arg1);
374: my %affiliates = ();
375: my %replies = ();
376: my $locresult = '';
377: my $querystr = &unescape($arg3);
378: foreach (split/%%/,$querystr) {
1.2 ! www 379: if (/^([^=]+)=([^=]+)$/) {
1.1 www 380: @{$affiliates{$1}} = split/,/,$2;
381: }
382: }
383: $locresult = &localenroll::fetch_enrollment($dom,\%affiliates,\%replies);
384: $result = &escape($locresult.':');
385: if ($locresult) {
386: $result .= &escape(join(':',map{$_.'='.$replies{$_}} keys %replies));
387: }
388: } elsif ($query eq 'prepare activity log') {
389: my ($cid,$domain) = map {&unescape($_);} ($arg1,$arg2);
390: &logthis('preparing activity log tables for '.$cid);
391: my $command =
392: qq{$perlvar{'lonDaemons'}/parse_activity_log.pl -course=$cid -domain=$domain};
393: system($command);
394: &logthis($command);
395: my $returnvalue = $?>>8;
396: if ($returnvalue) {
397: $result = 'error: parse_activity_log.pl returned '.
398: $returnvalue;
399: } else {
400: $result = 'success';
401: }
402: } else {
403: # Do an sql query
404: $result = &do_sql_query($query,$arg1,$arg2);
405: }
406: # result does not need to be escaped because it has already been
407: # escaped.
408: #$result=&escape($result);
409: &reply("queryreply:$queryid:$result",$conserver);
410: }
411: # tidy up gracefully and finish
412: #
1.2 ! www 413:
1.1 www 414: # this exit is VERY important, otherwise the child will become
415: # a producer of more and more children, forking yourself into
416: # process death.
417: exit;
418: }
419: }
420:
421: ########################################################
422: ########################################################
423:
424: =pod
425:
426: =item &do_sql_query
427:
428: Runs an sql metadata table query.
429:
430: Inputs: $query, $custom, $customshow
431:
432: Returns: A string containing escaped results.
433:
434: =cut
435:
436: ########################################################
437: ########################################################
438: {
439: my @metalist;
440:
441: sub process_file {
442: if ( -e $_ && # file exists
443: -f $_ && # and is a normal file
444: /\.meta$/ && # ends in meta
445: ! /^.+\.\d+\.[^\.]+\.meta$/ # is not a previous version
446: ) {
447: push(@metalist,$File::Find::name);
448: }
449: }
450:
451: sub do_sql_query {
1.2 ! www 452: my ($query) = @_;
1.1 www 453: &logthis('doing query '.$query);
1.2 ! www 454:
1.1 www 455: my @results = ();
1.2 ! www 456:
1.1 www 457: #
458: if ($query) {
459: #prepare and execute the query
1.2 ! www 460: my $aref=&nsdl_query($query);
! 461: foreach my $row (@$aref) {
! 462: my @b=map { &escape($_); } @$row;
! 463: push @results,join(",", @b);
! 464: }
! 465:
1.1 www 466: }
1.2 ! www 467: return join("&",@results);
1.1 www 468: } # End of &do_sql_query
469:
470: } # End of scoping curly braces for &process_file and &do_sql_query
471: ########################################################
472: ########################################################
473:
474: =pod
475:
476: =item &logthis
477:
478: Inputs: $message, the message to log
479:
480: Returns: nothing
481:
482: Writes $message to the logfile.
483:
484: =cut
485:
486: ########################################################
487: ########################################################
488: sub logthis {
489: my $message=shift;
490: my $execdir=$perlvar{'lonDaemons'};
491: my $fh=IO::File->new(">>$execdir/logs/lonsql.log");
492: my $now=time;
493: my $local=localtime($now);
494: print $fh "$local ($$): $message\n";
495: }
496:
497: # -------------------------------------------------- Non-critical communication
498:
499: ########################################################
500: ########################################################
501:
502: =pod
503:
504: =item &subreply
505:
506: Sends a command to a server. Called only by &reply.
507:
508: Inputs: $cmd,$server
509:
510: Returns: The results of the message or 'con_lost' on error.
511:
512: =cut
513:
514: ########################################################
515: ########################################################
516: sub subreply {
517: my ($cmd,$server)=@_;
518: my $peerfile="$perlvar{'lonSockDir'}/$server";
519: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
520: Type => SOCK_STREAM,
521: Timeout => 10)
522: or return "con_lost";
523: print $sclient "$cmd\n";
524: my $answer=<$sclient>;
525: chomp($answer);
526: $answer="con_lost" if (!$answer);
527: return $answer;
528: }
529:
530: ########################################################
531: ########################################################
532:
533: =pod
534:
535: =item &reply
536:
537: Sends a command to a server.
538:
539: Inputs: $cmd,$server
540:
541: Returns: The results of the message or 'con_lost' on error.
542:
543: =cut
544:
545: ########################################################
546: ########################################################
547: sub reply {
548: my ($cmd,$server)=@_;
549: my $answer;
550: if ($server ne $perlvar{'lonHostID'}) {
551: $answer=subreply($cmd,$server);
552: if ($answer eq 'con_lost') {
553: $answer=subreply("ping",$server);
554: $answer=subreply($cmd,$server);
555: }
556: } else {
557: $answer='self_reply';
558: $answer=subreply($cmd,$server);
559: }
560: return $answer;
561: }
562:
563: ########################################################
564: ########################################################
565:
566: =pod
567:
568: =item &escape
569:
570: Escape special characters in a string.
571:
572: Inputs: string to escape
573:
574: Returns: The input string with special characters escaped.
575:
576: =cut
577:
578: ########################################################
579: ########################################################
580: sub escape {
581: my $str=shift;
582: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
583: return $str;
584: }
585:
586: ########################################################
587: ########################################################
588:
589: =pod
590:
591: =item &unescape
592:
593: Unescape special characters in a string.
594:
595: Inputs: string to unescape
596:
597: Returns: The input string with special characters unescaped.
598:
599: =cut
600:
601: ########################################################
602: ########################################################
603: sub unescape {
604: my $str=shift;
605: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
606: return $str;
607: }
608:
609: ########################################################
610: ########################################################
611:
612: =pod
613:
614: =item &ishome
615:
616: Determine if the current machine is the home server for a user.
617: The determination is made by checking the filesystem for the users information.
618:
619: Inputs: $author
620:
621: Returns: 0 - this is not the authors home server, 1 - this is.
622:
623: =cut
624:
625: ########################################################
626: ########################################################
627: sub ishome {
628: my $author=shift;
629: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
630: my ($udom,$uname)=split(/\//,$author);
631: my $proname=propath($udom,$uname);
632: if (-e $proname) {
633: return 1;
634: } else {
635: return 0;
636: }
637: }
638:
639: ########################################################
640: ########################################################
641:
642: =pod
643:
644: =item &propath
645:
646: Inputs: user name, user domain
647:
648: Returns: The full path to the users directory.
649:
650: =cut
651:
652: ########################################################
653: ########################################################
654: sub propath {
655: my ($udom,$uname)=@_;
656: $udom=~s/\W//g;
657: $uname=~s/\W//g;
658: my $subdir=$uname.'__';
659: $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
660: my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
661: return $proname;
662: }
663:
664: ########################################################
665: ########################################################
666:
667: =pod
668:
669: =item &courselog
670:
671: Inputs: $path, $command
672:
673: Returns: unescaped string of values.
674:
675: =cut
676:
677: ########################################################
678: ########################################################
679: sub courselog {
680: my ($path,$command)=@_;
681: my %filters=();
682: foreach (split(/\:/,&unescape($command))) {
683: my ($name,$value)=split(/\=/,$_);
684: $filters{$name}=$value;
685: }
686: my @results=();
687: open(IN,$path.'/activity.log') or return ('file_error');
688: while (my $line=<IN>) {
689: chomp($line);
690: my ($timestamp,$host,$log)=split(/\:/,$line);
691: #
692: # $log has the actual log entries; currently still escaped, and
693: # %26(timestamp)%3a(url)%3a(user)%3a(domain)
694: # then additionally
695: # %3aPOST%3a(name)%3d(value)%3a(name)%3d(value)
696: # or
697: # %3aCSTORE%3a(name)%3d(value)%26(name)%3d(value)
698: #
699: # get delimiter between timestamped entries to be &&&
700: $log=~s/\%26(\d+)\%3a/\&\&\&$1\%3a/g;
701: # now go over all log entries
702: foreach (split(/\&\&\&/,&unescape($log))) {
703: my ($time,$res,$uname,$udom,$action,@values)=split(/\:/,$_);
704: my $values=&unescape(join(':',@values));
705: $values=~s/\&/\:/g;
706: $res=&unescape($res);
707: my $include=1;
708: if (($filters{'username'}) && ($uname ne $filters{'username'}))
709: { $include=0; }
710: if (($filters{'domain'}) && ($udom ne $filters{'domain'}))
711: { $include=0; }
712: if (($filters{'url'}) && ($res!~/$filters{'url'}/))
713: { $include=0; }
714: if (($filters{'start'}) && ($time<$filters{'start'}))
715: { $include=0; }
716: if (($filters{'end'}) && ($time>$filters{'end'}))
717: { $include=0; }
718: if (($filters{'action'} eq 'view') && ($action))
719: { $include=0; }
720: if (($filters{'action'} eq 'submit') && ($action ne 'POST'))
721: { $include=0; }
722: if (($filters{'action'} eq 'grade') && ($action ne 'CSTORE'))
723: { $include=0; }
724: if ($include) {
725: push(@results,($time<1000000000?'0':'').$time.':'.$res.':'.
726: $uname.':'.$udom.':'.
727: $action.':'.$values);
728: }
729: }
730: }
731: close IN;
732: return join('&',sort(@results));
733: }
734:
735: ########################################################
736: ########################################################
737:
738: =pod
739:
740: =item &userlog
741:
742: Inputs: $path, $command
743:
744: Returns: unescaped string of values.
745:
746: =cut
747:
748: ########################################################
749: ########################################################
750: sub userlog {
751: my ($path,$command)=@_;
752: my %filters=();
753: foreach (split(/\:/,&unescape($command))) {
754: my ($name,$value)=split(/\=/,$_);
755: $filters{$name}=$value;
756: }
757: my @results=();
758: open(IN,$path.'/activity.log') or return ('file_error');
759: while (my $line=<IN>) {
760: chomp($line);
761: my ($timestamp,$host,$log)=split(/\:/,$line);
762: $log=&unescape($log);
763: my $include=1;
764: if (($filters{'start'}) && ($timestamp<$filters{'start'}))
765: { $include=0; }
766: if (($filters{'end'}) && ($timestamp>$filters{'end'}))
767: { $include=0; }
768: if (($filters{'action'} eq 'log') && ($log!~/^Log/)) { $include=0; }
769: if (($filters{'action'} eq 'check') && ($log!~/^Check/))
770: { $include=0; }
771: if ($include) {
772: push(@results,$timestamp.':'.$log);
773: }
774: }
775: close IN;
776: return join('&',sort(@results));
777: }
778:
779: ########################################################
780: ########################################################
781:
782: =pod
783:
784: =item Functions required for forking
785:
786: =over 4
787:
788: =item REAPER
789:
790: REAPER takes care of dead children.
791:
792: =item HUNTSMAN
793:
794: Signal handler for SIGINT.
795:
796: =item HUPSMAN
797:
798: Signal handler for SIGHUP
799:
800: =item DISCONNECT
801:
802: Disconnects from database.
803:
804: =back
805:
806: =cut
807:
808: ########################################################
809: ########################################################
810: sub REAPER { # takes care of dead children
811: $SIG{CHLD} = \&REAPER;
812: my $pid = wait;
813: $children --;
814: &logthis("Child $pid died");
815: delete $children{$pid};
816: }
817:
818: sub HUNTSMAN { # signal handler for SIGINT
819: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
820: kill 'INT' => keys %children;
821: my $execdir=$perlvar{'lonDaemons'};
822: unlink("$execdir/logs/lonsql.pid");
823: &logthis("<font color='red'>CRITICAL: Shutting down</font>");
824: $unixsock = "mysqlsock";
825: my $port="$perlvar{'lonSockDir'}/$unixsock";
826: unlink($port);
827: exit; # clean up with dignity
828: }
829:
830: sub HUPSMAN { # signal handler for SIGHUP
831: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
832: kill 'INT' => keys %children;
833: close($server); # free up socket
834: &logthis("<font color='red'>CRITICAL: Restarting</font>");
835: my $execdir=$perlvar{'lonDaemons'};
836: $unixsock = "mysqlsock";
837: my $port="$perlvar{'lonSockDir'}/$unixsock";
838: unlink($port);
839: exec("$execdir/lonsql"); # here we go again
840: }
841:
1.2 ! www 842: #
! 843: # Takes SQL query
! 844: # sends it to NSDL
! 845: # has to return array reference
! 846: #
1.1 www 847:
1.2 ! www 848: sub nsdl_query {
! 849: my $query=shift;
! 850: }
1.1 www 851:
852: =pod
853:
854: =back
855:
856: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>