1 // -*- mode: cpp; mode: fold -*-
3 // $Id: genfilelist.cc,v 1.10 1999/12/26 06:59:01 jgg Exp $
4 /* ######################################################################
8 File list generation can be done with modification to the generation
9 order, ordering can be done by depth, breadth or by tree with and
10 a fitler can be applied to delay a directory till the end of processing.
12 The emitter simply generates the necessary structure and writes it to
13 the IO. The client can hook some of the functions to provide progress
14 reporting and md5 caching if so desired.
16 ##################################################################### */
18 // Include files /*{{{*/
20 #pragma implementation "dsync/genfilelist.h"
23 #include <dsync/genfilelist.h>
24 #include <dsync/error.h>
25 #include <dsync/fileutl.h>
26 #include <dsync/md5.h>
27 #include <dsync/fileutl.h>
28 #include <dsync/rsync-algo.h>
36 // GenFileList::dsGenFileList - Constructor /*{{{*/
37 // ---------------------------------------------------------------------
39 dsGenFileList::dsGenFileList() : IO(0), Type(Tree)
43 // GenFileList::~dsGenFileList - Destructor /*{{{*/
44 // ---------------------------------------------------------------------
46 dsGenFileList::~dsGenFileList()
50 // GenFileList::Go - Generate the list /*{{{*/
51 // ---------------------------------------------------------------------
52 /* This invokes the proper recursive directory scanner to build the file
53 names. Depth and Breath use a queue */
54 bool dsGenFileList::Go(string Base,dsFList::IO &IO)
56 // Setup the queues and store the current directory
57 string StartDir = SafeGetCWD();
58 Queue.erase(Queue.begin(),Queue.end());
59 DelayQueue.erase(Queue.begin(),Queue.end());
62 if (stat(Base.c_str(),&St) != 0)
63 return _error->Errno("stat","Could not stat the base directory");
73 // Change to the base directory
74 if (chdir(Base.c_str()) != 0)
75 return _error->Errno("chdir","Could not change to %s",Base.c_str());
80 if (DirDepthFirst(Cwd) == false)
82 chdir(StartDir.c_str());
86 // Now deal with the delay list
87 while (DelayQueue.empty() == false)
89 // Get the first delayed directory
90 string Dir = DelayQueue.front();
91 DelayQueue.pop_front();
93 // Change to it and emit it.
94 strcpy(Cwd,Dir.c_str());
97 if (DirDepthFirst(Cwd) == false)
99 chdir(StartDir.c_str());
110 // Change to the base directory
111 if (chdir(Base.c_str()) != 0)
112 return _error->Errno("chdir","Could not change to %s",Base.c_str());
116 while (Queue.empty() == false || DelayQueue.empty() == false)
118 if (DirTree() == false)
120 chdir(StartDir.c_str());
130 return _error->Error("Internal Error");
133 chdir(StartDir.c_str());
135 dsFList::Trailer Trail;
136 return Trail.Write(IO);
139 // GenFileList::DirDepthFirst - Depth first directory ordering /*{{{*/
140 // ---------------------------------------------------------------------
142 bool dsGenFileList::DirDepthFirst(char *CurDir)
144 // Scan the directory, first pass is to descend into the sub directories
145 DIR *DirSt = opendir(".");
147 return _error->Errno("opendir","Unable to open direcotry %s",CurDir);
149 bool EmittedThis = false;
151 while ((Ent = readdir(DirSt)) != 0)
154 if (strcmp(Ent->d_name,".") == 0 ||
155 strcmp(Ent->d_name,"..") == 0)
158 if (lstat(Ent->d_name,&St) != 0)
161 return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
165 if (S_ISDIR(St.st_mode) != 0)
168 snprintf(S,sizeof(S),"%s/",Ent->d_name);
171 if (Filter.Test(CurDir,S) == false)
174 // Emit a directory marker record for this directory
175 if (EmittedThis == false)
179 if (lstat(".",&St) != 0)
182 return _error->Errno("stat","Could not stat %s",CurDir);
185 if (DirectoryMarker(CurDir,St) == false)
192 // Check the delay filter
193 if (PreferFilter.Test(CurDir,S) == false)
195 snprintf(S,sizeof(S),"%s%s/",CurDir,Ent->d_name);
196 DelayQueue.push_back(S);
200 // Append the new directory to CurDir and decend
201 char *End = CurDir + strlen(CurDir);
206 return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
210 if (DirDepthFirst(CurDir) == false)
216 if (chdir("..") != 0)
219 return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
222 // Chop off the directory we added to the current dir
228 // Begin emitting this directory
229 if (lstat(".",&St) != 0)
232 return _error->Errno("stat","Could not stat %s",CurDir);
235 if (EnterDir(CurDir,St) == false)
241 while ((Ent = readdir(DirSt)) != 0)
244 if (strcmp(Ent->d_name,".") == 0 ||
245 strcmp(Ent->d_name,"..") == 0)
249 if (lstat(Ent->d_name,&St) != 0)
252 return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
256 if (S_ISDIR(St.st_mode) != 0)
259 snprintf(S,sizeof(S),"%s/",Ent->d_name);
262 if (Filter.Test(CurDir,S) == false)
268 if (Filter.Test(CurDir,Ent->d_name) == false)
272 if (DoFile(CurDir,Ent->d_name,St) == false)
280 if (LeaveDir(CurDir) == false)
286 // GenFileList::DirTree - Breadth/Tree directory ordering /*{{{*/
287 // ---------------------------------------------------------------------
288 /* Breadth ordering does all of the dirs at each depth before proceeding
289 to the next depth. We just treat the list as a queue to get this
290 effect. Tree ordering does things in a more normal recursive fashion,
291 we treat the queue as a stack to get that effect. */
292 bool dsGenFileList::DirTree()
295 if (Queue.empty() == false)
302 Dir = DelayQueue.front();
303 DelayQueue.pop_front();
307 if (Dir.empty() == false && chdir(Dir.c_str()) != 0 || stat(".",&St) != 0)
308 return _error->Errno("chdir","Could not change to %s",Dir.c_str());
310 if (EnterDir(Dir.c_str(),St) == false)
313 // Scan the directory
314 DIR *DirSt = opendir(".");
316 return _error->Errno("opendir","Unable to open direcotry %s",Dir.c_str());
318 while ((Ent = readdir(DirSt)) != 0)
321 if (strcmp(Ent->d_name,".") == 0 ||
322 strcmp(Ent->d_name,"..") == 0)
325 if (lstat(Ent->d_name,&St) != 0)
328 return _error->Errno("stat","Could not stat %s%s",Dir.c_str(),Ent->d_name);
332 if (S_ISDIR(St.st_mode) != 0)
335 snprintf(S,sizeof(S),"%s/",Ent->d_name);
338 if (Filter.Test(Dir.c_str(),S) == false)
341 // Check the delay filter
342 if (PreferFilter.Test(Dir.c_str(),S) == false)
344 snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
346 DelayQueue.push_front(S);
348 DelayQueue.push_back(S);
352 snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
362 if (Filter.Test(Dir.c_str(),Ent->d_name) == false)
366 if (DoFile(Dir.c_str(),Ent->d_name,St) == false)
374 if (LeaveDir(Dir.c_str()) == false)
381 // GenFileList::EnterDir - Called when a directory is entered /*{{{*/
382 // ---------------------------------------------------------------------
383 /* This is called to start a directory block the current working dir
384 should be set to the directory entered. This emits the directory start
386 bool dsGenFileList::EnterDir(const char *Dir,struct stat const &St)
388 if (Visit(Dir,0,St) != 0)
391 dsFList::Directory D;
392 D.Tag = dsFList::tDirStart;
393 D.ModTime = St.st_mtime - IO->Header.Epoch;
394 D.Permissions = St.st_mode & ~S_IFMT;
396 return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) &&
400 // GenFileList::LeaveDir - Called when a directory is left /*{{{*/
401 // ---------------------------------------------------------------------
402 /* Don't do anything for now */
403 bool dsGenFileList::LeaveDir(const char *Dir)
408 // GenFileList::DirectoryMarker - Called when a dir is skipped /*{{{*/
409 // ---------------------------------------------------------------------
410 /* This is used by the depth first ordering, when a dir is temporarily
411 skipped over this function is called to emit a marker */
412 bool dsGenFileList::DirectoryMarker(const char *Dir,
413 struct stat const &St)
415 dsFList::Directory D;
416 D.Tag = dsFList::tDirMarker;
417 D.ModTime = St.st_mtime - IO->Header.Epoch;
418 D.Permissions = St.st_mode & ~S_IFMT;
420 return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) &&
424 // GenFileList::DoFile - This does all other items in a directory /*{{{*/
425 // ---------------------------------------------------------------------
426 /* The different file types are emitted as perscribed by the file list
428 bool dsGenFileList::DoFile(const char *Dir,const char *File,
429 struct stat const &St)
431 int Res = Visit(Dir,File,St);
438 if (S_ISREG(St.st_mode) != 0)
440 dsFList::NormalFile F;
442 F.Tag = dsFList::tNormalFile;
443 F.ModTime = St.st_mtime - IO->Header.Epoch;
444 F.Permissions = St.st_mode & ~S_IFMT;
448 if (EmitOwner(St,F.User,F.Group,F.Tag,dsFList::NormalFile::FlOwner) == false)
451 // See if we need to emit rsync checksums
452 if (NeedsRSync(Dir,File,F) == true)
454 dsFList::RSyncChecksum Ck;
455 if (EmitRSync(Dir,File,St,F,Ck) == false)
458 // Write out the file record, the checksums and the end marker
459 return F.Write(*IO) && Ck.Write(*IO);
463 if (EmitMD5(Dir,File,St,F.MD5,F.Tag,
464 dsFList::NormalFile::FlMD5) == false)
472 if (S_ISDIR(St.st_mode) != 0)
474 dsFList::Directory D;
475 D.Tag = dsFList::tDirectory;
476 D.ModTime = St.st_mtime - IO->Header.Epoch;
477 D.Permissions = St.st_mode & ~S_IFMT;
479 return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) &&
484 if (S_ISLNK(St.st_mode) != 0)
487 L.Tag = dsFList::tSymlink;
488 L.ModTime = St.st_mtime - IO->Header.Epoch;
492 int Res = readlink(File,Buf,sizeof(Buf));
494 return _error->Errno("readlink","Unable to read symbolic link");
498 return EmitOwner(St,L.User,L.Group,L.Tag,dsFList::Symlink::FlOwner) &&
502 // Block special file
503 if (S_ISCHR(St.st_mode) != 0 || S_ISBLK(St.st_mode) != 0 ||
504 S_ISFIFO(St.st_mode) != 0)
506 dsFList::DeviceSpecial D;
507 D.Tag = dsFList::tDeviceSpecial;
508 D.ModTime = St.st_mtime - IO->Header.Epoch;
509 D.Permissions = St.st_mode & ~S_IFMT;
513 return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::DeviceSpecial::FlOwner) &&
517 return _error->Error("File %s%s is not a known type",Dir,File);
520 // GenFileList::EmitOwner - Set the entitiy ownership /*{{{*/
521 // ---------------------------------------------------------------------
522 /* This emits the necessary UID/GID mapping records and sets the feilds
524 bool dsGenFileList::EmitOwner(struct stat const &St,unsigned long &UID,
525 unsigned long &GID,unsigned int Tag,
528 if ((IO->Header.Flags[Tag] & Flag) != Flag)
531 return _error->Error("UID/GID storage is not supported yet");
534 // GenFileList::EmitMd5 - Generate the md5 hash for the file /*{{{*/
535 // ---------------------------------------------------------------------
536 /* This uses the MD5 class to generate the md5 hash for the entry. */
537 bool dsGenFileList::EmitMD5(const char *Dir,const char *File,
538 struct stat const &St,unsigned char MD5[16],
539 unsigned int Tag,unsigned int Flag)
541 if ((IO->Header.Flags[Tag] & Flag) != Flag)
546 FileFd Fd(File,FileFd::ReadOnly);
547 if (_error->PendingError() == true)
548 return _error->Error("MD5 generation failed for %s%s",Dir,File);
550 if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
551 return _error->Error("MD5 generation failed for %s%s",Dir,File);
553 Sum.Result().Value(MD5);
558 // GenFileList::EmitRSync - Emit a RSync checksum record /*{{{*/
559 // ---------------------------------------------------------------------
560 /* This just generates the checksum into the memory structure. */
561 bool dsGenFileList::EmitRSync(const char *Dir,const char *File,
562 struct stat const &St,dsFList::NormalFile &F,
563 dsFList::RSyncChecksum &Ck)
565 FileFd Fd(File,FileFd::ReadOnly);
566 if (_error->PendingError() == true)
567 return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
569 if (GenerateRSync(Fd,Ck,F.MD5) == false)
570 return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);