Annotation of loncom/lonsql, revision 1.35
1.1 harris41 1: #!/usr/bin/perl
1.2 harris41 2: # lonsql-based on the preforker:harsha jagasia:date:5/10/00
1.4 www 3: # 7/25 Gerd Kortemeyer
1.6 harris41 4: # many different dates Scott Harrison
1.7 harris41 5: # 03/22/2001 Scott Harrison
1.2 harris41 6: use IO::Socket;
7: use Symbol;
1.1 harris41 8: use POSIX;
9: use IO::Select;
10: use IO::File;
11: use Socket;
12: use Fcntl;
13: use Tie::RefHash;
14: use DBI;
15:
1.9 harris41 16: my @metalist;
17: # ----------------- Code to enable 'find' subroutine listing of the .meta files
18: require "find.pl";
19: sub wanted {
20: (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
21: -f _ &&
1.34 harris41 22: /^.*\.meta$/ && !/^.+\.\d+\.[^\.]+\.meta$/ &&
1.9 harris41 23: push(@metalist,"$dir/$_");
24: }
25:
1.1 harris41 26: $childmaxattempts=10;
1.2 harris41 27: $run =0;#running counter to generate the query-id
28:
1.1 harris41 29: # ------------------------------------ Read httpd access.conf and get variables
30: open (CONFIG,"/etc/httpd/conf/access.conf") || die "Can't read access.conf";
31:
32: while ($configline=<CONFIG>) {
33: if ($configline =~ /PerlSetVar/) {
34: my ($dummy,$varname,$varvalue)=split(/\s+/,$configline);
35: chomp($varvalue);
36: $perlvar{$varname}=$varvalue;
37: }
38: }
39: close(CONFIG);
1.4 www 40:
1.31 harris41 41: # ------------------------------------- Make sure that database can be accessed
42: {
43: my $dbh;
44: unless (
45: $dbh = DBI->connect("DBI:mysql:loncapa","www",$perlvar{'lonSqlAccess'},{ RaiseError =>0,PrintError=>0})
46: ) {
47: print "Cannot connect to database!\n";
48: exit;
49: }
50: else {
51: $dbh->disconnect;
52: }
53: }
54:
1.4 www 55: # --------------------------------------------- Check if other instance running
56:
57: my $pidfile="$perlvar{'lonDaemons'}/logs/lonsql.pid";
58:
59: if (-e $pidfile) {
60: my $lfh=IO::File->new("$pidfile");
61: my $pide=<$lfh>;
62: chomp($pide);
63: if (kill 0 => $pide) { die "already running"; }
64: }
1.1 harris41 65:
66: # ------------------------------------------------------------- Read hosts file
1.2 harris41 67: $PREFORK=4; # number of children to maintain, at least four spare
1.1 harris41 68:
69: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
70:
71: while ($configline=<CONFIG>) {
72: my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
73: chomp($ip);
74:
1.2 harris41 75: $hostip{$ip}=$id;
1.1 harris41 76: if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; }
77:
1.2 harris41 78: $PREFORK++;
1.1 harris41 79: }
80: close(CONFIG);
81:
1.2 harris41 82: $unixsock = "mysqlsock";
83: my $localfile="$perlvar{'lonSockDir'}/$unixsock";
84: my $server;
85: unlink ($localfile);
86: unless ($server=IO::Socket::UNIX->new(Local =>"$localfile",
87: Type => SOCK_STREAM,
88: Listen => 10))
89: {
90: print "in socket error:$@\n";
91: }
1.1 harris41 92:
93: # -------------------------------------------------------- Routines for forking
94: # global variables
1.2 harris41 95: $MAX_CLIENTS_PER_CHILD = 5; # number of clients each child should process
1.1 harris41 96: %children = (); # keys are current child process IDs
1.2 harris41 97: $children = 0; # current number of children
1.1 harris41 98:
99: sub REAPER { # takes care of dead children
100: $SIG{CHLD} = \&REAPER;
101: my $pid = wait;
1.2 harris41 102: $children --;
103: &logthis("Child $pid died");
1.1 harris41 104: delete $children{$pid};
105: }
106:
107: sub HUNTSMAN { # signal handler for SIGINT
108: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
109: kill 'INT' => keys %children;
110: my $execdir=$perlvar{'lonDaemons'};
111: unlink("$execdir/logs/lonsql.pid");
112: &logthis("<font color=red>CRITICAL: Shutting down</font>");
1.2 harris41 113: $unixsock = "mysqlsock";
114: my $port="$perlvar{'lonSockDir'}/$unixsock";
115: unlink(port);
1.1 harris41 116: exit; # clean up with dignity
117: }
118:
119: sub HUPSMAN { # signal handler for SIGHUP
120: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
121: kill 'INT' => keys %children;
122: close($server); # free up socket
123: &logthis("<font color=red>CRITICAL: Restarting</font>");
124: my $execdir=$perlvar{'lonDaemons'};
1.2 harris41 125: $unixsock = "mysqlsock";
126: my $port="$perlvar{'lonSockDir'}/$unixsock";
127: unlink(port);
1.1 harris41 128: exec("$execdir/lonsql"); # here we go again
129: }
130:
131: sub logthis {
132: my $message=shift;
133: my $execdir=$perlvar{'lonDaemons'};
1.2 harris41 134: my $fh=IO::File->new(">>$execdir/logs/lonsqlfinal.log");
1.1 harris41 135: my $now=time;
136: my $local=localtime($now);
137: print $fh "$local ($$): $message\n";
138: }
139: # ---------------------------------------------------- Fork once and dissociate
140: $fpid=fork;
141: exit if $fpid;
142: die "Couldn't fork: $!" unless defined ($fpid);
143:
144: POSIX::setsid() or die "Can't start new session: $!";
145:
146: # ------------------------------------------------------- Write our PID on disk
147:
148: $execdir=$perlvar{'lonDaemons'};
149: open (PIDSAVE,">$execdir/logs/lonsql.pid");
150: print PIDSAVE "$$\n";
151: close(PIDSAVE);
152: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
153:
154: # ----------------------------- Ignore signals generated during initial startup
155: $SIG{HUP}=$SIG{USR1}='IGNORE';
1.2 harris41 156: # ------------------------------------------------------- Now we are on our own
157: # Fork off our children.
158: for (1 .. $PREFORK) {
159: make_new_child();
1.1 harris41 160: }
161:
1.2 harris41 162: # Install signal handlers.
1.1 harris41 163: $SIG{CHLD} = \&REAPER;
164: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
165: $SIG{HUP} = \&HUPSMAN;
166:
167: # And maintain the population.
168: while (1) {
169: sleep; # wait for a signal (i.e., child's death)
1.2 harris41 170: for ($i = $children; $i < $PREFORK; $i++) {
171: make_new_child(); # top up the child pool
1.1 harris41 172: }
173: }
174:
1.2 harris41 175:
1.1 harris41 176: sub make_new_child {
177: my $pid;
178: my $sigset;
1.2 harris41 179:
1.1 harris41 180: # block signal for fork
181: $sigset = POSIX::SigSet->new(SIGINT);
182: sigprocmask(SIG_BLOCK, $sigset)
183: or die "Can't block SIGINT for fork: $!\n";
184:
1.2 harris41 185: die "fork: $!" unless defined ($pid = fork);
186:
1.1 harris41 187: if ($pid) {
188: # Parent records the child's birth and returns.
189: sigprocmask(SIG_UNBLOCK, $sigset)
190: or die "Can't unblock SIGINT for fork: $!\n";
191: $children{$pid} = 1;
192: $children++;
193: return;
194: } else {
1.2 harris41 195: # Child can *not* return from this subroutine.
1.1 harris41 196: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
197:
198: # unblock signals
199: sigprocmask(SIG_UNBLOCK, $sigset)
200: or die "Can't unblock SIGINT for fork: $!\n";
1.2 harris41 201:
202:
203: #open database handle
204: # making dbh global to avoid garbage collector
1.1 harris41 205: unless (
1.31 harris41 206: $dbh = DBI->connect("DBI:mysql:loncapa","www",$perlvar{'lonSqlAccess'},{ RaiseError =>0,PrintError=>0})
1.1 harris41 207: ) {
1.30 harris41 208: sleep(10+int(rand(20)));
1.1 harris41 209: &logthis("<font color=blue>WARNING: Couldn't connect to database ($st secs): $@</font>");
1.2 harris41 210: print "database handle error\n";
211: exit;
212:
213: };
214: # make sure that a database disconnection occurs with ending kill signals
215: $SIG{TERM}=$SIG{INT}=$SIG{QUIT}=$SIG{__DIE__}=\&DISCONNECT;
216:
1.1 harris41 217: # handle connections until we've reached $MAX_CLIENTS_PER_CHILD
218: for ($i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
219: $client = $server->accept() or last;
1.2 harris41 220:
221: # do something with the connection
1.1 harris41 222: $run = $run+1;
1.2 harris41 223: my $userinput = <$client>;
224: chomp($userinput);
225:
1.21 harris41 226: my ($conserver,$querytmp,
227: $customtmp,$customshowtmp)=split(/&/,$userinput);
1.3 harris41 228: my $query=unescape($querytmp);
1.7 harris41 229: my $custom=unescape($customtmp);
1.21 harris41 230: my $customshow=unescape($customshowtmp);
1.2 harris41 231:
232: #send query id which is pid_unixdatetime_runningcounter
233: $queryid = $thisserver;
234: $queryid .="_".($$)."_";
235: $queryid .= time."_";
236: $queryid .= $run;
237: print $client "$queryid\n";
238:
1.25 harris41 239: &logthis("QUERY: $query");
240: &logthis("QUERY: $query");
241: sleep 1;
1.2 harris41 242: #prepare and execute the query
1.3 harris41 243: my $sth = $dbh->prepare($query);
244: my $result;
1.20 harris41 245: my @files;
1.24 harris41 246: my $subsetflag=0;
1.26 harris41 247: if ($query) {
248: unless ($sth->execute())
249: {
250: &logthis("<font color=blue>WARNING: Could not retrieve from database: $@</font>");
251: $result="";
252: }
253: else {
254: my $r1=$sth->fetchall_arrayref;
255: my @r2;
256: map {my $a=$_;
257: my @b=map {escape($_)} @$a;
258: push @files,@{$a}[3];
259: push @r2,join(",", @b)
260: } (@$r1);
261: $result=join("&",@r2);
262: }
1.3 harris41 263: }
1.7 harris41 264: # do custom metadata searching here and build into result
1.28 harris41 265: if ($custom or $customshow) {
1.9 harris41 266: &logthis("am going to do custom query for $custom");
1.26 harris41 267: if ($query) {
1.23 harris41 268: @metalist=map {$perlvar{'lonDocRoot'}.$_.'.meta'} @files;
1.20 harris41 269: }
270: else {
271: @metalist=(); pop @metalist;
1.34 harris41 272: opendir(RESOURCES,"$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}");
273: my @homeusers=grep
274: {&ishome("$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}/$_")}
275: grep {!/^\.\.?$/} readdir(RESOURCES);
276: closedir RESOURCES;
277: foreach my $user (@homeusers) {
278: &find("$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}/$user");
279: }
1.20 harris41 280: }
1.23 harris41 281: # &logthis("FILELIST:" . join(":::",@metalist));
1.10 harris41 282: # if file is indicated in sql database and
283: # not part of sql-relevant query, do not pattern match.
284: # if file is not in sql database, output error.
285: # if file is indicated in sql database and is
286: # part of query result list, then do the pattern match.
1.12 harris41 287: my $customresult='';
1.26 harris41 288: my @r2;
1.11 harris41 289: foreach my $m (@metalist) {
290: my $fh=IO::File->new($m);
291: my @lines=<$fh>;
292: my $stuff=join('',@lines);
293: if ($stuff=~/$custom/s) {
1.18 harris41 294: foreach my $f ('abstract','author','copyright',
295: 'creationdate','keywords','language',
296: 'lastrevisiondate','mime','notes',
297: 'owner','subject','title') {
1.19 harris41 298: $stuff=~s/\n?\<$f[^\>]*\>.*?<\/$f[^\>]*\>\n?//;
1.18 harris41 299: }
1.19 harris41 300: my $m2=$m; my $docroot=$perlvar{'lonDocRoot'};
1.26 harris41 301: $m2=~s/^$docroot//;
302: $m2=~s/\.meta$//;
303: unless ($query) {
1.35 ! harris41 304: my $q2="select * from metadata where url like binary '$m2'";
1.27 harris41 305: my $sth = $dbh->prepare($q2);
1.26 harris41 306: $sth->execute();
307: my $r1=$sth->fetchall_arrayref;
308: map {my $a=$_;
309: my @b=map {escape($_)} @$a;
310: push @files,@{$a}[3];
311: push @r2,join(",", @b)
312: } (@$r1);
313: }
1.20 harris41 314: # &logthis("found: $stuff");
1.19 harris41 315: $customresult.='&custom='.escape($m2).','.escape($stuff);
1.11 harris41 316: }
317: }
1.26 harris41 318: $result=join("&",@r2) unless $query;
1.17 harris41 319: $result.=$customresult;
1.9 harris41 320: }
1.8 harris41 321: # reply with result
1.17 harris41 322: $result.="\n" if $result;
323: &reply("queryreply:$queryid:$result",$conserver);
1.2 harris41 324:
1.1 harris41 325: }
326:
327: # tidy up gracefully and finish
1.2 harris41 328:
329: #close the database handle
330: $dbh->disconnect
331: or &logthis("<font color=blue>WARNING: Couldn't disconnect from database $DBI::errstr ($st secs): $@</font>");
1.1 harris41 332:
333: # this exit is VERY important, otherwise the child will become
334: # a producer of more and more children, forking yourself into
335: # process death.
336: exit;
337: }
1.2 harris41 338: }
1.1 harris41 339:
1.2 harris41 340: sub DISCONNECT {
341: $dbh->disconnect or
342: &logthis("<font color=blue>WARNING: Couldn't disconnect from database $DBI::errstr ($st secs): $@</font>");
343: exit;
344: }
1.1 harris41 345:
1.2 harris41 346: # -------------------------------------------------- Non-critical communication
1.1 harris41 347:
1.2 harris41 348: sub subreply {
349: my ($cmd,$server)=@_;
350: my $peerfile="$perlvar{'lonSockDir'}/$server";
351: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
352: Type => SOCK_STREAM,
353: Timeout => 10)
354: or return "con_lost";
355: print $sclient "$cmd\n";
356: my $answer=<$sclient>;
357: chomp($answer);
358: if (!$answer) { $answer="con_lost"; }
359: return $answer;
360: }
1.1 harris41 361:
1.2 harris41 362: sub reply {
363: my ($cmd,$server)=@_;
364: my $answer;
365: if ($server ne $perlvar{'lonHostID'}) {
366: $answer=subreply($cmd,$server);
367: if ($answer eq 'con_lost') {
368: $answer=subreply("ping",$server);
369: $answer=subreply($cmd,$server);
370: }
371: } else {
372: $answer='self_reply';
1.33 harris41 373: $answer=subreply($cmd,$server);
1.2 harris41 374: }
375: return $answer;
376: }
1.1 harris41 377:
1.3 harris41 378: # -------------------------------------------------------- Escape Special Chars
379:
380: sub escape {
381: my $str=shift;
382: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
383: return $str;
384: }
385:
386: # ----------------------------------------------------- Un-Escape Special Chars
387:
388: sub unescape {
389: my $str=shift;
390: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
391: return $str;
392: }
1.34 harris41 393:
394: # --------------------------------------- Is this the home server of an author?
395: # (copied from lond, modification of the return value)
396: sub ishome {
397: my $author=shift;
398: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
399: my ($udom,$uname)=split(/\//,$author);
400: my $proname=propath($udom,$uname);
401: if (-e $proname) {
402: return 1;
403: } else {
404: return 0;
405: }
406: }
407:
408: # -------------------------------------------- Return path to profile directory
409: # (copied from lond)
410: sub propath {
411: my ($udom,$uname)=@_;
412: $udom=~s/\W//g;
413: $uname=~s/\W//g;
414: my $subdir=$uname.'__';
415: $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
416: my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
417: return $proname;
418: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>