1 // -*- mode: cpp; mode: fold -*-
3 // $Id: dsync-flist.cc,v 1.27 1999/12/26 06:59:00 jgg Exp $
4 /* ######################################################################
6 Dsync FileList is a tool to manipulate and generate the dsync file
9 Several usefull functions are provided, the most notable is to generate
10 the file list and to dump it. There is also a function to compare the
11 file list against a local directory tree.
13 ##################################################################### */
15 // Include files /*{{{*/
17 #pragma implementation "dsync-flist.h"
20 #include "dsync-flist.h"
21 #include <dsync/cmndline.h>
22 #include <dsync/error.h>
23 #include <dsync/md5.h>
24 #include <dsync/strutl.h>
28 #include <sys/types.h>
30 #include <sys/ioctl.h>
42 ostream c0out(cout.rdbuf());
43 ostream c1out(cout.rdbuf());
44 ostream c2out(cout.rdbuf());
45 ofstream devnull("/dev/null");
46 unsigned int ScreenWidth = 80;
49 // Progress::Progress - Constructor /*{{{*/
50 // ---------------------------------------------------------------------
55 if (_config->FindI("quiet",0) > 0)
62 gettimeofday(&StartTime,0);
65 // Progress::Done - Clear the progress meter /*{{{*/
66 // ---------------------------------------------------------------------
71 c0out << '\r' << BlankLine << '\r' << flush;
75 // Progress::ElaspedTime - Return the time that has elapsed /*{{{*/
76 // ---------------------------------------------------------------------
77 /* Computes the time difference with maximum accuracy */
78 double Progress::ElapsedTime()
80 // Compute the CPS and elapsed time
84 return Now.tv_sec - StartTime.tv_sec + (Now.tv_usec -
85 StartTime.tv_usec)/1000000.0;
88 // Progress::Update - Update the meter /*{{{*/
89 // ---------------------------------------------------------------------
91 void Progress::Update(const char *Directory)
93 LastCount = DirCount+LinkCount+FileCount;
98 // Put the number of files and bytes at the end of the meter
100 if (ScreenWidth > sizeof(S)-1)
101 ScreenWidth = sizeof(S)-1;
103 unsigned int Len = snprintf(S,sizeof(S),"|%lu %sb",
104 DirCount+LinkCount+FileCount,
105 SizeToStr(Bytes).c_str());
107 memmove(S + (ScreenWidth - Len),S,Len+1);
108 memset(S,' ',ScreenWidth - Len);
110 // Put the directory name at the front, possibly shortened
111 if (Directory == 0 || Directory[0] == 0)
112 S[snprintf(S,sizeof(S),"<root>")] = ' ';
115 // If the path is too long fix it and prefix it with '...'
116 if (strlen(Directory) >= ScreenWidth - Len - 1)
118 S[snprintf(S,sizeof(S),"%s",Directory +
119 strlen(Directory) - ScreenWidth + Len + 1)] = ' ';
120 S[0] = '.'; S[1] = '.'; S[2] = '.';
123 S[snprintf(S,sizeof(S),"%s",Directory)] = ' ';
127 c0out << S << '\r' << flush;
128 memset(BlankLine,' ',strlen(S));
129 BlankLine[strlen(S)] = 0;
132 // Progress::Stats - Show a statistics report /*{{{*/
133 // ---------------------------------------------------------------------
135 void Progress::Stats(bool CkSum)
137 // Display some interesting statistics
138 double Elapsed = ElapsedTime();
139 c1out << DirCount << " directories, " << FileCount <<
140 " files and " << LinkCount << " links (" <<
141 (DirCount+FileCount+LinkCount) << "). ";
144 if (CkSumBytes == Bytes)
145 c1out << "Total Size is " << SizeToStr(Bytes) << "b. ";
147 c1out << SizeToStr(CkSumBytes) << '/' <<
148 SizeToStr(Bytes) << "b hashed.";
151 c1out << "Total Size is " << SizeToStr(Bytes) << "b. ";
154 c1out << "Elapsed time " << TimeToStr((long)Elapsed) <<
155 " (" << SizeToStr((DirCount+FileCount+LinkCount)/Elapsed) <<
158 c1out << " (" << SizeToStr(CkSumBytes/Elapsed) << "b/s hash)";
163 // ListGenerator::ListGenerator - Constructor /*{{{*/
164 // ---------------------------------------------------------------------
166 ListGenerator::ListGenerator()
168 Act = !_config->FindB("noact",false);
169 StripDepth = _config->FindI("FileList::CkSum-PathStrip",0);
171 if (_config->FindI("verbose",0) > 0)
176 // Set RSync checksum limits
177 MinRSyncSize = _config->FindI("FileList::MinRSyncSize",0);
178 if (MinRSyncSize == 0)
180 if (_config->FindB("FileList::RSync-Hashes",false) == false)
183 // Load the rsync filter
184 if (RSyncFilter.LoadFilter(_config->Tree("FList::RSync-Filter")) == false)
187 // Load the clean filter
188 if (RemoveFilter.LoadFilter(_config->Tree("FList::Clean-Filter")) == false)
192 // ListGenerator::~ListGenerator - Destructor /*{{{*/
193 // ---------------------------------------------------------------------
195 ListGenerator::~ListGenerator()
201 // ListGenerator::Visit - Collect statistics about the tree /*{{{*/
202 // ---------------------------------------------------------------------
204 int ListGenerator::Visit(const char *Directory,const char *File,
205 struct stat const &Stat)
207 if (Prog.DirCount+Prog.LinkCount+Prog.FileCount - Prog.LastCount > 100 ||
209 Prog.Update(Directory);
211 // Ignore directory enters
215 // Increment our counters
216 if (S_ISDIR(Stat.st_mode) != 0)
220 if (S_ISLNK(Stat.st_mode) != 0)
227 if (S_ISREG(Stat.st_mode) != 0)
228 Prog.Bytes += Stat.st_size;
230 // Look for files to erase
231 if (S_ISDIR(Stat.st_mode) == 0 &&
232 RemoveFilter.Test(Directory,File) == false)
235 c1out << "Unlinking " << Directory << File << endl;
238 if (Act == true && unlink(File) != 0)
240 _error->Errno("unlink","Failed to remove %s%s",Directory,File);
250 // ListGenerator::EmitMD5 - Perform md5 lookup caching /*{{{*/
251 // ---------------------------------------------------------------------
252 /* This looks up the file in the cache to see if it is one we already
254 bool ListGenerator::EmitMD5(const char *Dir,const char *File,
255 struct stat const &St,unsigned char MD5[16],
256 unsigned int Tag,unsigned int Flag)
258 if ((IO->Header.Flags[Tag] & Flag) != Flag)
261 // Lookup the md5 in the old file list
262 if (DB != 0 && (DBIO->Header.Flags[Tag] & Flag) == Flag)
264 // Do a lookup and make sure the timestamps match
267 const char *iDir = Dir;
268 unsigned int Strip = StripDepth;
271 if (DB->Lookup(*DBIO,iDir,File,List) == true && List.Entity != 0)
273 if ((signed)(List.Entity->ModTime + List.Head.Epoch) == St.st_mtime)
282 for (; *iDir != 0 && *iDir != '/'; iDir++);
283 if (*iDir == 0 || iDir[1] == 0)
290 /* Both hardlinks and normal files have md5s, also check that the
292 if (List.File != 0 && List.File->Size == (unsigned)St.st_size)
294 memcpy(MD5,List.File->MD5,sizeof(List.File->MD5));
300 Prog.CkSumBytes += St.st_size;
305 c1out << "MD5 " << Dir << File << endl;
309 return dsGenFileList::EmitMD5(Dir,File,St,MD5,Tag,Flag);
312 // ListGenerator::NeedsRSync - Check if a file is rsyncable /*{{{*/
313 // ---------------------------------------------------------------------
314 /* This checks the rsync filter list and the rsync size limit*/
315 bool ListGenerator::NeedsRSync(const char *Dir,const char *File,
316 dsFList::NormalFile &F)
318 if (MinRSyncSize == 0)
321 if (F.Size <= MinRSyncSize)
324 if (RSyncFilter.Test(Dir,File) == false)
327 /* Add it to the counters, EmitMD5 will not be called if rsync checksums
329 Prog.CkSumBytes += F.Size;
333 c1out << "RSYNC " << Dir << File << endl;
341 // Compare::Compare - Constructor /*{{{*/
342 // ---------------------------------------------------------------------
347 if (_config->FindI("verbose",0) > 0)
349 Act = !_config->FindB("noact",false);
350 DoDelete = _config->FindB("delete",false);
353 // Compare::Visit - Collect statistics about the tree /*{{{*/
354 // ---------------------------------------------------------------------
356 bool Compare::Visit(dsFList &List,string Dir)
358 if (Prog.DirCount+Prog.LinkCount+Prog.FileCount - Prog.LastCount > 100 ||
359 List.Tag == dsFList::tDirStart)
360 Prog.Update(Dir.c_str());
362 // Increment our counters
363 if (List.Tag == dsFList::tDirectory)
367 if (List.Tag == dsFList::tSymlink)
370 if (List.Tag == dsFList::tNormalFile ||
371 List.Tag == dsFList::tHardLink ||
372 List.Tag == dsFList::tDeviceSpecial)
378 Prog.Bytes += List.File->Size;
383 // Compare::PrintPath - Print out a path string /*{{{*/
384 // ---------------------------------------------------------------------
385 /* This handles the absolute paths that can occure while processing */
386 void Compare::PrintPath(ostream &out,string Dir,string Name)
389 out << Dir << Name << endl;
391 out << string(Name,Base.length()) << endl;
395 // LookupPath - Find a full path within the database /*{{{*/
396 // ---------------------------------------------------------------------
397 /* This does the necessary path simplification and symlink resolution
398 to locate the path safely. The file must exist locally inorder to
399 resolve the local symlinks. */
400 bool LookupPath(const char *Path,dsFList &List,dsFileListDB &DB,
406 if (SimplifyPath(Buffer) == false ||
407 ResolveLink(Buffer,sizeof(Buffer)) == false)
410 // Strip off the final component name
411 char *I = Buffer + strlen(Buffer);
412 for (; I != Buffer && (*I == '/' || *I == 0); I--);
413 for (; I != Buffer && *I != '/'; I--);
416 memmove(I+1,I,strlen(I) + 1);
420 if (DB.Lookup(IO,Buffer,I,List) == false)
425 if (DB.Lookup(IO,"",I,List) == false)
432 // PrintMD5 - Prints the MD5 of a file in the form similar to md5sum /*{{{*/
433 // ---------------------------------------------------------------------
435 void PrintMD5(dsFList &List,const char *Dir,const char *File = 0)
437 if (List.File == 0 ||
438 List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5 == 0)
442 for (unsigned int I = 0; I != 16; I++)
443 sprintf(S+2*I,"%02x",List.File->MD5[I]);
446 cout << S << " " << Dir << List.File->Name << endl;
448 cout << S << " " << File << endl;
452 // DoGenerate - The Generate Command /*{{{*/
453 // ---------------------------------------------------------------------
455 bool DoGenerate(CommandLine &CmdL)
458 if (_error->PendingError() == true)
461 // Load the filter list
462 if (Gen.Filter.LoadFilter(_config->Tree("FileList::Filter")) == false)
465 // Load the delay filter list
466 if (Gen.PreferFilter.LoadFilter(_config->Tree("FileList::Prefer-Filter")) == false)
469 // Determine the ordering to use
470 string Ord = _config->Find("FileList::Order","tree");
471 if (stringcasecmp(Ord,"tree") == 0)
472 Gen.Type = dsGenFileList::Tree;
475 if (stringcasecmp(Ord,"breadth") == 0)
476 Gen.Type = dsGenFileList::Breadth;
479 if (stringcasecmp(Ord,"depth") == 0)
480 Gen.Type = dsGenFileList::Depth;
482 return _error->Error("Invalid ordering %s, must be tree, breadth or detph",Ord.c_str());
486 if (CmdL.FileList[1] == 0)
487 return _error->Error("You must specify a file name");
489 string List = CmdL.FileList[1];
491 // Open the original file to pull cached Check Sums out of
492 if (FileExists(List) == true &&
493 _config->FindB("FileList::MD5-Hashes",false) == true)
495 Gen.DBIO = new dsMMapIO(List);
496 if (_error->PendingError() == true)
498 Gen.DB = new dsFileListDB;
499 if (Gen.DB->Generate(*Gen.DBIO) == false)
503 // Sub scope to close the file
505 FdIO IO(List + ".new",FileFd::WriteEmpty);
507 // Set the flags for the list
508 if (_config->FindB("FileList::MD5-Hashes",false) == true)
510 IO.Header.Flags[dsFList::tNormalFile] |= dsFList::NormalFile::FlMD5;
511 IO.Header.Flags[dsFList::tHardLink] |= dsFList::HardLink::FlMD5;
513 if (_config->FindB("FileList::Permissions",false) == true)
515 IO.Header.Flags[dsFList::tDirectory] |= dsFList::Directory::FlPerm;
516 IO.Header.Flags[dsFList::tNormalFile] |= dsFList::NormalFile::FlPerm;
517 IO.Header.Flags[dsFList::tHardLink] |= dsFList::HardLink::FlPerm;
519 if (_config->FindB("FileList::Ownership",false) == true)
521 IO.Header.Flags[dsFList::tDirectory] |= dsFList::Directory::FlOwner;
522 IO.Header.Flags[dsFList::tNormalFile] |= dsFList::NormalFile::FlOwner;
523 IO.Header.Flags[dsFList::tSymlink] |= dsFList::Symlink::FlOwner;
524 IO.Header.Flags[dsFList::tDeviceSpecial] |= dsFList::DeviceSpecial::FlOwner;
525 IO.Header.Flags[dsFList::tHardLink] |= dsFList::HardLink::FlOwner;
528 if (Gen.Go("./",IO) == false)
531 Gen.Prog.Stats(_config->FindB("FileList::MD5-Hashes",false));
540 if (_error->PendingError() == true)
544 bool OldExists = FileExists(List);
545 if (OldExists == true && rename(List.c_str(),(List + "~").c_str()) != 0)
546 return _error->Errno("rename","Unable to rename %s to %s~",List.c_str(),List.c_str());
547 if (rename((List + ".new").c_str(),List.c_str()) != 0)
548 return _error->Errno("rename","Unable to rename %s.new to %s",List.c_str(),List.c_str());
549 if (OldExists == true && unlink((List + "~").c_str()) != 0)
550 return _error->Errno("unlink","Unable to unlink %s~",List.c_str());
555 // DoDump - Dump the contents of a file list /*{{{*/
556 // ---------------------------------------------------------------------
557 /* This displays a short one line dump of each record in the file */
558 bool DoDump(CommandLine &CmdL)
560 if (CmdL.FileList[1] == 0)
561 return _error->Error("You must specify a file name");
564 dsMMapIO IO(CmdL.FileList[1]);
565 if (_error->PendingError() == true)
569 unsigned long CountDir = 0;
570 unsigned long CountFile = 0;
571 unsigned long CountLink = 0;
572 unsigned long CountLinkReal = 0;
573 unsigned long NumFiles = 0;
574 unsigned long NumDirs = 0;
575 unsigned long NumLinks = 0;
578 while (List.Step(IO) == true)
580 if (List.Print(cout) == false)
585 case dsFList::tDirMarker:
586 case dsFList::tDirStart:
587 case dsFList::tDirectory:
589 CountDir += List.Dir.Name.length();
590 if (List.Tag == dsFList::tDirectory)
595 case dsFList::tHardLink:
596 case dsFList::tNormalFile:
598 CountFile += List.File->Name.length();
600 Bytes += List.File->Size;
604 case dsFList::tSymlink:
606 CountFile += List.SLink.Name.length();
607 CountLink += List.SLink.To.length();
609 unsigned int Tmp = List.SLink.To.length();
610 if ((List.SLink.Compress & (1<<7)) == (1<<7))
611 Tmp -= List.SLink.Name.length();
612 Tmp -= List.SLink.Compress & 0x7F;
613 CountLinkReal += Tmp;
618 if (List.Tag == dsFList::tTrailer)
621 cout << "String Sizes: Dirs=" << CountDir << " Files=" << CountFile <<
622 " Links=" << CountLink << " (" << CountLinkReal << ")";
623 cout << " Total=" << CountDir+CountFile+CountLink << endl;
624 cout << "Entries: Dirs=" << NumDirs << " Files=" << NumFiles <<
625 " Links=" << NumLinks << " Total=" << NumDirs+NumFiles+NumLinks << endl;
626 cout << "Totals " << SizeToStr(Bytes) << "b." << endl;
631 // DoMkHardLinks - Generate hardlinks for duplicated files /*{{{*/
632 // ---------------------------------------------------------------------
633 /* This scans the archive for any duplicated files, it uses the MD5 of each
634 file and searches a map for another match then links the two */
637 unsigned char MD5[16];
638 int operator <(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) < 0;};
639 int operator <=(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) <= 0;};
640 int operator >=(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) >= 0;};
641 int operator >(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) > 0;};
642 int operator ==(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) == 0;};
644 Md5Cmp(unsigned char Md[16]) {memcpy(MD5,Md,sizeof(MD5));};
653 Location(string Dir,string File) : Dir(Dir), File(File) {};
656 bool DoMkHardLinks(CommandLine &CmdL)
658 if (CmdL.FileList[1] == 0)
659 return _error->Error("You must specify a file name");
662 dsMMapIO IO(CmdL.FileList[1]);
663 if (_error->PendingError() == true)
667 if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
668 return _error->Error("Unable to read header");
670 // Make sure we have hashes
671 if ((IO.Header.Flags[dsFList::tNormalFile] &
672 dsFList::NormalFile::FlMD5) == 0 ||
673 (IO.Header.Flags[dsFList::tHardLink] &
674 dsFList::HardLink::FlMD5) == 0)
675 return _error->Error("The file list must contain MD5 hashes");
679 unsigned long Hits = 0;
680 bool Act = !_config->FindB("noact",false);
681 map<Md5Cmp,Location> Map;
682 while (List.Step(IO) == true)
684 // Entering a new directory, just store it..
685 if (List.Tag == dsFList::tDirStart)
687 LastDir = List.Dir.Name;
691 /* Handle normal file entities. Pre-existing hard links we treat
692 exactly like a normal file, if two hard link chains are identical
693 one will be destroyed and its items placed on the other
697 map<Md5Cmp,Location>::const_iterator I = Map.find(Md5Cmp(List.File->MD5));
700 Map[Md5Cmp(List.File->MD5)] = Location(LastDir,List.File->Name);
704 // Compute full file names for both
705 string FileA = (*I).second.Dir + (*I).second.File;
707 string FileB = LastDir + List.File->Name;
711 if (lstat(FileA.c_str(),&StA) != 0)
713 _error->Warning("Unable to stat %s",FileA.c_str());
716 if (lstat(FileB.c_str(),&StB) != 0)
718 _error->Warning("Unable to stat %s",FileB.c_str());
722 // Verify they are on the same filesystem
723 if (StA.st_dev != StB.st_dev || StA.st_size != StB.st_size)
727 if (StA.st_ino == StB.st_ino)
730 c1out << "Dup " << FileA << endl;
731 c1out << " " << FileB << endl;
733 // Relink the file and copy the mod time from the oldest one.
736 if (unlink(FileB.c_str()) != 0)
737 return _error->Errno("unlink","Failed to unlink %s",FileB.c_str());
738 if (link(FileA.c_str(),FileB.c_str()) != 0)
739 return _error->Errno("link","Failed to link %s to %s",FileA.c_str(),FileB.c_str());
740 if (StB.st_mtime > StA.st_mtime)
743 Time.actime = Time.modtime = StB.st_mtime;
744 if (utime(FileB.c_str(),&Time) != 0)
745 _error->Warning("Unable to set mod time for %s",FileB.c_str());
750 Savings += List.File->Size;
756 if (List.Tag == dsFList::tTrailer)
760 cout << "Total space saved by merging " <<
761 SizeToStr(Savings) << "b. " << Hits << " files affected." << endl;
765 // DoLookup - Lookup a single file in the listing /*{{{*/
766 // ---------------------------------------------------------------------
768 bool DoLookup(CommandLine &CmdL)
770 if (CmdL.FileSize() < 4)
771 return _error->Error("You must specify a file name, directory name and a entry");
774 dsMMapIO IO(CmdL.FileList[1]);
775 if (_error->PendingError() == true)
780 if (DB.Generate(IO) == false)
784 if (DB.Lookup(IO,CmdL.FileList[2],CmdL.FileList[3],List) == false)
785 return _error->Error("Unable to locate item");
790 // DoMD5Cache - Lookup a stream of files in the listing /*{{{*/
791 // ---------------------------------------------------------------------
792 /* This takes a list of files names and prints out their MD5s, if possible
793 data is used from the cache to save IO */
794 bool DoMD5Cache(CommandLine &CmdL)
796 struct timeval Start;
797 gettimeofday(&Start,0);
799 if (CmdL.FileList[1] == 0)
800 return _error->Error("You must specify a file name");
803 dsMMapIO IO(CmdL.FileList[1]);
804 if (_error->PendingError() == true)
808 if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
809 return _error->Error("Unable to read header");
811 // Make sure we have hashes
812 if ((IO.Header.Flags[dsFList::tNormalFile] &
813 dsFList::NormalFile::FlMD5) == 0 ||
814 (IO.Header.Flags[dsFList::tHardLink] &
815 dsFList::HardLink::FlMD5) == 0)
816 return _error->Error("The file list must contain MD5 hashes");
820 if (DB.Generate(IO) == false)
826 unsigned long Files = 0;
827 unsigned long Errors = 0;
829 while (!cin == false)
832 cin.getline(Buf2,sizeof(Buf2));
839 if (stat(Buf2,&St) != 0)
841 cout << "<ERROR> " << Buf2 << "(stat)" << endl;
846 // Lookup in the cache and make sure the file has not changed
847 if (LookupPath(Buf2,List,DB,IO) == false ||
848 (signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime ||
849 (List.File != 0 && List.File->Size != (unsigned)St.st_size))
851 _error->DumpErrors();
853 // Open the file and hash it
855 FileFd Fd(Buf2,FileFd::ReadOnly);
856 if (_error->PendingError() == true)
858 cout << "<ERROR> " << Buf2 << "(open)" << endl;
862 if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
864 cout << "<ERROR> " << Buf2 << "(md5)" << endl;
868 // Store the new hash
869 List.Tag = dsFList::tNormalFile;
870 Sum.Result().Value(List.File->MD5);
871 List.File->Size = (unsigned)St.st_size;
873 MD5Bytes += List.File->Size;
876 PrintMD5(List,0,Buf2);
877 Bytes += List.File->Size;
880 // Print out a summary
882 gettimeofday(&Now,0);
883 double Delta = Now.tv_sec - Start.tv_sec + (Now.tv_usec - Start.tv_usec)/1000000.0;
884 cerr << Files << " files, " << SizeToStr(MD5Bytes) << "/" <<
885 SizeToStr(Bytes) << " MD5'd, " << TimeToStr((unsigned)Delta) << endl;;
890 // DoMD5Dump - Dump the md5 list /*{{{*/
891 // ---------------------------------------------------------------------
892 /* This displays a short one line dump of each record in the file */
893 bool DoMD5Dump(CommandLine &CmdL)
895 if (CmdL.FileList[1] == 0)
896 return _error->Error("You must specify a file name");
899 dsMMapIO IO(CmdL.FileList[1]);
900 if (_error->PendingError() == true)
904 if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
905 return _error->Error("Unable to read header");
907 // Make sure we have hashes
908 if ((IO.Header.Flags[dsFList::tNormalFile] &
909 dsFList::NormalFile::FlMD5) == 0 ||
910 (IO.Header.Flags[dsFList::tHardLink] &
911 dsFList::HardLink::FlMD5) == 0)
912 return _error->Error("The file list must contain MD5 hashes");
915 while (List.Step(IO) == true)
917 if (List.Tag == dsFList::tDirStart)
923 PrintMD5(List,Dir.c_str());
925 if (List.Tag == dsFList::tTrailer)
931 // DoVerify - Verify the local tree against a file list /*{{{*/
932 // ---------------------------------------------------------------------
934 bool DoVerify(CommandLine &CmdL)
936 if (CmdL.FileList[1] == 0)
937 return _error->Error("You must specify a file name");
940 dsMMapIO IO(CmdL.FileList[1]);
941 if (_error->PendingError() == true)
944 /* Set the hashing type, we can either do a full verify or only a date
947 if (_config->FindB("FileList::MD5-Hashes",false) == true)
948 Comp.HashLevel = dsDirCompare::Md5Always;
950 Comp.HashLevel = dsDirCompare::Md5Date;
952 // Scan the file list
953 if (Comp.Process(".",IO) == false)
958 Comp.Prog.Stats((IO.Header.Flags[dsFList::tNormalFile] & dsFList::NormalFile::FlMD5) != 0 ||
959 (IO.Header.Flags[dsFList::tHardLink] & dsFList::HardLink::FlMD5) != 0);
964 // SigWinch - Window size change signal handler /*{{{*/
965 // ---------------------------------------------------------------------
973 if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col >= 5)
974 ScreenWidth = ws.ws_col - 1;
975 if (ScreenWidth > 250)
980 // ShowHelp - Show the help screen /*{{{*/
981 // ---------------------------------------------------------------------
983 bool ShowHelp(CommandLine &CmdL)
985 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
986 " compiled on " << __DATE__ << " " << __TIME__ << endl;
989 "Usage: dsync-flist [options] command [file]\n"
991 "dsync-flist is a tool for manipulating dsync binary file lists.\n"
992 "It can generate the lists and check them against a tree.\n"
995 " generate - Build a file list\n"
996 " help - This help text\n"
997 " dump - Display the contents of the list\n"
998 " md5sums - Print out 'indices' file, suitable for use with md5sum\n"
999 " md5cache - Print out md5sums of the files given on stdin\n"
1000 " link-dups - Look for duplicate files\n"
1001 " lookup - Display a single file record\n"
1002 " verify - Compare the file list against the local directory\n"
1005 " -h This help text.\n"
1006 " -q Loggable output - no progress indicator\n"
1007 " -qq No output except for errors\n"
1008 " -i=? Include pattern\n"
1009 " -e=? Exclude pattern\n"
1010 " -c=? Read this configuration file\n"
1011 " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp\n"
1012 "See the dsync-flist(1) and dsync.conf(5) manual\n"
1013 "pages for more information." << endl;
1018 int main(int argc, const char *argv[])
1020 CommandLine::Args Args[] = {
1021 {'h',"help","help",0},
1022 {'q',"quiet","quiet",CommandLine::IntLevel},
1023 {'q',"silent","quiet",CommandLine::IntLevel},
1024 {'i',"include","FileList::Filter:: + ",CommandLine::HasArg},
1025 {'e',"exclude","FileList::Filter:: - ",CommandLine::HasArg},
1026 {'n',"no-act","noact",0},
1027 {'v',"verbose","verbose",CommandLine::IntLevel},
1028 {0,"delete","delete",0},
1029 {0,"prefer-include","FileList::Prefer-Filter:: + ",CommandLine::HasArg},
1030 {0,"prefer-exclude","FileList::Prefer-Filter:: - ",CommandLine::HasArg},
1031 {0,"pi","FileList::Prefer-Filter:: + ",CommandLine::HasArg},
1032 {0,"pe","FileList::Prefer-Filter:: - ",CommandLine::HasArg},
1033 {0,"clean-include","FList::Clean-Filter:: + ",CommandLine::HasArg},
1034 {0,"clean-exclude","FList::Clean-Filter:: - ",CommandLine::HasArg},
1035 {0,"ci","FList::Clean-Filter:: + ",CommandLine::HasArg},
1036 {0,"ce","FList::Clean-Filter:: - ",CommandLine::HasArg},
1037 {0,"rsync-include","FList::RSync-Filter:: + ",CommandLine::HasArg},
1038 {0,"rsync-exclude","FList::RSync-Filter:: - ",CommandLine::HasArg},
1039 {0,"ri","FList::RSync-Filter:: + ",CommandLine::HasArg},
1040 {0,"re","FList::RSync-Filter:: - ",CommandLine::HasArg},
1041 {0,"md5","FileList::MD5-Hashes",0},
1042 {0,"rsync","FileList::RSync-Hashes",0},
1043 {0,"rsync-min","FileList::MinRSyncSize",CommandLine::HasArg},
1044 {0,"perm","FileList::Permissions",0},
1045 {0,"owner","FileList::Ownership",0},
1046 {0,"order","FileList::Order",CommandLine::HasArg},
1047 {'c',"config-file",0,CommandLine::ConfigFile},
1048 {'o',"option",0,CommandLine::ArbItem},
1050 CommandLine::Dispatch Cmds[] = {{"generate",&DoGenerate},
1053 {"link-dups",&DoMkHardLinks},
1054 {"md5sums",&DoMD5Dump},
1055 {"md5cache",&DoMD5Cache},
1056 {"lookup",&DoLookup},
1057 {"verify",&DoVerify},
1059 CommandLine CmdL(Args,_config);
1060 if (CmdL.Parse(argc,argv) == false)
1062 _error->DumpErrors();
1066 // See if the help should be shown
1067 if (_config->FindB("help") == true ||
1068 CmdL.FileSize() == 0)
1069 return ShowHelp(CmdL);
1071 // Setup the output streams
1072 /* c0out.rdbuf(cout.rdbuf());
1073 c1out.rdbuf(cout.rdbuf());
1074 c2out.rdbuf(cout.rdbuf()); */
1075 if (_config->FindI("quiet",0) > 0)
1076 c0out.rdbuf(devnull.rdbuf());
1077 if (_config->FindI("quiet",0) > 1)
1078 c1out.rdbuf(devnull.rdbuf());
1080 // Setup the signals
1081 signal(SIGWINCH,SigWinch);
1084 // Match the operation
1085 CmdL.DispatchArg(Cmds);
1087 // Print any errors or warnings found during parsing
1088 if (_error->empty() == false)
1091 bool Errors = _error->PendingError();
1092 _error->DumpErrors();
1093 return Errors == true?100:0;