--- loncom/lonnet/perl/lonnet.pm 2004/08/25 16:03:17 1.532 +++ loncom/lonnet/perl/lonnet.pm 2005/03/03 07:45:01 1.604 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.532 2004/08/25 16:03:17 albertel Exp $ +# $Id: lonnet.pm,v 1.604 2005/03/03 07:45:01 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -35,23 +35,24 @@ use HTTP::Headers; use HTTP::Date; # use Date::Parse; use vars -qw(%perlvar %hostname %homecache %badServerCache %hostip %iphost %spareid %hostdom - %libserv %pr %prp %metacache %packagetab %titlecache %courseresversioncache %resversioncache +qw(%perlvar %hostname %badServerCache %iphost %spareid %hostdom + %libserv %pr %prp $memcache %packagetab %courselogs %accesshash %userrolehash $processmarker $dumpcount - %coursedombuf %coursenumbuf %coursehombuf %coursedescrbuf %courseinstcodebuf %courseresdatacache - %userresdatacache %usectioncache %domaindescription %domain_auth_def %domain_auth_arg_def - %domain_lang_def %domain_city %domain_longi %domain_lati $tmpdir); + %coursedombuf %coursenumbuf %coursehombuf %coursedescrbuf %courseinstcodebuf %courseownerbuf + %domaindescription %domain_auth_def %domain_auth_arg_def + %domain_lang_def %domain_city %domain_longi %domain_lati $tmpdir $_64bit); use IO::Socket; use GDBM_File; use Apache::Constants qw(:common :http); use HTML::LCParser; use Fcntl qw(:flock); -use Apache::loncoursedata; use Apache::lonlocal; -use Storable qw(lock_store lock_nstore lock_retrieve freeze thaw); -use Time::HiRes(); +use Storable qw(lock_store lock_nstore lock_retrieve freeze thaw nfreeze); +use Time::HiRes qw( gettimeofday tv_interval ); +use Cache::Memcached; 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; } @@ -131,22 +158,6 @@ sub reply { my ($cmd,$server)=@_; unless (defined($hostname{$server})) { return 'no_such_host'; } my $answer=subreply($cmd,$server); - if ($answer eq 'con_lost') { - #sleep 5; - #$answer=subreply($cmd,$server); - #if ($answer eq 'con_lost') { - # &logthis("Second attempt con_lost on $server"); - # my $peerfile="$perlvar{'lonSockDir'}/$server"; - # my $client=IO::Socket::UNIX->new(Peer =>"$peerfile", - # Type => SOCK_STREAM, - # Timeout => 10) - # or return "con_lost"; - # &logthis("Killing socket"); - # print $client "close_connection_exit\n"; - #sleep 5; - # $answer=subreply($cmd,$server); - #} - } if (($answer=~/^refused/) || ($answer=~/^rejected/)) { &logthis("WARNING:". " $cmd to $server returned $answer"); @@ -194,11 +205,8 @@ sub critical { } my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { - my $pingreply=reply('ping',$server); &reconlonc("$perlvar{'lonSockDir'}/$server"); - my $pongreply=reply('pong',$server); - &logthis("Ping/Pong for $server: $pingreply/$pongreply"); - $answer=reply($cmd,$server); + my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { my $now=time; my $middlename=$cmd; @@ -434,7 +442,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 ''; } @@ -551,12 +559,12 @@ sub authenticate { # ---------------------- Find the homebase for a user from domain's lib servers +my %homecache; sub homeserver { my ($uname,$udom,$ignoreBadCache)=@_; my $index="$uname:$udom"; - my ($result,$cached)=&is_cached(\%homecache,$index,'home',86400); - if (defined($cached)) { return $result; } + if (exists($homecache{$index})) { return $homecache{$index}; } my $tryserver; foreach $tryserver (keys %libserv) { next if ($ignoreBadCache ne 'true' && @@ -564,7 +572,7 @@ sub homeserver { if ($hostdom{$tryserver} eq $udom) { my $answer=reply("home:$udom:$uname",$tryserver); if ($answer eq 'found') { - return &do_cache(\%homecache,$index,$tryserver,'home'); + return $homecache{$index}=$tryserver; } elsif ($answer eq 'no_host') { $badServerCache{$tryserver}=1; } @@ -769,8 +777,14 @@ sub validate_access_key { sub getsection { my ($udom,$unam,$courseid)=@_; + my $cachetime=1800; $courseid=~s/\_/\//g; $courseid=~s/^(\w)/\/$1/; + + my $hashid="$udom:$unam:$courseid"; + my ($result,$cached)=&is_cached_new('getsection',$hashid); + if (defined($cached)) { return $result; } + my %Pending; my %Expired; # @@ -795,200 +809,113 @@ 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_new('getsection',$hashid,$section,$cachetime); } # # 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_new('getsection',$hashid,$Pending{$time},$cachetime); } if (scalar(keys(%Expired))) { my @sorted = sort {$a <=> $b} keys(%Expired); my $time = pop(@sorted); - return $Expired{$time}; + return &do_cache_new('getsection',$hashid,$Expired{$time},$cachetime); } - return '-1'; + return &do_cache_new('getsection',$hashid,'-1',$cachetime); } - -my $disk_caching_disabled=1; - -sub devalidate_cache { - my ($cache,$id,$name) = @_; - delete $$cache{$id.'.time'}; - delete $$cache{$id}; - if ($disk_caching_disabled) { return; } - my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db"; - open(DB,"$filename.lock"); - flock(DB,LOCK_EX); - my %hash; - if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) { - eval <<'EVALBLOCK'; - delete($hash{$id}); - delete($hash{$id.'.time'}); -EVALBLOCK - if ($@) { - &logthis("devalidate_cache blew up :$@:$name"); - unlink($filename); - } - } else { - if (-e $filename) { - &logthis("Unable to tie hash (devalidate cache): $name"); - unlink($filename); - } - } - untie(%hash); - flock(DB,LOCK_UN); - close(DB); -} - -sub is_cached { - my ($cache,$id,$name,$time) = @_; - if (!$time) { $time=300; } - if (!exists($$cache{$id.'.time'})) { - &load_cache_item($cache,$name,$id); - } - 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); - } - } - return ($$cache{$id},1); -} - -sub do_cache { - my ($cache,$id,$value,$name) = @_; - $$cache{$id.'.time'}=time; - $$cache{$id}=$value; -# &logthis("Caching $id as :$value:"); - &save_cache_item($cache,$name,$id); - # do_cache implictly return the set value - $$cache{$id}; -} - -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}}); -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); - } - } - untie(%hash); - flock(DB,LOCK_UN); - close(DB); -# &logthis("save_cache_item $name took ".(&Time::HiRes::time()-$starttime)); -} - -sub load_cache_item { - my ($cache,$name,$id)=@_; - 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"); - flock(DB,LOCK_SH); - if (tie(%hash,'GDBM_File',$filename,&GDBM_READER(),0640)) { - eval <<'EVALBLOCK'; - if (!%$cache) { - my $count; - while (my ($key,$value)=each(%hash)) { - $count++; - if ($key =~ /\.time$/) { - $$cache{$key}=$value; - } else { - my $hashref=thaw($value); - $$cache{$key}=$hashref->{'item'}; - } - } -# &logthis("Initial load: $count"); - } else { - my $hashref=thaw($hash{$id}); - $$cache{$id}=$hashref->{'item'}; - $$cache{$id.'.time'}=$hash{$id.'.time'}; - } -EVALBLOCK - if ($@) { - &logthis("load_cache blew up :$@:$name"); - unlink($filename); - } - } else { - if (-e $filename) { - &logthis("Unable to tie hash (load cache item): $name ($!)"); - unlink($filename); - } - } - untie(%hash); - flock(DB,LOCK_UN); - close(DB); -# &logthis("After Loading $name size is ".scalar(%$cache)); -# &logthis("load_cache_item $name took ".(&Time::HiRes::time()-$starttime)); +sub save_cache { + &purge_remembered(); } -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'); - } - } +my $to_remember=-1; +my %remembered; +my %accessed; +my $kicks=0; +my $hits=0; +sub devalidate_cache_new { + my ($name,$id,$debug) = @_; + if ($debug) { &Apache::lonnet::logthis("deleting $name:$id"); } + $id=&escape($name.':'.$id); + $memcache->delete($id); + delete($remembered{$id}); + delete($accessed{$id}); +} + +sub is_cached_new { + my ($name,$id,$debug) = @_; + $id=&escape($name.':'.$id); + if (exists($remembered{$id})) { + if ($debug) { &Apache::lonnet::logthis("Earyl return $id of $remembered{$id} "); } + $accessed{$id}=[&gettimeofday()]; + $hits++; + return ($remembered{$id},1); + } + my $value = $memcache->get($id); + if (!(defined($value))) { + if ($debug) { &Apache::lonnet::logthis("getting $id is not defined"); } + return (undef,undef); } - return &do_cache(\%usectioncache,$hashid,'-1','usection'); + if ($value eq '__undef__') { + if ($debug) { &Apache::lonnet::logthis("getting $id is __undef__"); } + $value=undef; + } + &make_room($id,$value,$debug); + if ($debug) { &Apache::lonnet::logthis("getting $id is $value"); } + return ($value,1); +} + +sub do_cache_new { + my ($name,$id,$value,$time,$debug) = @_; + $id=&escape($name.':'.$id); + my $setvalue=$value; + if (!defined($setvalue)) { + $setvalue='__undef__'; + } + if ($debug) { &Apache::lonnet::logthis("Setting $id to $value"); } + $memcache->set($id,$setvalue,$time); + # need to make a copy of $value + #&make_room($id,$value,$debug); + return $value; +} + +sub make_room { + my ($id,$value,$debug)=@_; + $remembered{$id}=$value; + if ($to_remember<0) { return; } + $accessed{$id}=[&gettimeofday()]; + if (scalar(keys(%remembered)) <= $to_remember) { return; } + my $to_kick; + my $max_time=0; + foreach my $other (keys(%accessed)) { + if (&tv_interval($accessed{$other}) > $max_time) { + $to_kick=$other; + $max_time=&tv_interval($accessed{$other}); + } + } + delete($remembered{$to_kick}); + delete($accessed{$to_kick}); + $kicks++; + if ($debug) { &logthis("kicking $to_kick $max_time $kicks\n"); } + return; +} + +sub purge_remembered { + #&logthis("Tossing ".scalar(keys(%remembered))); + #&logthis(sprintf("%-20s is %s",'%remembered',length(&freeze(\%remembered)))); + undef(%remembered); + undef(%accessed); } - # ------------------------------------- Read an entry from a user's environment sub userenvironment { @@ -1026,7 +953,7 @@ sub getversion { sub currentversion { my $fname=shift; - my ($result,$cached)=&is_cached(\%resversioncache,$fname,'resversion',600); + my ($result,$cached)=&is_cached_new('resversion',$fname); if (defined($cached)) { return $result; } my $author=$fname; $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; @@ -1039,7 +966,7 @@ sub currentversion { if (($answer eq 'con_lost') || ($answer eq 'rejected')) { return -1; } - return &do_cache(\%resversioncache,$fname,$answer,'resversion'); + return &do_cache_new('resversion',$fname,$answer,600); } # ----------------------------- Subscribe to a resource, return URL if possible @@ -1067,22 +994,27 @@ sub subscribe { sub repcopy { my $filename=shift; $filename=~s/\/+/\//g; - if ($filename=~/^\/home\/httpd\/html\/adm\//) { return OK; } + if ($filename=~m|^/home/httpd/html/adm/|) { return 'OK'; } + if ($filename=~m|^/home/httpd/html/lonUsers/|) { return 'OK'; } + if ($filename=~m|^/home/httpd/html/userfiles/| or + $filename=~m|^/*uploaded/|) { + return &repcopy_userfile($filename); + } $filename=~s/[\n\r]//g; my $transname="$filename.in.transfer"; - if ((-e $filename) || (-e $transname)) { return OK; } + if ((-e $filename) || (-e $transname)) { return 'OK'; } my $remoteurl=subscribe($filename); if ($remoteurl =~ /^con_lost by/) { &logthis("Subscribe returned $remoteurl: $filename"); - return HTTP_SERVICE_UNAVAILABLE; + return 'HTTP_SERVICE_UNAVAILABLE'; } elsif ($remoteurl eq 'not_found') { #&logthis("Subscribe returned not_found: $filename"); - return HTTP_NOT_FOUND; + return 'HTTP_NOT_FOUND'; } elsif ($remoteurl =~ /^rejected by/) { &logthis("Subscribe returned $remoteurl: $filename"); - return FORBIDDEN; + return 'FORBIDDEN'; } elsif ($remoteurl eq 'directory') { - return OK; + return 'OK'; } else { my $author=$filename; $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; @@ -1093,7 +1025,7 @@ sub repcopy { my $path="/$parts[1]/$parts[2]/$parts[3]/$parts[4]"; if ($path ne "$perlvar{'lonDocRoot'}/res") { &logthis("Malconfiguration for replication: $filename"); - return HTTP_BAD_REQUEST; + return 'HTTP_BAD_REQUEST'; } my $count; for ($count=5;$count<$#parts;$count++) { @@ -1110,7 +1042,7 @@ sub repcopy { my $message=$response->status_line; &logthis("WARNING:" ." LWP get: $message: $filename"); - return HTTP_SERVICE_UNAVAILABLE; + return 'HTTP_SERVICE_UNAVAILABLE'; } else { if ($remoteurl!~/\.meta$/) { my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta'); @@ -1122,7 +1054,7 @@ sub repcopy { } } rename($transname,$filename); - return OK; + return 'OK'; } } } @@ -1133,8 +1065,7 @@ sub ssi_body { my ($filelink,%form)=@_; my $output=($filelink=~/^http\:/?&externalssi($filelink): &ssi($filelink,%form)); - $output=~ - s/\/\/ BEGIN LON\-CAPA Internal.+\/\/ END LON\-CAPA Internal\s//gs; + $output=~s|//(\s*)?\s||gs; $output=~s/^.*?\]*\>//si; $output=~s/(.*)\<\/body\s*\>.*?$/$1/si; return $output; @@ -1279,6 +1210,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; } @@ -1349,13 +1283,13 @@ sub finishuserfileupload { } # Save the file { - #&Apache::lonnet::logthis("Saving to $filepath $file"); - open(my $fh,'>'.$filepath.'/'.$file); - print $fh $ENV{'form.'.$formname}; - close($fh); + open(FH,'>'.$filepath.'/'.$file); + print FH $ENV{'form.'.$formname}; + close(FH); } # Notify homeserver to grep it # + &Apache::lonnet::logthis("fetching ".$path.$file); my $fetchresult= &reply('fetchuserfile:'.$path.$file,$docuhome); if ($fetchresult eq 'ok') { # @@ -1433,12 +1367,12 @@ sub flushcourselogs { if ($courseidbuffer{$coursehombuf{$crsid}}) { $courseidbuffer{$coursehombuf{$crsid}}.='&'. &escape($crsid).'='.&escape($coursedescrbuf{$crsid}). - '='.&escape($courseinstcodebuf{$crsid}); + ':'.&escape($courseinstcodebuf{$crsid}).':'.&escape($courseownerbuf{$crsid}); } else { $courseidbuffer{$coursehombuf{$crsid}}= &escape($crsid).'='.&escape($coursedescrbuf{$crsid}). - '='.&escape($courseinstcodebuf{$crsid}); - } + ':'.&escape($courseinstcodebuf{$crsid}).':'.&escape($courseownerbuf{$crsid}); + } } # # Write course id database (reverse lookup) to homeserver of courses @@ -1513,6 +1447,8 @@ sub courselog { $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; $courseinstcodebuf{$ENV{'request.course.id'}}= $ENV{'course.'.$ENV{'request.course.id'}.'.internal.coursecode'}; + $courseownerbuf{$ENV{'request.course.id'}}= + $ENV{'course.'.$ENV{'request.course.id'}.'.internal.courseowner'}; if (defined $courselogs{$ENV{'request.course.id'}}) { $courselogs{$ENV{'request.course.id'}}.='&'.$what; } else { @@ -1529,11 +1465,23 @@ sub courseacclog { my $what=$fnsymb.':'.$ENV{'user.name'}.':'.$ENV{'user.domain'}; if ($fnsymb=~/(problem|exam|quiz|assess|survey|form|page)$/) { $what.=':POST'; + # FIXME: Probably ought to escape things.... foreach (keys %ENV) { if ($_=~/^form\.(.*)/) { $what.=':'.$1.'='.$ENV{$_}; } } + } elsif ($fnsymb =~ m:^/adm/searchcat:) { + # FIXME: We should not be depending on a form parameter that someone + # editing lonsearchcat.pm might change in the future. + if ($ENV{'form.phase'} eq 'course_search') { + $what.= ':POST'; + # FIXME: Probably ought to escape things.... + foreach my $element ('courseexp','crsfulltext','crsrelated', + 'crsdiscuss') { + $what.=':'.$element.'='.$ENV{'form.'.$element}; + } + } } &courselog($what); } @@ -1585,6 +1533,7 @@ sub get_course_adv_roles { if (($tend) && ($tend<$now)) { next; } if (($tstart) && ($now<$tstart)) { next; } my ($role,$username,$domain,$section)=split(/\:/,$_); + if ($username eq '' || $domain eq '') { next; } if ((&privileged($username,$domain)) && (!$nothide{$username.':'.$domain})) { next; } my $key=&plaintext($role); @@ -1656,7 +1605,7 @@ sub courseidput { } sub courseiddump { - my ($domfilter,$descfilter,$sincefilter,$hostidflag,$hostidref)=@_; + my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,$hostidflag,$hostidref)=@_; my %returnhash=(); unless ($domfilter) { $domfilter=''; } foreach my $tryserver (keys %libserv) { @@ -1664,7 +1613,8 @@ sub courseiddump { if ((!$domfilter) || ($hostdom{$tryserver} eq $domfilter)) { foreach ( split(/\&/,&reply('courseiddump:'.$hostdom{$tryserver}.':'. - $sincefilter.':'.&escape($descfilter), + $sincefilter.':'.&escape($descfilter).':'. + &escape($instcodefilter).':'.&escape($ownerfilter), $tryserver))) { my ($key,$value)=split(/\=/,$_); if (($key) && ($value)) { @@ -1685,19 +1635,27 @@ sub get_first_access { my ($symb,$courseid,$udom,$uname)=&Apache::lonxml::whichuser(); if ($argsymb) { $symb=$argsymb; } my ($map,$id,$res)=&decode_symb($symb); - if ($type eq 'map') { $res=$map; } - my %times=&get('firstaccesstimes',[$res],$udom,$uname); - return $times{$res}; + if ($type eq 'map') { + $res=&symbread($map); + } else { + $res=$symb; + } + my %times=&get('firstaccesstimes',["$courseid\0$res"],$udom,$uname); + return $times{"$courseid\0$res"}; } sub set_first_access { my ($type)=@_; my ($symb,$courseid,$udom,$uname)=&Apache::lonxml::whichuser(); my ($map,$id,$res)=&decode_symb($symb); - if ($type eq 'map') { $res=$map; } - my $firstaccess=&get_first_access($type); + if ($type eq 'map') { + $res=&symbread($map); + } else { + $res=$symb; + } + my $firstaccess=&get_first_access($type,$symb); if (!$firstaccess) { - return &put('firstaccesstimes',{$res=>time},$udom,$uname); + return &put('firstaccesstimes',{"$courseid\0$res"=>time},$udom,$uname); } return 'already_set'; } @@ -1755,7 +1713,7 @@ sub checkin { my $now=time; my ($ta,$tb,$lonhost)=split(/\*/,$token); $lonhost=~tr/A-Z/a-z/; - my $dtoken=$ta.'_'.$hostip{$lonhost}.'_'.$tb; + my $dtoken=$ta.'_'.$hostname{$lonhost}.'_'.$tb; $dtoken=~s/\W/\_/g; my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)= split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost))); @@ -1814,6 +1772,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', @@ -1828,6 +1787,7 @@ sub devalidate { $uname.' at '.$udom.' for '. $symb.': '.$status); } + &delenv('user.state.'.$cid); } } @@ -2036,9 +1996,11 @@ sub tmpreset { $namespace=~s/\//\_/g; $namespace=~s/\W//g; - #FIXME needs to do something for /pub resources if (!$domain) { $domain=$ENV{'user.domain'}; } if (!$stuname) { $stuname=$ENV{'user.name'}; } + if ($domain eq 'public' && $stuname eq 'public') { + $stuname=$ENV{'REMOTE_ADDR'}; + } my $path=$perlvar{'lonDaemons'}.'/tmp'; my %hash; if (tie(%hash,'GDBM_File', @@ -2071,9 +2033,11 @@ sub tmpstore { } $namespace=~s/\//\_/g; $namespace=~s/\W//g; -#FIXME needs to do something for /pub resources if (!$domain) { $domain=$ENV{'user.domain'}; } if (!$stuname) { $stuname=$ENV{'user.name'}; } + if ($domain eq 'public' && $stuname eq 'public') { + $stuname=$ENV{'REMOTE_ADDR'}; + } my $now=time; my %hash; my $path=$perlvar{'lonDaemons'}.'/tmp'; @@ -2085,7 +2049,7 @@ sub tmpstore { my $allkeys=''; foreach my $key (keys(%$storehash)) { $allkeys.=$key.':'; - $hash{"$version:$symb:$key"}=$$storehash{$key}; + $hash{"$version:$symb:$key"}=&freeze_escape($$storehash{$key}); } $hash{"$version:$symb:timestamp"}=$now; $allkeys.='timestamp'; @@ -2112,10 +2076,12 @@ sub tmprestore { $symb=escape($symb); if (!$namespace) { $namespace=$ENV{'request.state'}; } - #FIXME needs to do something for /pub resources + if (!$domain) { $domain=$ENV{'user.domain'}; } if (!$stuname) { $stuname=$ENV{'user.name'}; } - + if ($domain eq 'public' && $stuname eq 'public') { + $stuname=$ENV{'REMOTE_ADDR'}; + } my %returnhash; $namespace=~s/\//\_/g; $namespace=~s/\W//g; @@ -2133,8 +2099,8 @@ sub tmprestore { my $key; $returnhash{"$scope:keys"}=$vkeys; foreach $key (@keys) { - $returnhash{"$scope:$key"}=$hash{"$scope:$symb:$key"}; - $returnhash{"$key"}=$hash{"$scope:$symb:$key"}; + $returnhash{"$scope:$key"}=&thaw_unescape($hash{"$scope:$symb:$key"}); + $returnhash{"$key"}=&thaw_unescape($hash{"$scope:$symb:$key"}); } } if (!(untie(%hash))) { @@ -2175,7 +2141,7 @@ sub store { my $namevalue=''; foreach (keys %$storehash) { - $namevalue.=escape($_).'='.escape($$storehash{$_}).'&'; + $namevalue.=&escape($_).'='.&freeze_escape($$storehash{$_}).'&'; } $namevalue=~s/\&$//; &courselog($symb.':'.$stuname.':'.$domain.':STORE:'.$namevalue); @@ -2211,7 +2177,7 @@ sub cstore { my $namevalue=''; foreach (keys %$storehash) { - $namevalue.=escape($_).'='.escape($$storehash{$_}).'&'; + $namevalue.=&escape($_).'='.&freeze_escape($$storehash{$_}).'&'; } $namevalue=~s/\&$//; &courselog($symb.':'.$stuname.':'.$domain.':CSTORE:'.$namevalue); @@ -2245,7 +2211,7 @@ sub restore { my %returnhash=(); foreach (split(/\&/,$answer)) { my ($name,$value)=split(/\=/,$_); - $returnhash{&unescape($name)}=&unescape($value); + $returnhash{&unescape($name)}=&thaw_unescape($value); } my $version; for ($version=1;$version<=$returnhash{'version'};$version++) { @@ -2301,7 +2267,7 @@ sub privileged { my $now=time; if ($rolesdump ne '') { foreach (split(/&/,$rolesdump)) { - if ($_!~/^rolesdef\&/) { + if ($_!~/^rolesdef_/) { my ($area,$role)=split(/=/,$_); $area=~s/\_\w\w$//; my ($trole,$tend,$tstart)=split(/_/,$role); @@ -2328,105 +2294,37 @@ sub rolesinit { my $rolesdump=reply("dump:$domain:$username:roles",$authhost); if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return ''; } my %allroles=(); - my %thesepriv=(); my $now=time; my $userroles="user.login.time=$now\n"; - my $thesestr; if ($rolesdump ne '') { foreach (split(/&/,$rolesdump)) { - if ($_!~/^rolesdef\&/) { + if ($_!~/^rolesdef_/) { my ($area,$role)=split(/=/,$_); - $area=~s/\_\w\w$//; - my ($trole,$tend,$tstart)=split(/_/,$role); - $userroles.='user.role.'.$trole.'.'.$area.'='. - $tstart.'.'.$tend."\n"; -# log the associated role with the area - &userrolelog($trole,$username,$domain,$area,$tstart,$tend); - if ($tend!=0) { - if ($tend<$now) { - $trole=''; - } - } - if ($tstart!=0) { - if ($tstart>$now) { - $trole=''; - } - } + $area=~s/\_\w\w$//; + + my ($trole,$tend,$tstart); + if ($role=~/^cr/) { + ($trole,my $trest)=($role=~m|^(cr/\w+/\w+/[a-zA-Z0-9]+)_(.*)$|); + ($tend,$tstart)=split('_',$trest); + } else { + ($trole,$tend,$tstart)=split(/_/,$role); + } + $userroles.=&set_arearole($trole,$area,$tstart,$tend,$domain,$username); + if (($tend!=0) && ($tend<$now)) { $trole=''; } + if (($tstart!=0) && ($tstart>$now)) { $trole=''; } if (($area ne '') && ($trole ne '')) { my $spec=$trole.'.'.$area; my ($tdummy,$tdomain,$trest)=split(/\//,$area); if ($trole =~ /^cr\//) { - my ($rdummy,$rdomain,$rauthor,$rrole)=split(/\//,$trole); - my $homsvr=homeserver($rauthor,$rdomain); - if ($hostname{$homsvr} ne '') { - my ($rdummy,$roledef)= - &get('roles',["rolesdef_$rrole"],$rdomain,$rauthor); - - if (($rdummy ne 'con_lost') && ($roledef ne '')) { - my ($syspriv,$dompriv,$coursepriv)= - split(/\_/,$roledef); - if (defined($syspriv)) { - $allroles{'cm./'}.=':'.$syspriv; - $allroles{$spec.'./'}.=':'.$syspriv; - } - if ($tdomain ne '') { - if (defined($dompriv)) { - $allroles{'cm./'.$tdomain.'/'}.=':'.$dompriv; - $allroles{$spec.'./'.$tdomain.'/'}.=':'.$dompriv; - } - if ($trest ne '') { - if (defined($coursepriv)) { - $allroles{'cm.'.$area}.=':'.$coursepriv; - $allroles{$spec.'.'.$area}.=':'.$coursepriv; - } - } - } - } - } + &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area); } else { - if (defined($pr{$trole.':s'})) { - $allroles{'cm./'}.=':'.$pr{$trole.':s'}; - $allroles{$spec.'./'}.=':'.$pr{$trole.':s'}; - } - if ($tdomain ne '') { - if (defined($pr{$trole.':d'})) { - $allroles{'cm./'.$tdomain.'/'}.=':'.$pr{$trole.':d'}; - $allroles{$spec.'./'.$tdomain.'/'}.=':'.$pr{$trole.':d'}; - } - if ($trest ne '') { - if (defined($pr{$trole.':c'})) { - $allroles{'cm.'.$area}.=':'.$pr{$trole.':c'}; - $allroles{$spec.'.'.$area}.=':'.$pr{$trole.':c'}; - } - } - } + &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); } } } } - my $adv=0; - my $author=0; - foreach (keys %allroles) { - %thesepriv=(); - if (($_!~/^st/) && ($_!~/^ta/) && ($_!~/^cm/)) { $adv=1; } - if (($_=~/^au/) || ($_=~/^ca/)) { $author=1; } - foreach (split(/:/,$allroles{$_})) { - if ($_ ne '') { - my ($privilege,$restrictions)=split(/&/,$_); - if ($restrictions eq '') { - $thesepriv{$privilege}='F'; - } else { - if ($thesepriv{$privilege} ne 'F') { - $thesepriv{$privilege}.=$restrictions; - } - } - } - } - $thesestr=''; - foreach (keys %thesepriv) { $thesestr.=':'.$_.'&'.$thesepriv{$_}; } - $userroles.='user.priv.'.$_.'='.$thesestr."\n"; - } + my ($author,$adv) = &set_userprivs(\$userroles,\%allroles); $userroles.='user.adv='.$adv."\n". 'user.author='.$author."\n"; $ENV{'user.adv'}=$adv; @@ -2434,6 +2332,84 @@ sub rolesinit { return $userroles; } +sub set_arearole { + my ($trole,$area,$tstart,$tend,$domain,$username) = @_; +# log the associated role with the area + &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + return 'user.role.'.$trole.'.'.$area.'='.$tstart.'.'.$tend."\n"; +} + +sub custom_roleprivs { + my ($allroles,$trole,$tdomain,$trest,$spec,$area) = @_; + my ($rdummy,$rdomain,$rauthor,$rrole)=split(/\//,$trole); + my $homsvr=homeserver($rauthor,$rdomain); + if ($hostname{$homsvr} ne '') { + my ($rdummy,$roledef)= + &get('roles',["rolesdef_$rrole"],$rdomain,$rauthor); + if (($rdummy ne 'con_lost') && ($roledef ne '')) { + my ($syspriv,$dompriv,$coursepriv)=split(/\_/,$roledef); + if (defined($syspriv)) { + $$allroles{'cm./'}.=':'.$syspriv; + $$allroles{$spec.'./'}.=':'.$syspriv; + } + if ($tdomain ne '') { + if (defined($dompriv)) { + $$allroles{'cm./'.$tdomain.'/'}.=':'.$dompriv; + $$allroles{$spec.'./'.$tdomain.'/'}.=':'.$dompriv; + } + if (($trest ne '') && (defined($coursepriv))) { + $$allroles{'cm.'.$area}.=':'.$coursepriv; + $$allroles{$spec.'.'.$area}.=':'.$coursepriv; + } + } + } + } +} + + +sub standard_roleprivs { + my ($allroles,$trole,$tdomain,$spec,$trest,$area) = @_; + if (defined($pr{$trole.':s'})) { + $$allroles{'cm./'}.=':'.$pr{$trole.':s'}; + $$allroles{$spec.'./'}.=':'.$pr{$trole.':s'}; + } + if ($tdomain ne '') { + if (defined($pr{$trole.':d'})) { + $$allroles{'cm./'.$tdomain.'/'}.=':'.$pr{$trole.':d'}; + $$allroles{$spec.'./'.$tdomain.'/'}.=':'.$pr{$trole.':d'}; + } + if (($trest ne '') && (defined($pr{$trole.':c'}))) { + $$allroles{'cm.'.$area}.=':'.$pr{$trole.':c'}; + $$allroles{$spec.'.'.$area}.=':'.$pr{$trole.':c'}; + } + } +} + +sub set_userprivs { + my ($userroles,$allroles) = @_; + my $author=0; + my $adv=0; + foreach (keys %{$allroles}) { + my %thesepriv=(); + if (($_=~/^au/) || ($_=~/^ca/)) { $author=1; } + foreach (split(/:/,$$allroles{$_})) { + if ($_ ne '') { + my ($privilege,$restrictions)=split(/&/,$_); + if ($restrictions eq '') { + $thesepriv{$privilege}='F'; + } elsif ($thesepriv{$privilege} ne 'F') { + $thesepriv{$privilege}.=$restrictions; + } + if ($thesepriv{'adv'} eq 'F') { $adv=1; } + } + } + my $thesestr=''; + foreach (keys %thesepriv) { $thesestr.=':'.$_.'&'.$thesepriv{$_}; } + $$userroles.='user.priv.'.$_.'='.$thesestr."\n"; + } + return ($author,$adv); +} + # --------------------------------------------------------------- get interface sub get { @@ -2455,7 +2431,7 @@ sub get { my %returnhash=(); my $i=0; foreach (@$storearr) { - $returnhash{$_}=unescape($pairs[$i]); + $returnhash{$_}=&thaw_unescape($pairs[$i]); $i++; } return %returnhash; @@ -2494,7 +2470,7 @@ sub dump { my %returnhash=(); foreach (@pairs) { my ($key,$value)=split(/=/,$_); - $returnhash{unescape($key)}=unescape($value); + $returnhash{unescape($key)}=&thaw_unescape($value); } return %returnhash; } @@ -2540,7 +2516,7 @@ sub currentdump { my ($key,$value)=split(/=/,$_); my ($symb,$param) = split(/:/,$key); $returnhash{&unescape($symb)}->{&unescape($param)} = - &unescape($value); + &thaw_unescape($value); } } return %returnhash; @@ -2606,7 +2582,7 @@ sub put { my $uhome=&homeserver($uname,$udomain); my $items=''; foreach (keys %$storehash) { - $items.=&escape($_).'='.&escape($$storehash{$_}).'&'; + $items.=&escape($_).'='.&freeze_escape($$storehash{$_}).'&'; } $items=~s/\&$//; return &reply("put:$udomain:$uname:$namespace:$items",$uhome); @@ -2626,7 +2602,7 @@ sub putstore { my $key = $1.':keys:'.$2; $allitems{$key} .= $3.':'; } - $items.=$_.'='.&escape($$storehash{$_}).'&'; + $items.=$_.'='.&freeze_escape($$storehash{$_}).'&'; } foreach (keys %allitems) { $allitems{$_} =~ s/\:$//; @@ -2645,7 +2621,7 @@ sub cput { my $uhome=&homeserver($uname,$udomain); my $items=''; foreach (keys %$storehash) { - $items.=escape($_).'='.escape($$storehash{$_}).'&'; + $items.=escape($_).'='.&freeze_escape($$storehash{$_}).'&'; } $items=~s/\&$//; return &critical("put:$udomain:$uname:$namespace:$items",$uhome); @@ -2668,7 +2644,7 @@ sub eget { my %returnhash=(); my $i=0; foreach (@$storearr) { - $returnhash{$_}=unescape($pairs[$i]); + $returnhash{$_}=&thaw_unescape($pairs[$i]); $i++; } return %returnhash; @@ -2711,11 +2687,13 @@ sub customaccess { # ------------------------------------------------- Check for a user privilege sub allowed { - my ($priv,$uri)=@_; + my ($priv,$uri,$symb)=@_; $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$|)) @@ -2723,6 +2701,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') { @@ -2983,7 +2968,7 @@ sub allowed { if ($thisallowed=~/X/) { if ($ENV{'acc.randomout'}) { - my $symb=&symbread($uri,1); + if (!$symb) { $symb=&symbread($uri,1); } if (($symb) && ($ENV{'acc.randomout'}=~/\&\Q$symb\E\&/)) { return ''; } @@ -3124,8 +3109,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); } @@ -3143,8 +3130,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'}) { @@ -3278,11 +3270,18 @@ sub auto_instcode_format { my $courses = ''; my $homeserver; if ($caller eq 'global') { - $homeserver = $perlvar{'lonHostID'}; + foreach my $tryserver (keys %libserv) { + if ($hostdom{$tryserver} eq $codedom) { + $homeserver = $tryserver; + last; + } + } + if (($ENV{'user.name'}) && ($ENV{'user.domain'} eq $codedom)) { + $homeserver = &homeserver($ENV{'user.name'},$codedom); + } } else { $homeserver = &homeserver($caller,$codedom); } - my $host=$hostname{$homeserver}; foreach (keys %{$instcodes}) { $courses .= &escape($_).'='.&escape($$instcodes{$_}).'&'; } @@ -3473,9 +3472,12 @@ sub modifyuser { if (defined($middle)) { $names{'middlename'} = $middle; } if ($last) { $names{'lastname'} = $last; } if (defined($gene)) { $names{'generation'} = $gene; } - if ($email) { $names{'notification'} = $email; - $names{'critnotification'} = $email; } - + if ($email) { + $email=~s/[^\w\@\.\-\,]//gs; + if ($email=~/\@/) { $names{'notification'} = $email; + $names{'critnotification'} = $email; + $names{'permanentemail'} = $email; } + } my $reply = &put('environment', \%names, $udom,$uname); if ($reply ne 'ok') { return 'error: '.$reply; } &logthis('Success modifying user '.$udom.', '.$uname.', '.$uid.', '. @@ -3551,8 +3553,7 @@ sub modify_student_enrollment { $gene = $tmp{'generation'} if (!defined($gene) || $gene eq ''); $uid = $tmp{'id'} if (!defined($uid) || $uid eq ''); } - my $fullname = &Apache::loncoursedata::ProcessFullName($last,$gene, - $first,$middle); + my $fullname = &format_name($first,$middle,$last,$gene,'lastname'); my $reply=cput('classlist', {"$uname:$udom" => join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype) }, @@ -3569,6 +3570,25 @@ sub modify_student_enrollment { return &assignrole($udom,$uname,$uurl,'st',$end,$start); } +sub format_name { + my ($firstname,$middlename,$lastname,$generation,$first)=@_; + my $name; + if ($first ne 'lastname') { + $name=$firstname.' '.$middlename.' '.$lastname.' '.$generation; + } else { + if ($lastname=~/\S/) { + $name.= $lastname.' '.$generation.', '.$firstname.' '.$middlename; + $name=~s/\s+,/,/; + } else { + $name.= $firstname.' '.$middlename.' '.$generation; + } + } + $name=~s/^\s+//; + $name=~s/\s+$//; + $name=~s/\s+/ /g; + return $name; +} + # ------------------------------------------------- Write to course preferences sub writecoursepref { @@ -3591,7 +3611,7 @@ sub writecoursepref { # ---------------------------------------------------------- Make/modify course sub createcourse { - my ($udom,$description,$url,$course_server,$nonstandard,$inst_code)=@_; + my ($udom,$description,$url,$course_server,$nonstandard,$inst_code,$course_owner)=@_; $url=&declutter($url); my $cid=''; unless (&allowed('ccc',$udom)) { @@ -3626,7 +3646,7 @@ sub createcourse { # ----------------------------------------------------------------- Course made # log existence &courseidput($udom,&escape($udom.'_'.$uname).'='.&escape($description). - '='.&escape($inst_code),$uhome); + ':'.&escape($inst_code).':'.&escape($course_owner),$uhome); &flushcourselogs(); # set toplevel url my $topurl=$url; @@ -3679,41 +3699,177 @@ sub revokecustomrole { $deleteflag); } +# ------------------------------------------------------------ Disk usage +sub diskusage { + my ($udom,$uname,$directoryRoot)=@_; + $directoryRoot =~ s/\/$//; + my $listing=&reply('du:'.$directoryRoot,homeserver($uname,$udom)); + return $listing; +} -# ------------------------------------------------------------ Portfolio Director Lister -# returns listing of contents of user's /userfiles/portfolio/ directory -# +sub is_locked { + my ($file_name, $domain, $user) = @_; + my @check; + my $is_locked; + push @check, $file_name; + my %locked = &Apache::lonnet::get('file_permissions',\@check, + $ENV{'user.domain'},$ENV{'user.name'}); + if (ref($locked{$file_name}) eq 'ARRAY') { + $is_locked = 'true'; + } else { + $is_locked = 'false'; + } +} -sub portfoliolist { - my ($currentPath, $currentFile) = @_; - my ($udom, $uname, $portfolioRoot); - $uname=$ENV{'user.name'}; - $udom=$ENV{'user.domain'}; - # really should interrogate the system for home directory information, but . . . - $portfolioRoot = '/home/httpd/lonUsers/'.$udom.'/'; - $uname =~ /^(.?)(.?)(.?)/; - $portfolioRoot = $portfolioRoot.$1.'/'.$2.'/'.$3.'/'.$uname.'/userfiles/portfolio'; - my $listing = &reply('ls:'.$portfolioRoot.$currentPath, &homeserver($uname,$udom)); - return $listing; +# ------------------------------------------------------------- Mark as Read Only + +sub mark_as_readonly { + my ($domain,$user,$files,$what) = @_; + my %current_permissions = &Apache::lonnet::dump('file_permissions',$domain,$user); + foreach my $file (@{$files}) { + push(@{$current_permissions{$file}},$what); + } + &Apache::lonnet::put('file_permissions',\%current_permissions,$domain,$user); + return; } -sub portfoliomanage { +# ------------------------------------------------------------Save Selected Files -#FIXME please user the existing remove userfile function instead and -#add a userfilerename functions. -#FIXME uhome should never be an argument to any lonnet functions - - # handles deleting and renaming files in user's userfiles/portfolio/ directory - # - my ($filename, $fileaction, $filenewname) = @_; - my ($udom, $uname, $uhome); - $uname=$ENV{'user.name'}; - $udom=$ENV{'user.domain'}; - $uhome=$ENV{'user.home'}; - my $listing = reply('portfoliomanage:'.$uname.':'.$udom.':'.$filename.':'.$fileaction.':'.$filenewname, $uhome); - return $listing; +sub save_selected_files { + my ($user, $path, @files) = @_; + my $filename = $user."savedfiles"; + my @other_files = &files_not_in_path($user, $path); + open (OUT, '>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + foreach my $file (@files) { + print (OUT $ENV{'form.currentpath'}.$file."\n"); + } + foreach my $file (@other_files) { + print (OUT $file."\n"); + } + close (OUT); + return 'ok'; } +sub clear_selected_files { + my ($user) = @_; + my $filename = $user."savedfiles"; + open (OUT, '>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + print (OUT undef); + close (OUT); + return ("ok"); +} + +sub files_in_path { + my ($user, $path) = @_; + my $filename = $user."savedfiles"; + my %return_files; + open (IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + while (my $line_in = ) { + chomp ($line_in); + my @paths_and_file = split (m!/!, $line_in); + my $file_part = pop (@paths_and_file); + my $path_part = join ('/', @paths_and_file); + $path_part.='/'; + my $path_and_file = $path_part.$file_part; + if ($path_part eq $path) { + $return_files{$file_part}= 'selected'; + } + } + close (IN); + return (\%return_files); +} + +# called in portfolio select mode, to show files selected NOT in current directory +sub files_not_in_path { + my ($user, $path) = @_; + my $filename = $user."savedfiles"; + my @return_files; + my $path_part; + open (IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + while () { + #ok, I know it's clunky, but I want it to work + my @paths_and_file = split m!/!, $_; + my $file_part = pop (@paths_and_file); + chomp ($file_part); + my $path_part = join ('/', @paths_and_file); + $path_part .= '/'; + my $path_and_file = $path_part.$file_part; + if ($path_part ne $path) { + push (@return_files, ($path_and_file)); + } + } + close (OUT); + return (@return_files); +} + +#--------------------------------------------------------------Get Marked as Read Only + +sub get_marked_as_readonly { + my ($domain,$user,$what) = @_; + my %current_permissions = &Apache::lonnet::dump('file_permissions',$domain,$user); + my @readonly_files; + while (my ($file_name,$value) = each(%current_permissions)) { + if (ref($value) eq "ARRAY"){ + foreach my $stored_what (@{$value}) { + if ($stored_what eq $what) { + push(@readonly_files, $file_name); + } elsif (!defined($what)) { + push(@readonly_files, $file_name); + } + } + } + } + return @readonly_files; +} +#-----------------------------------------------------------Get Marked as Read Only Hash + +sub get_marked_as_readonly_hash { + my ($domain,$user,$what) = @_; + my %current_permissions = &Apache::lonnet::dump('file_permissions',$domain,$user); + my %readonly_files; + while (my ($file_name,$value) = each(%current_permissions)) { + if (ref($value) eq "ARRAY"){ + foreach my $stored_what (@{$value}) { + if ($stored_what eq $what) { + $readonly_files{$file_name} = 'locked'; + } elsif (!defined($what)) { + $readonly_files{$file_name} = 'locked'; + } + } + } + } + return %readonly_files; +} +# ------------------------------------------------------------ Unmark as Read Only + +sub unmark_as_readonly { + # unmarks all files locked by $what + # for portfolio submissions, $what contains $crsid and $symb + my ($domain,$user,$what) = @_; + my %current_permissions = &Apache::lonnet::dump('file_permissions',$domain,$user); + my @readonly_files = &Apache::lonnet::get_marked_as_readonly($domain,$user,$what); + foreach my $file(@readonly_files){ + my $current_locks = $current_permissions{$file}; + my @new_locks; + my @del_keys; + if (ref($current_locks) eq "ARRAY"){ + foreach my $locker (@{$current_locks}) { + unless ($locker eq $what) { + push(@new_locks, $what); + } + } + if (@new_locks > 0) { + $current_permissions{$file} = \@new_locks; + } else { + push(@del_keys, $file); + &Apache::lonnet::del('file_permissions',\@del_keys, $domain, $user); + delete $current_permissions{$file}; + } + } + } + &Apache::lonnet::put('file_permissions',\%current_permissions,$domain,$user); + return; +} # ------------------------------------------------------------ Directory lister @@ -3823,6 +3979,9 @@ sub GetFileTimestamp { sub directcondval { my $number=shift; + if (!defined($ENV{'user.state.'.$ENV{'request.course.id'}})) { + &Apache::lonuserstate::evalstate(); + } if ($ENV{'user.state.'.$ENV{'request.course.id'}}) { return substr($ENV{'user.state.'.$ENV{'request.course.id'}},$number,1); } else { @@ -3876,7 +4035,7 @@ sub condval { sub devalidatecourseresdata { my ($coursenum,$coursedomain)=@_; my $hashid=$coursenum.':'.$coursedomain; - &devalidate_cache(\%courseresdatacache,$hashid,'courseres'); + &devalidate_cache_new('courseres',$hashid); } # --------------------------------------------------- Course Resourcedata Query @@ -3885,18 +4044,18 @@ sub courseresdata { my ($coursenum,$coursedomain,@which)=@_; my $coursehom=&homeserver($coursenum,$coursedomain); my $hashid=$coursenum.':'.$coursedomain; - my ($result,$cached)=&is_cached(\%courseresdatacache,$hashid,'courseres'); + my ($result,$cached)=&is_cached_new('courseres',$hashid); unless (defined($cached)) { my %dumpreply=&dump('resourcedata',$coursedomain,$coursenum); $result=\%dumpreply; my ($tmp) = keys(%dumpreply); if ($tmp !~ /^(con_lost|error|no_such_host)/i) { - &do_cache(\%courseresdatacache,$hashid,$result,'courseres'); + &do_cache_new('courseres',$hashid,$result,600); } elsif ($tmp =~ /^(con_lost|no_such_host)/) { return $tmp; } elsif ($tmp =~ /^(error)/) { $result=undef; - &do_cache(\%courseresdatacache,$hashid,$result,'courseres'); + &do_cache_new('courseres',$hashid,$result,600); } } foreach my $item (@which) { @@ -4048,11 +4207,15 @@ sub EXT { my $section; if (defined($courseid) && $courseid eq $ENV{'request.course.id'}) { + if (!$symbparm) { $symbparm=&symbread(); } + } + my ($courselevelm,$courselevel); + if ($symbparm && defined($courseid) && + $courseid eq $ENV{'request.course.id'}) { #print '
'.$space.' - '.$qualifier.' - '.$spacequalifierrest; # ----------------------------------------------------- Cascading lookup scheme - if (!$symbparm) { $symbparm=&symbread(); } my $symbp=$symbparm; my $mapp=(&decode_symb($symbp))[0]; @@ -4063,31 +4226,30 @@ sub EXT { ($ENV{'user.domain'} eq $udom)) { $section=$ENV{'request.course.sec'}; } else { - if (! defined($usection)) { - $section=&usection($udom,$uname,$courseid); - } else { - $section = $usection; - } + if (! defined($usection)) { + $section=&getsection($udom,$uname,$courseid); + } else { + $section = $usection; + } } my $seclevel=$courseid.'.['.$section.'].'.$spacequalifierrest; my $seclevelr=$courseid.'.['.$section.'].'.$symbparm; my $seclevelm=$courseid.'.['.$section.'].'.$mapparm; - my $courselevel=$courseid.'.'.$spacequalifierrest; + $courselevel=$courseid.'.'.$spacequalifierrest; my $courselevelr=$courseid.'.'.$symbparm; - my $courselevelm=$courseid.'.'.$mapparm; + $courselevelm=$courseid.'.'.$mapparm; # ----------------------------------------------------------- first, check user #most student don\'t have any data set, check if there is some data if (! &EXT_cache_status($udom,$uname)) { my $hashid="$udom:$uname"; - my ($result,$cached)=&is_cached(\%userresdatacache,$hashid, - 'userres'); + my ($result,$cached)=&is_cached_new('userres',$hashid); if (!defined($cached)) { my %resourcedata=&dump('resourcedata',$udom,$uname); $result=\%resourcedata; - &do_cache(\%userresdatacache,$hashid,$result,'userres'); + &do_cache_new('userres',$hashid,$result); } my ($tmp)=keys(%$result); if (($tmp!~/^error\:/) && ($tmp!~/^con_lost/)) { @@ -4105,20 +4267,19 @@ sub EXT { $uname." at ".$udom.": ". $tmp.""); } elsif ($tmp=~/error: 2 /) { - &EXT_cache_set($udom,$uname); + &EXT_cache_set($udom,$uname); } elsif ($tmp =~ /^(con_lost|no_such_host)/) { return $tmp; } } } -# -------------------------------------------------------- second, check course +# ------------------------------------------------ second, check some of course my $coursereply=&courseresdata($ENV{'course.'.$courseid.'.num'}, - $ENV{'course.'.$courseid.'.domain'}, - ($seclevelr,$seclevelm,$seclevel, - $courselevelr,$courselevelm, - $courselevel)); + $ENV{'course.'.$courseid.'.domain'}, + ($seclevelr,$seclevelm,$seclevel, + $courselevelr)); if (defined($coursereply)) { return $coursereply; } # ------------------------------------------------------ third, check map parms @@ -4132,7 +4293,7 @@ sub EXT { } if ($thisparm) { return $thisparm; } } -# --------------------------------------------- last, look in resource metadata +# ------------------------------------------ fourth, look in resource metadata $spacequalifierrest=~s/\./\_/; my $filename; @@ -4147,6 +4308,14 @@ sub EXT { $metadata=&metadata($filename,'parameter_'.$spacequalifierrest); if (defined($metadata)) { return $metadata; } +# ---------------------------------------------- fourth, look in rest pf course + if ($symbparm && defined($courseid) && + $courseid eq $ENV{'request.course.id'}) { + my $coursereply=&courseresdata($ENV{'course.'.$courseid.'.num'}, + $ENV{'course.'.$courseid.'.domain'}, + ($courselevelm,$courselevel)); + if (defined($coursereply)) { return $coursereply; } + } # ------------------------------------------------------------------ Cascade up unless ($space eq '0') { my @parts=split(/_/,$space); @@ -4189,6 +4358,7 @@ sub packages_tab_default { if (defined($packagetab{"$pack_type&$name&default"})) { return $packagetab{"$pack_type&$name&default"}; } + if ($pack_type eq 'part') { $pack_part='0'; } if (defined($packagetab{$pack_type."_".$pack_part."&$name&default"})) { return $packagetab{$pack_type."_".$pack_part."&$name&default"}; } @@ -4214,6 +4384,7 @@ sub add_prefix_and_part { # ---------------------------------------------------------------- Get metadata +my %metaentry; sub metadata { my ($uri,$what,$liburi,$prefix,$depthcount)=@_; $uri=&declutter($uri); @@ -4233,27 +4404,30 @@ sub metadata { # Everything is cached by the main uri, libraries are never directly cached # if (!defined($liburi)) { - my ($result,$cached)=&is_cached(\%metacache,$uri,'meta'); + my ($result,$cached)=&is_cached_new('meta',$uri); if (defined($cached)) { return $result->{':'.$what}; } } { # # Is this a recursive call for a library? # - if (! exists($metacache{$uri})) { - $metacache{$uri}={}; - } +# if (! exists($metacache{$uri})) { +# $metacache{$uri}={}; +# } if ($liburi) { $liburi=&declutter($liburi); $filename=$liburi; } else { - &devalidate_cache(\%metacache,$uri,'meta'); + &devalidate_cache_new('meta',$uri); + undef(%metaentry); } my %metathesekeys=(); unless ($filename=~/\.meta$/) { $filename.='.meta'; } my $metastring; if ($uri !~ m|^uploaded/|) { - $metastring=&getfile(&filelocation('',&clutter($filename))); + my $file=&filelocation('',&clutter($filename)); + #push(@{$metaentry{$uri.'.file'}},$file); + $metastring=&getfile($file); } my $parser=HTML::LCParser->new(\$metastring); my $token; @@ -4269,10 +4443,10 @@ sub metadata { if (defined($token->[2]->{'id'})) { $keyroot.='_'.$token->[2]->{'id'}; } - if ($metacache{$uri}->{':packages'}) { - $metacache{$uri}->{':packages'}.=','.$package.$keyroot; + if ($metaentry{':packages'}) { + $metaentry{':packages'}.=','.$package.$keyroot; } else { - $metacache{$uri}->{':packages'}=$package.$keyroot; + $metaentry{':packages'}=$package.$keyroot; } foreach (keys %packagetab) { my $part=$keyroot; @@ -4294,14 +4468,14 @@ sub metadata { if ($subp eq 'display') { $value.=' [Part: '.$part.']'; } - $metacache{$uri}->{':'.$unikey.'.part'}=$part; + $metaentry{':'.$unikey.'.part'}=$part; $metathesekeys{$unikey}=1; - unless (defined($metacache{$uri}->{':'.$unikey.'.'.$subp})) { - $metacache{$uri}->{':'.$unikey.'.'.$subp}=$value; + unless (defined($metaentry{':'.$unikey.'.'.$subp})) { + $metaentry{':'.$unikey.'.'.$subp}=$value; } - if (defined($metacache{$uri}->{':'.$unikey.'.default'})) { - $metacache{$uri}->{':'.$unikey}= - $metacache{$uri}->{':'.$unikey.'.default'}; + if (defined($metaentry{':'.$unikey.'.default'})) { + $metaentry{':'.$unikey}= + $metaentry{':'.$unikey.'.default'}; } } } @@ -4334,7 +4508,7 @@ sub metadata { foreach (sort(split(/\,/,&metadata($uri,'keys', $location,$unikey, $depthcount+1)))) { - $metacache{$uri}->{':'.$_}=$metacache{$uri}->{':'.$_}; + $metaentry{':'.$_}=$metaentry{':'.$_}; $metathesekeys{$_}=1; } } @@ -4345,18 +4519,18 @@ sub metadata { } $metathesekeys{$unikey}=1; foreach (@{$token->[3]}) { - $metacache{$uri}->{':'.$unikey.'.'.$_}=$token->[2]->{$_}; + $metaentry{':'.$unikey.'.'.$_}=$token->[2]->{$_}; } my $internaltext=&HTML::Entities::decode($parser->get_text('/'.$entry)); - my $default=$metacache{$uri}->{':'.$unikey.'.default'}; + my $default=$metaentry{':'.$unikey.'.default'}; if ( $internaltext =~ /^\s*$/ && $default !~ /^\s*$/) { # only ws inside the tag, and not in default, so use default # as value - $metacache{$uri}->{':'.$unikey}=$default; + $metaentry{':'.$unikey}=$default; } else { # either something interesting inside the tag or default # uninteresting - $metacache{$uri}->{':'.$unikey}=$internaltext; + $metaentry{':'.$unikey}=$internaltext; } # end of not-a-package not-a-library import } @@ -4373,7 +4547,7 @@ sub metadata { &metadata_create_package_def($uri,$key,'extension_'.$extension, \%metathesekeys); } - if (!exists($metacache{$uri}->{':packages'})) { + if (!exists($metaentry{':packages'})) { foreach my $key (sort(keys(%packagetab))) { #no specific packages well let's get default then if ($key!~/^default&/) { next; } @@ -4382,31 +4556,31 @@ sub metadata { } } # are there custom rights to evaluate - if ($metacache{$uri}->{':copyright'} eq 'custom') { + if ($metaentry{':copyright'} eq 'custom') { # # Importing a rights file here # unless ($depthcount) { - my $location=$metacache{$uri}->{':customdistributionfile'}; + my $location=$metaentry{':customdistributionfile'}; my $dir=$filename; $dir=~s|[^/]*$||; $location=&filelocation($dir,$location); foreach (sort(split(/\,/,&metadata($uri,'keys', $location,'_rights', $depthcount+1)))) { - $metacache{$uri}->{':'.$_}=$metacache{$uri}->{':'.$_}; + #$metaentry{':'.$_}=$metacache{$uri}->{':'.$_}; $metathesekeys{$_}=1; } } } - $metacache{$uri}->{':keys'}=join(',',keys %metathesekeys); - &metadata_generate_part0(\%metathesekeys,$metacache{$uri},$uri); - $metacache{$uri}->{':allpossiblekeys'}=join(',',keys %metathesekeys); - &do_cache(\%metacache,$uri,$metacache{$uri},'meta'); + $metaentry{':keys'}=join(',',keys %metathesekeys); + &metadata_generate_part0(\%metathesekeys,\%metaentry,$uri); + $metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys); + &do_cache_new('meta',$uri,\%metaentry); # this is the end of "was not already recently cached } - return $metacache{$uri}->{':'.$what}; + return $metaentry{':'.$what}; } sub metadata_create_package_def { @@ -4414,22 +4588,22 @@ sub metadata_create_package_def { my ($pack,$name,$subp)=split(/\&/,$key); if ($subp eq 'default') { next; } - if (defined($metacache{$uri}->{':packages'})) { - $metacache{$uri}->{':packages'}.=','.$package; + if (defined($metaentry{':packages'})) { + $metaentry{':packages'}.=','.$package; } else { - $metacache{$uri}->{':packages'}=$package; + $metaentry{':packages'}=$package; } my $value=$packagetab{$key}; my $unikey; $unikey='parameter_0_'.$name; - $metacache{$uri}->{':'.$unikey.'.part'}=0; + $metaentry{':'.$unikey.'.part'}=0; $$metathesekeys{$unikey}=1; - unless (defined($metacache{$uri}->{':'.$unikey.'.'.$subp})) { - $metacache{$uri}->{':'.$unikey.'.'.$subp}=$value; + unless (defined($metaentry{':'.$unikey.'.'.$subp})) { + $metaentry{':'.$unikey.'.'.$subp}=$value; } - if (defined($metacache{$uri}->{':'.$unikey.'.default'})) { - $metacache{$uri}->{':'.$unikey}= - $metacache{$uri}->{':'.$unikey.'.default'}; + if (defined($metaentry{':'.$unikey.'.default'})) { + $metaentry{':'.$unikey}= + $metaentry{':'.$unikey.'.default'}; } } @@ -4466,27 +4640,30 @@ sub metadata_generate_part0 { sub gettitle { my $urlsymb=shift; my $symb=&symbread($urlsymb); - unless ($symb) { - unless ($urlsymb) { $urlsymb=$ENV{'request.filename'}; } - return &metadata($urlsymb,'title'); - } - my ($result,$cached)=&is_cached(\%titlecache,$symb,'title',600); - if (defined($cached)) { return $result; } - my ($map,$resid,$url)=&decode_symb($symb); - my $title=''; - my %bighash; - if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', - &GDBM_READER(),0640)) { - my $mapid=$bighash{'map_pc_'.&clutter($map)}; - $title=$bighash{'title_'.$mapid.'.'.$resid}; - untie %bighash; - } - $title=~s/\&colon\;/\:/gs; - if ($title) { - return &do_cache(\%titlecache,$symb,$title,'title'); - } else { - return &metadata($urlsymb,'title'); - } + if ($symb) { + my $key=$ENV{'request.course.id'}."\0".$symb; + my ($result,$cached)=&is_cached_new('title',$key); + if (defined($cached)) { + return $result; + } + my ($map,$resid,$url)=&decode_symb($symb); + my $title=''; + my %bighash; + if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', + &GDBM_READER(),0640)) { + my $mapid=$bighash{'map_pc_'.&clutter($map)}; + $title=$bighash{'title_'.$mapid.'.'.$resid}; + untie %bighash; + } + $title=~s/\&colon\;/\:/gs; + if ($title) { + return &do_cache_new('title',$key,$title,600); + } + $urlsymb=$url; + } + my $title=&metadata($urlsymb,'title'); + if (!$title) { $title=(split('/',$urlsymb))[-1]; } + return $title; } # ------------------------------------------------- Update symbolic store links @@ -4499,7 +4676,8 @@ sub symblist { if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.'_symb.db', &GDBM_WRCREAT(),0640)) { foreach (keys %newhash) { - $hash{declutter($_)}=$mapname.'___'.&deversion($newhash{$_}); + $hash{declutter($_)}=&encode_symb($mapname,$newhash{$_}->[1], + $newhash{$_}->[0]); } if (untie(%hash)) { return 'ok'; @@ -4544,8 +4722,11 @@ sub symbverify { if ( &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn) eq $symb) { - $okay=1; - } + if (($ENV{'request.role.adv'}) || + $bighash{'encrypted_'.$_} eq $ENV{'request.enc'}) { + $okay=1; + } + } } } untie(%bighash); @@ -4557,7 +4738,7 @@ sub symbverify { sub symbclean { my $symb=shift; - + if ($symb=~m|^/enc/|) { $symb=&Apache::lonenc::unencrypted($symb); } # remove version from map $symb=~s/\.(\d+)\.(\w+)\_\_\_/\.$2\_\_\_/; @@ -4578,7 +4759,9 @@ sub encode_symb { } sub decode_symb { - my ($map,$resid,$url)=split(/\_\_\_/,shift); + my $symb=shift; + if ($symb=~m|^/enc/|) { $symb=&Apache::lonenc::unencrypted($symb); } + my ($map,$resid,$url)=split(/___/,$symb); return (&fixversion($map),$resid,&fixversion($url)); } @@ -4589,8 +4772,7 @@ sub fixversion { my $uri=&clutter($fn); my $key=$ENV{'request.course.id'}.'_'.$uri; # is this cached? - my ($result,$cached)=&is_cached(\%courseresversioncache,$key, - 'courseresversion',600); + my ($result,$cached)=&is_cached_new('courseresversion',$key); if (defined($cached)) { return $result; } # unfortunately not cached, or expired if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', @@ -4604,8 +4786,7 @@ sub fixversion { } untie %bighash; } - return &do_cache - (\%courseresversioncache,$key,&declutter($uri),'courseresversion'); + return &do_cache_new('courseresversion',$key,&declutter($uri),600); } sub deversion { @@ -4618,14 +4799,21 @@ sub deversion { sub symbread { my ($thisfn,$donotrecurse)=@_; + 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'}) { return &symbclean($ENV{'request.symb'}); } + if ($ENV{'request.symb'}) { + return $ENV{$cache_str}=&symbclean($ENV{'request.symb'}); + } $thisfn=$ENV{'request.filename'}; } + if ($thisfn=~m|^/enc/|) { $thisfn=&Apache::lonenc::unencrypted($thisfn); } # is that filename actually a symb? Verify, clean, and return if ($thisfn=~/\_\_\_\d+\_\_\_(.*)$/) { - if (&symbverify($thisfn,$1)) { return &symbclean($thisfn); } + if (&symbverify($thisfn,$1)) { + return $ENV{$cache_str}=&symbclean($thisfn); + } } $thisfn=declutter($thisfn); my %hash; @@ -4643,13 +4831,13 @@ sub symbread { } # ---------------------------------------------------------- There was an entry if ($syval) { - unless ($syval=~/\_\d+$/) { - unless ($ENV{'form.request.prefix'}=~/\.(\d+)\_$/) { - &appenv('request.ambiguous' => $thisfn); - return ''; - } - $syval.=$1; - } + #unless ($syval=~/\_\d+$/) { + #unless ($ENV{'form.request.prefix'}=~/\.(\d+)\_$/) { + #&appenv('request.ambiguous' => $thisfn); + #return $ENV{$cache_str}=''; + #} + #$syval.=$1; + #} } else { # ------------------------------------------------------- Was not in symb table if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', @@ -4693,11 +4881,12 @@ sub symbread { } } if ($syval) { - return &symbclean($syval.'___'.$thisfn); + return $ENV{$cache_str}=$syval; + #return $ENV{$cache_str}=&symbclean($syval.'___'.$thisfn); } } &appenv('request.ambiguous' => $thisfn); - return ''; + return $ENV{$cache_str}=''; } # ---------------------------------------------------------- Return random seed @@ -4711,6 +4900,7 @@ sub numval { $txt=~tr/U-Z/0-5/; $txt=~tr/u-z/0-5/; $txt=~s/\D//g; + if ($_64bit) { if ($txt > 2**32) { return -1; } } return int($txt); } @@ -4726,11 +4916,29 @@ sub numval2 { my @txts=split(/(\d\d\d\d\d\d\d\d\d)/,$txt); my $total; foreach my $val (@txts) { $total+=$val; } + if ($_64bit) { if ($total > 2**32) { return -1; } } return int($total); } +sub numval3 { + use integer; + my $txt=shift; + $txt=~tr/A-J/0-9/; + $txt=~tr/a-j/0-9/; + $txt=~tr/K-T/0-9/; + $txt=~tr/k-t/0-9/; + $txt=~tr/U-Z/0-5/; + $txt=~tr/u-z/0-5/; + $txt=~s/\D//g; + my @txts=split(/(\d\d\d\d\d\d\d\d\d)/,$txt); + my $total; + foreach my $val (@txts) { $total+=$val; } + if ($_64bit) { $total=(($total<<32)>>32); } + return $total; +} + sub latest_rnd_algorithm_id { - return '64bit3'; + return '64bit4'; } sub get_rand_alg { @@ -4742,10 +4950,16 @@ sub get_rand_alg { return &latest_rnd_algorithm_id(); } +sub validCODE { + my ($CODE)=@_; + if (defined($CODE) && $CODE ne '' && $CODE =~ /^\w+$/) { return 1; } + return 0; +} + sub getCODE { - if (defined($ENV{'form.CODE'})) { return $ENV{'form.CODE'}; } + if (&validCODE($ENV{'form.CODE'})) { return $ENV{'form.CODE'}; } if (defined($Apache::lonhomework::parsing_a_problem) && - defined($Apache::lonhomework::history{'resource.CODE'})) { + &validCODE($Apache::lonhomework::history{'resource.CODE'})) { return $Apache::lonhomework::history{'resource.CODE'}; } return undef; @@ -4763,7 +4977,13 @@ sub rndseed { if (!$username) { $username=$wusername } my $which=&get_rand_alg(); if (defined(&getCODE())) { - return &rndseed_CODE_64bit($symb,$courseid,$domain,$username); + if ($which eq '64bit4') { + return &rndseed_CODE_64bit4($symb,$courseid,$domain,$username); + } else { + return &rndseed_CODE_64bit($symb,$courseid,$domain,$username); + } + } elsif ($which eq '64bit4') { + return &rndseed_64bit4($symb,$courseid,$domain,$username); } elsif ($which eq '64bit3') { return &rndseed_64bit3($symb,$courseid,$domain,$username); } elsif ($which eq '64bit2') { @@ -4787,6 +5007,7 @@ sub rndseed_32bit { my $num=$symbseed+$nameseed+$domainseed+$courseseed+$namechck+$symbchck; #&Apache::lonxml::debug("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck"); #&Apache::lonxml::debug("rndseed :$num:$symb"); + if ($_64bit) { $num=(($num<<32)>>32); } return $num; } } @@ -4807,6 +5028,8 @@ sub rndseed_64bit { my $num2=$nameseed+$domainseed+$courseseed; #&Apache::lonxml::debug("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck"); #&Apache::lonxml::debug("rndseed :$num:$symb"); + if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); } + if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); } return "$num1,$num2"; } } @@ -4850,7 +5073,33 @@ sub rndseed_64bit3 { my $num1=$symbchck+$symbseed+$namechck; my $num2=$nameseed+$domainseed+$courseseed; #&Apache::lonxml::debug("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck"); - #&Apache::lonxml::debug("rndseed :$num:$symb"); + #&Apache::lonxml::debug("rndseed :$num1:$num2:$_64bit"); + if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); } + + return "$num1:$num2"; + } +} + +sub rndseed_64bit4 { + my ($symb,$courseid,$domain,$username)=@_; + { + use integer; + # strings need to be an even # of cahracters long, it it is odd the + # last characters gets thrown away + my $symbchck=unpack("%32S*",$symb.' ') << 21; + my $symbseed=numval3($symb) << 10; + my $namechck=unpack("%32S*",$username.' '); + + my $nameseed=numval3($username) << 21; + my $domainseed=unpack("%32S*",$domain.' ') << 10; + my $courseseed=unpack("%32S*",$courseid.' '); + + my $num1=$symbchck+$symbseed+$namechck; + my $num2=$nameseed+$domainseed+$courseseed; + #&Apache::lonxml::debug("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck"); + #&Apache::lonxml::debug("rndseed :$num1:$num2:$_64bit"); + if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); } + return "$num1:$num2"; } } @@ -4868,6 +5117,27 @@ sub rndseed_CODE_64bit { my $num2=$CODEseed+$courseseed+$symbchck; #&Apache::lonxml::debug("$symbseed:$CODEchck|$CODEseed:$courseseed:$symbchck"); #&Apache::lonxml::debug("rndseed :$num1:$num2:$symb"); + if ($_64bit) { $num1=(($num1<<32)>>32); } + if ($_64bit) { $num2=(($num2<<32)>>32); } + return "$num1:$num2"; + } +} + +sub rndseed_CODE_64bit4 { + my ($symb,$courseid,$domain,$username)=@_; + { + use integer; + my $symbchck=unpack("%32S*",$symb.' ') << 16; + my $symbseed=numval3($symb); + my $CODEchck=unpack("%32S*",&getCODE().' ') << 16; + my $CODEseed=numval3(&getCODE()); + my $courseseed=unpack("%32S*",$courseid.' '); + my $num1=$symbseed+$CODEchck; + my $num2=$CODEseed+$courseseed+$symbchck; + #&Apache::lonxml::debug("$symbseed:$CODEchck|$CODEseed:$courseseed:$symbchck"); + #&Apache::lonxml::debug("rndseed :$num1:$num2:$symb"); + if ($_64bit) { $num1=(($num1<<32)>>32); } + if ($_64bit) { $num2=(($num2<<32)>>32); } return "$num1:$num2"; } } @@ -4958,30 +5228,30 @@ sub receipt { # the local server. sub getfile { - my ($file,$caller) = @_; + my ($file) = @_; - if ($file !~ m|^/*uploaded/(\w+)/(\w+)/(.+)$|) { - # normal file from res space - &repcopy($file); - return &readfile($file); - } - - my $info; - my $cdom = $1; - my $cnum = $2; - my $filename = $3; - my $path = $Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles'; - my ($lwpresp,$rtncode); - my $localfile = $path.'/'.$cdom.'/'.$cnum.'/'.$filename; - if (-e "$localfile") { - my @fileinfo = stat($localfile); - $lwpresp = &getuploaded('HEAD',$file,$cdom,$cnum,\$info,\$rtncode); + if ($file =~ m|^/*uploaded/|) { $file=&filelocation("",$file); } + &repcopy($file); + return &readfile($file); +} + +sub repcopy_userfile { + my ($file)=@_; + if ($file =~ m|^/*uploaded/|) { $file=&filelocation("",$file); } + if ($file =~ m|^/home/httpd/html/lonUsers/|) { return 'OK'; } + my ($cdom,$cnum,$filename) = + ($file=~m|^\Q$perlvar{'lonDocRoot'}\E/+userfiles/+([^/]+)/+([^/]+)/+(.*)|); + my ($info,$rtncode); + my $uri="/uploaded/$cdom/$cnum/$filename"; + if (-e "$file") { + my @fileinfo = stat($file); + my $lwpresp = &getuploaded('HEAD',$uri,$cdom,$cnum,\$info,\$rtncode); if ($lwpresp ne 'ok') { if ($rtncode eq '404') { - unlink($localfile); + unlink($file); } #my $ua=new LWP::UserAgent; - #my $request=new HTTP::Request('GET',&tokenwrapper($file)); + #my $request=new HTTP::Request('GET',&tokenwrapper($uri)); #my $response=$ua->request($request); #if ($response->is_success()) { # return $response->content; @@ -4991,21 +5261,21 @@ sub getfile { return -1; } if ($info < $fileinfo[9]) { - return &readfile($localfile); + return 'OK'; } $info = ''; - $lwpresp = &getuploaded('GET',$file,$cdom,$cnum,\$info,\$rtncode); + $lwpresp = &getuploaded('GET',$uri,$cdom,$cnum,\$info,\$rtncode); if ($lwpresp ne 'ok') { return -1; } } else { - $lwpresp = &getuploaded('GET',$file,$cdom,$cnum,\$info,\$rtncode); + my $lwpresp = &getuploaded('GET',$uri,$cdom,$cnum,\$info,\$rtncode); if ($lwpresp ne 'ok') { my $ua=new LWP::UserAgent; - my $request=new HTTP::Request('GET',&tokenwrapper($file)); + my $request=new HTTP::Request('GET',&tokenwrapper($uri)); my $response=$ua->request($request); if ($response->is_success()) { - return $response->content; + $info=$response->content; } else { return -1; } @@ -5014,6 +5284,7 @@ sub getfile { if ($filename =~ m|^(.+)/[^/]+$|) { push @parts, split(/\//,$1); } + my $path = $perlvar{'lonDocRoot'}.'/userfiles'; foreach my $part (@parts) { $path .= '/'.$part; if (!-e $path) { @@ -5021,24 +5292,23 @@ sub getfile { } } } - open (FILE,">$localfile"); + open(FILE,">$file"); print FILE $info; close(FILE); - if ($caller eq 'uploadrep') { - return 'ok'; - } - return $info; + return 'OK'; } 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 { @@ -5076,41 +5346,39 @@ sub readfile { } sub filelocation { - my ($dir,$file) = @_; - my $location; - $file=~ s/^\s*(\S+)\s*$/$1/; ## strip off leading and trailing spaces - if ($file=~m:^/~:) { # is a contruction space reference - $location = $file; - $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:; - } elsif ($file=~/^\/*uploaded/) { # is an uploaded file - if ($file=~/^\/uploaded\/([^\/]+)\/([^\/]+)\/(\/)?simplepage\/([^\/]+)$/) { - $location=&Apache::loncommon::propath($1,$2).'/userfiles/simplepage/'.$4; - if (not -e $location) { - $file=~/^\/uploaded\/(.*)$/; - $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'.$1; - } - } elsif ($file=~/^\/uploaded\/([^\/]+)\/([^\/]+)\/aboutme\/([^\/]+)$/) { - $location=&Apache::loncommon::propath($1,$2).'/userfiles/aboutme/'.$3; - if (not -e $location) { - $file=~/^\/uploaded\/(.*)$/; - $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'.$1; - } - } else { - $location=$file; - } - } else { - $file=~s/^\Q$perlvar{'lonDocRoot'}\E//; - $file=~s:^/res/:/:; - if ( !( $file =~ m:^/:) ) { - $location = $dir. '/'.$file; + my ($dir,$file) = @_; + my $location; + $file=~ s/^\s*(\S+)\s*$/$1/; ## strip off leading and trailing spaces + if ($file=~m:^/~:) { # is a contruction space reference + $location = $file; + $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:; + } elsif ($file=~/^\/*uploaded/) { # is an uploaded file + my ($udom,$uname,$filename)= + ($file=~m|^/+uploaded/+([^/]+)/+([^/]+)/+(.*)$|); + my $home=&homeserver($uname,$udom); + my $is_me=0; + my @ids=¤t_machine_ids(); + foreach my $id (@ids) { if ($id eq $home) { $is_me=1; } } + if ($is_me) { + $location=&Apache::loncommon::propath($udom,$uname). + '/userfiles/'.$filename; + } else { + $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'. + $udom.'/'.$uname.'/'.$filename; + } } else { - $location = '/home/httpd/html/res'.$file; + $file=~s/^\Q$perlvar{'lonDocRoot'}\E//; + $file=~s:^/res/:/:; + if ( !( $file =~ m:^/:) ) { + $location = $dir. '/'.$file; + } else { + $location = '/home/httpd/html/res'.$file; + } } - } - $location=~s://+:/:g; # remove duplicate / - while ($location=~m:/\.\./:) {$location=~ s:/[^/]+/\.\./:/:g;} #remove dir/.. - while ($location=~m:/\./:) {$location=~ s:/\./:/:g;} #remove /./ - return $location; + $location=~s://+:/:g; # remove duplicate / + while ($location=~m:/\.\./:) {$location=~ s:/[^/]+/\.\./:/:g;} #remove dir/.. + while ($location=~m:/\./:) {$location=~ s:/\./:/:g;} #remove /./ + return $location; } sub hreflocation { @@ -5156,6 +5424,7 @@ sub current_machine_ids { sub declutter { my $thisfn=shift; + if ($thisfn=~m|^/enc/|) { $thisfn=&Apache::lonenc::unencrypted($thisfn); } $thisfn=~s/^\Q$perlvar{'lonDocRoot'}\E//; $thisfn=~s/^\///; $thisfn=~s/^res\///; @@ -5173,6 +5442,15 @@ sub clutter { return $thisfn; } +sub freeze_escape { + my ($value)=@_; + if (ref($value)) { + $value=&nfreeze($value); + return '__FROZEN__'.&escape($value); + } + return &escape($value); +} + # -------------------------------------------------------- Escape Special Chars sub escape { @@ -5189,11 +5467,21 @@ sub unescape { return $str; } +sub thaw_unescape { + my ($value)=@_; + if ($value =~ /^__FROZEN__/) { + substr($value,0,10,undef); + $value=&unescape($value); + return &thaw($value); + } + return &unescape($value); +} + sub mod_perl_version { + return 1; if (defined($perlvar{'MODPERL2'})) { return 2; } - return 1; } sub correct_line_ends { @@ -5206,17 +5494,20 @@ sub correct_line_ends { sub goodbye { &logthis("Starting Shut down"); #not converted to using infrastruture and probably shouldn't be - &logthis(sprintf("%-20s is %s",'%badServerCache',scalar(%badServerCache))); + &logthis(sprintf("%-20s is %s",'%badServerCache',length(&freeze(\%badServerCache)))); #converted - &logthis(sprintf("%-20s is %s",'%metacache',scalar(%metacache))); - &logthis(sprintf("%-20s is %s",'%homecache',scalar(%homecache))); - &logthis(sprintf("%-20s is %s",'%titlecache',scalar(%titlecache))); - &logthis(sprintf("%-20s is %s",'%courseresdatacache',scalar(%courseresdatacache))); +# &logthis(sprintf("%-20s is %s",'%metacache',scalar(%metacache))); + &logthis(sprintf("%-20s is %s",'%homecache',length(&freeze(\%homecache)))); +# &logthis(sprintf("%-20s is %s",'%titlecache',length(&freeze(\%titlecache)))); +# &logthis(sprintf("%-20s is %s",'%courseresdatacache',length(&freeze(\%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",'%courseresversioncache',scalar(%courseresversioncache))); - &logthis(sprintf("%-20s is %s",'%resversioncache',scalar(%resversioncache))); +# &logthis(sprintf("%-20s is %s",'%userresdatacache',length(&freeze(\%userresdatacache)))); +# &logthis(sprintf("%-20s is %s",'%getsectioncache',length(&freeze(\%getsectioncache)))); +# &logthis(sprintf("%-20s is %s",'%courseresversioncache',length(&freeze(\%courseresversioncache)))); +# &logthis(sprintf("%-20s is %s",'%resversioncache',length(&freeze(\%resversioncache)))); + &logthis(sprintf("%-20s is %s",'%remembered',length(&freeze(\%remembered)))); + &logthis(sprintf("%-20s is %s",'kicks',$kicks)); + &logthis(sprintf("%-20s is %s",'hits',$hits)); &flushcourselogs(); &logthis("Shutting down"); return DONE; @@ -5226,6 +5517,7 @@ BEGIN { # ----------------------------------- Read loncapa.conf and loncapa_apache.conf unless ($readit) { { + # FIXME: Use LONCAPA::Configuration::read_conf here and omit next block open(my $config,") { @@ -5286,22 +5578,32 @@ BEGIN { while (my $configline=<$config>) { next if ($configline =~ /^(\#|\s*$)/); chomp($configline); - my ($id,$domain,$role,$name,$ip,$domdescr)=split(/:/,$configline); - if ($id && $domain && $role && $name && $ip) { + my ($id,$domain,$role,$name)=split(/:/,$configline); + $name=~s/\s//g; + if ($id && $domain && $role && $name) { $hostname{$id}=$name; $hostdom{$id}=$domain; - $hostip{$id}=$ip; - $iphost{$ip}=$id; if ($role eq 'library') { $libserv{$id}=$name; } - } else { - if ($configline) { - &logthis("Skipping hosts.tab line -$configline-"); - } } } close($config); } +sub get_iphost { + if (%iphost) { return %iphost; } + foreach my $id (keys(%hostname)) { + my $name=$hostname{$id}; + my $ip = gethostbyname($name); + if (!$ip || length($ip) ne 4) { + &logthis("Skipping host $id name $name no IP found\n"); + next; + } + $ip=inet_ntoa($ip); + push(@{$iphost{$ip}},$id); + } + return %iphost; +} + # ------------------------------------------------------ Read spare server file { open(my $config,"<$perlvar{'lonTabDir'}/spare.tab"); @@ -5365,7 +5667,7 @@ BEGIN { } -%metacache=(); +$memcache=new Cache::Memcached({'servers'=>['127.0.0.1:11211']}); $processmarker='_'.time.'_'.$perlvar{'lonHostID'}; $dumpcount=0; @@ -5373,6 +5675,12 @@ $dumpcount=0; &logtouch(); &logthis('INFO: Read configuration'); $readit=1; + { + use integer; + my $test=(2**32)+1; + if ($test != 0) { $_64bit=1; } else { $_64bit=0; } + &logthis(" Detected 64bit platform ($_64bit)"); + } } } @@ -5600,8 +5908,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" @@ -5804,8 +6112,8 @@ subscribe($fname) : subscribe to a resou repcopy($filename) : subscribes to the requested file, and attempts to replicate from the owning library server, Might return -HTTP_SERVICE_UNAVAILABLE, HTTP_NOT_FOUND, FORBIDDEN, OK, or -HTTP_BAD_REQUEST, also attempts to grab the metadata for the +'HTTP_SERVICE_UNAVAILABLE', 'HTTP_NOT_FOUND', 'FORBIDDEN', 'OK', or +'HTTP_BAD_REQUEST', also attempts to grab the metadata for the resource. Expects the local filesystem pathname (/home/httpd/html/res/....) @@ -5859,9 +6167,10 @@ returns the data handle =item * symbverify($symb,$thisfn) : verifies that $symb actually exists and is -a possible symb for the URL in $thisfn, returns a 1 on success, 0 on -failure, user must be in a course, as it assumes the existance of the -course initi hash, and uses $ENV('request.course.id'} +a possible symb for the URL in $thisfn, and if is an encryypted +resource that the user accessed using /enc/ returns a 1 on success, 0 +on failure, user must be in a course, as it assumes the existance of +the course initial hash, and uses $ENV('request.course.id'} =item *