// -*- mode: cpp; mode: fold -*-
// Description								/*{{{*/
// $Id: pkgcachegen.cc,v 1.26 1998/12/14 03:39:15 jgg Exp $
/* ######################################################################
   
   Package Cache Generator - Generator for the cache structure.
   
   This builds the cache structure from the abstract package list parser. 
   
   ##################################################################### */
									/*}}}*/
// Include Files							/*{{{*/
#ifdef __GNUG__
#pragma implementation "apt-pkg/pkgcachegen.h"
#endif

#include <apt-pkg/pkgcachegen.h>
#include <apt-pkg/error.h>
#include <apt-pkg/version.h>
#include <apt-pkg/progress.h>
#include <apt-pkg/sourcelist.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/deblistparser.h>

#include <strutl.h>

#include <sys/stat.h>
#include <unistd.h>
									/*}}}*/

// CacheGenerator::pkgCacheGenerator - Constructor			/*{{{*/
// ---------------------------------------------------------------------
/* We set the diry flag and make sure that is written to the disk */
pkgCacheGenerator::pkgCacheGenerator(DynamicMMap &Map,OpProgress &Prog) :
                    Map(Map), Cache(Map), Progress(Prog)
{
   if (_error->PendingError() == true)
      return;
   
   if (Map.Size() == 0)
   {
      Map.RawAllocate(sizeof(pkgCache::Header));
      *Cache.HeaderP = pkgCache::Header();
   }
   Cache.HeaderP->Dirty = true;
   Map.Sync(0,sizeof(pkgCache::Header));
   Map.UsePools(*Cache.HeaderP->Pools,sizeof(Cache.HeaderP->Pools)/sizeof(Cache.HeaderP->Pools[0]));
}
									/*}}}*/
// CacheGenerator::~pkgCacheGenerator - Destructor 			/*{{{*/
// ---------------------------------------------------------------------
/* We sync the data then unset the dirty flag in two steps so as to
   advoid a problem during a crash */
pkgCacheGenerator::~pkgCacheGenerator()
{
   if (_error->PendingError() == true)
      return;
   if (Map.Sync() == false)
      return;
   
   Cache.HeaderP->Dirty = false;
   Map.Sync(0,sizeof(pkgCache::Header));
}
									/*}}}*/
// CacheGenerator::MergeList - Merge the package list			/*{{{*/
// ---------------------------------------------------------------------
/* This provides the generation of the entries in the cache. Each loop
   goes through a single package record from the underlying parse engine. */
bool pkgCacheGenerator::MergeList(ListParser &List)
{
   List.Owner = this;

   int Counter = 0;
   while (List.Step() == true)
   {
      // Get a pointer to the package structure
      string PackageName = List.Package();
      pkgCache::PkgIterator Pkg;
      if (NewPackage(Pkg,PackageName) == false)
	 return _error->Error("Error occured while processing %s (NewPackage)",PackageName.c_str());
      Counter++;
      if (Counter % 100 == 0)
	 Progress.Progress(List.Offset());

      /* Get a pointer to the version structure. We know the list is sorted
         so we use that fact in the search. Insertion of new versions is
	 done with correct sorting */
      string Version = List.Version();
      if (Version.empty() == true)
      {
	 if (List.UsePackage(Pkg,pkgCache::VerIterator(Cache)) == false)
	    return _error->Error("Error occured while processing %s (UsePackage1)",PackageName.c_str());
	 continue;
      }

      pkgCache::VerIterator Ver = Pkg.VersionList();
      __apt_ptrloc *Last = &Pkg->VersionList;
      int Res = 1;
      for (; Ver.end() == false; Last = &Ver->NextVer, Ver++)
      {
	 Res = pkgVersionCompare(Version.begin(),Version.end(),Ver.VerStr(),
				 Ver.VerStr() + strlen(Ver.VerStr()));
	 if (Res >= 0)
	    break;
      }
      
      /* We already have a version for this item, record that we
         saw it */
      if (Res == 0)
      {
	 if (List.UsePackage(Pkg,Ver) == false)
	    return _error->Error("Error occured while processing %s (UsePackage2)",PackageName.c_str());
	 
	 if (NewFileVer(Ver,List) == false)
	    return _error->Error("Error occured while processing %s (NewFileVer1)",PackageName.c_str());
	 
	 continue;
      }      

      // Add a new version
      *Last = NewVersion(Ver,Version,*Last);
      Ver->ParentPkg = Pkg.Index();
      if (List.NewVersion(Ver) == false)
	 return _error->Error("Error occured while processing %s (NewVersion1)",PackageName.c_str());

      if (List.UsePackage(Pkg,Ver) == false)
	 return _error->Error("Error occured while processing %s (UsePackage3)",PackageName.c_str());
      
      if (NewFileVer(Ver,List) == false)
	 return _error->Error("Error occured while processing %s (NewVersion2)",PackageName.c_str());
   }

   return true;
}
									/*}}}*/
