1 // -*- mode: cpp; mode: fold -*-
3 // $Id: compare.cc,v 1.6 1999/12/26 06:59:00 jgg Exp $
4 /* ######################################################################
6 Compare a file list with a local directory
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
17 ##################################################################### */
19 // Include files /*{{{*/
21 #pragma implementation "dsync/compare.h"
24 #include <dsync/compare.h>
25 #include <dsync/error.h>
26 #include <dsync/fileutl.h>
27 #include <dsync/md5.h>
29 #include <sys/types.h>
37 // DirCompre::dsDirCompare - Constructor /*{{{*/
38 // ---------------------------------------------------------------------
40 dsDirCompare::dsDirCompare() : IndexSize(0), IndexAlloc(0), Indexes(0),
41 NameAlloc(0), Names(0), Verify(true), HashLevel(Md5Date)
44 Indexes = (unsigned int *)malloc(sizeof(*Indexes)*IndexAlloc);
46 Names = (char *)malloc(sizeof(*Names)*NameAlloc);
47 if (Names == 0 || Indexes == 0)
48 _error->Error("Cannot allocate memory");
51 // DirCompare::~dsDirCompare - Destructor /*{{{*/
52 // ---------------------------------------------------------------------
54 dsDirCompare::~dsDirCompare()
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
64 bool dsDirCompare::LoadDir()
67 DIR *DirSt = opendir(".");
69 return _error->Errno("opendir","Unable to open directory %s",SafeGetCWD().c_str());
72 char *End = Names + 1;
73 while ((Ent = readdir(DirSt)) != 0)
76 if (strcmp(Ent->d_name,".") == 0 ||
77 strcmp(Ent->d_name,"..") == 0)
80 // Grab some more bytes in the name allocation
81 if ((unsigned)(NameAlloc - (End - Names)) <= strlen(Ent->d_name)+1)
83 unsigned long OldEnd = End - Names;
84 char *New = (char *)realloc(Names,sizeof(*Names)*NameAlloc + 4*4096);
88 return _error->Error("Cannot allocate memory");
96 // Grab some more bytes in the index allocation
97 if (IndexSize >= IndexAlloc)
99 unsigned int *New = (unsigned int *)realloc(Indexes,
100 sizeof(*Indexes)*IndexAlloc + 1000);
104 return _error->Error("Cannot allocate memory");
108 IndexAlloc += 4*4096;
112 Indexes[IndexSize] = End - Names;
114 strcpy(End,Ent->d_name);
115 End += strlen(End) + 1;
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)
127 // Setup the queues and store the current directory
128 string StartDir = SafeGetCWD();
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());
138 bool Missing = false;
139 while (List.Step(IO) == true)
141 if (Visit(List,CurDir) == false)
146 // Handle a forward directory reference
147 case dsFList::tDirMarker:
149 // Ingore the root directory
150 if (List.Entity->Name.empty() == true)
155 snprintf(S,sizeof(S),"%s%s",Base.c_str(),List.Entity->Name.c_str());
157 /* We change the path to be absolute for the benifit of the
159 List.Entity->Name = S;
161 // Stat the marker dir
164 if (lstat(S,&St) != 0)
165 Res = Fetch(List,string(),0);
167 Res = Fetch(List,string(),&St);
175 case dsFList::tDirStart:
177 if (DoDelete(CurDir) == false)
179 if (chdir(Base.c_str()) != 0)
180 return _error->Errno("chdir","Could not change to %s",Base.c_str());
182 CurDir = List.Dir.Name;
185 if (List.Dir.Name.empty() == false)
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)
193 return _error->Errno("chdir","Unable to cd to %s%s.",Base.c_str(),List.Dir.Name.c_str());
198 if (Missing == false)
203 // Finalize the directory
204 case dsFList::tDirEnd:
206 if (DoDelete(CurDir) == false)
209 if (chdir(Base.c_str()) != 0)
210 return _error->Errno("chdir","Could not change to %s",Base.c_str());
215 // We have some sort of normal entity
216 if (List.Entity != 0 && List.Tag != dsFList::tDirMarker &&
217 List.Tag != dsFList::tDirStart)
219 // See if it exists, if it does then stat it
221 if (Missing == true || DirExists(List.Entity->Name) == false)
222 Res = Fetch(List,CurDir,0);
226 if (lstat(List.Entity->Name.c_str(),&St) != 0)
227 Res = Fetch(List,CurDir,0);
229 Res = Fetch(List,CurDir,&St);
236 if (List.Tag == dsFList::tTrailer)
238 if (DoDelete(CurDir) == false)
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)
253 for (unsigned int I = 0; I != IndexSize; I++)
257 if (Delete(Dir,Names + Indexes[I]) == false)
264 // DirCompare::Fetch - Fetch an entity /*{{{*/
265 // ---------------------------------------------------------------------
266 /* This examins an entry to see what sort of fetch should be done. There
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)
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");
279 // This is a new entitiy
281 return GetNew(List,Dir);
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))
292 return Delete(Dir,List.Entity->Name.c_str(),true) && GetNew(List,Dir);
295 // First we check permissions and mod time
296 bool ModTime = (signed)(List.Entity->ModTime + List.Head.Epoch) == St->st_mtime;
298 if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
299 Perm = List.Entity->Permissions == (unsigned)(St->st_mode & ~S_IFMT);
302 if (List.Tag == dsFList::tNormalFile)
304 // Size mismatch is an immedate fail
305 if (List.NFile.Size != (unsigned)St->st_size)
306 return GetChanged(List,Dir);
308 // Try to check the stored MD5
309 if (HashLevel == Md5Always ||
310 (HashLevel == Md5Date && ModTime == false))
312 if ((List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5) != 0)
314 if (CheckHash(List,Dir,List.NFile.MD5) == true)
315 return FixMeta(List,Dir,*St);
317 return GetChanged(List,Dir);
321 // Look at the modification time
323 return FixMeta(List,Dir,*St);
324 return GetChanged(List,Dir);
328 if (List.Tag == dsFList::tSymlink)
331 int Res = readlink(List.Entity->Name.c_str(),Buf,sizeof(Buf));
336 if (Res < 0 || List.SLink.To != Buf)
337 return GetNew(List,Dir);
339 return FixMeta(List,Dir,*St);
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);
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)
355 for (unsigned int I = 0; I != IndexSize; I++)
359 if (Name == Names + Indexes[I])
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])
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());
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());
385 unsigned char MyMD5[16];
386 Sum.Result().Value(MyMD5);
388 return memcmp(MD5,MyMD5,sizeof(MyMD5)) == 0;
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)
397 // Check the mod time
398 if (List.Tag != dsFList::tSymlink)
400 if ((signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime)
401 if (SetTime(List,Dir) == false)
404 // Check the permissions
405 if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
407 if (List.Entity->Permissions != (St.st_mode & ~S_IFMT))
408 if (SetPerm(List,Dir) == false)
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)
422 if (List.Tag == dsFList::tDirectory)
424 unsigned long PermDir = 0666;
425 if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
426 PermDir = List.Entity->Permissions;
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());
432 // Stat the newly created file for FixMeta's benifit
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());
438 return FixMeta(List,Dir,St);
441 if (List.Tag == dsFList::tSymlink)
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());
447 // Stat the newly created file for FixMeta's benifit
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());
453 return FixMeta(List,Dir,St);
456 if (List.Tag == dsFList::tDeviceSpecial)
458 unsigned long PermDev;
459 if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
460 PermDev = List.Entity->Permissions;
462 return _error->Error("Corrupted file list");
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());
468 // Stat the newly created file for FixMeta's benifit
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);
477 // DirCorrect::DirUnlink - Unlink a directory /*{{{*/
478 // ---------------------------------------------------------------------
479 /* This just recursively unlinks stuff */
480 bool dsDirCorrect::DirUnlink(const char *Path)
482 // Record what dir we were in
484 if (lstat(".",&Dir) != 0)
485 return _error->Errno("lstat","Unable to stat .!");
487 if (chdir(Path) != 0)
488 return _error->Errno("chdir","Unable to change to %s",Path);
490 // Scan the directory
491 DIR *DirSt = opendir(".");
495 return _error->Errno("opendir","Unable to open directory %s",Path);
498 // Erase this directory
500 while ((Ent = readdir(DirSt)) != 0)
503 if (strcmp(Ent->d_name,".") == 0 ||
504 strcmp(Ent->d_name,"..") == 0)
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)
512 // Try to unlink the file
513 if (unlink(Ent->d_name) != 0)
516 return _error->Errno("unlink","Unable to remove file %s",Ent->d_name);
521 if (DirUnlink(Ent->d_name) == false)
532 /* Make sure someone didn't screw with the directory layout while we
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!");
540 if (rmdir(Path) != 0)
541 return _error->Errno("rmdir","Unable to remove directory %s",Ent->d_name);
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)
552 if (lstat(Name,&St) != 0)
553 return _error->Errno("stat","Unable to stat %s%s",Dir.c_str(),Name);
555 if (S_ISDIR(St.st_mode) == 0)
557 if (unlink(Name) != 0)
558 return _error->Errno("unlink","Unable to remove %s%s",Dir.c_str(),Name);
562 if (DirUnlink(Name) == false)
563 return _error->Error("Unable to erase directory %s%s",Dir.c_str(),Name);
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)
576 // DirCorrect::SetTime - Change the timestamp /*{{{*/
577 // ---------------------------------------------------------------------
578 /* This fixes the mod time of the file */
579 bool dsDirCorrect::SetTime(dsFList &List,string Dir)
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());
589 // DirCorrect::SetPerm - Change the permissions /*{{{*/
590 // ---------------------------------------------------------------------
591 /* This fixes the permissions */
592 bool dsDirCorrect::SetPerm(dsFList &List,string Dir)
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());
600 // Dircorrect::SetOwner - Change ownership /*{{{*/
601 // ---------------------------------------------------------------------
602 /* This fixes the file ownership */
603 bool dsDirCorrect::SetOwners(dsFList &List,string Dir)
605 return _error->Error("Ownership is not yet supported");