]> git.decadent.org.uk Git - dak.git/blobdiff - tools/dsync-0.0/libdsync/compare.cc
Added another tool used in dak (and placed nowhere else), dsync
[dak.git] / tools / dsync-0.0 / libdsync / compare.cc
diff --git a/tools/dsync-0.0/libdsync/compare.cc b/tools/dsync-0.0/libdsync/compare.cc
new file mode 100644 (file)
index 0000000..95a286b
--- /dev/null
@@ -0,0 +1,608 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: compare.cc,v 1.6 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+   
+   Compare a file list with a local directory
+   
+   The first step in the compare is to read the names of each entry
+   in the local directory into ram. This list is the first step to
+   creating a delete list. Next we begin scanning the file list, checking
+   each entry against the dir contents, if a match is found it is removed
+   from the dir list and then stat'd to verify against the file list
+   contents. If no match is found then the entry is marked for download.
+   When done the local directory in ram will only contain entries that 
+   need to be erased.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/compare.h"
+#endif
+
+#include <dsync/compare.h>
+#include <dsync/error.h>
+#include <dsync/fileutl.h>
+#include <dsync/md5.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <utime.h>
+#include <stdio.h>
+                                                                       /*}}}*/
+
+// DirCompre::dsDirCompare - Constructor                               /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsDirCompare::dsDirCompare() : IndexSize(0), IndexAlloc(0), Indexes(0),
+                     NameAlloc(0), Names(0), Verify(true), HashLevel(Md5Date)
+{
+   IndexAlloc = 1000;
+   Indexes = (unsigned int *)malloc(sizeof(*Indexes)*IndexAlloc);
+   NameAlloc = 4096*5;
+   Names = (char *)malloc(sizeof(*Names)*NameAlloc);
+   if (Names == 0 || Indexes == 0)
+      _error->Error("Cannot allocate memory");
+}
+                                                                       /*}}}*/
+// DirCompare::~dsDirCompare - Destructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsDirCompare::~dsDirCompare()
+{
+   free(Names);
+   free(Indexes);
+}
+                                                                       /*}}}*/
+// DirCompare::LoadDir - Load all the names in the directory           /*{{{*/
+// ---------------------------------------------------------------------
+/* Such in every name in the directory, we store them as a packed, indexed
+   array of strings */
+bool dsDirCompare::LoadDir()
+{
+   // Scan the directory
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+      return _error->Errno("opendir","Unable to open directory %s",SafeGetCWD().c_str());
+   struct dirent *Ent;
+   IndexSize = 0;
+   char *End = Names + 1;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      // Grab some more bytes in the name allocation
+      if ((unsigned)(NameAlloc - (End - Names)) <= strlen(Ent->d_name)+1)
+      {
+        unsigned long OldEnd = End - Names;
+        char *New = (char *)realloc(Names,sizeof(*Names)*NameAlloc + 4*4096);
+        if (New == 0)
+        {
+           closedir(DirSt);
+           return _error->Error("Cannot allocate memory");
+        }
+        
+        Names = New;
+        NameAlloc += 4*4096;
+        End = Names + OldEnd;
+      }
+      
+      // Grab some more bytes in the index allocation
+      if (IndexSize >= IndexAlloc)
+      {
+        unsigned int *New = (unsigned int *)realloc(Indexes,
+                            sizeof(*Indexes)*IndexAlloc + 1000);
+        if (New == 0)
+        {   
+           closedir(DirSt);
+           return _error->Error("Cannot allocate memory");
+        }
+        
+        Indexes = New;
+        IndexAlloc += 4*4096;
+      }
+      
+      // Store it
+      Indexes[IndexSize] = End - Names;
+      IndexSize++;
+      strcpy(End,Ent->d_name);
+      End += strlen(End) + 1;
+   }
+   
+   closedir(DirSt);
+   return true;
+}
+                                                                       /*}}}*/
+// DirCompare::Process - Process the file list stream                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This scans over the dirs from the IO and decides what to do with them */
+bool dsDirCompare::Process(string Base,dsFList::IO &IO)
+{
+   // Setup the queues and store the current directory
+   string StartDir = SafeGetCWD();
+   
+   // Change to the base directory
+   if (chdir(Base.c_str()) != 0)
+      return _error->Errno("chdir","Could not change to %s",Base.c_str());
+   Base = SafeGetCWD();
+   this->Base = Base;
+   
+   string CurDir;
+   dsFList List;
+   bool Missing = false;
+   while (List.Step(IO) == true)
+   {
+      if (Visit(List,CurDir) == false)
+        return false;
+      
+      switch (List.Tag)
+      {
+        // Handle a forward directory reference
+        case dsFList::tDirMarker:
+        {
+           // Ingore the root directory
+           if (List.Entity->Name.empty() == true)
+              continue;
+           
+           char S[1024];
+
+           snprintf(S,sizeof(S),"%s%s",Base.c_str(),List.Entity->Name.c_str());
+           
+           /* We change the path to be absolute for the benifit of the 
+              routines below */
+           List.Entity->Name = S;
+           
+           // Stat the marker dir
+           struct stat St;
+           bool Res;
+           if (lstat(S,&St) != 0)
+              Res = Fetch(List,string(),0);
+           else
+              Res = Fetch(List,string(),&St);
+
+           if (Res == false)
+              return false;
+           break;
+        }
+        
+        // Start a directory
+        case dsFList::tDirStart:
+        {          
+           if (DoDelete(CurDir) == false)
+              return false;
+           if (chdir(Base.c_str()) != 0)
+              return _error->Errno("chdir","Could not change to %s",Base.c_str());
+           
+           CurDir = List.Dir.Name;
+           Missing = false;
+           IndexSize = 0;
+           if (List.Dir.Name.empty() == false)
+           {
+              /* Instead of erroring out we just mark them as missing and
+                 do not re-stat. This is to support the verify mode, the
+                 actual downloader should never get this. */
+              if (chdir(List.Dir.Name.c_str()) != 0)
+              {
+                 if (Verify == false)
+                    return _error->Errno("chdir","Unable to cd to %s%s.",Base.c_str(),List.Dir.Name.c_str());
+                 Missing = true;
+              }               
+           }
+           
+           if (Missing == false)
+              LoadDir();
+           break;
+        }
+        
+        // Finalize the directory
+        case dsFList::tDirEnd:
+        {
+           if (DoDelete(CurDir) == false)
+              return false;
+           IndexSize = 0;
+           if (chdir(Base.c_str()) != 0)
+              return _error->Errno("chdir","Could not change to %s",Base.c_str());
+           break;
+        }       
+      }
+      
+      // We have some sort of normal entity
+      if (List.Entity != 0 && List.Tag != dsFList::tDirMarker &&
+         List.Tag != dsFList::tDirStart)
+      {
+        // See if it exists, if it does then stat it
+        bool Res = true;
+        if (Missing == true || DirExists(List.Entity->Name) == false)
+           Res = Fetch(List,CurDir,0);
+        else
+        {
+           struct stat St;
+           if (lstat(List.Entity->Name.c_str(),&St) != 0)
+              Res = Fetch(List,CurDir,0);
+           else
+              Res = Fetch(List,CurDir,&St);
+        }
+        if (Res == false)
+           return false;
+      }
+      
+      // Fini
+      if (List.Tag == dsFList::tTrailer)
+      {
+        if (DoDelete(CurDir) == false)
+           return false;
+        return true;
+      }      
+   }
+   
+   return false;
+}
+                                                                       /*}}}*/
+// DirCompare::DoDelete - Delete files in the delete list              /*{{{*/
+// ---------------------------------------------------------------------
+/* The delete list is created by removing names that were found till only
+   extra names remain */
+bool dsDirCompare::DoDelete(string Dir)
+{
+   for (unsigned int I = 0; I != IndexSize; I++)
+   {
+      if (Indexes[I] == 0)
+        continue;
+      if (Delete(Dir,Names + Indexes[I]) == false)
+        return false;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCompare::Fetch - Fetch an entity                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This examins an entry to see what sort of fetch should be done. There
+   are three sorts, 
+     New - There is no existing data
+     Changed - There is existing data
+     Meta - The data is fine but the timestamp/owner/perms might not be */
+bool dsDirCompare::Fetch(dsFList &List,string Dir,struct stat *St)
+{
+   if (List.Tag != dsFList::tNormalFile && List.Tag != dsFList::tDirectory &&
+       List.Tag != dsFList::tSymlink && List.Tag != dsFList::tDeviceSpecial &&
+       List.Tag != dsFList::tDirMarker)
+      return _error->Error("dsDirCompare::Fetch called for an entity "
+                          "that it does not understand");
+   
+   // This is a new entitiy
+   if (St == 0)
+      return GetNew(List,Dir);
+
+   /* Check the types for a mis-match, if they do not match then 
+      we have to erase the entity and get a new one */
+   if ((S_ISREG(St->st_mode) != 0 && List.Tag != dsFList::tNormalFile) ||
+       (S_ISDIR(St->st_mode) != 0 && (List.Tag != dsFList::tDirectory && 
+                                     List.Tag != dsFList::tDirMarker)) ||
+       (S_ISLNK(St->st_mode) != 0 && List.Tag != dsFList::tSymlink) ||
+       ((S_ISCHR(St->st_mode) != 0 || S_ISBLK(St->st_mode) != 0 || 
+        S_ISFIFO(St->st_mode) != 0) && List.Tag != dsFList::tDeviceSpecial))
+   {
+      return Delete(Dir,List.Entity->Name.c_str(),true) && GetNew(List,Dir);
+   }
+   
+   // First we check permissions and mod time
+   bool ModTime = (signed)(List.Entity->ModTime + List.Head.Epoch) == St->st_mtime;
+   bool Perm = true;
+   if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+      Perm = List.Entity->Permissions == (unsigned)(St->st_mode & ~S_IFMT);
+      
+   // Normal file
+   if (List.Tag == dsFList::tNormalFile)
+   {
+      // Size mismatch is an immedate fail
+      if (List.NFile.Size != (unsigned)St->st_size)
+        return GetChanged(List,Dir);
+
+      // Try to check the stored MD5
+      if (HashLevel == Md5Always || 
+         (HashLevel == Md5Date && ModTime == false))
+      {
+        if ((List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5) != 0)
+        {
+           if (CheckHash(List,Dir,List.NFile.MD5) == true)
+              return FixMeta(List,Dir,*St);
+           else
+              return GetChanged(List,Dir);
+        }       
+      }
+      
+      // Look at the modification time
+      if (ModTime == true)
+        return FixMeta(List,Dir,*St);
+      return GetChanged(List,Dir);
+   }
+
+   // Check symlinks
+   if (List.Tag == dsFList::tSymlink)
+   {
+      char Buf[1024];
+      int Res = readlink(List.Entity->Name.c_str(),Buf,sizeof(Buf));
+      if (Res > 0)
+        Buf[Res] = 0;
+      
+      // Link is invalid
+      if (Res < 0 || List.SLink.To != Buf)
+        return GetNew(List,Dir);
+      
+      return FixMeta(List,Dir,*St);
+   }
+
+   // Check directories and dev special files
+   if (List.Tag == dsFList::tDirectory || List.Tag == dsFList::tDeviceSpecial ||
+       List.Tag == dsFList::tDirMarker)
+      return FixMeta(List,Dir,*St);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCompare::DirExists - See if the entry exists in our dir table    /*{{{*/
+// ---------------------------------------------------------------------
+/* We look at the dir table for one that exists */
+bool dsDirCompare::DirExists(string Name)
+{
+   for (unsigned int I = 0; I != IndexSize; I++)
+   {
+      if (Indexes[I] == 0)
+        continue;
+      if (Name == Names + Indexes[I])
+      {
+        Indexes[I] = 0;
+        return true;
+      }
+   }
+   return false;
+}
+                                                                       /*}}}*/
+// DirCompare::CheckHash - Check the MD5 of a entity                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This is invoked to see of the local file we have is the file the remote
+   says we should have. */
+bool dsDirCompare::CheckHash(dsFList &List,string Dir,unsigned char MD5[16])
+{
+   // Open the file
+   MD5Summation Sum;
+   FileFd Fd(List.Entity->Name,FileFd::ReadOnly);
+   if (_error->PendingError() == true)
+      return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
+                          List.Entity->Name.c_str());
+
+   if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
+      return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
+                          List.Entity->Name.c_str());
+
+   unsigned char MyMD5[16];
+   Sum.Result().Value(MyMD5);
+
+   return memcmp(MD5,MyMD5,sizeof(MyMD5)) == 0;
+}
+                                                                       /*}}}*/
+// DirCompare::FixMeta - Fix timestamps, ownership and permissions     /*{{{*/
+// ---------------------------------------------------------------------
+/* This checks if it is necessary to correct the timestamps, ownership and
+   permissions of an entity */
+bool dsDirCompare::FixMeta(dsFList &List,string Dir,struct stat &St)
+{   
+   // Check the mod time
+   if (List.Tag != dsFList::tSymlink)
+   {
+      if ((signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime)
+        if (SetTime(List,Dir) == false)
+           return false;
+   
+      // Check the permissions
+      if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+      {
+        if (List.Entity->Permissions != (St.st_mode & ~S_IFMT))
+           if (SetPerm(List,Dir) == false)
+              return false;
+      }
+   }
+      
+   return true;
+}
+                                                                       /*}}}*/
+
+// DirCorrect::GetNew - Create a new entry                             /*{{{*/
+// ---------------------------------------------------------------------
+/* We cannot create files but we do generate everything else. */
+bool dsDirCorrect::GetNew(dsFList &List,string Dir)
+{
+   if (List.Tag == dsFList::tDirectory)
+   {
+      unsigned long PermDir = 0666;
+      if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+        PermDir = List.Entity->Permissions;
+        
+      if (mkdir(List.Entity->Name.c_str(),PermDir) != 0)
+        return _error->Errno("mkdir","Unable to create directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      // Stat the newly created file for FixMeta's benifit
+      struct stat St;
+      if (lstat(List.Entity->Name.c_str(),&St) != 0)
+        return _error->Errno("stat","Unable to stat directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      return FixMeta(List,Dir,St);
+   }
+
+   if (List.Tag == dsFList::tSymlink)
+   {
+      if (symlink(List.SLink.To.c_str(),List.Entity->Name.c_str()) != 0)
+        return _error->Errno("symlink","Unable to create symlink, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      // Stat the newly created file for FixMeta's benifit
+      struct stat St;
+      if (lstat(List.Entity->Name.c_str(),&St) != 0)
+        return _error->Errno("stat","Unable to stat directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      return FixMeta(List,Dir,St);
+   }
+   
+   if (List.Tag == dsFList::tDeviceSpecial)
+   {
+      unsigned long PermDev;
+      if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+        PermDev = List.Entity->Permissions;
+      else
+        return _error->Error("Corrupted file list");
+      
+      if (mknod(List.Entity->Name.c_str(),PermDev,List.DevSpecial.Dev) != 0)
+        return _error->Errno("mkdir","Unable to create directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      // Stat the newly created file for FixMeta's benifit
+      struct stat St;
+      if (lstat(List.Entity->Name.c_str(),&St) != 0)
+        return _error->Errno("stat","Unable to stat directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+      return FixMeta(List,Dir,St);
+   }   
+}
+                                                                       /*}}}*/
+// DirCorrect::DirUnlink - Unlink a directory                          /*{{{*/
+// ---------------------------------------------------------------------
+/* This just recursively unlinks stuff */
+bool dsDirCorrect::DirUnlink(const char *Path)
+{
+   // Record what dir we were in
+   struct stat Dir;
+   if (lstat(".",&Dir) != 0)
+      return _error->Errno("lstat","Unable to stat .!");
+
+   if (chdir(Path) != 0)
+      return _error->Errno("chdir","Unable to change to %s",Path);
+            
+   // Scan the directory
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+   {
+      chdir("..");
+      return _error->Errno("opendir","Unable to open directory %s",Path);
+   }
+   
+   // Erase this directory
+   struct dirent *Ent;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+
+      struct stat St;
+      if (lstat(Ent->d_name,&St) != 0)
+        return _error->Errno("stat","Unable to stat %s",Ent->d_name);
+      if (S_ISDIR(St.st_mode) == 0)
+      {
+        // Try to unlink the file
+        if (unlink(Ent->d_name) != 0)
+        {
+           chdir("..");
+           return _error->Errno("unlink","Unable to remove file %s",Ent->d_name);
+        }       
+      }
+      else
+      {
+        if (DirUnlink(Ent->d_name) == false)
+        {
+           chdir("..");
+           closedir(DirSt);
+           return false;
+        }       
+      }         
+   }
+   closedir(DirSt);
+   chdir("..");
+   
+   /* Make sure someone didn't screw with the directory layout while we
+      were erasing */
+   struct stat Dir2;
+   if (lstat(".",&Dir2) != 0)
+      return _error->Errno("lstat","Unable to stat .!");
+   if (Dir2.st_ino != Dir.st_ino || Dir2.st_dev != Dir.st_dev)
+      return _error->Error("Hey! Someone is fiddling with the dir tree as I erase it!");
+
+   if (rmdir(Path) != 0)
+      return _error->Errno("rmdir","Unable to remove directory %s",Ent->d_name);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCorrect::Delete - Delete an entry                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This obliterates an entity - recursively, use with caution. */
+bool dsDirCorrect::Delete(string Dir,const char *Name,bool Now)
+{
+   struct stat St;
+   if (lstat(Name,&St) != 0)
+      return _error->Errno("stat","Unable to stat %s%s",Dir.c_str(),Name);
+      
+   if (S_ISDIR(St.st_mode) == 0)
+   {
+      if (unlink(Name) != 0)
+        return _error->Errno("unlink","Unable to remove %s%s",Dir.c_str(),Name);
+   }
+   else
+   {
+      if (DirUnlink(Name) == false)
+        return _error->Error("Unable to erase directory %s%s",Dir.c_str(),Name);
+   }
+   return true;   
+}
+                                                                       /*}}}*/
+// DirCorrect::GetChanged - Get a changed entry                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This is only called for normal files, we cannot do anything here. */
+bool dsDirCorrect::GetChanged(dsFList &List,string Dir)
+{   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCorrect::SetTime - Change the timestamp                          /*{{{*/
+// ---------------------------------------------------------------------
+/* This fixes the mod time of the file */
+bool dsDirCorrect::SetTime(dsFList &List,string Dir)
+{
+   struct utimbuf Time;
+   Time.actime = Time.modtime = List.Entity->ModTime + List.Head.Epoch;
+   if (utime(List.Entity->Name.c_str(),&Time) != 0)
+      return _error->Errno("utimes","Unable to change mod time for %s%s",
+                          Dir.c_str(),List.Entity->Name.c_str());
+   return true;
+}
+                                                                       /*}}}*/
+// DirCorrect::SetPerm - Change the permissions                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This fixes the permissions */
+bool dsDirCorrect::SetPerm(dsFList &List,string Dir)
+{
+   if (chmod(List.Entity->Name.c_str(),List.Entity->Permissions) != 0)
+      return _error->Errno("chmod","Unable to change permissions for %s%s",
+                          Dir.c_str(),List.Entity->Name.c_str());
+   return true;
+}
+                                                                       /*}}}*/
+// Dircorrect::SetOwner - Change ownership                             /*{{{*/
+// ---------------------------------------------------------------------
+/* This fixes the file ownership */
+bool dsDirCorrect::SetOwners(dsFList &List,string Dir)
+{
+   return _error->Error("Ownership is not yet supported");
+}
+                                                                       /*}}}*/
+