// CacheGenerator::NewPackage - Add a new package			/*{{{*/
// ---------------------------------------------------------------------
/* This creates a new package structure and adds it to the hash table */
bool pkgCacheGenerator::NewPackage(pkgCache::PkgIterator &Pkg,string Name)
{
   Pkg = Cache.FindPkg(Name);
   if (Pkg.end() == false)
      return true;
       
   // Get a structure
   unsigned long Package = Map.Allocate(sizeof(pkgCache::Package));
   if (Package == 0)
      return false;
   
   Pkg = pkgCache::PkgIterator(Cache,Cache.PkgP + Package);
   
   // Insert it into the hash table
   unsigned long Hash = Cache.Hash(Name);
   Pkg->NextPackage = Cache.HeaderP->HashTable[Hash];
   Cache.HeaderP->HashTable[Hash] = Package;
   
   // Set the name and the ID
   Pkg->Name = Map.WriteString(Name);
   if (Pkg->Name == 0)
      return false;
   Pkg->ID = Cache.HeaderP->PackageCount++;
   
   return true;
}
									/*}}}*/
// CacheGenerator::NewFileVer - Create a new File<->Version association	/*{{{*/
// ---------------------------------------------------------------------
/* */
bool pkgCacheGenerator::NewFileVer(pkgCache::VerIterator &Ver,
				   ListParser &List)
{
   // Get a structure
   unsigned long VerFile = Map.Allocate(sizeof(pkgCache::VerFile));
   if (VerFile == 0)
      return 0;
   
   pkgCache::VerFileIterator VF(Cache,Cache.VerFileP + VerFile);
   VF->File = CurrentFile - Cache.PkgFileP;
   
   // Link it to the end of the list
   __apt_ptrloc *Last = &Ver->FileList;
   for (pkgCache::VerFileIterator V = Ver.FileList(); V.end() == false; V++)
      Last = &V->NextFile;
   VF->NextFile = *Last;
   *Last = VF.Index();
   
   VF->Offset = List.Offset();
   VF->Size = List.Size();
   if (Cache.HeaderP->MaxVerFileSize < VF->Size)
      Cache.HeaderP->MaxVerFileSize = VF->Size;
   Cache.HeaderP->VerFileCount++;
   
   return true;
}
									/*}}}*/
// CacheGenerator::NewVersion - Create a new Version 			/*{{{*/
// ---------------------------------------------------------------------
/* This puts a version structure in the linked list */
unsigned long pkgCacheGenerator::NewVersion(pkgCache::VerIterator &Ver,
					    string VerStr,
					    unsigned long Next)
{
   // Get a structure
   unsigned long Version = Map.Allocate(sizeof(pkgCache::Version));
   if (Version == 0)
      return 0;
   
   // Fill it in
   Ver = pkgCache::VerIterator(Cache,Cache.VerP + Version);
   Ver->NextVer = Next;
   Ver->ID = Cache.HeaderP->VersionCount++;
   Ver->VerStr = Map.WriteString(VerStr);
   if (Ver->VerStr == 0)
      return 0;
   
   return Version;
}
									/*}}}*/
// ListParser::NewDepends - Create a dependency element			/*{{{*/
// ---------------------------------------------------------------------
/* This creates a dependency element in the tree. It is linked to the
   version and to the package that it is pointing to. */
