#!/usr/bin/perl -w ##### # freecache.cgi # Author: Ralf Muehlen, based on earlier code from Jon Aizen and Brad Tofel. # Idea from Brewster Kahle. # Version: 2.0 # Description: This is a cgi script that implements a freecache server as part of the # freecache system. It should be usuable on any Apache server that # runs Perl cgi scripts. To use this file, please modify the lines # in the USER CONFIGURATION section. # # For feedback, please use the online forum at http://freecache.org/ # use strict; ############################################################################### ## ## USER CONFIGURATION ## # MODIFY NOTHING ABOVE THIS LINE. # SEE http://freecache.org/ FOR MORE INFORMATION # my $debug_level = 2; # 0=least, 3=most debugging info my $freecache_password = 'ChangeMe'; my $server_admin_email = $ENV{'SERVER_ADMIN'}; # take it from Apache # $server_admin_email = 'webmaster@example.com'; # or specify it yourself # # Put the largest directory first; that's a little more efficient. my @cache_dirs = ('/1/freecache','/2/freecache','/3/freecache','/4/freecache','/5/freecache','/6/freecache','/7/freecache','/0/freecache'); my @cache_sizes = (110,110,110,110,110,110,110,90); # in GBytes my $serving_ip_range = '0.0.0.0/0'; my $unzip_cmd = '/usr/local/bin/unzip'; # The following 2 entires are for experimental support of the # Distributed Storage Initiative on Internet2. my $dsi = 0; # Is this FC close to the Internet2? 0=no, 1=yes my $lors_bin_dir = '/home/muehlen/lors/lors-runtime/bin'; # Only needed if you set $dsi to 1. # # All FreeCaches need be on a public IP to able to communicate with the # Redirector and other FreeCaches. $serving_ip_range will be taken into # consideration by the Redirector. Currently you can only specify one # IP range. This IP range does not have to reflect actual routing rules. # Examples: # serve anyone '0.0.0.0/0'; # serve 1 class C network: '209.237.233.0/24'; # serve 2 class C networks 209.237.233.0/24 and 209.237.234.0/24 plus a few more: # '209.237.232.0/22'; # # # After changing this configuration, invoke FreeCache and start it: # http://your.site/cgi-bin/freecache.cgi # # The heartbeat of this cgi can be monitored as: # http://your.site/cgi-bin/freecache.cgi?action=status # # # MODIFY NOTHING BELOW THESE LINES. # SEE http://freecache.org/ FOR MORE INFORMATION # ############################################################################### # # TODO globally: die with HTTP error my $free_cache_agent = "InternetArchiveFreeCache(tm)v.2.0"; my $redirector = 'freecache.org'; use Digest::MD5; use POSIX; use LWP::UserAgent; use HTTP::Request; use HTTP::Response; my $start = (POSIX::times())[0]; my $section = "main"; my $total_read = 0; my $total_wrote = 0; $SIG{ABRT} = sub { signal_handler("ABRT"); }; $SIG{ALRM} = sub { signal_handler("ALRM"); }; $SIG{HUP} = sub { signal_handler("HUP"); }; $SIG{PIPE} = sub { signal_handler("PIPE"); }; $SIG{TERM} = sub { signal_handler("TERM"); }; #sig_name="ZERO HUP INT QUIT ILL TRAP IOT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM ABRT USR1 USR2"",0" my $GBytes = 1024 * 1024 * 1024; #my $cache_request_timeout = 60 * 60 * 24; my $cache_request_timeout = 600; my $cache_request_block_size = 1024 * 64; # should be bigger, small for testing my $client_buff_size = 1024 * 128; # same deal # not used anymore: #my $client_max_write = 1024 * 4; # ditto #my $cache_request_block_size = 1024 * 1; # should be bigger, small for testing #my $client_buff_size = 1024 * 1; # same deal #my $client_max_write = 1024 * 1; # ditto my $buffer = ""; my $error_buffer = ""; my $tail_file_sleep = 5; my $my_protocol = $ENV{'SERVER_PROTOCOL'}; my $my_hostname = $ENV{'SERVER_NAME'}; my $my_port = $ENV{'SERVER_PORT'}; my $my_ip = $ENV{'SERVER_ADDR'}; my $remote_ip = $ENV{'REMOTE_ADDR'}; my $remote_ua = $ENV{'HTTP_USER_AGENT'} || ""; my $request_uri = $ENV{'REQUEST_URI'}; my $method = $ENV{'REQUEST_METHOD'}; my $range = $ENV{'HTTP_RANGE'}; my $pid; my $CACHE_MISS = "0"; my $CACHE_HIT = "1"; my $CACHE_CACHING = "2"; my $get_cache_source_url_template = "http://%s/%s"; my $num_redirect_attempts = 5; my $number_of_retries = 2; # we'll use this a few times: my $ua = LWP::UserAgent->new; $ua->agent($free_cache_agent); $ua->from($server_admin_email); $ua->timeout($cache_request_timeout); ### # FreeCache Alogrithm: # # if fill_cache # get file size # if disk_free + file_size > $high_watermark # delete_files # download file # calculate MD5 hash # if url # rename file # notify redirector of url_addition # else /* md5 */ # if md5sum != filename # notify redirector of md5_mismatch # else # notify redirector of md5_addition # # # if file_request # if file found # serve file # notify redirector of serving # if file not found # redirect client to redirector with file_not_found # # sub_delete # find the oldest files and their sizes # notify redirector of deletions # delete files till low_watermark is reached # done # # ### # time stamp my ($sec,$min,$hour,$mday,$mon,$year,$wday, $yday,$isdst)=localtime(time); my $timestamp = sprintf "%4d-%02d-%02d %02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec; # parse arguments and query envirnment $my_protocol =~ s/\/.*//; $my_protocol =~ tr/[A-Z]/[a-z]/; my $fc_url = $my_protocol ."://$my_hostname"; $fc_url .= ":". $my_port unless $my_port == 80; my $my_path = $request_uri; $my_path =~ s/(.*freecache\.cgi).*/$1/; $fc_url .= $my_path; $request_uri =~ s/^\///; debug_msg("Invoked at ". (POSIX::times())[0] ."=$timestamp from $remote_ip with $request_uri ua:$remote_ua",2); #my $cgi = new CGI; #my $fill_cache = $cgi->param("fill_cache") || ""; #my $md5 = $cgi->param("md5") || ""; #my $xnode = $cgi->param("xnode") || ""; #my $zip = $cgi->param("zip") || ""; #my $action = $cgi->param("action") || ""; #my $password = $cgi->param("password") || ""; #my $redirector_ip = $cgi->param("redirectorIP") || $redirector; #my $mime = $cgi->param("mime") || ""; #my $file_base = $cgi->param("base") || ""; my $fill_cache; my $md5; my $xnode; my $zip; my $action; my $password; my $redirector_ip; my $mime; my $file_base; if ($request_uri =~ 'freecache.cgi\?') { # invoked via rewrite-rule my ($path,$args) = split(/freecache.cgi\?/, $request_uri); my ($command,$parameters) = split('&',$args,2); debug_msg("? invokation: command($command), parameters($parameters)",2); if ($command =~ s/fill_cache=//) { $fill_cache = $command; if ($parameters =~ s/redirectorIP=//) { $redirector_ip = $parameters;} } if ($command =~ s/action=//) { $action = $command; if ($parameters =~ s/password=//) { $password = $parameters;} } if ($command =~ s/md5=//) { $md5 = $command; ($redirector_ip,$mime,$file_base) = split('&',$parameters,3); } if ($command =~ s/xnode=//) { $xnode = $command; ($redirector_ip,$mime,$file_base) = split('&',$parameters,3); } if ($command =~ s/zip=//) { $zip = $command; ($redirector_ip,$mime,$file_base) = split('&',$parameters,3); } if ($redirector_ip) { $redirector_ip =~ s/redirectorIP=//; } if ($mime) { $mime =~ s/mime=//; } if ($file_base) { $file_base =~ s/base=//; } $redirector_ip = $redirector_ip || $redirector; } if ($request_uri =~ 'freecache.cgi\/') { # invoked via rewrite-rule # fill the parameter variables correctly my ($path,$args) = split(/freecache.cgi\//, $request_uri); (my $command,my $url,$redirector_ip,$mime,$file_base) = split('/', $args, 5); $redirector_ip = $redirector_ip || $redirector; $mime = $mime || ""; $file_base = $file_base || ""; debug_msg("/ invokation: $command,$url,$redirector_ip,$mime,$file_base",3); if ($command =~ "fill_cache") { $fill_cache = $command; } if ($command =~ "action") { $action = $command; } if ($command =~ "md5") { $md5 = $url; } if ($command =~ "xnode") { $xnode = $url; } if ($command =~ "zip") { $zip = $url; } } if ($mime) { $mime =~ s/-/\//; } ################################################################################ ### main ### ################################################################################ if ($fill_cache) { $section = "fill_cache"; debug_msg("FILL request: $fill_cache",3); $|++; # autoflush to avoid time-out ok_response("Filling."); # Do it early otherwise redirector thinks fill operation failed. my $request_url = $fill_cache; # get file size my $request = HTTP::Request->new(HEAD => $request_url); my $response = $ua->request($request); my $file_min_size; my $header = $response->headers_as_string; if ($header =~ s/^[\s\S]*X-Content-Minimum-Length: (\d+)[\s\S]*$/$1/m) { $file_min_size = int (1.1 *$header); } debug_msg("file_min_size($file_min_size)",2); my $ori_file_size = $response->content_length; my $file_size = $response->content_length || $file_min_size || 2 * $GBytes; my $last_modified = $response->last_modified || ''; my $mime = $response->content_type || ''; if ($dsi) { $file_size *= 1.1; } # leave space for xnode # choose cache_dir my $cache_dir; my $cache_size; my $max_disk_avail =0; my $disk_usage =0; # choose random cache_dir my $j = int(rand scalar(@cache_dirs)); $cache_dir = $cache_dirs[$j]; $cache_size = $cache_sizes[$j] * $GBytes; $disk_usage = disk_usage($cache_dir); my $high_watermark = int($cache_size * 0.95); my $low_watermark = int($cache_size * 0.9); debug_msg("cache_dir:$cache_dir cache_size:$cache_size",2); # do we have enough disk space? if ( (0 + $disk_usage + $file_size) > $high_watermark ) { debug_msg("high_watermark ($high_watermark) reached. Deleting files.",3); delete_files($cache_dir,$disk_usage,$low_watermark); } my $disk_free = disk_free($cache_dir); if ($disk_free <= $file_size * 2) { debug_msg("Low disk space: $disk_free free. Admin should adjust cache_size in config section.",1); $low_watermark -= $file_size; delete_files($cache_dir,$disk_usage,$low_watermark); } # need a temporary file name that will repeatably map the URL to a file: my $cache_file = $cache_dir . "/" . Digest::MD5::md5_hex($request_url) .".temp"; if (-e $cache_file) { error_msg("temp file exists:$cache_file($!)"); } # download file debug_msg("Requesting($cache_file,$request_url)",3); $request = HTTP::Request->new(GET => $request_url); # this deserves a comment: # We want to build the MD5 as we recieve the data, so we use the 2nd argument(and 3rd) # arguments to UserAgent->request(), but we want the callback function to have access # to the lexically(locally) scoped Digest::MD5 object. We have to use an anonymous sub # to achieve this. If you understand why, please let me (Brad) know. open(CACHE_FILE,">$cache_file") || error_msg("open $cache_file($!)"); #binmode(CACHE_FILE); my $i =0; my $ctx = new Digest::MD5(); $total_read = 0; my $read_sub = sub { my $data = shift; $total_read += length($data); $ctx->add($data); print CACHE_FILE $data; if ($i++ > 1024) { print ".\n"; $i =0; } }; $response = $ua->request($request,$read_sub,$cache_request_block_size); debug_msg("Done with request($cache_file,$request_url,$total_read bytes)",3); close(CACHE_FILE) || error_msg("close CACHE_FILE($!)"); # to get file_size right if($response->is_success) { debug_msg("downloaded: $cache_file",3); } else { my $status_line = $response->status_line; fail("Failed to download $cache_file: $status_line",$cache_file); } # test size $file_size = (-s $cache_file); if (($ori_file_size) && ($ori_file_size != $file_size) ) { fail("file_size($file_size) does not match Content-Length($ori_file_size)",$cache_file); } # test zip files # unzip does not work with files over 2GB if ( ($file_size < 2* $GBytes) && ($request_uri =~ m|http://\w*.archive.org/.*.zip|) ) { open(UZ, "$unzip_cmd -l $cache_file 2>&1 1>/dev/null |"); if () { fail("ZIP file is corrupted",$cache_file); } close UZ; } my $calc_md5 = $ctx->hexdigest(); debug_msg("calc_md5: $calc_md5",3); my $final_cache_file = $cache_dir . "/" . $calc_md5; # is it a MD5 ? if($request_url =~ /[0-9a-f]{32}/) { my $want_md5 = $request_url; #debug_msg("want_md5: $want_md5",3); $want_md5 =~ s/.*([0-9a-f]{32})+.*/$1/; debug_msg("want_md5: $want_md5",3); # do the MD5s match? if($calc_md5 ne $want_md5) { notify($redirector_ip,"md5_mismatch:$request_url:$calc_md5"); fail("Got wrong md5. Got ($calc_md5) but wanted ($want_md5)",$cache_file); } else { debug_msg("renaming $cache_file to $final_cache_file",3); rename($cache_file,$final_cache_file) || error_msg("unable to rename $cache_file -> $final_cache_file($!)"); my $elapsed = (POSIX::times())[0] - $start; debug_msg("add_md5:$calc_md5",3); notify($redirector_ip,"add_md5:$calc_md5:$elapsed"); } } else { # It's an URL. debug_msg("renaming $cache_file to $final_cache_file",3); rename($cache_file,$final_cache_file) || error_msg("unable to rename $cache_file -> $final_cache_file($!)"); $mime =~ s/\//-/; # In FC communications and DB, the MIME type should always be xxx-yyy, not xxx/yyy. my $elapsed = (POSIX::times())[0] - $start; debug_msg("$redirector_ip,add_url:$calc_md5:$last_modified:$mime:$file_size:$elapsed:$request_url",2); notify($redirector_ip,"add_url:$calc_md5:$last_modified:$mime:$file_size:$elapsed:$request_url"); if ($dsi) { chdir $cache_dir; if ($file_size < 0.042 * $GBytes) { create_xnode($request_url,$calc_md5,$file_size,$mime,$last_modified); } } } print "Done filling.\n"; } # fill_cache elsif ($md5 || $xnode || $zip) { # MD5 $section = "md5"; if ($xnode) { $md5 = $xnode; } if ($zip) { $md5 = $zip; } debug_msg("MD5 Request URL($md5) MIME($mime) FILEBASE($file_base)",3); my $cache_dir; foreach $cache_dir (@cache_dirs) { my $cache_file = $cache_dir . "/" . $md5; if(-e $cache_file) { debug_msg("Request method($method)",3); my $action; # TODO: take $file_size from redirector my $file_size = (-s $cache_file); if ($method =~ "HEAD") { if ($xnode) { return_header($mime,$file_base); } else { return_header($mime,$file_base,$file_size); } $action = "servedhead"; } elsif ($method =~ "GET") { if ($remote_ua =~ /.*FreeCache.*/i) { $action = "served2fc"; } else { $action = "served2u"; } my $start_byte; my $end_byte; $start_byte=0; $end_byte=$file_size-1; if ($range) { debug_msg("range($range) ua:$remote_ua",2); if ( ($range =~ s/^bytes=(\d*)-(\d*)$/$1-$2/) && ($range !~ s/^-$/$1/) ) { if ($range =~ s/^-(\d+)/$1/) { $start_byte = $file_size - $range; $end_byte = $file_size -1;} elsif ($range =~ s/(\d+)-$/$1/) { $start_byte = $range; $end_byte = $file_size -1;} else { ($start_byte, $end_byte) = split('-',$range); } debug_msg("start_byte($start_byte), end_byte($end_byte)",3); $action = "servedPu"; } else { debug_msg("Unknown range request: $range",1); notify($redirector_ip,"error_range:$range:$request_uri:$remote_ua"); $range = ""; } } if ($xnode) { return_header($mime,$file_base); return_xnode($cache_file,$mime,$file_base,$start_byte,$end_byte); } elsif ($zip) { if ($remote_ua =~ /.*FreeCache.*/i) { $action = "served2fc"; } else { $action = "served2u"; } return_header($mime,$file_base); if (!return_zip($cache_file,$mime,$file_base)) { $action="servedEu"; } } else { return_header($mime,$file_base,$file_size,$start_byte,$end_byte); if (!return_file($cache_file,$mime,$file_base,$start_byte,$end_byte)) { $action="servedEu"; } } } else { # request method not supported error_msg("Unsupported request method($method)"); } my $elapsed = (POSIX::times())[0] - $start; if ($action) { notify($redirector_ip,"$action:$md5:$total_wrote:$elapsed:$remote_ip"); } debug_msg("Done and exiting! elapsed($elapsed)\n",2); exit(); } } # file not found in any cache_dir debug_msg("MD5 cache miss ($md5)",1); #print $cgi->header(-status=>'404 Not Found'); print "Status: 404 Not Found\n\n"; notify($redirector_ip,"not_found:$md5"); debug_msg("Exiting with error!\n",2); exit; } # MD5 elsif ($action) { # action debug_msg("action invoked:$action",3); my $message = ""; # actions triggered remotely if ($action eq "status") { $message = "$free_cache_agent running on $my_hostname ($my_ip)"; } elsif ($action eq "inventory") { $|++; # autoflush to avoid time-out my $cache_dir; my $cache_size = 0; my $total_size = 0; my $disk_free = 0; my $disk_usage = 0; foreach $cache_size (@cache_sizes) { $total_size += $cache_size; } $total_size *= $GBytes; ok_response("cache_size $total_size"); foreach $cache_dir (@cache_dirs) { $disk_free += disk_free($cache_dir); $disk_usage += disk_usage($cache_dir); } print "disk_free $disk_free\n"; print "disk_usage $disk_usage\n"; foreach $cache_dir (@cache_dirs) { disk_inventory($cache_dir); } my $elapsed = (POSIX::times())[0] - $start; debug_msg("Done and exiting! elapsed($elapsed)\n",2); exit(); } elsif ($action =~ /delete=[0-9a-f]{32}(\.temp){0,1}/) { my $file = $action; $file =~ s/(\w*)=//; my $cache_dir; foreach $cache_dir (@cache_dirs) { my $cache_file = $cache_dir . "/" . $file; if(-e $cache_file) { debug_msg("deleting $cache_file",3); unlink("$cache_file") or error_msg("Unable to Unlink($cache_file)($!)"); if ($dsi) { my $cache_file = $cache_file .".xnd"; if (-e $cache_file) { debug_msg("deleting $cache_file",3); unlink("$cache_file") or error_msg("Unable to Unlink($cache_file)($!)"); } } if ($file =~ /[0-9a-f]{32}/) { notify($redirector_ip,"del_md5:$file"); } else { notify($redirector_ip,"del_temp:$file"); } $message = "deleted $cache_file"; last; } } } elsif ($action =~ /refresh=[0-9a-f]{32}(\.xnd){1}/) { my $file = $action; $file =~ s/(\w*)=//; my $cache_dir; foreach $cache_dir (@cache_dirs) { my $cache_file = $cache_dir . "/" . $file; if(-e $cache_file) { debug_msg("refreshing xnode $cache_file",2); ok_response("refreshing xnode $file"); if ($dsi) { refresh_xnode($cache_file); } my $elapsed = (POSIX::times())[0] - $start; debug_msg("Done and exiting! elapsed($elapsed)\n",2); exit; } } } # actions triggered by host admin via HTML form elsif ($action eq "Start") { if ($password ne $freecache_password) { $message = "Wrong password."; } else { my $cache_dir; foreach $cache_dir (@cache_dirs) { # write test file my $cache_file = $cache_dir . "/freecache_test"; open(CACHE_FILE,">$cache_file") || error_msg("open $cache_file($!)"); print CACHE_FILE "freecache_test\n"; close(CACHE_FILE) || error_msg("close CACHE_FILE($!)"); # delete test file unlink("$cache_file") or error_msg("Unable to Unlink($cache_file)($!)"); # write another test file $cache_file = $cache_dir . "/.TEST"; open(CACHE_FILE,">$cache_file") || error_msg("open $cache_file($!)"); print CACHE_FILE "freecache_test\n"; close(CACHE_FILE) || error_msg("close CACHE_FILE($!)"); } my $cache_size = 0; my $total_size = 0; foreach $cache_size (@cache_sizes) { $total_size += $cache_size; } $total_size *= $GBytes; # register with redirector my $start_template = "start:%s,%s,%s,%s,%s"; my $registration = sprintf($start_template,$my_hostname,$serving_ip_range,$total_size,$server_admin_email,$fc_url); notify($redirector,$registration); $message = "
Started successfully.\n";
      $message .= "my_ip($my_ip)\n";
      $message .= "hostname($my_hostname)\n";
      $message .= "serving_ip_range($serving_ip_range)\n";
      $message .= "total_size($total_size)\n";
      $message .= "server_admin_email($server_admin_email)\n";
      $message .= "fc_url($fc_url)\n";
    }
  }
  elsif ($action eq "Stop") {
    if ($password ne $freecache_password) { $message = "Wrong password."; }
    else {
      my $cache_size = 0;
      my $total_size = 0;
      foreach $cache_size (@cache_sizes) { $total_size += $cache_size; }
      $total_size *= $GBytes;

      # unregister with redirector
      my $stop_template  =  "stop:%s,%s,%s,%s,%s";
      my $registration = sprintf($stop_template,$my_hostname,$serving_ip_range,$total_size,$server_admin_email,$fc_url);
      notify($redirector,$registration);
      $message = "Stopped successfully.";
    }
  }
  elsif ($action eq "Flush") {
    if ($password ne $freecache_password) { $message = "Wrong password."; }
    else {
      $|++; # autoflush to avoid time-out
      ok_response("flushing");
      my $cache_dir;
      foreach $cache_dir (@cache_dirs) {
        my $disk_usage = disk_usage($cache_dir);
        delete_files($cache_dir,$disk_usage,0);
      }
      print "Files flushed.";
      my $elapsed = (POSIX::times())[0] - $start;
      debug_msg("Done and exiting! elapsed($elapsed)\n",2);
      exit();
    }
  }
  else {
      $message = "Unknown action";
  } 
  ok_response($message);
}

