--- loncom/lonnet/perl/lonnet.pm 2004/09/02 21:23:13 1.539
+++ loncom/lonnet/perl/lonnet.pm 2004/10/26 17:20:09 1.554
@@ -1,7 +1,7 @@
# The LearningOnline Network
# TCP networking package
#
-# $Id: lonnet.pm,v 1.539 2004/09/02 21:23:13 albertel Exp $
+# $Id: lonnet.pm,v 1.554 2004/10/26 17:20:09 www Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -39,7 +39,7 @@ qw(%perlvar %hostname %homecache %badSer
%libserv %pr %prp %metacache %packagetab %titlecache %courseresversioncache %resversioncache
%courselogs %accesshash %userrolehash $processmarker $dumpcount
%coursedombuf %coursenumbuf %coursehombuf %coursedescrbuf %courseinstcodebuf %courseresdatacache
- %userresdatacache %usectioncache %domaindescription %domain_auth_def %domain_auth_arg_def
+ %userresdatacache %getsectioncache %domaindescription %domain_auth_def %domain_auth_arg_def
%domain_lang_def %domain_city %domain_longi %domain_lati $tmpdir);
use IO::Socket;
@@ -52,6 +52,7 @@ use Apache::lonlocal;
use Storable qw(lock_store lock_nstore lock_retrieve freeze thaw);
use Time::HiRes qw( gettimeofday tv_interval );
my $readit;
+my $max_connection_retries = 10; # Or some such value.
=pod
@@ -116,14 +117,40 @@ sub logperm {
sub subreply {
my ($cmd,$server)=@_;
my $peerfile="$perlvar{'lonSockDir'}/$server";
- my $client=IO::Socket::UNIX->new(Peer =>"$peerfile",
- Type => SOCK_STREAM,
- Timeout => 10)
- or return "con_lost";
- print $client "$cmd\n";
- my $answer=<$client>;
- if (!$answer) { $answer="con_lost"; }
- chomp($answer);
+ #
+ # With loncnew process trimming, there's a timing hole between lonc server
+ # process exit and the master server picking up the listen on the AF_UNIX
+ # socket. In that time interval, a lock file will exist:
+
+ my $lockfile=$peerfile.".lock";
+ while (-e $lockfile) { # Need to wait for the lockfile to disappear.
+ sleep(1);
+ }
+ # At this point, either a loncnew parent is listening or an old lonc
+ # or loncnew child is listening so we can connect or everything's dead.
+ #
+ # We'll give the connection a few tries before abandoning it. If
+ # connection is not possible, we'll con_lost back to the client.
+ #
+ my $client;
+ for (my $retries = 0; $retries < $max_connection_retries; $retries++) {
+ $client=IO::Socket::UNIX->new(Peer =>"$peerfile",
+ Type => SOCK_STREAM,
+ Timeout => 10);
+ if($client) {
+ last; # Connected!
+ }
+ sleep(1); # Try again later if failed connection.
+ }
+ my $answer;
+ if ($client) {
+ print $client "$cmd\n";
+ $answer=<$client>;
+ if (!$answer) { $answer="con_lost"; }
+ chomp($answer);
+ } else {
+ $answer = 'con_lost'; # Failed connection.
+ }
return $answer;
}
@@ -434,7 +461,7 @@ sub overloaderror {
if ($overload>0) {
$r->err_headers_out->{'Retry-After'}=$overload;
$r->log_error('Overload of '.$overload.' on '.$checkserver);
- return 409;
+ return 413;
}
return '';
}
@@ -771,6 +798,11 @@ sub getsection {
my ($udom,$unam,$courseid)=@_;
$courseid=~s/\_/\//g;
$courseid=~s/^(\w)/\/$1/;
+
+ my $hashid="$udom:$unam:$courseid";
+ my ($result,$cached)=&is_cached(\%getsectioncache,$hashid,'getsection');
+ if (defined($cached)) { return $result; }
+
my %Pending;
my %Expired;
#
@@ -795,29 +827,29 @@ sub getsection {
if ($key eq $courseid.'_st') { $section=''; }
my ($dummy,$end,$start)=split(/\_/,&unescape($value));
my $now=time;
- if (defined($end) && ($now > $end)) {
+ if (defined($end) && $end && ($now > $end)) {
$Expired{$end}=$section;
next;
}
- if (defined($start) && ($now < $start)) {
+ if (defined($start) && $start && ($now < $start)) {
$Pending{$start}=$section;
next;
}
- return $section;
+ return &do_cache(\%getsectioncache,$hashid,$section,'getsection');
}
#
# Presumedly there will be few matching roles from the above
# loop and the sorting time will be negligible.
if (scalar(keys(%Pending))) {
my ($time) = sort {$a <=> $b} keys(%Pending);
- return $Pending{$time};
+ return &do_cache(\%getsectioncache,$hashid,$Pending{$time},'getsection');
}
if (scalar(keys(%Expired))) {
my @sorted = sort {$a <=> $b} keys(%Expired);
my $time = pop(@sorted);
- return $Expired{$time};
+ return &do_cache(\%getsectioncache,$hashid,$Expired{$time},'getsection');
}
- return '-1';
+ return &do_cache(\%getsectioncache,$hashid,'-1','getsection');
}
@@ -826,10 +858,12 @@ my $disk_caching_disabled=1;
sub devalidate_cache {
my ($cache,$id,$name) = @_;
delete $$cache{$id.'.time'};
+ delete $$cache{$id.'.file'};
delete $$cache{$id};
- if ($disk_caching_disabled) { return; }
+ if (1 || $disk_caching_disabled) { return; }
my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
- open(DB,"$filename.lock");
+ if (!-e $filename) { return; }
+ open(DB,">$filename.lock");
flock(DB,LOCK_EX);
my %hash;
if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) {
@@ -856,16 +890,32 @@ sub is_cached {
my ($cache,$id,$name,$time) = @_;
if (!$time) { $time=300; }
if (!exists($$cache{$id.'.time'})) {
- &load_cache_item($cache,$name,$id);
+ &load_cache_item($cache,$name,$id,$time);
}
if (!exists($$cache{$id.'.time'})) {
# &logthis("Didn't find $id");
return (undef,undef);
} else {
if (time-($$cache{$id.'.time'})>$time) {
-# &logthis("Devalidating $id - ".time-($$cache{$id.'.time'}));
- &devalidate_cache($cache,$id,$name);
- return (undef,undef);
+ if (exists($$cache{$id.'.file'})) {
+ foreach my $filename (@{ $$cache{$id.'.file'} }) {
+ my $mtime=(stat($filename))[9];
+ #+1 is to take care of edge effects
+ if ($mtime && (($mtime+1) < ($$cache{$id.'.time'}))) {
+# &logthis("Upping $mtime - ".$$cache{$id.'.time'}.
+# "$id because of $filename");
+ } else {
+ &logthis("Devalidating $filename $id - ".(time-($$cache{$id.'.time'})));
+ &devalidate_cache($cache,$id,$name);
+ return (undef,undef);
+ }
+ }
+ $$cache{$id.'.time'}=time;
+ } else {
+# &logthis("Devalidating $id - ".time-($$cache{$id.'.time'}));
+ &devalidate_cache($cache,$id,$name);
+ return (undef,undef);
+ }
}
}
return ($$cache{$id},1);
@@ -881,44 +931,69 @@ sub do_cache {
$$cache{$id};
}
+my %do_save_item;
+my %do_save;
sub save_cache_item {
my ($cache,$name,$id)=@_;
if ($disk_caching_disabled) { return; }
- my $starttime=&Time::HiRes::time();
-# &logthis("Saving :$name:$id");
- my %hash;
- my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
- open(DB,"$filename.lock");
- flock(DB,LOCK_EX);
- if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) {
- eval <<'EVALBLOCK';
- $hash{$id.'.time'}=$$cache{$id.'.time'};
- $hash{$id}=freeze({'item'=>$$cache{$id}});
+ $do_save{$name}=$cache;
+ if (!exists($do_save_item{$name})) { $do_save_item{$name}={} }
+ $do_save_item{$name}->{$id}=1;
+ return;
+}
+
+sub save_cache {
+ if ($disk_caching_disabled) { return; }
+ my ($cache,$name,$id);
+ foreach $name (keys(%do_save)) {
+ $cache=$do_save{$name};
+
+ my $starttime=&Time::HiRes::time();
+ &logthis("Saving :$name:");
+ my %hash;
+ my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
+ open(DB,">$filename.lock");
+ flock(DB,LOCK_EX);
+ if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) {
+ foreach $id (keys(%{ $do_save_item{$name} })) {
+ eval <<'EVALBLOCK';
+ $hash{$id.'.time'}=$$cache{$id.'.time'};
+ $hash{$id}=freeze({'item'=>$$cache{$id}});
+ if (exists($$cache{$id.'.file'})) {
+ $hash{$id.'.file'}=freeze({'item'=>$$cache{$id.'.file'}});
+ }
EVALBLOCK
- if ($@) {
- &logthis("save_cache blew up :$@:$name");
- unlink($filename);
- }
- } else {
- if (-e $filename) {
- &logthis("Unable to tie hash (save cache item): $name ($!)");
- unlink($filename);
+ if ($@) {
+ &logthis("save_cache blew up :$@:$name");
+ unlink($filename);
+ last;
+ }
+ }
+ } else {
+ if (-e $filename) {
+ &logthis("Unable to tie hash (save cache): $name ($!)");
+ unlink($filename);
+ }
}
+ untie(%hash);
+ flock(DB,LOCK_UN);
+ close(DB);
+ &logthis("save_cache $name took ".(&Time::HiRes::time()-$starttime));
}
- untie(%hash);
- flock(DB,LOCK_UN);
- close(DB);
-# &logthis("save_cache_item $name took ".(&Time::HiRes::time()-$starttime));
+ undef(%do_save);
+ undef(%do_save_item);
+
}
sub load_cache_item {
- my ($cache,$name,$id)=@_;
+ my ($cache,$name,$id,$time)=@_;
if ($disk_caching_disabled) { return; }
my $starttime=&Time::HiRes::time();
# &logthis("Before Loading $name for $id size is ".scalar(%$cache));
my %hash;
my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
- open(DB,"$filename.lock");
+ if (!-e $filename) { return; }
+ open(DB,">$filename.lock");
flock(DB,LOCK_SH);
if (tie(%hash,'GDBM_File',$filename,&GDBM_READER(),0640)) {
eval <<'EVALBLOCK';
@@ -935,9 +1010,17 @@ sub load_cache_item {
}
# &logthis("Initial load: $count");
} else {
- my $hashref=thaw($hash{$id});
- $$cache{$id}=$hashref->{'item'};
- $$cache{$id.'.time'}=$hash{$id.'.time'};
+ if (($$cache{$id.'.time'}+$time) < time) {
+ $$cache{$id.'.time'}=$hash{$id.'.time'};
+ {
+ my $hashref=thaw($hash{$id});
+ $$cache{$id}=$hashref->{'item'};
+ }
+ if (exists($hash{$id.'.file'})) {
+ my $hashref=thaw($hash{$id.'.file'});
+ $$cache{$id.'.file'}=$hashref->{'item'};
+ }
+ }
}
EVALBLOCK
if ($@) {
@@ -957,38 +1040,6 @@ EVALBLOCK
# &logthis("load_cache_item $name took ".(&Time::HiRes::time()-$starttime));
}
-sub usection {
- my ($udom,$unam,$courseid)=@_;
- my $hashid="$udom:$unam:$courseid";
-
- my ($result,$cached)=&is_cached(\%usectioncache,$hashid,'usection');
- if (defined($cached)) { return $result; }
- $courseid=~s/\_/\//g;
- $courseid=~s/^(\w)/\/$1/;
- foreach (split(/\&/,&reply('dump:'.$udom.':'.$unam.':roles',
- &homeserver($unam,$udom)))) {
- my ($key,$value)=split(/\=/,$_);
- $key=&unescape($key);
- if ($key=~/^\Q$courseid\E(?:\/)*(\w+)*\_st$/) {
- my $section=$1;
- if ($key eq $courseid.'_st') { $section=''; }
- my ($dummy,$end,$start)=split(/\_/,&unescape($value));
- my $now=time;
- my $notactive=0;
- if ($start) {
- if ($now<$start) { $notactive=1; }
- }
- if ($end) {
- if ($now>$end) { $notactive=1; }
- }
- unless ($notactive) {
- return &do_cache(\%usectioncache,$hashid,$section,'usection');
- }
- }
- }
- return &do_cache(\%usectioncache,$hashid,'-1','usection');
-}
-
# ------------------------------------- Read an entry from a user's environment
sub userenvironment {
@@ -1284,6 +1335,9 @@ sub clean_filename {
$fname=~s/\s+/\_/g;
# Replace all other weird characters by nothing
$fname=~s/[^\w\.\-]//g;
+# Replace all .\d. sequences with _\d. so they no longer look like version
+# numbers
+ $fname=~s/\.(\d+)(?=\.)/_$1/g;
return $fname;
}
@@ -1819,6 +1873,7 @@ sub devalidate {
# - the student level sheet of this user in course's homespace
# - the assessment level sheet for this resource
# for this user in user's homespace
+ # - current conditional state info
my $key=$uname.':'.$udom.':';
my $status=
&del('nohist_calculatedsheets',
@@ -1833,6 +1888,7 @@ sub devalidate {
$uname.' at '.$udom.' for '.
$symb.': '.$status);
}
+ &delenv('user.state.'.$cid);
}
}
@@ -2720,7 +2776,9 @@ sub allowed {
$uri=&deversion($uri);
my $orguri=$uri;
$uri=&declutter($uri);
-
+
+
+
if (defined($ENV{'allowed.'.$priv})) { return $ENV{'allowed.'.$priv}; }
# Free bre access to adm and meta resources
if (((($uri=~/^adm\//) && ($uri !~ m|/bulletinboard$|))
@@ -2728,6 +2786,13 @@ sub allowed {
return 'F';
}
+# Free bre access to user's own portfolio contents
+ my ($space,$domain,$name,$dir)=split('/',$uri);
+ if (('uploaded' eq $space) && ($ENV{'user.name'} eq $name) &&
+ ($ENV{'user.domain'} eq $domain) && ('portfolio' eq $dir)) {
+ return 'F';
+ }
+
# Free bre to public access
if ($priv eq 'bre') {
@@ -2993,6 +3058,9 @@ sub allowed {
return '';
}
}
+ if (!defined($ENV{'user.state.'.$ENV{'request.course.id'}})) {
+ &Apache::lonuserstate::evalstate();
+ }
if (&condval($statecond)) {
return '2';
} else {
@@ -3129,8 +3197,10 @@ sub log_query {
sub fetch_enrollment_query {
my ($context,$affiliatesref,$replyref,$dom,$cnum) = @_;
my $homeserver;
+ my $maxtries = 1;
if ($context eq 'automated') {
$homeserver = $perlvar{'lonHostID'};
+ $maxtries = 10; # will wait for up to 2000s for retrieval of classlist data before timeout
} else {
$homeserver = &homeserver($cnum,$dom);
}
@@ -3148,8 +3218,13 @@ sub fetch_enrollment_query {
return 'error: '.$queryid;
}
my $reply = &get_query_reply($queryid);
+ my $tries = 1;
+ while (($reply=~/^timeout/) && ($tries < $maxtries)) {
+ $reply = &get_query_reply($queryid);
+ $tries ++;
+ }
if ( ($reply =~/^timeout/) || ($reply =~/^error/) ) {
- &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$ENV{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum);
+ &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$ENV{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum.' maxtries: '.$maxtries.' tries: '.$tries);
} else {
my @responses = split/:/,$reply;
if ($homeserver eq $perlvar{'lonHostID'}) {
@@ -4045,7 +4120,7 @@ sub EXT {
$section=$ENV{'request.course.sec'};
} else {
if (! defined($usection)) {
- $section=&usection($udom,$uname,$courseid);
+ $section=&getsection($udom,$uname,$courseid);
} else {
$section = $usection;
}
@@ -4234,7 +4309,9 @@ sub metadata {
unless ($filename=~/\.meta$/) { $filename.='.meta'; }
my $metastring;
if ($uri !~ m|^uploaded/|) {
- $metastring=&getfile(&filelocation('',&clutter($filename)));
+ my $file=&filelocation('',&clutter($filename));
+ push(@{$metacache{$uri.'.file'}},$file);
+ $metastring=&getfile($file);
}
my $parser=HTML::LCParser->new(\$metastring);
my $token;
@@ -4599,22 +4676,19 @@ sub deversion {
sub symbread {
my ($thisfn,$donotrecurse)=@_;
- if (defined($ENV{'request.symbread.cached'})) {
- return $ENV{'request.symbread.cached'};
- }
+ my $cache_str='request.symbread.cached.'.$thisfn;
+ if (defined($ENV{$cache_str})) { return $ENV{$cache_str}; }
# no filename provided? try from environment
unless ($thisfn) {
if ($ENV{'request.symb'}) {
- $ENV{'request.symbread.cached'}=&symbclean($ENV{'request.symb'});
- return $ENV{'request.symbread.cached'};
+ return $ENV{$cache_str}=&symbclean($ENV{'request.symb'});
}
$thisfn=$ENV{'request.filename'};
}
# is that filename actually a symb? Verify, clean, and return
if ($thisfn=~/\_\_\_\d+\_\_\_(.*)$/) {
if (&symbverify($thisfn,$1)) {
- $ENV{'request.symbread.cached'}=&symbclean($thisfn);
- return $ENV{'request.symbread.cached'};
+ return $ENV{$cache_str}=&symbclean($thisfn);
}
}
$thisfn=declutter($thisfn);
@@ -4636,8 +4710,7 @@ sub symbread {
unless ($syval=~/\_\d+$/) {
unless ($ENV{'form.request.prefix'}=~/\.(\d+)\_$/) {
&appenv('request.ambiguous' => $thisfn);
- $ENV{'request.symbread.cached'}='';
- return '';
+ return $ENV{$cache_str}='';
}
$syval.=$1;
}
@@ -4684,13 +4757,11 @@ sub symbread {
}
}
if ($syval) {
- $ENV{'request.symbread.cached'}=&symbclean($syval.'___'.$thisfn);
- return $ENV{'request.symbread.cached'};
+ return $ENV{$cache_str}=&symbclean($syval.'___'.$thisfn);
}
}
&appenv('request.ambiguous' => $thisfn);
- $ENV{'request.symbread.cached'}='';
- return '';
+ return $ENV{$cache_str}='';
}
# ---------------------------------------------------------- Return random seed
@@ -5025,13 +5096,15 @@ sub repcopy_userfile {
sub tokenwrapper {
my $uri=shift;
- $uri=~s/^http\:\/\/([^\/]+)//;
- $uri=~s/^\///;
+ $uri=~s|^http\://([^/]+)||;
+ $uri=~s|^/||;
$ENV{'user.environment'}=~/\/([^\/]+)\.id/;
my $token=$1;
- if ($uri=~/^uploaded\/([^\/]+)\/([^\/]+)\/([^\/]+)(\?\.*)*$/) {
- &appenv('userfile.'.$1.'/'.$2.'/'.$3 => $ENV{'request.course.id'});
- return 'http://'.$hostname{ &homeserver($2,$1)}.'/'.$uri.
+ my (undef,$udom,$uname,$file)=split('/',$uri,4);
+ if ($udom && $uname && $file) {
+ $file=~s|(\?\.*)*$||;
+ &appenv("userfile.$udom/$uname/$file" => $ENV{'request.course.id'});
+ return 'http://'.$hostname{ &homeserver($uname,$udom)}.'/'.$uri.
(($uri=~/\?/)?'&':'?').'token='.$token.
'&tokenissued='.$perlvar{'lonHostID'};
} else {
@@ -5205,7 +5278,7 @@ sub goodbye {
&logthis(sprintf("%-20s is %s",'%courseresdatacache',scalar(%courseresdatacache)));
#1.1 only
&logthis(sprintf("%-20s is %s",'%userresdatacache',scalar(%userresdatacache)));
- &logthis(sprintf("%-20s is %s",'%usectioncache',scalar(%usectioncache)));
+ &logthis(sprintf("%-20s is %s",'%getsectioncache',scalar(%getsectioncache)));
&logthis(sprintf("%-20s is %s",'%courseresversioncache',scalar(%courseresversioncache)));
&logthis(sprintf("%-20s is %s",'%resversioncache',scalar(%resversioncache)));
&flushcourselogs();
@@ -5587,8 +5660,8 @@ X
B: get user privileges
=item *
-X
-B: finds the section of student in the
+X
+B: finds the section of student in the
course $cname, return section name/number or '' for "not in course"
and '-1' for "no section"