bool pkgCacheGenerator::ListParser::NewDepends(pkgCache::VerIterator Ver,
					       string PackageName,
					       string Version,
					       unsigned int Op,
					       unsigned int Type)
{
   pkgCache &Cache = Owner->Cache;
   
   // Get a structure
   unsigned long Dependency = Owner->Map.Allocate(sizeof(pkgCache::Dependency));
   if (Dependency == 0)
      return false;
   
   // Fill it in
   pkgCache::DepIterator Dep(Cache,Cache.DepP + Dependency);
   Dep->ParentVer = Ver.Index();
   Dep->Type = Type;
   Dep->CompareOp = Op;
   Dep->ID = Cache.HeaderP->DependsCount++;
   
   // Locate the target package
   pkgCache::PkgIterator Pkg;
   if (Owner->NewPackage(Pkg,PackageName) == false)
      return false;
   
   // Probe the reverse dependency list for a version string that matches
   if (Version.empty() == false)
   {
      for (pkgCache::DepIterator I = Pkg.RevDependsList(); I.end() == false; I++)
	 if (I->Version != 0 && I.TargetVer() == Version)
	    Dep->Version = I->Version;
      if (Dep->Version == 0)
	 if ((Dep->Version = WriteString(Version)) == 0)
	    return false;
   }
      
   // Link it to the package
   Dep->Package = Pkg.Index();
   Dep->NextRevDepends = Pkg->RevDepends;
   Pkg->RevDepends = Dep.Index();
   
   /* Link it to the version (at the end of the list)
      Caching the old end point speeds up generation substantially */
   static pkgCache::VerIterator OldVer(Cache);
   static __apt_ptrloc *OldLast;
   if (OldVer != Ver)
   {
      OldLast = &Ver->DependsList;
      for (pkgCache::DepIterator D = Ver.DependsList(); D.end() == false; D++)
	 OldLast = &D->NextDepends;
      OldVer = Ver;
   }
   
   Dep->NextDepends = *OldLast;
   *OldLast = Dep.Index();
   OldLast = &Dep->NextDepends;

   return true;
}
									/*}}}*/
// ListParser::NewProvides - Create a Provides element			/*{{{*/
// ---------------------------------------------------------------------
/* */
bool pkgCacheGenerator::ListParser::NewProvides(pkgCache::VerIterator Ver,
					        string PackageName,
						string Version)
{
   pkgCache &Cache = Owner->Cache;

   // We do not add self referencing provides
   if (Ver.ParentPkg().Name() == PackageName)
      return true;
   
   // Get a structure
   unsigned long Provides = Owner->Map.Allocate(sizeof(pkgCache::Provides));
   if (Provides == 0)
      return false;
   Cache.HeaderP->ProvidesCount++;
   
   // Fill it in
   pkgCache::PrvIterator Prv(Cache,Cache.ProvideP + Provides,Cache.PkgP);
   Prv->Version = Ver.Index();
   Prv->NextPkgProv = Ver->ProvidesList;
   Ver->ProvidesList = Prv.Index();
   if (Version.empty() == false && (Prv->Version = WriteString(Version)) == 0)
      return false;
   
   // Locate the target package
   pkgCache::PkgIterator Pkg;
   if (Owner->NewPackage(Pkg,PackageName) == false)
      return false;
   
   // Link it to the package
   Prv->ParentPkg = Pkg.Index();
   Prv->NextProvides = Pkg->ProvidesList;
   Pkg->ProvidesList = Prv.Index();
   
   return true;
}
									/*}}}*/
// CacheGenerator::SelectFile - Select the current file being parsed	/*{{{*/
// ---------------------------------------------------------------------
/* This is used to select which file is to be associated with all newly
   added versions. */
bool pkgCacheGenerator::SelectFile(string File,unsigned long Flags)
{
   struct stat Buf;
   if (stat(File.c_str(),&Buf) == -1)
      return _error->Errno("stat","Couldn't stat ",File.c_str());
   
   // Get some space for the structure
   CurrentFile = Cache.PkgFileP + Map.Allocate(sizeof(*CurrentFile));
   if (CurrentFile == Cache.PkgFileP)
      return false;
   
   // Fill it in
   CurrentFile->FileName = Map.WriteString(File);
   CurrentFile->Size = Buf.st_size;
   CurrentFile->mtime = Buf.st_mtime;
   CurrentFile->NextFile = Cache.HeaderP->FileList;
   CurrentFile->Flags = Flags;
   CurrentFile->ID = Cache.HeaderP->PackageFileCount;
   PkgFileName = File;
   Cache.HeaderP->FileList = CurrentFile - Cache.PkgFileP;
   Cache.HeaderP->PackageFileCount++;
      
   if (CurrentFile->FileName == 0)
      return false;
   
   Progress.SubProgress(Buf.st_size);
   return true;
}
									/*}}}*/
