# donetdbm.pl
#
# Copyright (c) 1993, 1994, 1995, 1996, 1997, 1998, 1999 by RIPE NCC
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of the author not be
# used in advertising or publicity pertaining to distribution of the
# software without specific, written prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
# AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# $Id: donetdbm.pl,v 2.6 1999/10/06 16:41:06 marek Exp $
#
#	$RCSfile: donetdbm.pl,v $
#	$Revision: 2.6 $
#	$Author: marek $
#	$Date: 1999/10/06 16:41:06 $

require "defines.pl";
require "dbopen.pl";
require "dbclose.pl";

require "enread.pl";
require "enwrite.pl";

require "addkey.pl";
require "enkeys.pl";
require "enukey.pl";

#
# exit cleanly when asked to ...

sub quitdonetdbm {

    unlink(@DONETDBMFILES);
    
    print "\n$PROGRAMNAME terminated by signal.\n";
    
    exit;
    
}    

sub flushkeys {
   local(*database, *normalkeys)=@_;
   
   # print STDERR "do flush to disk for normal keys (happens every $MAXLISTLENGTH keys) ...";
   
   local($oldoffsets,$key,$value,$length);
      
   #
   # old overflow files
       
   local(@toobig)=();
      
   #
   # new overflow files
      
   local(@toobignewkeys)=();
   local(@toobignewvalues)=();
      
   #
   # normal keys
      
   local(@addkeys)=();
   local(@addvalues)=();
      
   while (($key,$value)=each(%normalkeys)) {
         
      $length=length($key);
         
      $key=~ s/^.{$OVERFLOWSIZE-1,$OVERFLOWSIZE-1}.*$/$1/ if ($length>=$OVERFLOWSIZE);
         
      #
      # check if the key already existed, that makes life
      # more complicated
         
      if (($oldoffsets=$database{$key})) {
          
         #
         # add to overflow file
            
         if ($oldoffsets=~ s/^$OVERFLOWPREFIXREGULAR//o) {
               
            push(@toobig, $oldoffsets, $value);
               
            next;
               
         }
            
         $value=join("\,", $oldoffsets, $value);
         
      }   
            
      #
      # do a value addition
         
      $length+=length($value);
         
      if ($length>=$OVERFLOWSIZE) {
         
         push(@toobignewkeys, $key);
         push(@toobignewvalues, $value);
           
      }
      else {
            
         push(@addkeys, $key);
         push(@addvalues, $value);
         
      }
         
   }
   
   #
   # clean up the memory
   
   %normalkeys=();
      
   #
   # add data to old overflow files
      
   local($dbfile)=$database[1].$OVERFLOWEXTENSION;
      
   if (@toobig) {
      
      while (@toobig) {
         
         if (open(OVERFLOWADD, ">>".$dbfile.shift(@toobig))) {
         
            &fatalerror("in donetdbm flushkeys: cannot write $dbfile.$_ value: ".shift(@toobig)." code: $!") if ( ! print OVERFLOWADD "\,", shift(@toobig) );
            close(OVERFLOWADD);
                             
         }
         else {
            &fatalerror("in donetdbm flushkeys: cannot open for addition $dbfile.$_ value: ".shift(@toobig)." code: $!");
         }
         
      }
      
   }
      
   #
   # create new overflow files
      
   if (@toobignewkeys) {
      
      local($filenumber)=$database{$OVERFLOWKEY};
      local(@overflowvalues)=(++$filenumber .. ($filenumber+scalar(@toobignewkeys)-1));
      push(@addkeys, $OVERFLOWKEY);
      push(@addvalues, $overflowvalues[$#overflowvalues]);
         
      #print STDERR join(" ", @toobignewkeys), "\n";
      #print STDERR join(" ", @toobignewvalues), "\n";
      #print STDERR join(" ", @overflowvalues), "\n";
      
      foreach (@overflowvalues) {
         
         if (open(OVERFLOWNEW, ">".$dbfile.$_)) {
         
            &fatalerror("in donetdbm flushkeys: cannot write $dbfile.$_ value: ".shift(@toobignewvalues)." code: $!") if ( ! print OVERFLOWNEW shift(@toobignewvalues) );
            close(OVERFLOWNEW);
              
         }
         else {
            &fatalerror("in donetdbm flushkeys: cannot open for creation $dbfile.$_ value: ".shift(@toobignewvalues)." code: $!");
         }
         
      }
         
      push(@addkeys, @toobignewkeys);
      push(@addvalues, (@overflowvalues=grep($_=$OVERFLOWPREFIX.$_, @overflowvalues)));
         
   }
      
   #
   # add all keys to the physical database in one single command !!!
      
   @database{@addkeys}=@addvalues;
      
   
   # print STDERR " done\n";
   
   return 0;
   
}

sub flushclkeys {
   local($basename, $i, *list, *notexistingprefixes)=@_;
   
   #print STDERR "do flush to disk prefix \/$i (happens every $MAXSTRINGCOMPONENTS prefixes) ...";
   
   local($file)=$basename.$i;
   
   push(@DONETDBMFILES, $file) if (grep($_ ne $file, @DONETDBMFILES)==scalar(@DONETDBMFILES));
   
   open(FLUSHCLKEYS, ">>".$file);
      
   &fatalerror("in donetdbm flushclkeys: cannot write $file . code: $!") if ( ! print FLUSHCLKEYS $list[$i] );
      
   close(FLUSHCLKEYS);
   
   #print STDERR " done\n";
   
   $list[$i]="";
   
   $notexistingprefixes[$i]=0;
      
   return 0;
   
}


sub donetdbm {
    local($filename, $todir, $options)=@_;
    
    local($type,$uniquekey,$offset,$prefix);
    local($NEWDIR, $newfilename);
    
    local($classless)=$options & $CLASSLESSOPTION;
    local($cleaning)=$options & $CLEANOPTION;
    
    local(%entry);
    
    local($objseen) = 0;
    local($processed)=0;
    local(@clalist)=();
    local(@classlesslist)=();
    local(@nrofentries)=();
    local(%normalkeys)=();
    
    local(@notexistingprefixes)=(1) x 129;

    local(@keys,@otherkeys,@pointsto,@otherpointsto,@classless);
    
    local($basename);
    
    local($newdb)='newdb';
    local(%newdb,@newdb);
    
    local(%nothing)=();
    local($lockfile);
    
    # print STDERR "donetdbm - file: $filename classless: $classless cleaning: $cleaning\n";

    if ($cleaning) {
       
       ($NEWDIR, $newfilename)= $filename=~ /$SPLITFILENAME/o;
       
       $lockfile=$CLEANLOCK.$newfilename;
       
       # We will create a temporary directory to build the new database and
       # indexes.
                   
       $NEWDIR.=$newfilename."-newindex.".$$;
       $newfilename=$NEWDIR."\/".$newfilename;
       
       &fatalerror("Temporary directory \"$NEWDIR\" already exists!") if (-d $NEWDIR);
               
       if (!mkdir($NEWDIR, 0750)) {
          &fatalerror("Failed to create temporary directory ($!): $NEWDIR");
       }
       
       &dbopen(*newdb, *nothing, 1, $newfilename ) || &fatalerror("Cannot open $newfilename");
       seek($newdb,0,2);
       
       
       
    }
    else {
       
       &delormoveindices($filename, "", $classless);
       
    }
    
    local(%dummyobject)=();
    
    local($db)='db';
    local(%db,@db);
    &dbopen(*db,*dummyobject,1,$filename) || &fatalerror("$0: error opening database: $filename");
    seek($db,0,0);
    
    if ($cleaning) {
       
       &fatalerror("lockfile: $lockfile already exists") if (-e $lockfile);
       
       push(@DONETDBMFILES, $lockfile);
       
       open(LOCKFILE, ">".$lockfile) || &fatalerror("cannot create $lockfile"); 
       print LOCKFILE "$$\n";
       close(LOCKFILE);
       
    }
    
    if ($classless) {
       
      local($prefixfilename) = $filename;

      $prefixfilename =~ s!.*/!!;     # remove path
      $prefixfilename =~ s:\s*$::;    # remove trailing spaces

       
      $basename=$TMPDIR."/prefixdata.".$$.".".$prefixfilename;
       
      #
      # remove any possible left over files...

      local(@files)=();
    
      for $i (0..128) {
	push(@files, $basename.$i) if (-f $basename.$i);
      }
    
      unlink(@files) if (@files);
    
    }
    
    #
    # exit clean on a signal (except for the cleaning files, I am sorry
    #                         no time for the real fancy stuff)
    
    $SIG{'INT'}='quitdonetdbm';
    $SIG{'KILL'}='quitdonetdbm';
    $SIG{'TERM'}='quitdonetdbm'; 
    
    @OPENNETDBMFILES=();
    
    #
    # start gathering the keys...

    while ($type=&enread($db, *entry, -1)) {
        
        next if (!$OBJATSQ{$type});

        # print STDERR "type: $type\n";
         
	# $objseen++;
	# if ($objseen % 400 == 0) {
	#    print STDERR "donetdbm - indexed $objseen\n";
	# }
	# print STDERR "entry ($type): ", $entry{$type}, "\n";
	# print STDERR "\noffset: ", $entry{"offset"};
	# print STDERR "\nuniquekey: $uniquekey\n";
	
        
	if ($cleaning) {
	   
	   $offset=&enwrite($newdb,*entry);
	
	}
	else {
	
	   $offset=$entry{"offset"};
	   
	}   
	
	&enkeys(*entry, $OBJKEYS{$type}, *keys, *otherkeys, *pointsto, *otherpointsto, *classless, $classless);
	
	$processed+=grep((($normalkeys{$_}) && 
	                  ($normalkeys{$_}=join("\,", $normalkeys{$_}, $offset))) ||
	                 ($normalkeys{$_}=$offset), ($uniquekey=&enukey(*entry, $type)), 
	                                            @keys,
	                                            @otherkeys,
	                                            @pointsto,
	                                            @otherpointsto);
	
	if ($cleaning) {
	   
	   $processed=&flushkeys(*newdb, *normalkeys) if ($processed>$MAXLISTLENGTH);
	   
  	}
  	else {
  	
  	   $processed=&flushkeys(*db, *normalkeys) if ($processed>$MAXLISTLENGTH);
  	   
  	}
  	
  	if ($classless) {
  	    
  	   foreach (@classless) {
        
  	      /^(.+)\/(\d+)$/;
  	      $classlesslist[$2]=join("", $classlesslist[$2], $1, "\%", $uniquekey, "\%");
  	         
	      $nrofentries[$2]=&flushclkeys($basename,$2,*classlesslist,*notexistingprefixes) if (++$nrofentries[$2]>$MAXSTRINGCOMPONENTS);
	         
  	   }
  	   
        }

    }
    
    #
    # note: we do the wait for a child flush *after* the classless indexing
    #       it might save us some extra time!
    
    if ($cleaning) {
       &flushkeys(*newdb, *normalkeys);
    }
    else {
       &flushkeys(*db, *normalkeys);
    }
    
    #
    # now we start with the classless indices
    
    if ($classless) {
       
       undef %normalkeys;
       
       print STDERR "docldbm - start inserting prefixes\n" if $opt_V;

       if ($cleaning) {
          &dbclopen(*dummyobject,1,$newfilename);
       }
       else {
          &delormoveindices($filename.$CLASSLESSEXT, "", $classless);
          &dbclopen(*dummyobject,1,$filename);
       }
       
       $objseen = 0;
       
       local($entry);
       local($p,$i,$cla,$newcla);
       
       
       
       
       #
       # we need to read entries separated by '%' characters
       # 
       # trickey: we don't need to reset it for:
       #
       # flushclkeys, getvalues, addkey since
       # they are not affected.
       
       local($/)="\%";
       
       for $i (1..128) {
	
	   print STDERR "try $basename$i\n" if ($opt_V);
	   
	   #
	   # flush first the remaining prefixes...
	
	   &flushclkeys($basename, $i, *classlesslist, *notexistingprefixes) if ($nrofentries[$i]);
	   
	   next if ($notexistingprefixes[$i]);
	   
	   undef $nrofentries[$i];
	
	   if (open(FLUSHCLKEYS, "<".$basename.$i)) {
	   
	      print STDERR "opened $basename$i\n" if ($opt_V);
	      
	      $entry=1;
	      
	      while ($entry) {
	   
	         print STDERR "read from $basename$i\n" if ($opt_V);
	   
	         $processed=0;
	   
	         undef @clalist;
	         
	         while (($processed<$MAXLISTLENGTH) && ($entry=<FLUSHCLKEYS>)) {
	         
	             $key=<FLUSHCLKEYS>;
	             
	             chop($entry);
	             chop($key);
	         
                     push(@clalist,$entry,$key);	   
	   
	             $processed++;
	   
	         }
	         
	         while ($prefix=shift(@clalist)) {
	            
	            # print STDERR $prefix, "\n";
	            
	            if (&addkey(*mspnxl, ($cla=join("\/", $prefix, $i)), shift(@clalist))) {
	            
                       for ($p=$i-1;
                            ($p>0) &&
                            (($notexistingprefixes[$p]) ||
                            (!defined($mspnxl{($newcla=join("\/", &iprightzeromask($prefix,$p), $p))})));
                            $p--) {};
    
                       # print STDERR $p, " ",$i, " ", $newcla, "\n";
    
                       &addkey(*mspnxl, $p>0?$newcla:"0\/0", $cla);
                       
                    }
                       
	         }
	         
	      }
	   
	      close(FLUSHCLKEYS);
	   
	      push(@DONETDBMFILES, $basename.$i);
	   
	   }
	   else {
	     
	      &fatalerror("Couldn\'t open prefix file: $basename$i ($!)");
	   
	   }
       
       }
       unlink(@DONETDBMFILES) if (@DONETDBMFILES);           
       
       #
       # make the not existing prefixes array
       
       $mspnxl{$NOTEXISTINGPREFIXESKEY}=join(" ", @notexistingprefixes);
       
       &dbclclose();
           
    }
    
    #
    # we might still have a child process for the normal keys...
    
    # wait;
    
    # print STDERR "closing\n";
        
    if ($todir) {
       
       ($NEWDIR, $newfilename)= $filename=~ /$SPLITFILENAME/o;
                  
       $lockfile=$CLEANLOCK.$newfilename; 
                   
       &fatalerror("lockfile: $lockfile already exists") if (-e $lockfile);
       
       #
       # we don't accept termination of the program from here
       
       @DONETDBMFILES=();
       $SIG{'INT'}='IGNORE';
       $SIG{'KILL'}='IGNORE';
       $SIG{'TERM'}='IGNORE'; 
       
       open(LOCKFILE, ">".$lockfile) || &fatalerror("cannot create $lockfile"); 
       print LOCKFILE "$$\n";
       close(LOCKFILE);
       
       $todir.="\/" if ($todir!~ /\/\s*$/);
       
       &dbclose(*db);
       &delormoveindices($filename.$CLASSLESSEXT, $todir.$newfilename.$CLASSLESSEXT, $classless) if ($classless);
       &delormoveindices($filename, $todir.$newfilename, 0);
          
       unlink($lockfile);
    
    }
    elsif ($cleaning) {
       
       &dbclose(*newdb);
       &dbclose(*db);
       
       #
       # we don't accept termination of the program from here
       
       @DONETDBMFILES=();
       $SIG{'INT'}='IGNORE';
       $SIG{'KILL'}='IGNORE';
       $SIG{'TERM'}='IGNORE'; 
       
       &delormoveindices($newfilename.$CLASSLESSEXT, $filename.$CLASSLESSEXT, $classless) if ($classless);
       &delormoveindices($newfilename, $filename, 0);
       
       unlink($lockfile);
       
       warn "could\'t remove directory ($NEWDIR) errorcode: $!\n" if (!rmdir($NEWDIR));
          
    }
    else {
       # print STDERR "close db\n";
       &dbclose(*db);
       # print STDERR "done db\n";
    }
    
    # print STDERR "done -$/- db\n";
    
    return;
    
    #print STDERR "done db\n";

}

1;
