]> git.decadent.org.uk Git - dak.git/blobdiff - tools/dsync-0.0/libdsync/genfilelist.cc
Added another tool used in dak (and placed nowhere else), dsync
[dak.git] / tools / dsync-0.0 / libdsync / genfilelist.cc
diff --git a/tools/dsync-0.0/libdsync/genfilelist.cc b/tools/dsync-0.0/libdsync/genfilelist.cc
new file mode 100644 (file)
index 0000000..7c5b10a
--- /dev/null
@@ -0,0 +1,574 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: genfilelist.cc,v 1.10 1999/12/26 06:59:01 jgg Exp $
+/* ######################################################################
+   
+   Generate File List 
+
+   File list generation can be done with modification to the generation
+   order, ordering can be done by depth, breadth or by tree with and
+   a fitler can be applied to delay a directory till the end of processing.
+   
+   The emitter simply generates the necessary structure and writes it to
+   the IO. The client can hook some of the functions to provide progress
+   reporting and md5 caching if so desired.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/genfilelist.h"
+#endif
+
+#include <dsync/genfilelist.h>
+#include <dsync/error.h>
+#include <dsync/fileutl.h>
+#include <dsync/md5.h>
+#include <dsync/fileutl.h>
+#include <dsync/rsync-algo.h>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdio.h>
+                                                                       /*}}}*/
+
+// GenFileList::dsGenFileList - Constructor                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsGenFileList::dsGenFileList() : IO(0), Type(Tree)
+{
+}
+                                                                       /*}}}*/
+// GenFileList::~dsGenFileList - Destructor                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsGenFileList::~dsGenFileList()
+{
+}
+                                                                       /*}}}*/
+// GenFileList::Go - Generate the list                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This invokes the proper recursive directory scanner to build the file
+   names. Depth and Breath use a queue */
+bool dsGenFileList::Go(string Base,dsFList::IO &IO)
+{
+   // Setup the queues and store the current directory
+   string StartDir = SafeGetCWD();
+   Queue.erase(Queue.begin(),Queue.end());
+   DelayQueue.erase(Queue.begin(),Queue.end());
+
+   struct stat St;
+   if (stat(Base.c_str(),&St) != 0)
+      return _error->Errno("stat","Could not stat the base directory");
+   
+   // Begin
+   this->IO = &IO;
+   IO.Header.Write(IO);
+   
+   switch (Type)
+   {
+      case Depth:
+      {
+        // 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();
+        
+        char Cwd[1024];
+        Cwd[0] = 0;
+        if (DirDepthFirst(Cwd) == false)
+        {
+           chdir(StartDir.c_str());
+           return false;
+        }
+
+        // Now deal with the delay list
+        while (DelayQueue.empty() == false)
+        {
+           // Get the first delayed directory
+           string Dir = DelayQueue.front();
+           DelayQueue.pop_front();
+           
+           // Change to it and emit it.
+           strcpy(Cwd,Dir.c_str());
+           chdir(Base.c_str());
+           chdir(Cwd);
+           if (DirDepthFirst(Cwd) == false)
+           {
+              chdir(StartDir.c_str());
+              return false;
+           }       
+        }
+        
+        break;
+      }
+      
+      case Tree:
+      case Breadth:
+      {
+        // 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();
+
+        Queue.push_back("");
+        while (Queue.empty() == false || DelayQueue.empty() == false)
+        {
+           if (DirTree() == false)
+           {
+              chdir(StartDir.c_str());
+              return false;
+           }
+
+           chdir(Base.c_str());
+        }
+        break;
+      }
+
+      default:
+      return _error->Error("Internal Error");
+   }; 
+
+   chdir(StartDir.c_str());
+   
+   dsFList::Trailer Trail;
+   return Trail.Write(IO);
+}
+                                                                       /*}}}*/
+// GenFileList::DirDepthFirst - Depth first directory ordering         /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsGenFileList::DirDepthFirst(char *CurDir)
+{
+   // Scan the directory, first pass is to descend into the sub directories
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+      return _error->Errno("opendir","Unable to open direcotry %s",CurDir);
+   struct dirent *Ent;
+   bool EmittedThis = false;
+   struct stat St;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      if (lstat(Ent->d_name,&St) != 0)
+      {
+        closedir(DirSt);
+        return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
+      }
+      
+      // it is a directory
+      if (S_ISDIR(St.st_mode) != 0)
+      {
+        char S[1024];
+        snprintf(S,sizeof(S),"%s/",Ent->d_name);
+        
+        // Check the Filter
+        if (Filter.Test(CurDir,S) == false)
+           continue;
+
+        // Emit a directory marker record for this directory
+        if (EmittedThis == false)
+        {
+           EmittedThis = true;
+
+           if (lstat(".",&St) != 0)
+           {
+              closedir(DirSt);
+              return _error->Errno("stat","Could not stat %s",CurDir);
+           }
+           
+           if (DirectoryMarker(CurDir,St) == false)
+           {
+              closedir(DirSt);
+              return false;
+           }       
+        }
+
+        // Check the delay filter
+        if (PreferFilter.Test(CurDir,S) == false)
+        {
+           snprintf(S,sizeof(S),"%s%s/",CurDir,Ent->d_name);
+           DelayQueue.push_back(S);        
+           continue;
+        }
+        
+        // Append the new directory to CurDir and decend
+        char *End = CurDir + strlen(CurDir);
+        strcat(End,S);
+        if (chdir(S) != 0)
+        {
+           closedir(DirSt);
+           return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
+        }
+        
+        // Recurse
+        if (DirDepthFirst(CurDir) == false)
+        {
+           closedir(DirSt);
+           return false;
+        }
+
+        if (chdir("..") != 0)
+        {
+           closedir(DirSt);
+           return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
+        }
+        
+        // Chop off the directory we added to the current dir
+        *End = 0;
+      }
+   }
+   rewinddir(DirSt);
+
+   // Begin emitting this directory
+   if (lstat(".",&St) != 0)
+   {
+      closedir(DirSt);
+      return _error->Errno("stat","Could not stat %s",CurDir);
+   }
+   
+   if (EnterDir(CurDir,St) == false)
+   {
+      closedir(DirSt);
+      return false;
+   }
+      
+   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)
+      {
+        closedir(DirSt);
+        return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
+      }
+      
+      // it is a directory
+      if (S_ISDIR(St.st_mode) != 0)
+      {
+        char S[1024];
+        snprintf(S,sizeof(S),"%s/",Ent->d_name);
+        
+        // Check the Filter
+        if (Filter.Test(CurDir,S) == false)
+           continue;
+      }
+      else
+      {
+        // Check the Filter
+        if (Filter.Test(CurDir,Ent->d_name) == false)
+           continue;
+      }
+      
+      if (DoFile(CurDir,Ent->d_name,St) == false)
+      {
+        closedir(DirSt);
+        return false;
+      }      
+   }
+   closedir(DirSt);
+   
+   if (LeaveDir(CurDir) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// GenFileList::DirTree - Breadth/Tree directory ordering              /*{{{*/
+// ---------------------------------------------------------------------
+/* Breadth ordering does all of the dirs at each depth before proceeding 
+   to the next depth. We just treat the list as a queue to get this
+   effect. Tree ordering does things in a more normal recursive fashion,
+   we treat the queue as a stack to get that effect. */
+bool dsGenFileList::DirTree()
+{
+   string Dir;
+   if (Queue.empty() == false)
+   {
+      Dir = Queue.front();
+      Queue.pop_front();
+   }
+   else
+   {
+      Dir = DelayQueue.front();
+      DelayQueue.pop_front();
+   }
+   
+   struct stat St;
+   if (Dir.empty() == false && chdir(Dir.c_str()) != 0 || stat(".",&St) != 0)
+      return _error->Errno("chdir","Could not change to %s",Dir.c_str());
+
+   if (EnterDir(Dir.c_str(),St) == false)
+      return false;
+   
+   // Scan the directory
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+      return _error->Errno("opendir","Unable to open direcotry %s",Dir.c_str());
+   struct dirent *Ent;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      if (lstat(Ent->d_name,&St) != 0)
+      {
+        closedir(DirSt);
+        return _error->Errno("stat","Could not stat %s%s",Dir.c_str(),Ent->d_name);
+      }
+      
+      // It is a directory
+      if (S_ISDIR(St.st_mode) != 0)
+      {
+        char S[1024];
+        snprintf(S,sizeof(S),"%s/",Ent->d_name);
+        
+        // Check the Filter
+        if (Filter.Test(Dir.c_str(),S) == false)
+           continue;
+
+        // Check the delay filter
+        if (PreferFilter.Test(Dir.c_str(),S) == false)
+        {
+           snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
+           if (Type == Tree)
+              DelayQueue.push_front(S);
+           else
+              DelayQueue.push_back(S);     
+           continue;
+        }
+        
+        snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
+        
+        if (Type == Tree)
+           Queue.push_front(S);
+        else
+           Queue.push_back(S);
+      }
+      else
+      {
+        // Check the Filter
+        if (Filter.Test(Dir.c_str(),Ent->d_name) == false)
+           continue;
+      }
+      
+      if (DoFile(Dir.c_str(),Ent->d_name,St) == false)
+      {
+        closedir(DirSt);
+        return false;
+      }      
+   }
+   closedir(DirSt);
+   
+   if (LeaveDir(Dir.c_str()) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// GenFileList::EnterDir - Called when a directory is entered          /*{{{*/
+// ---------------------------------------------------------------------
+/* This is called to start a directory block the current working dir
+   should be set to the directory entered. This emits the directory start
+   record */
+bool dsGenFileList::EnterDir(const char *Dir,struct stat const &St)
+{
+   if (Visit(Dir,0,St) != 0)
+      return false;
+
+   dsFList::Directory D;
+   D.Tag = dsFList::tDirStart;
+   D.ModTime = St.st_mtime - IO->Header.Epoch;
+   D.Permissions = St.st_mode & ~S_IFMT;
+   D.Name = Dir;
+   return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
+      D.Write(*IO);    
+}
+                                                                       /*}}}*/
+// GenFileList::LeaveDir - Called when a directory is left             /*{{{*/
+// ---------------------------------------------------------------------
+/* Don't do anything for now */
+bool dsGenFileList::LeaveDir(const char *Dir)
+{
+   return true;
+}
+                                                                       /*}}}*/
+// GenFileList::DirectoryMarker - Called when a dir is skipped         /*{{{*/
+// ---------------------------------------------------------------------
+/* This is used by the depth first ordering, when a dir is temporarily
+   skipped over this function is called to emit a marker */
+bool dsGenFileList::DirectoryMarker(const char *Dir,
+                                   struct stat const &St)
+{
+   dsFList::Directory D;
+   D.Tag = dsFList::tDirMarker;
+   D.ModTime = St.st_mtime - IO->Header.Epoch;
+   D.Permissions = St.st_mode & ~S_IFMT;
+   D.Name = Dir;
+   return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
+      D.Write(*IO);    
+}
+                                                                       /*}}}*/
+// GenFileList::DoFile - This does all other items in a directory      /*{{{*/
+// ---------------------------------------------------------------------
+/* The different file types are emitted as perscribed by the file list
+   document */
+bool dsGenFileList::DoFile(const char *Dir,const char *File,
+                          struct stat const &St)
+{
+   int Res = Visit(Dir,File,St);
+   if (Res < 0)
+      return false;
+   if (Res > 0)
+      return true;
+   
+   // Regular file
+   if (S_ISREG(St.st_mode) != 0)
+   {
+      dsFList::NormalFile F;
+      
+      F.Tag = dsFList::tNormalFile;
+      F.ModTime = St.st_mtime - IO->Header.Epoch;
+      F.Permissions = St.st_mode & ~S_IFMT;
+      F.Name = File;
+      F.Size = St.st_size;
+
+      if (EmitOwner(St,F.User,F.Group,F.Tag,dsFList::NormalFile::FlOwner) == false)
+        return false;
+      
+      // See if we need to emit rsync checksums
+      if (NeedsRSync(Dir,File,F) == true)
+      {
+        dsFList::RSyncChecksum Ck;
+        if (EmitRSync(Dir,File,St,F,Ck) == false)
+           return false;
+
+        // Write out the file record, the checksums and the end marker
+        return F.Write(*IO) && Ck.Write(*IO);
+      }
+      else
+      {
+        if (EmitMD5(Dir,File,St,F.MD5,F.Tag,
+                    dsFList::NormalFile::FlMD5) == false)
+           return false;
+      
+        return F.Write(*IO);
+      }      
+   }
+   
+   // Directory
+   if (S_ISDIR(St.st_mode) != 0)
+   {
+      dsFList::Directory D;
+      D.Tag = dsFList::tDirectory;
+      D.ModTime = St.st_mtime - IO->Header.Epoch;
+      D.Permissions = St.st_mode & ~S_IFMT;
+      D.Name = File;
+      return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
+        D.Write(*IO);    
+   }
+
+   // Link
+   if (S_ISLNK(St.st_mode) != 0)
+   {
+      dsFList::Symlink L;
+      L.Tag = dsFList::tSymlink;
+      L.ModTime = St.st_mtime - IO->Header.Epoch;
+      L.Name = File;
+
+      char Buf[1024];
+      int Res = readlink(File,Buf,sizeof(Buf));
+      if (Res <= 0)
+        return _error->Errno("readlink","Unable to read symbolic link");
+      Buf[Res] = 0;
+      L.To = Buf;
+
+      return EmitOwner(St,L.User,L.Group,L.Tag,dsFList::Symlink::FlOwner) && 
+        L.Write(*IO);    
+   }
+   
+   // Block special file
+   if (S_ISCHR(St.st_mode) != 0 || S_ISBLK(St.st_mode) != 0 || 
+       S_ISFIFO(St.st_mode) != 0)
+   {
+      dsFList::DeviceSpecial D;
+      D.Tag = dsFList::tDeviceSpecial;
+      D.ModTime = St.st_mtime - IO->Header.Epoch;
+      D.Permissions = St.st_mode & ~S_IFMT;
+      D.Dev = St.st_dev;
+      D.Name = File;
+      
+      return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::DeviceSpecial::FlOwner) && 
+        D.Write(*IO);
+   }
+   
+   return _error->Error("File %s%s is not a known type",Dir,File);
+}
+                                                                       /*}}}*/
+// GenFileList::EmitOwner - Set the entitiy ownership                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This emits the necessary UID/GID mapping records and sets the feilds
+   in */
+bool dsGenFileList::EmitOwner(struct stat const &St,unsigned long &UID,
+                             unsigned long &GID,unsigned int Tag,
+                             unsigned int Flag)
+{
+   if ((IO->Header.Flags[Tag] & Flag) != Flag)
+      return true;
+   
+   return _error->Error("UID/GID storage is not supported yet");
+}
+                                                                       /*}}}*/
+// GenFileList::EmitMd5 - Generate the md5 hash for the file           /*{{{*/
+// ---------------------------------------------------------------------
+/* This uses the MD5 class to generate the md5 hash for the entry. */
+bool dsGenFileList::EmitMD5(const char *Dir,const char *File,
+                           struct stat const &St,unsigned char MD5[16],
+                           unsigned int Tag,unsigned int Flag)
+{
+   if ((IO->Header.Flags[Tag] & Flag) != Flag)
+      return true;
+
+   // Open the file
+   MD5Summation Sum;
+   FileFd Fd(File,FileFd::ReadOnly);
+   if (_error->PendingError() == true)
+      return _error->Error("MD5 generation failed for %s%s",Dir,File);
+
+   if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
+      return _error->Error("MD5 generation failed for %s%s",Dir,File);
+   
+   Sum.Result().Value(MD5);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// GenFileList::EmitRSync - Emit a RSync checksum record               /*{{{*/
+// ---------------------------------------------------------------------
+/* This just generates the checksum into the memory structure. */
+bool dsGenFileList::EmitRSync(const char *Dir,const char *File,
+                             struct stat const &St,dsFList::NormalFile &F,
+                             dsFList::RSyncChecksum &Ck)
+{
+   FileFd Fd(File,FileFd::ReadOnly);
+   if (_error->PendingError() == true)
+      return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
+   
+   if (GenerateRSync(Fd,Ck,F.MD5) == false)
+      return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
+   
+   return true;
+}
+                                                                       /*}}}*/