// CacheGenerator::WriteUniqueString - Insert a unique string		/*{{{*/
// ---------------------------------------------------------------------
/* This is used to create handles to strings. Given the same text it
   always returns the same number */
unsigned long pkgCacheGenerator::WriteUniqString(const char *S,
						 unsigned int Size)
{
   // Search for an insertion point
   pkgCache::StringItem *I = Cache.StringItemP + Cache.HeaderP->StringList;
   int Res = 1;
   __apt_ptrloc *Last = &Cache.HeaderP->StringList;
   for (; I != Cache.StringItemP; Last = &I->NextItem, 
        I = Cache.StringItemP + I->NextItem)
   {
      Res = stringcmp(S,S+Size,Cache.StrP + I->String);
      if (Res >= 0)
	 break;
   }
   
   // Match
   if (Res == 0)
      return I->String;
   
   // Get a structure
   unsigned long Item = Map.Allocate(sizeof(pkgCache::StringItem));
   if (Item == 0)
      return 0;

   // Fill in the structure
   pkgCache::StringItem *ItemP = Cache.StringItemP + Item;
   ItemP->NextItem = I - Cache.StringItemP;
   *Last = Item;
   ItemP->String = Map.WriteString(S,Size);
   if (ItemP->String == 0)
      return 0;
   
   return ItemP->String;
}
									/*}}}*/

// SrcCacheCheck - Check if the source package cache is uptodate	/*{{{*/
// ---------------------------------------------------------------------
/* The source cache is checked against the source list and the files 
   on disk, any difference results in a false. */
bool pkgSrcCacheCheck(pkgSourceList &List)
{
   if (_error->PendingError() == true)
      return false;

   string CacheFile = _config->FindFile("Dir::Cache::srcpkgcache");
   string ListDir = _config->FindDir("Dir::State::lists");

   // Count the number of missing files
   int Missing = 0;
   for (pkgSourceList::const_iterator I = List.begin(); I != List.end(); I++)
   {
      string File = ListDir + URItoFileName(I->PackagesURI());
      struct stat Buf;
      if (stat(File.c_str(),&Buf) != 0)
      {
	 _error->WarningE("stat","Couldn't stat source package list '%s' (%s)",
			  I->PackagesInfo().c_str(),File.c_str());	 
	 Missing++;
      }      
   }
      
   // Open the source package cache
   if (FileExists(CacheFile) == false)
      return false;
   
   FileFd CacheF(CacheFile,FileFd::ReadOnly);
   if (_error->PendingError() == true)
   {
      _error->Discard();
      return false;
   }
   
   MMap Map(CacheF,MMap::Public | MMap::ReadOnly);
   if (_error->PendingError() == true || Map.Size() == 0)
   {
      _error->Discard();
      return false;
   }
   
   pkgCache Cache(Map);
   if (_error->PendingError() == true)
   {
      _error->Discard();
      return false;
   }

   // They are certianly out of sync
   if (Cache.Head().PackageFileCount != List.size() - Missing)
      return false;
   
   for (pkgCache::PkgFileIterator F(Cache); F.end() == false; F++)
   {
      // Search for a match in the source list
      bool Bad = true;
      for (pkgSourceList::const_iterator I = List.begin(); 
	   I != List.end(); I++)
      {
	 string File = ListDir + URItoFileName(I->PackagesURI());
	 if (F.FileName() == File)
	 {
	    Bad = false;
	    break;
	 }
      }
      
      // Check if the file matches what was cached
      Bad |= !F.IsOk();
      if (Bad == true)
	 return false;
   }
   
   return true;
}
									/*}}}*/
