]> git.decadent.org.uk Git - dak.git/blob - tools/dsync-0.0/libdsync/compare.cc
Cope with empty queue
[dak.git] / tools / dsync-0.0 / libdsync / compare.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: compare.cc,v 1.6 1999/12/26 06:59:00 jgg Exp $
4 /* ######################################################################
5    
6    Compare a file list with a local directory
7    
8    The first step in the compare is to read the names of each entry
9    in the local directory into ram. This list is the first step to
10    creating a delete list. Next we begin scanning the file list, checking
11    each entry against the dir contents, if a match is found it is removed
12    from the dir list and then stat'd to verify against the file list
13    contents. If no match is found then the entry is marked for download.
14    When done the local directory in ram will only contain entries that 
15    need to be erased.
16    
17    ##################################################################### */
18                                                                         /*}}}*/
19 // Include files                                                        /*{{{*/
20 #ifdef __GNUG__
21 #pragma implementation "dsync/compare.h"
22 #endif
23
24 #include <dsync/compare.h>
25 #include <dsync/error.h>
26 #include <dsync/fileutl.h>
27 #include <dsync/md5.h>
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <dirent.h>
33 #include <utime.h>
34 #include <stdio.h>
35                                                                         /*}}}*/
36
37 // DirCompre::dsDirCompare - Constructor                                /*{{{*/
38 // ---------------------------------------------------------------------
39 /* */
40 dsDirCompare::dsDirCompare() : IndexSize(0), IndexAlloc(0), Indexes(0),
41                      NameAlloc(0), Names(0), Verify(true), HashLevel(Md5Date)
42 {
43    IndexAlloc = 1000;
44    Indexes = (unsigned int *)malloc(sizeof(*Indexes)*IndexAlloc);
45    NameAlloc = 4096*5;
46    Names = (char *)malloc(sizeof(*Names)*NameAlloc);
47    if (Names == 0 || Indexes == 0)
48       _error->Error("Cannot allocate memory");
49 }
50                                                                         /*}}}*/
51 // DirCompare::~dsDirCompare - Destructor                               /*{{{*/
52 // ---------------------------------------------------------------------
53 /* */
54 dsDirCompare::~dsDirCompare()
55 {
56    free(Names);
57    free(Indexes);
58 }
59                                                                         /*}}}*/
60 // DirCompare::LoadDir - Load all the names in the directory            /*{{{*/
61 // ---------------------------------------------------------------------
62 /* Such in every name in the directory, we store them as a packed, indexed
63    array of strings */
64 bool dsDirCompare::LoadDir()
65 {
66    // Scan the directory
67    DIR *DirSt = opendir(".");
68    if (DirSt == 0)
69       return _error->Errno("opendir","Unable to open directory %s",SafeGetCWD().c_str());
70    struct dirent *Ent;
71    IndexSize = 0;
72    char *End = Names + 1;
73    while ((Ent = readdir(DirSt)) != 0)
74    {
75       // Skip . and ..
76       if (strcmp(Ent->d_name,".") == 0 ||
77           strcmp(Ent->d_name,"..") == 0)
78          continue;
79       
80       // Grab some more bytes in the name allocation
81       if ((unsigned)(NameAlloc - (End - Names)) <= strlen(Ent->d_name)+1)
82       {
83          unsigned long OldEnd = End - Names;
84          char *New = (char *)realloc(Names,sizeof(*Names)*NameAlloc + 4*4096);
85          if (New == 0)
86          {
87             closedir(DirSt);
88             return _error->Error("Cannot allocate memory");
89          }
90          
91          Names = New;
92          NameAlloc += 4*4096;
93          End = Names + OldEnd;
94       }
95       
96       // Grab some more bytes in the index allocation
97       if (IndexSize >= IndexAlloc)
98       {
99          unsigned int *New = (unsigned int *)realloc(Indexes,
100                              sizeof(*Indexes)*IndexAlloc + 1000);
101          if (New == 0)
102          {   
103             closedir(DirSt);
104             return _error->Error("Cannot allocate memory");
105          }
106          
107          Indexes = New;
108          IndexAlloc += 4*4096;
109       }
110       
111       // Store it
112       Indexes[IndexSize] = End - Names;
113       IndexSize++;
114       strcpy(End,Ent->d_name);
115       End += strlen(End) + 1;
116    }
117    
118    closedir(DirSt);
119    return true;
120 }
121                                                                         /*}}}*/
122 // DirCompare::Process - Process the file list stream                   /*{{{*/
123 // ---------------------------------------------------------------------
124 /* This scans over the dirs from the IO and decides what to do with them */
125 bool dsDirCompare::Process(string Base,dsFList::IO &IO)
126 {
127    // Setup the queues and store the current directory
128    string StartDir = SafeGetCWD();
129    
130    // Change to the base directory
131    if (chdir(Base.c_str()) != 0)
132       return _error->Errno("chdir","Could not change to %s",Base.c_str());
133    Base = SafeGetCWD();
134    this->Base = Base;
135    
136    string CurDir;
137    dsFList List;
138    bool Missing = false;
139    while (List.Step(IO) == true)
140    {
141       if (Visit(List,CurDir) == false)
142          return false;
143       
144       switch (List.Tag)
145       {
146          // Handle a forward directory reference
147          case dsFList::tDirMarker:
148          {
149             // Ingore the root directory
150             if (List.Entity->Name.empty() == true)
151                continue;
152             
153             char S[1024];
154
155             snprintf(S,sizeof(S),"%s%s",Base.c_str(),List.Entity->Name.c_str());
156             
157             /* We change the path to be absolute for the benifit of the 
158                routines below */
159             List.Entity->Name = S;
160             
161             // Stat the marker dir
162             struct stat St;
163             bool Res;
164             if (lstat(S,&St) != 0)
165                Res = Fetch(List,string(),0);
166             else
167                Res = Fetch(List,string(),&St);
168
169             if (Res == false)
170                return false;
171             break;
172          }
173          
174          // Start a directory
175          case dsFList::tDirStart:
176          {          
177             if (DoDelete(CurDir) == false)
178                return false;
179             if (chdir(Base.c_str()) != 0)
180                return _error->Errno("chdir","Could not change to %s",Base.c_str());
181             
182             CurDir = List.Dir.Name;
183             Missing = false;
184             IndexSize = 0;
185             if (List.Dir.Name.empty() == false)
186             {
187                /* Instead of erroring out we just mark them as missing and
188                   do not re-stat. This is to support the verify mode, the
189                   actual downloader should never get this. */
190                if (chdir(List.Dir.Name.c_str()) != 0)
191                {
192                   if (Verify == false)
193                      return _error->Errno("chdir","Unable to cd to %s%s.",Base.c_str(),List.Dir.Name.c_str());
194                   Missing = true;
195                }               
196             }
197             
198             if (Missing == false)
199                LoadDir();
200             break;
201          }
202          
203          // Finalize the directory
204          case dsFList::tDirEnd:
205          {
206             if (DoDelete(CurDir) == false)
207                return false;
208             IndexSize = 0;
209             if (chdir(Base.c_str()) != 0)
210                return _error->Errno("chdir","Could not change to %s",Base.c_str());
211             break;
212          }       
213       }
214       
215       // We have some sort of normal entity
216       if (List.Entity != 0 && List.Tag != dsFList::tDirMarker &&
217           List.Tag != dsFList::tDirStart)
218       {
219          // See if it exists, if it does then stat it
220          bool Res = true;
221          if (Missing == true || DirExists(List.Entity->Name) == false)
222             Res = Fetch(List,CurDir,0);
223          else
224          {
225             struct stat St;
226             if (lstat(List.Entity->Name.c_str(),&St) != 0)
227                Res = Fetch(List,CurDir,0);
228             else
229                Res = Fetch(List,CurDir,&St);
230          }
231          if (Res == false)
232             return false;
233       }
234       
235       // Fini
236       if (List.Tag == dsFList::tTrailer)
237       {
238          if (DoDelete(CurDir) == false)
239             return false;
240          return true;
241       }      
242    }
243    
244    return false;
245 }
246                                                                         /*}}}*/
247 // DirCompare::DoDelete - Delete files in the delete list               /*{{{*/
248 // ---------------------------------------------------------------------
249 /* The delete list is created by removing names that were found till only
250    extra names remain */
251 bool dsDirCompare::DoDelete(string Dir)
252 {
253    for (unsigned int I = 0; I != IndexSize; I++)
254    {
255       if (Indexes[I] == 0)
256          continue;
257       if (Delete(Dir,Names + Indexes[I]) == false)
258          return false;
259    }
260    
261    return true;
262 }
263                                                                         /*}}}*/
264 // DirCompare::Fetch - Fetch an entity                                  /*{{{*/
265 // ---------------------------------------------------------------------
266 /* This examins an entry to see what sort of fetch should be done. There
267    are three sorts, 
268      New - There is no existing data
269      Changed - There is existing data
270      Meta - The data is fine but the timestamp/owner/perms might not be */
271 bool dsDirCompare::Fetch(dsFList &List,string Dir,struct stat *St)
272 {
273    if (List.Tag != dsFList::tNormalFile && List.Tag != dsFList::tDirectory &&
274        List.Tag != dsFList::tSymlink && List.Tag != dsFList::tDeviceSpecial &&
275        List.Tag != dsFList::tDirMarker)
276       return _error->Error("dsDirCompare::Fetch called for an entity "
277                            "that it does not understand");
278    
279    // This is a new entitiy
280    if (St == 0)
281       return GetNew(List,Dir);
282
283    /* Check the types for a mis-match, if they do not match then 
284       we have to erase the entity and get a new one */
285    if ((S_ISREG(St->st_mode) != 0 && List.Tag != dsFList::tNormalFile) ||
286        (S_ISDIR(St->st_mode) != 0 && (List.Tag != dsFList::tDirectory && 
287                                       List.Tag != dsFList::tDirMarker)) ||
288        (S_ISLNK(St->st_mode) != 0 && List.Tag != dsFList::tSymlink) ||
289        ((S_ISCHR(St->st_mode) != 0 || S_ISBLK(St->st_mode) != 0 || 
290          S_ISFIFO(St->st_mode) != 0) && List.Tag != dsFList::tDeviceSpecial))
291    {
292       return Delete(Dir,List.Entity->Name.c_str(),true) && GetNew(List,Dir);
293    }
294    
295    // First we check permissions and mod time
296    bool ModTime = (signed)(List.Entity->ModTime + List.Head.Epoch) == St->st_mtime;
297    bool Perm = true;
298    if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
299       Perm = List.Entity->Permissions == (unsigned)(St->st_mode & ~S_IFMT);
300       
301    // Normal file
302    if (List.Tag == dsFList::tNormalFile)
303    {
304       // Size mismatch is an immedate fail
305       if (List.NFile.Size != (unsigned)St->st_size)
306          return GetChanged(List,Dir);
307
308       // Try to check the stored MD5
309       if (HashLevel == Md5Always || 
310           (HashLevel == Md5Date && ModTime == false))
311       {
312          if ((List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5) != 0)
313          {
314             if (CheckHash(List,Dir,List.NFile.MD5) == true)
315                return FixMeta(List,Dir,*St);
316             else
317                return GetChanged(List,Dir);
318          }       
319       }
320       
321       // Look at the modification time
322       if (ModTime == true)
323          return FixMeta(List,Dir,*St);
324       return GetChanged(List,Dir);
325    }
326
327    // Check symlinks
328    if (List.Tag == dsFList::tSymlink)
329    {
330       char Buf[1024];
331       int Res = readlink(List.Entity->Name.c_str(),Buf,sizeof(Buf));
332       if (Res > 0)
333          Buf[Res] = 0;
334       
335       // Link is invalid
336       if (Res < 0 || List.SLink.To != Buf)
337          return GetNew(List,Dir);
338       
339       return FixMeta(List,Dir,*St);
340    }
341
342    // Check directories and dev special files
343    if (List.Tag == dsFList::tDirectory || List.Tag == dsFList::tDeviceSpecial ||
344        List.Tag == dsFList::tDirMarker)
345       return FixMeta(List,Dir,*St);
346    
347    return true;
348 }
349                                                                         /*}}}*/
350 // DirCompare::DirExists - See if the entry exists in our dir table     /*{{{*/
351 // ---------------------------------------------------------------------
352 /* We look at the dir table for one that exists */
353 bool dsDirCompare::DirExists(string Name)
354 {
355    for (unsigned int I = 0; I != IndexSize; I++)
356    {
357       if (Indexes[I] == 0)
358          continue;
359       if (Name == Names + Indexes[I])
360       {
361          Indexes[I] = 0;
362          return true;
363       }
364    }
365    return false;
366 }
367                                                                         /*}}}*/
368 // DirCompare::CheckHash - Check the MD5 of a entity                    /*{{{*/
369 // ---------------------------------------------------------------------
370 /* This is invoked to see of the local file we have is the file the remote
371    says we should have. */
372 bool dsDirCompare::CheckHash(dsFList &List,string Dir,unsigned char MD5[16])
373 {
374    // Open the file
375    MD5Summation Sum;
376    FileFd Fd(List.Entity->Name,FileFd::ReadOnly);
377    if (_error->PendingError() == true)
378       return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
379                            List.Entity->Name.c_str());
380
381    if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
382       return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
383                            List.Entity->Name.c_str());
384
385    unsigned char MyMD5[16];
386    Sum.Result().Value(MyMD5);
387
388    return memcmp(MD5,MyMD5,sizeof(MyMD5)) == 0;
389 }
390                                                                         /*}}}*/
391 // DirCompare::FixMeta - Fix timestamps, ownership and permissions      /*{{{*/
392 // ---------------------------------------------------------------------
393 /* This checks if it is necessary to correct the timestamps, ownership and
394    permissions of an entity */
395 bool dsDirCompare::FixMeta(dsFList &List,string Dir,struct stat &St)
396 {   
397    // Check the mod time
398    if (List.Tag != dsFList::tSymlink)
399    {
400       if ((signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime)
401          if (SetTime(List,Dir) == false)
402             return false;
403    
404       // Check the permissions
405       if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
406       {
407          if (List.Entity->Permissions != (St.st_mode & ~S_IFMT))
408             if (SetPerm(List,Dir) == false)
409                return false;
410       }
411    }
412       
413    return true;
414 }
415                                                                         /*}}}*/
416
417 // DirCorrect::GetNew - Create a new entry                              /*{{{*/
418 // ---------------------------------------------------------------------
419 /* We cannot create files but we do generate everything else. */
420 bool dsDirCorrect::GetNew(dsFList &List,string Dir)
421 {
422    if (List.Tag == dsFList::tDirectory)
423    {
424       unsigned long PermDir = 0666;
425       if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
426          PermDir = List.Entity->Permissions;
427          
428       if (mkdir(List.Entity->Name.c_str(),PermDir) != 0)
429          return _error->Errno("mkdir","Unable to create directory, %s%s",
430                               Dir.c_str(),List.Entity->Name.c_str());
431
432       // Stat the newly created file for FixMeta's benifit
433       struct stat St;
434       if (lstat(List.Entity->Name.c_str(),&St) != 0)
435          return _error->Errno("stat","Unable to stat directory, %s%s",
436                               Dir.c_str(),List.Entity->Name.c_str());
437
438       return FixMeta(List,Dir,St);
439    }
440
441    if (List.Tag == dsFList::tSymlink)
442    {
443       if (symlink(List.SLink.To.c_str(),List.Entity->Name.c_str()) != 0)
444          return _error->Errno("symlink","Unable to create symlink, %s%s",
445                               Dir.c_str(),List.Entity->Name.c_str());
446
447       // Stat the newly created file for FixMeta's benifit
448       struct stat St;
449       if (lstat(List.Entity->Name.c_str(),&St) != 0)
450          return _error->Errno("stat","Unable to stat directory, %s%s",
451                               Dir.c_str(),List.Entity->Name.c_str());
452
453       return FixMeta(List,Dir,St);
454    }
455    
456    if (List.Tag == dsFList::tDeviceSpecial)
457    {
458       unsigned long PermDev;
459       if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
460          PermDev = List.Entity->Permissions;
461       else
462          return _error->Error("Corrupted file list");
463       
464       if (mknod(List.Entity->Name.c_str(),PermDev,List.DevSpecial.Dev) != 0)
465          return _error->Errno("mkdir","Unable to create directory, %s%s",
466                               Dir.c_str(),List.Entity->Name.c_str());
467
468       // Stat the newly created file for FixMeta's benifit
469       struct stat St;
470       if (lstat(List.Entity->Name.c_str(),&St) != 0)
471          return _error->Errno("stat","Unable to stat directory, %s%s",
472                               Dir.c_str(),List.Entity->Name.c_str());
473       return FixMeta(List,Dir,St);
474    }   
475 }
476                                                                         /*}}}*/
477 // DirCorrect::DirUnlink - Unlink a directory                           /*{{{*/
478 // ---------------------------------------------------------------------
479 /* This just recursively unlinks stuff */
480 bool dsDirCorrect::DirUnlink(const char *Path)
481 {
482    // Record what dir we were in
483    struct stat Dir;
484    if (lstat(".",&Dir) != 0)
485       return _error->Errno("lstat","Unable to stat .!");
486
487    if (chdir(Path) != 0)
488       return _error->Errno("chdir","Unable to change to %s",Path);
489              
490    // Scan the directory
491    DIR *DirSt = opendir(".");
492    if (DirSt == 0)
493    {
494       chdir("..");
495       return _error->Errno("opendir","Unable to open directory %s",Path);
496    }
497    
498    // Erase this directory
499    struct dirent *Ent;
500    while ((Ent = readdir(DirSt)) != 0)
501    {
502       // Skip . and ..
503       if (strcmp(Ent->d_name,".") == 0 ||
504           strcmp(Ent->d_name,"..") == 0)
505          continue;
506
507       struct stat St;
508       if (lstat(Ent->d_name,&St) != 0)
509          return _error->Errno("stat","Unable to stat %s",Ent->d_name);
510       if (S_ISDIR(St.st_mode) == 0)
511       {
512          // Try to unlink the file
513          if (unlink(Ent->d_name) != 0)
514          {
515             chdir("..");
516             return _error->Errno("unlink","Unable to remove file %s",Ent->d_name);
517          }       
518       }
519       else
520       {
521          if (DirUnlink(Ent->d_name) == false)
522          {
523             chdir("..");
524             closedir(DirSt);
525             return false;
526          }       
527       }  
528    }
529    closedir(DirSt);
530    chdir("..");
531    
532    /* Make sure someone didn't screw with the directory layout while we
533       were erasing */
534    struct stat Dir2;
535    if (lstat(".",&Dir2) != 0)
536       return _error->Errno("lstat","Unable to stat .!");
537    if (Dir2.st_ino != Dir.st_ino || Dir2.st_dev != Dir.st_dev)
538       return _error->Error("Hey! Someone is fiddling with the dir tree as I erase it!");
539
540    if (rmdir(Path) != 0)
541       return _error->Errno("rmdir","Unable to remove directory %s",Ent->d_name);
542    
543    return true;
544 }
545                                                                         /*}}}*/
546 // DirCorrect::Delete - Delete an entry                                 /*{{{*/
547 // ---------------------------------------------------------------------
548 /* This obliterates an entity - recursively, use with caution. */
549 bool dsDirCorrect::Delete(string Dir,const char *Name,bool Now)
550 {
551    struct stat St;
552    if (lstat(Name,&St) != 0)
553       return _error->Errno("stat","Unable to stat %s%s",Dir.c_str(),Name);
554       
555    if (S_ISDIR(St.st_mode) == 0)
556    {
557       if (unlink(Name) != 0)
558          return _error->Errno("unlink","Unable to remove %s%s",Dir.c_str(),Name);
559    }
560    else
561    {
562       if (DirUnlink(Name) == false)
563          return _error->Error("Unable to erase directory %s%s",Dir.c_str(),Name);
564    }
565    return true;   
566 }
567                                                                         /*}}}*/
568 // DirCorrect::GetChanged - Get a changed entry                         /*{{{*/
569 // ---------------------------------------------------------------------
570 /* This is only called for normal files, we cannot do anything here. */
571 bool dsDirCorrect::GetChanged(dsFList &List,string Dir)
572 {   
573    return true;
574 }
575                                                                         /*}}}*/
576 // DirCorrect::SetTime - Change the timestamp                           /*{{{*/
577 // ---------------------------------------------------------------------
578 /* This fixes the mod time of the file */
579 bool dsDirCorrect::SetTime(dsFList &List,string Dir)
580 {
581    struct utimbuf Time;
582    Time.actime = Time.modtime = List.Entity->ModTime + List.Head.Epoch;
583    if (utime(List.Entity->Name.c_str(),&Time) != 0)
584       return _error->Errno("utimes","Unable to change mod time for %s%s",
585                            Dir.c_str(),List.Entity->Name.c_str());
586    return true;
587 }
588                                                                         /*}}}*/
589 // DirCorrect::SetPerm - Change the permissions                         /*{{{*/
590 // ---------------------------------------------------------------------
591 /* This fixes the permissions */
592 bool dsDirCorrect::SetPerm(dsFList &List,string Dir)
593 {
594    if (chmod(List.Entity->Name.c_str(),List.Entity->Permissions) != 0)
595       return _error->Errno("chmod","Unable to change permissions for %s%s",
596                            Dir.c_str(),List.Entity->Name.c_str());
597    return true;
598 }
599                                                                         /*}}}*/
600 // Dircorrect::SetOwner - Change ownership                              /*{{{*/
601 // ---------------------------------------------------------------------
602 /* This fixes the file ownership */
603 bool dsDirCorrect::SetOwners(dsFList &List,string Dir)
604 {
605    return _error->Error("Ownership is not yet supported");
606 }
607                                                                         /*}}}*/
608