else { # unknown request
  debug_msg("Unknown request: $request_uri",1);

  # Show the admin page
  print "Status: 401 Unknown Request\n";
  print "Content-type: text/html\n\n";
  print '
	
	FreeCache Administration
	
	
	

FreeCache Administration

'; print $my_hostname; print '

                Flushing deletes all files in your FreeCache. It might take a while...

Your FreeCache password:

'; } debug_msg("Done and exiting!\n",2); exit(); ################################################################################ ### subs ### ################################################################################ # FUNCTIONS: # Divided into 4 chunks: # Helpers: # disk_usage # delete_files # debug_msg # REDIRECTOR Communications: # notify # Cache Interface: # return_file # Header Stuff: # return_header sub ok_response { my $message = shift; print "Status: 200 OK\n"; print "Content-type: text/html\n\n"; print $message ."\n"; debug_msg("ok_response: $message",3); } sub disk_usage { # Tested on SunOS 5.8, FreeBSD 4.4, Linux Debian woody, Linux RedHat. my $dir = shift; my $du = ""; open(DU, "du -sk $dir |"); # -k is the default, but we want to be sure. while () { chomp; s/(\S+)(\s+)(\S+)/$1/; $du = $_; } $du *= 1024; close DU; return $du ; } sub disk_inventory { my $dir = shift; my $di = ""; open(DI, "ls -l $dir |"); while () { s|(\S+\s+){4}(\S+).*([0-9a-f]{32})$|$2 $3|; print; } print "End of inventory.\n"; debug_msg("End of inventory.",3); } sub disk_free { my $dir = shift; my $df = ""; open(DF, "df -k $dir |"); # -k is the default, but we want to be sure. while () { chomp; s/\S+\s+\S+\s+\S+\s+(\S+)\s+\S+\s+\S+/$1/; $df = $_; } $df *= 1024; close DF; return $df ; } sub delete_files { # find the oldest files and their sizes # notify redirector of deletions # unlink files till low_watermark is reached # Tested on SunOS 5.8, FreeBSD 4.4, Linux Debian woody, Linux RedHat. my ($dir, $disk_usage, $min) = @_; my $size; my $file; # Order by oldest accessed. On a noatime file system, we default to ctime. #open(LS, "ls -rtuks1 $dir |"); # atime #open(LS, "ls -rttks1 $dir |"); # mtime open(LS, "ls -rtcks1 $dir |"); # ctime while () { ($size,$file) = split; if ($size eq "total") { next; } # discard (on non-BSD Unix) notify($redirector_ip,"del_md5:$file"); debug_msg("deleting $dir/$file",2); print "deleting $dir/$file\n"; # keeps the connection alive in case many files get deleted unlink("$dir/$file") or error_msg("Unable to Unlink($dir/$file)($!)"); $disk_usage -= ($size * 1024); if ($disk_usage < $min) { last; } } close LS; return; } sub notify { $section = "notify"; my ($redirector_ip,$message) = @_; my $success = 0; my $notify_redirector_url_template = "http://%s/NOTIFY:%s:%s"; my $notify_redirector_url = sprintf($notify_redirector_url_template,$redirector_ip,$my_ip,$message); debug_msg("Notifying redirector($notify_redirector_url)",3); while ($number_of_retries > 0) { my $request = HTTP::Request->new(GET => $notify_redirector_url); my $response = $ua->simple_request($request); if($response->is_success) { $success = 1; debug_msg("Notified redirector($redirector_ip): $message",2); last; } else { debug_msg("Failed TO NOTIFY redirector($redirector_ip) of ($message)",1); $number_of_retries--; } } #return $success; $section = "main"; return; ### ### } sub return_file { $section = "return_file"; my ($cache_file,$mime,$file_base,$start_byte,$end_byte) = @_; debug_msg("return_file($cache_file)",3); $|++; # autoflush to avoid time-out my $success = 1; (sysopen(READ_FILE, $cache_file, O_RDONLY)) || error_msg("can't open $cache_file ($!)"); #binmode(STDOUT); #$client_buff_size = (stat STDOUT)[11] || $client_buff_size; #debug_msg("client_buff_size for STDOUT: $client_buff_size",2); my $position = sysseek(READ_FILE,$start_byte,0); $position--; LOOP: while(1) { if ($position + $client_buff_size > $end_byte) { $client_buff_size = $end_byte - $position; } my $num_read = sysread(READ_FILE,$buffer,$client_buff_size); if (not defined($num_read)) { $success = 0; error_msg("Got undef bytes back from sysread!\n"); } elsif ($num_read == 0) { $success = 0; debug_msg("Got 0 bytes back from sysread: $client_buff_size = $end_byte - $position",2); last; } # handle partial writes my $num_wrote = 0; while ($num_read) { my $num_wrote = syswrite(STDOUT,$buffer,$num_read,$num_wrote); #error_msg("wrote 0 bytes") unless $num_wrote; if (!$num_wrote) { $success = 0; debug_msg("wrote 0 bytes",2); last LOOP; } $total_wrote += $num_wrote; $position += $num_wrote; $num_read -= $num_wrote; if ($num_read) { debug_msg("Short syswrite. Tried to write $num_read+$num_wrote, actually wrote $num_wrote.",2); $success = 0; last LOOP; #last; } } #debug_msg("client_buff_size($client_buff_size) position($position) end_byte($end_byte)",2); if ($position == $end_byte) { last LOOP; } } close(READ_FILE); debug_msg("leaving return_file($cache_file)",3); $section = "main"; return $success; } sub return_header { $section = "return_header"; my ($mime,$file_base,$file_size,$start_byte,$end_byte) = @_; $mime = $mime || "application/octet-stream"; $file_base = $file_base || ""; $file_size = $file_size || 0; $start_byte = $start_byte || 0; $end_byte = $end_byte || 0; my $range_length = 1 + $end_byte - $start_byte; debug_msg("return_header($mime,$file_base,$file_size)\n",3); my $http_header; if ($range) { $http_header = "Status: 206 Partial content\n"; $http_header .= "Content-Range: bytes $start_byte-$end_byte/$file_size\n"; $http_header .= "Content-Length: $range_length\n"; } else { $http_header = "Status: 200 OK\n"; if ($file_size) { $http_header .= "Content-Length: $file_size\n"; } } # evil browser: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt) requests 1024 bytes at a time $http_header .= "Accept-Ranges: bytes\n" unless ($remote_ua eq 'Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)'); $http_header .= "Content-Transfer-Encoding: binary\n"; if ($file_base) { $http_header .= "Content-Type: $mime\n"; } $http_header .= "Connection: close\n\n"; my $header_length = length($http_header); my $num_wrote = syswrite(STDOUT,$http_header); error_msg("Bad HTTP Header syswrite($!) tried($header_length) wrote($num_wrote)\n") unless ($header_length == $num_wrote); #debug_msg("HTTP header: $http_header",3); #debug_msg("Sent HTTP header file_base: ($file_base) length($header_length)",3); $section = "main"; } sub create_xnode { my ($url,$md5,$file_size,$mime,$last_modified) = @_; debug_msg("creating xnode: $url,$md5,$file_size,$mime",2); my $stderr; my $last_line; open(LU, "$lors_bin_dir/lors_upload -d 10d -f $md5 2>&1 |") or error_msg("LORS error"); while () { print; # to keep the connection with the filler going chomp; $last_line = $_; $stderr .= $last_line; } debug_msg("last_line: $last_line",2); if ($last_line eq "End Success") { my $elapsed = (POSIX::times())[0] - $start; notify($redirector_ip,"add_xnode:$md5:$last_modified:$mime:$file_size:$elapsed:$url"); } else { debug_msg("LORS failed: $stderr",2); my $cache_file = $md5 .".xnd"; (1==unlink($cache_file)) or error_msg("Unable to Unlink($cache_file)($!)"); } } sub refresh_xnode { my $cache_file = shift; $cache_file .= '.xnd'; debug_msg("refresh_xnode($cache_file)",2); $|++; # autoflush to avoid time-out open(LR, "$lors_bin_dir/lors_refresh -d 2d $cache_file 2>&1 |") or error_msg("LORS error"); while () { print; } } sub return_xnode { my ($cache_file,$mime,$file_base,$start_byte,$end_byte) = @_; $cache_file .= '.xnd'; debug_msg("return_xnode($cache_file)",2); $|++; # autoflush to avoid time-out open(LD, "$lors_bin_dir/lors_download $cache_file 2>/dev/null |") or error_msg("LORS error"); while () { print; } debug_msg("leaving return_xnode($cache_file)",2); } sub return_zip { my ($cache_file,$mime,$file_base) = @_; debug_msg("return_zip: cache_file($cache_file) mime($mime) file_base($file_base)",2); $|++; # autoflush to avoid time-out $total_wrote =0; debug_msg("$unzip_cmd -p $cache_file $file_base 2>/dev/null |",2); open(UZ, "$unzip_cmd -p $cache_file $file_base 2>/dev/null |") or error_msg("UNZIP error"); #binmode(UZ); while () { $total_wrote += length($_); print; } debug_msg("leaving return_zip($cache_file)",2); } sub fail { my ($msg, $file) = @_; print "Status: 500 Internal Server Error\n"; print "Content-type: text/plain\n\n"; print "$msg\n"; debug_msg("Exiting with error! $msg\n",1); (1==unlink($file)) or error_msg("Unable to Unlink($file)($!)"); exit; } sub error_msg { my $msg = shift; debug_msg("Error: $msg,$!,". $request_uri ,0); #print STDERR "\nFCe($$): error_buffer:\n$error_buffer\n"; print "Status: 500 Internal Server Error\n"; print "Content-type: text/plain\n\n"; print "An error occurred.\n"; my $elapsed =(POSIX::times())[0] -$start; notify($redirector_ip,"exit_error:$total_wrote:$elapsed:$md5:$remote_ip:$request_uri:$msg:$remote_ua"); debug_msg("Exiting with error! elapsed($elapsed)\n",2); exit; } sub debug_msg { my ($msg,$level) = @_; if ($level <= $debug_level) { print STDERR "FC ($$):$level: $msg\n"; } $error_buffer .= "FCe($$):$level: $msg\n"; } sub signal_handler { my $signal = shift; my $elapsed =(POSIX::times())[0] -$start; $section = "" unless $section; $total_read = 0 unless $total_read; my $severity = 3; if ($signal eq "TERM") { $severity = 3; } if ($^S) { debug_msg("$signal signal:$section,$^S,$^E,$elapsed cs,total read($total_read)",$severity); } else { debug_msg("$signal signal:$section,,$^E,$elapsed cs,total read($total_read)",$severity); } }