// PkgCacheCheck - Check if the package cache is uptodate		/*{{{*/
// ---------------------------------------------------------------------
/* This does a simple check of all files used to compose the cache */
bool pkgPkgCacheCheck(string CacheFile)
{
   if (_error->PendingError() == true)
      return false;
   
   // Open the source package cache
   if (FileExists(CacheFile) == false)
      return false;
   
   FileFd CacheF(CacheFile,FileFd::ReadOnly);
   if (_error->PendingError() == true)
   {
      _error->Discard();
      return false;
   }
   
   MMap Map(CacheF,MMap::Public | MMap::ReadOnly);
   if (_error->PendingError() == true || Map.Size() == 0)
   {
      _error->Discard();
      return false;
   }
   
   pkgCache Cache(Map);
   if (_error->PendingError() == true)
   {
      _error->Discard();
      return false;
   }

   // Status files that must be in the cache
   string Status[3];
   Status[0] = _config->FindFile("Dir::State::xstatus");
   Status[1]= _config->FindFile("Dir::State::userstatus");
   Status[2] = _config->FindFile("Dir::State::status");
   
   // Cheack each file
   for (pkgCache::PkgFileIterator F(Cache); F.end() == false; F++)
   {
      if (F.IsOk() == false)
	 return false;
      
      // See if this is one of the status files
      for (int I = 0; I != 3; I++)
	 if (F.FileName() == Status[I])
	    Status[I] = string();
   }
   
   // Make sure all the status files are loaded.
   for (int I = 0; I != 3; I++)
   {
      if (Status[I].empty() == false && FileExists(Status[I]) == true)
	 return false;
   }   
   
   return true;
}
									/*}}}*/
// AddSourcesSize - Add the size of the status files			/*{{{*/
// ---------------------------------------------------------------------
/* This adds the size of all the status files to the size counter */
static bool pkgAddSourcesSize(unsigned long &TotalSize)
{
   // Grab the file names
   string xstatus = _config->FindFile("Dir::State::xstatus");
   string userstatus = _config->FindFile("Dir::State::userstatus");
   string status = _config->FindFile("Dir::State::status");
   
   // Grab the sizes
   struct stat Buf;
   if (stat(xstatus.c_str(),&Buf) == 0)
      TotalSize += Buf.st_size;
   if (stat(userstatus.c_str(),&Buf) == 0)
      TotalSize += Buf.st_size;
   if (stat(status.c_str(),&Buf) != 0)
      return _error->Errno("stat","Couldn't stat the status file %s",status.c_str());
   TotalSize += Buf.st_size;
   
   return true;
}
									/*}}}*/
// MergeStatus - Add the status files to the cache			/*{{{*/
// ---------------------------------------------------------------------
/* This adds the status files to the map */
static bool pkgMergeStatus(OpProgress &Progress,pkgCacheGenerator &Gen,
			   unsigned long &CurrentSize,unsigned long TotalSize)
{
   // Grab the file names   
   string Status[3];
   Status[0] = _config->FindFile("Dir::State::xstatus");
   Status[1]= _config->FindFile("Dir::State::userstatus");
   Status[2] = _config->FindFile("Dir::State::status");
   
   for (int I = 0; I != 3; I++)
   {
      // Check if the file exists and it is not the primary status file.
      string File = Status[I];
      if (I != 2 && FileExists(File) == false)
	 continue;
	 
      FileFd Pkg(File,FileFd::ReadOnly);
      debListParser Parser(Pkg);
      Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),"Reading Package Lists");
      if (_error->PendingError() == true)
	 return _error->Error("Problem opening %s",File.c_str());
      CurrentSize += Pkg.Size();

      Progress.SubProgress(0,"Local Package State - " + flNotDir(File));
      if (Gen.SelectFile(File,pkgCache::Flag::NotSource) == false)
	 return _error->Error("Problem with SelectFile %s",File.c_str());
      
      if (Gen.MergeList(Parser) == false)
	 return _error->Error("Problem with MergeList %s",File.c_str());
      Progress.Progress(Pkg.Size());
   }
   
   return true;
}
									/*}}}*/
// MakeStatusCache - Generates a cache that includes the status files	/*{{{*/
// ---------------------------------------------------------------------
/* This copies the package source cache and then merges the status and 
   xstatus files into it. */
