]> git.decadent.org.uk Git - dak.git/blob - tools/dsync-0.0/libdsync/genfilelist.cc
Give slightly nicer error message on db conn failure
[dak.git] / tools / dsync-0.0 / libdsync / genfilelist.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: genfilelist.cc,v 1.10 1999/12/26 06:59:01 jgg Exp $
4 /* ######################################################################
5    
6    Generate File List 
7
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.
11    
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.
15    
16    ##################################################################### */
17                                                                         /*}}}*/
18 // Include files                                                        /*{{{*/
19 #ifdef __GNUG__
20 #pragma implementation "dsync/genfilelist.h"
21 #endif
22
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>
29
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <dirent.h>
33 #include <stdio.h>
34                                                                         /*}}}*/
35
36 // GenFileList::dsGenFileList - Constructor                             /*{{{*/
37 // ---------------------------------------------------------------------
38 /* */
39 dsGenFileList::dsGenFileList() : IO(0), Type(Tree)
40 {
41 }
42                                                                         /*}}}*/
43 // GenFileList::~dsGenFileList - Destructor                             /*{{{*/
44 // ---------------------------------------------------------------------
45 /* */
46 dsGenFileList::~dsGenFileList()
47 {
48 }
49                                                                         /*}}}*/
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)
55 {
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());
60
61    struct stat St;
62    if (stat(Base.c_str(),&St) != 0)
63       return _error->Errno("stat","Could not stat the base directory");
64    
65    // Begin
66    this->IO = &IO;
67    IO.Header.Write(IO);
68    
69    switch (Type)
70    {
71       case Depth:
72       {
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());
76          Base = SafeGetCWD();
77          
78          char Cwd[1024];
79          Cwd[0] = 0;
80          if (DirDepthFirst(Cwd) == false)
81          {
82             chdir(StartDir.c_str());
83             return false;
84          }
85
86          // Now deal with the delay list
87          while (DelayQueue.empty() == false)
88          {
89             // Get the first delayed directory
90             string Dir = DelayQueue.front();
91             DelayQueue.pop_front();
92             
93             // Change to it and emit it.
94             strcpy(Cwd,Dir.c_str());
95             chdir(Base.c_str());
96             chdir(Cwd);
97             if (DirDepthFirst(Cwd) == false)
98             {
99                chdir(StartDir.c_str());
100                return false;
101             }       
102          }
103          
104          break;
105       }
106       
107       case Tree:
108       case Breadth:
109       {
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());
113          Base = SafeGetCWD();
114
115          Queue.push_back("");
116          while (Queue.empty() == false || DelayQueue.empty() == false)
117          {
118             if (DirTree() == false)
119             {
120                chdir(StartDir.c_str());
121                return false;
122             }
123
124             chdir(Base.c_str());
125          }
126          break;
127       }
128
129       default:
130       return _error->Error("Internal Error");
131    }; 
132
133    chdir(StartDir.c_str());
134    
135    dsFList::Trailer Trail;
136    return Trail.Write(IO);
137 }
138                                                                         /*}}}*/
139 // GenFileList::DirDepthFirst - Depth first directory ordering          /*{{{*/
140 // ---------------------------------------------------------------------
141 /* */
142 bool dsGenFileList::DirDepthFirst(char *CurDir)
143 {
144    // Scan the directory, first pass is to descend into the sub directories
145    DIR *DirSt = opendir(".");
146    if (DirSt == 0)
147       return _error->Errno("opendir","Unable to open direcotry %s",CurDir);
148    struct dirent *Ent;
149    bool EmittedThis = false;
150    struct stat St;
151    while ((Ent = readdir(DirSt)) != 0)
152    {
153       // Skip . and ..
154       if (strcmp(Ent->d_name,".") == 0 ||
155           strcmp(Ent->d_name,"..") == 0)
156          continue;
157       
158       if (lstat(Ent->d_name,&St) != 0)
159       {
160          closedir(DirSt);
161          return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
162       }
163       
164       // it is a directory
165       if (S_ISDIR(St.st_mode) != 0)
166       {
167          char S[1024];
168          snprintf(S,sizeof(S),"%s/",Ent->d_name);
169          
170          // Check the Filter
171          if (Filter.Test(CurDir,S) == false)
172             continue;
173
174          // Emit a directory marker record for this directory
175          if (EmittedThis == false)
176          {
177             EmittedThis = true;
178
179             if (lstat(".",&St) != 0)
180             {
181                closedir(DirSt);
182                return _error->Errno("stat","Could not stat %s",CurDir);
183             }
184             
185             if (DirectoryMarker(CurDir,St) == false)
186             {
187                closedir(DirSt);
188                return false;
189             }       
190          }
191
192          // Check the delay filter
193          if (PreferFilter.Test(CurDir,S) == false)
194          {
195             snprintf(S,sizeof(S),"%s%s/",CurDir,Ent->d_name);
196             DelayQueue.push_back(S);        
197             continue;
198          }
199          
200          // Append the new directory to CurDir and decend
201          char *End = CurDir + strlen(CurDir);
202          strcat(End,S);
203          if (chdir(S) != 0)
204          {
205             closedir(DirSt);
206             return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
207          }
208          
209          // Recurse
210          if (DirDepthFirst(CurDir) == false)
211          {
212             closedir(DirSt);
213             return false;
214          }
215
216          if (chdir("..") != 0)
217          {
218             closedir(DirSt);
219             return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
220          }
221          
222          // Chop off the directory we added to the current dir
223          *End = 0;
224       }
225    }
226    rewinddir(DirSt);
227
228    // Begin emitting this directory
229    if (lstat(".",&St) != 0)
230    {
231       closedir(DirSt);
232       return _error->Errno("stat","Could not stat %s",CurDir);
233    }
234    
235    if (EnterDir(CurDir,St) == false)
236    {
237       closedir(DirSt);
238       return false;
239    }
240       
241    while ((Ent = readdir(DirSt)) != 0)
242    {
243       // Skip . and ..
244       if (strcmp(Ent->d_name,".") == 0 ||
245           strcmp(Ent->d_name,"..") == 0)
246          continue;
247       
248       struct stat St;
249       if (lstat(Ent->d_name,&St) != 0)
250       {
251          closedir(DirSt);
252          return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
253       }
254       
255       // it is a directory
256       if (S_ISDIR(St.st_mode) != 0)
257       {
258          char S[1024];
259          snprintf(S,sizeof(S),"%s/",Ent->d_name);
260          
261          // Check the Filter
262          if (Filter.Test(CurDir,S) == false)
263             continue;
264       }
265       else
266       {
267          // Check the Filter
268          if (Filter.Test(CurDir,Ent->d_name) == false)
269             continue;
270       }
271       
272       if (DoFile(CurDir,Ent->d_name,St) == false)
273       {
274          closedir(DirSt);
275          return false;
276       }      
277    }
278    closedir(DirSt);
279    
280    if (LeaveDir(CurDir) == false)
281       return false;
282    
283    return true;
284 }
285                                                                         /*}}}*/
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()
293 {
294    string Dir;
295    if (Queue.empty() == false)
296    {
297       Dir = Queue.front();
298       Queue.pop_front();
299    }
300    else
301    {
302       Dir = DelayQueue.front();
303       DelayQueue.pop_front();
304    }
305    
306    struct stat St;
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());
309
310    if (EnterDir(Dir.c_str(),St) == false)
311       return false;
312    
313    // Scan the directory
314    DIR *DirSt = opendir(".");
315    if (DirSt == 0)
316       return _error->Errno("opendir","Unable to open direcotry %s",Dir.c_str());
317    struct dirent *Ent;
318    while ((Ent = readdir(DirSt)) != 0)
319    {
320       // Skip . and ..
321       if (strcmp(Ent->d_name,".") == 0 ||
322           strcmp(Ent->d_name,"..") == 0)
323          continue;
324       
325       if (lstat(Ent->d_name,&St) != 0)
326       {
327          closedir(DirSt);
328          return _error->Errno("stat","Could not stat %s%s",Dir.c_str(),Ent->d_name);
329       }
330       
331       // It is a directory
332       if (S_ISDIR(St.st_mode) != 0)
333       {
334          char S[1024];
335          snprintf(S,sizeof(S),"%s/",Ent->d_name);
336          
337          // Check the Filter
338          if (Filter.Test(Dir.c_str(),S) == false)
339             continue;
340
341          // Check the delay filter
342          if (PreferFilter.Test(Dir.c_str(),S) == false)
343          {
344             snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
345             if (Type == Tree)
346                DelayQueue.push_front(S);
347             else
348                DelayQueue.push_back(S);     
349             continue;
350          }
351          
352          snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
353          
354          if (Type == Tree)
355             Queue.push_front(S);
356          else
357             Queue.push_back(S);
358       }
359       else
360       {
361          // Check the Filter
362          if (Filter.Test(Dir.c_str(),Ent->d_name) == false)
363             continue;
364       }
365       
366       if (DoFile(Dir.c_str(),Ent->d_name,St) == false)
367       {
368          closedir(DirSt);
369          return false;
370       }      
371    }
372    closedir(DirSt);
373    
374    if (LeaveDir(Dir.c_str()) == false)
375       return false;
376    
377    return true;
378 }
379                                                                         /*}}}*/
380
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
385    record */
386 bool dsGenFileList::EnterDir(const char *Dir,struct stat const &St)
387 {
388    if (Visit(Dir,0,St) != 0)
389       return false;
390
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;
395    D.Name = Dir;
396    return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
397       D.Write(*IO);    
398 }
399                                                                         /*}}}*/
400 // GenFileList::LeaveDir - Called when a directory is left              /*{{{*/
401 // ---------------------------------------------------------------------
402 /* Don't do anything for now */
403 bool dsGenFileList::LeaveDir(const char *Dir)
404 {
405    return true;
406 }
407                                                                         /*}}}*/
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)
414 {
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;
419    D.Name = Dir;
420    return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
421       D.Write(*IO);    
422 }
423                                                                         /*}}}*/
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
427    document */
428 bool dsGenFileList::DoFile(const char *Dir,const char *File,
429                            struct stat const &St)
430 {
431    int Res = Visit(Dir,File,St);
432    if (Res < 0)
433       return false;
434    if (Res > 0)
435       return true;
436    
437    // Regular file
438    if (S_ISREG(St.st_mode) != 0)
439    {
440       dsFList::NormalFile F;
441       
442       F.Tag = dsFList::tNormalFile;
443       F.ModTime = St.st_mtime - IO->Header.Epoch;
444       F.Permissions = St.st_mode & ~S_IFMT;
445       F.Name = File;
446       F.Size = St.st_size;
447
448       if (EmitOwner(St,F.User,F.Group,F.Tag,dsFList::NormalFile::FlOwner) == false)
449          return false;
450       
451       // See if we need to emit rsync checksums
452       if (NeedsRSync(Dir,File,F) == true)
453       {
454          dsFList::RSyncChecksum Ck;
455          if (EmitRSync(Dir,File,St,F,Ck) == false)
456             return false;
457
458          // Write out the file record, the checksums and the end marker
459          return F.Write(*IO) && Ck.Write(*IO);
460       }
461       else
462       {
463          if (EmitMD5(Dir,File,St,F.MD5,F.Tag,
464                      dsFList::NormalFile::FlMD5) == false)
465             return false;
466       
467          return F.Write(*IO);
468       }      
469    }
470    
471    // Directory
472    if (S_ISDIR(St.st_mode) != 0)
473    {
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;
478       D.Name = File;
479       return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
480          D.Write(*IO);    
481    }
482
483    // Link
484    if (S_ISLNK(St.st_mode) != 0)
485    {
486       dsFList::Symlink L;
487       L.Tag = dsFList::tSymlink;
488       L.ModTime = St.st_mtime - IO->Header.Epoch;
489       L.Name = File;
490
491       char Buf[1024];
492       int Res = readlink(File,Buf,sizeof(Buf));
493       if (Res <= 0)
494          return _error->Errno("readlink","Unable to read symbolic link");
495       Buf[Res] = 0;
496       L.To = Buf;
497
498       return EmitOwner(St,L.User,L.Group,L.Tag,dsFList::Symlink::FlOwner) && 
499          L.Write(*IO);    
500    }
501    
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)
505    {
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;
510       D.Dev = St.st_dev;
511       D.Name = File;
512       
513       return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::DeviceSpecial::FlOwner) && 
514          D.Write(*IO);
515    }
516    
517    return _error->Error("File %s%s is not a known type",Dir,File);
518 }
519                                                                         /*}}}*/
520 // GenFileList::EmitOwner - Set the entitiy ownership                   /*{{{*/
521 // ---------------------------------------------------------------------
522 /* This emits the necessary UID/GID mapping records and sets the feilds
523    in */
524 bool dsGenFileList::EmitOwner(struct stat const &St,unsigned long &UID,
525                               unsigned long &GID,unsigned int Tag,
526                               unsigned int Flag)
527 {
528    if ((IO->Header.Flags[Tag] & Flag) != Flag)
529       return true;
530    
531    return _error->Error("UID/GID storage is not supported yet");
532 }
533                                                                         /*}}}*/
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)
540 {
541    if ((IO->Header.Flags[Tag] & Flag) != Flag)
542       return true;
543
544    // Open the file
545    MD5Summation Sum;
546    FileFd Fd(File,FileFd::ReadOnly);
547    if (_error->PendingError() == true)
548       return _error->Error("MD5 generation failed for %s%s",Dir,File);
549
550    if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
551       return _error->Error("MD5 generation failed for %s%s",Dir,File);
552    
553    Sum.Result().Value(MD5);
554    
555    return true;
556 }
557                                                                         /*}}}*/
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)
564 {
565    FileFd Fd(File,FileFd::ReadOnly);
566    if (_error->PendingError() == true)
567       return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
568    
569    if (GenerateRSync(Fd,Ck,F.MD5) == false)
570       return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
571    
572    return true;
573 }
574                                                                         /*}}}*/