bool pkgMakeStatusCache(pkgSourceList &List,OpProgress &Progress)
{
   Progress.OverallProgress(0,1,1,"Reading Package Lists");
   
   string CacheFile = _config->FindFile("Dir::Cache::pkgcache");
   bool SrcOk = pkgSrcCacheCheck(List);
   bool PkgOk = SrcOk && pkgPkgCacheCheck(CacheFile);
   
   // Rebuild the source and package caches   
   if (SrcOk == false)
   {      
      string SCacheFile = _config->FindFile("Dir::Cache::srcpkgcache");
      string ListDir = _config->FindDir("Dir::State::lists");
      FileFd SCacheF(SCacheFile,FileFd::WriteEmpty);
      FileFd CacheF(CacheFile,FileFd::WriteEmpty);
      DynamicMMap Map(CacheF,MMap::Public);
      if (_error->PendingError() == true)
	 return false;
      
      pkgCacheGenerator Gen(Map,Progress);
      
      // Prepare the progress indicator
      unsigned long TotalSize = 0;
      struct stat Buf;
      for (pkgSourceList::const_iterator I = List.begin(); I != List.end(); I++)
      {
	 string File = ListDir + URItoFileName(I->PackagesURI());
	 if (stat(File.c_str(),&Buf) != 0)
	    continue;
	 TotalSize += Buf.st_size;
      }
      
      if (pkgAddSourcesSize(TotalSize) == false)
	 return false;
      
      // Generate the pkg source cache
      unsigned long CurrentSize = 0;
      for (pkgSourceList::const_iterator I = List.begin(); I != List.end(); I++)
      {
	 string File = ListDir + URItoFileName(I->PackagesURI());
	 
	 if (FileExists(File) == false)
	    continue;
	 
	 FileFd Pkg(File,FileFd::ReadOnly);
	 debListParser Parser(Pkg);
	 Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),"Reading Package Lists");
	 if (_error->PendingError() == true)
	    return _error->Error("Problem opening %s",File.c_str());
	 CurrentSize += Pkg.Size();

	 Progress.SubProgress(0,I->PackagesInfo());
	 if (Gen.SelectFile(File) == false)
	    return _error->Error("Problem with SelectFile %s",File.c_str());
	 
	 if (Gen.MergeList(Parser) == false)
	    return _error->Error("Problem with MergeList %s",File.c_str());

	 // Check the release file
	 string RFile = ListDir + URItoFileName(I->ReleaseURI());
	 if (FileExists(RFile) == true)
	 {
	    FileFd Rel(RFile,FileFd::ReadOnly);
	    if (_error->PendingError() == true)
	       return false;
	    Parser.LoadReleaseInfo(Gen.GetCurFile(),Rel);
	 }
      }	       
      
      // Write the src cache
      Gen.GetCache().HeaderP->Dirty = false;
      if (SCacheF.Write(Map.Data(),Map.Size()) == false)
	 return _error->Error("IO Error saving source cache");
      Gen.GetCache().HeaderP->Dirty = true;
      
      // Merge in the source caches
      return pkgMergeStatus(Progress,Gen,CurrentSize,TotalSize);
   }

   if (PkgOk == true)
   {
      Progress.OverallProgress(1,1,1,"Reading Package Lists");      
      return true;
   }
   
   // We use the source cache to generate the package cache
   string SCacheFile = _config->FindFile("Dir::Cache::srcpkgcache");

   FileFd SCacheF(SCacheFile,FileFd::ReadOnly);
   FileFd CacheF(CacheFile,FileFd::WriteEmpty);
   DynamicMMap Map(CacheF,MMap::Public);
   if (_error->PendingError() == true)
      return false;
   
   // Preload the map with the source cache
   if (SCacheF.Read((unsigned char *)Map.Data() + Map.RawAllocate(SCacheF.Size()),
		    SCacheF.Size()) == false)
      return false;
      
   pkgCacheGenerator Gen(Map,Progress);
   
   // Compute the progress
   unsigned long TotalSize = 0;
   if (pkgAddSourcesSize(TotalSize) == false)
      return false;

   unsigned long CurrentSize = 0;
   return pkgMergeStatus(Progress,Gen,CurrentSize,TotalSize);
}
									/*}}}*/